Smart Contract
DisruptArtAuction
A.cd946ef9b13804c6.DisruptArtAuction
1// DisruptArt NFT Marketplace
2// Auction smart contract
3// NFT Marketplace : www.disrupt.art
4// Owner : Disrupt Art, INC.
5// Developer : www.blaze.ws
6// Version : 0.0.1
7// Blockchain : Flow www.onFlow.org
8
9import FungibleToken from 0xf233dcee88fe0abe
10import DisruptArt from 0xcd946ef9b13804c6
11import NonFungibleToken from 0x1d7e57aa55817448
12import DisruptArtMarketplace from 0xcd946ef9b13804c6
13
14pub contract DisruptArtAuction {
15
16 // The total amount of AuctionItems that have been created
17 pub var totalAuctions: UInt64
18
19 // Events
20 pub event TokenAddedToAuctionItems(auctionID: UInt64, startPrice: UFix64, minimumBidIncrement: UFix64, auctionStartBlock: UInt64, tokenID: UInt64, endTime:Fix64)
21 pub event NewBid(auctionID: UInt64, bidPrice: UFix64, bidder: Address?)
22 pub event AuctionSettled(auctionID: UInt64, price: UFix64)
23 pub event Canceled(auctionID: UInt64)
24
25 // Auction Storage Path
26 pub let auctionStoragePath: StoragePath
27
28 /// Auction Public Path
29 pub let auctionPublicPath: PublicPath
30
31 // This struct aggreates status for the auction and is exposed in order to create websites using auction information
32 pub struct AuctionStatus{
33 pub let id: UInt64
34 pub let price : UFix64
35 pub let bidIncrement : UFix64
36 pub let bids : UInt64
37 pub let active: Bool
38 pub let endTime : Fix64
39 pub let startTime : Fix64
40 pub let artId: UInt64?
41 pub let owner: Address
42 pub let leader: Address?
43 pub let completed: Bool
44 pub let expired: Bool
45
46 init(id:UInt64,
47 currentPrice: UFix64,
48 bids:UInt64,
49 active: Bool,
50 artId: UInt64?,
51 leader:Address?,
52 bidIncrement: UFix64,
53 owner: Address,
54 startTime: Fix64,
55 endTime: Fix64,
56 completed: Bool,
57 expired:Bool
58 ) {
59 self.id=id
60 self.price= currentPrice
61 self.bids=bids
62 self.active=active
63 self.artId=artId
64 self.leader= leader
65 self.bidIncrement=bidIncrement
66 self.owner=owner
67 self.startTime=startTime
68 self.endTime=endTime
69 self.completed=completed
70 self.expired=expired
71 }
72 }
73
74 // AuctionItem contains the Resources for a single auction
75 pub resource AuctionItem {
76
77 //Number of bids made, that is aggregated to the status struct
78 priv var numberOfBids: UInt64
79
80 // Resources
81 priv var NFT: @NonFungibleToken.NFT?
82 priv let bidVault: @FungibleToken.Vault
83
84 // Auction Settings
85 pub let auctionID: UInt64
86 priv let minimumBidIncrement: UFix64
87
88 // Auction State
89 access(account) var startPrice: UFix64
90 priv var currentPrice: UFix64
91
92 priv let auctionStartBlock: UInt64
93 priv var auctionCompleted: Bool
94
95 priv let endTime : Fix64
96 priv let startTime : Fix64
97
98 priv let resale: Bool
99 priv let creator: Address?
100
101 // Recipient's Receiver Capabilities
102 priv var recipientCollectionCap: Capability<&{DisruptArt.DisruptArtCollectionPublic}>
103 priv var recipientVaultCap: Capability<&{FungibleToken.Receiver}>?
104
105 // Owner's Receiver Capabilities
106 priv let ownerCollectionCap: Capability<&{DisruptArt.DisruptArtCollectionPublic}>
107 priv let ownerVaultCap: Capability<&{FungibleToken.Receiver}>
108
109 init(
110 NFT: @NonFungibleToken.NFT,
111 bidVault: @FungibleToken.Vault,
112 auctionID: UInt64,
113 minimumBidIncrement: UFix64,
114 startPrice: UFix64,
115 auctionStartBlock: UInt64,
116 startTime : Fix64,
117 endTime : Fix64,
118 resale: Bool,
119 creator: Address?,
120 ownerCollectionCap: Capability<&{DisruptArt.DisruptArtCollectionPublic}>,
121 ownerVaultCap: Capability<&{FungibleToken.Receiver}>
122 ) {
123 self.NFT <- NFT
124 self.bidVault <- bidVault
125 self.auctionID = auctionID
126 self.minimumBidIncrement = minimumBidIncrement
127 self.startPrice = startPrice
128 self.currentPrice = startPrice
129 self.auctionStartBlock = auctionStartBlock
130 self.auctionCompleted = false
131 self.endTime = endTime
132 self.startTime = startTime
133 self.resale = resale
134 self.creator = creator
135 self.recipientCollectionCap = ownerCollectionCap
136 self.recipientVaultCap = ownerVaultCap
137 self.ownerCollectionCap = ownerCollectionCap
138 self.ownerVaultCap = ownerVaultCap
139 self.numberOfBids = 0
140 }
141
142 // depositBidTokens deposits the bidder's tokens into the AuctionItem's Vault
143 pub fun depositBidTokens(vault: @FungibleToken.Vault) {
144 self.bidVault.deposit(from: <-vault)
145 }
146
147 // withdrawNFT removes the NFT from the AuctionItem and returns it to the caller
148 pub fun withdrawNFT(): @NonFungibleToken.NFT {
149 let NFT <- self.NFT <- nil
150 return <- NFT!
151 }
152
153 // sendNFT sends the NFT to the Collection belonging to the provided Capability
154 access(contract) fun sendNFT(_ capability: Capability<&{DisruptArt.DisruptArtCollectionPublic}>) {
155 // borrow a reference to the owner's NFT receiver
156 if let collectionRef = capability.borrow() {
157 let NFT <- self.withdrawNFT()
158 // deposit the token into the owner's collection
159 collectionRef.deposit(token: <-NFT)
160 } else {
161 panic("sendNFT(): unable to borrow collection ref")
162 }
163 }
164
165 // sendBidTokens sends the bid tokens to the Vault Receiver belonging to the provided Capability
166 access(contract) fun sendBidTokens(_ capability: Capability<&{FungibleToken.Receiver}>, sale: Bool) {
167 // borrow a reference to the owner's NFT receiver
168 if let vaultRef = capability.borrow() {
169 let bidVaultRef = &self.bidVault as &FungibleToken.Vault
170 var balance = 0.0
171 if(sale){
172 let marketShare = (bidVaultRef.balance / 100.0 ) * DisruptArtMarketplace.marketFee
173 let royalityShare = (bidVaultRef.balance / 100.0 ) * DisruptArtMarketplace.royality
174 balance = bidVaultRef.balance - (marketShare + royalityShare)
175
176 let marketCut <- bidVaultRef.withdraw(amount: marketShare)
177 let royalityCut <- bidVaultRef.withdraw(amount: royalityShare)
178
179 let disruptartvaultRef = getAccount(DisruptArtMarketplace.marketAddress)
180 .getCapability(/public/fusdReceiver)
181 .borrow<&{FungibleToken.Receiver}>()
182 ?? panic("failed to borrow reference to Marketplace vault")
183
184 // let itemRef = (&self.auctionItems[id] as? &AuctionItem?)!
185
186 let creatorvaultRef = getAccount(self.creator!!)
187 .getCapability(/public/fusdReceiver)
188 .borrow<&{FungibleToken.Receiver}>()
189 ?? panic("failed to borrow reference to owner vault")
190
191 disruptartvaultRef.deposit(from: <-marketCut)
192
193 if(self.resale) {
194 creatorvaultRef.deposit(from: <-royalityCut)
195 } else {
196 disruptartvaultRef.deposit(from: <-royalityCut)
197 }
198
199 } else {
200 balance = bidVaultRef.balance
201 }
202
203 vaultRef.deposit(from: <-bidVaultRef.withdraw(amount:balance))
204 } else {
205 panic("couldn't get vault ref")
206 }
207 }
208
209 // settleAuction sends the auction item to the highest bidder
210 // and deposits the FungibleTokens into the auction owner's account
211 pub fun settleAuction() {
212
213 pre {
214 !self.auctionCompleted : "The auction is already settled"
215 self.NFT != nil: "NFT in auction does not exist"
216 self.isAuctionExpired() : "Auction has not completed yet"
217 }
218
219 // return if there are no bids to settle
220 if self.currentPrice == self.startPrice {
221 self.returnAuctionItemToOwner()
222 } else {
223 self.exchangeTokens()
224 }
225
226 self.auctionCompleted = true
227
228 emit AuctionSettled(auctionID: self.auctionID, price: self.currentPrice)
229
230 }
231
232 // isAuctionExpired returns true if the auction has exceeded it's length in blocks,
233 // otherwise it returns false
234 pub fun isAuctionExpired(): Bool {
235
236 let currentTime = getCurrentBlock().timestamp
237
238 if Fix64(self.endTime) < Fix64(currentTime) {
239 return true
240 } else {
241 return false
242 }
243 }
244
245 // returnAuctionItemToOwner releases any bids and returns the NFT
246 // to the owner's Collection
247 pub fun returnAuctionItemToOwner() {
248 pre {
249 self.NFT != nil: "NFT in auction does not exist"
250 }
251
252 // release the bidder's tokens
253 self.releasePreviousBid()
254
255 // deposit the NFT into the owner's collection
256 self.sendNFT(self.ownerCollectionCap)
257 }
258
259
260 // exchangeTokens sends the purchased NFT to the buyer and the bidTokens to the seller
261 pub fun exchangeTokens() {
262 pre {
263 self.NFT != nil: "NFT in auction does not exist"
264 }
265
266 self.sendNFT(self.recipientCollectionCap)
267 self.sendBidTokens(self.ownerVaultCap, sale:true)
268 }
269
270 // releasePreviousBid returns the outbid user's tokens to
271 // their vault receiver
272 pub fun releasePreviousBid() {
273 // release the bidTokens from the vault back to the bidder
274 if let vaultCap = self.recipientVaultCap {
275 self.sendBidTokens(self.recipientVaultCap!, sale:false)
276 } else {
277 panic("unable to get vault capability")
278 }
279 }
280
281 pub fun cancelAuction() {
282 pre {
283 !self.auctionCompleted : "The auction is already settled"
284 self.NFT != nil: "NFT in auction does not exist"
285 self.isAuctionExpired() == false : "Auciton expired, can't cancel"
286 }
287
288 self.returnAuctionItemToOwner()
289
290 self.auctionCompleted = true
291
292 emit Canceled(auctionID: self.auctionID)
293
294 }
295
296 pub fun placeBid(bidTokens: @FungibleToken.Vault, vaultCap: Capability<&{FungibleToken.Receiver}>, collectionCap: Capability<&{DisruptArt.DisruptArtCollectionPublic}>) {
297 pre {
298 !self.auctionCompleted : "The auction is already settled"
299 self.NFT != nil: "NFT in auction does not exist"
300 !self.isAuctionExpired() : "Auciton expired, can't place a bid"
301 }
302
303 if bidTokens.balance < (self.currentPrice + self.minimumBidIncrement) {
304 panic("bid amount be larger than minimum bid increment")
305 }
306
307 if self.bidVault.balance != UFix64(0) {
308 if let vaultCapy = self.recipientVaultCap {
309 self.sendBidTokens(vaultCapy, sale:false)
310 } else {
311 panic("unable to get recipient Vault capability")
312 }
313 }
314
315 // Update the auction item
316 self.depositBidTokens(vault: <-bidTokens)
317
318 // Update the current price of the token
319 self.currentPrice = self.bidVault.balance
320
321 // Add the bidder's Vault and NFT receiver references
322 self.recipientCollectionCap = collectionCap
323 self.recipientVaultCap = vaultCap
324 self.numberOfBids=self.numberOfBids+(1 as UInt64)
325
326 emit NewBid(auctionID: self.auctionID, bidPrice: self.currentPrice, bidder: vaultCap.address)
327 }
328
329 pub fun getAuctionStatus() :AuctionStatus {
330
331 var leader:Address?= nil
332 if let recipient = self.recipientVaultCap {
333 leader=recipient.address
334 }
335
336 return AuctionStatus(
337 id:self.auctionID,
338 currentPrice: self.currentPrice,
339 bids: self.numberOfBids,
340 active: !self.auctionCompleted && !self.isAuctionExpired(),
341 artId: self.NFT?.id,
342 leader: leader,
343 bidIncrement: self.minimumBidIncrement,
344 owner: self.ownerVaultCap.address,
345 startTime: Fix64(self.startTime),
346 endTime: Fix64(self.endTime),
347 completed: self.auctionCompleted,
348 expired: self.isAuctionExpired()
349 )
350 }
351
352
353 destroy() {
354 // send the NFT back to auction owner
355 self.sendNFT(self.ownerCollectionCap)
356
357 // if there's a bidder...
358 if let vaultCap = self.recipientVaultCap {
359 // ...send the bid tokens back to the bidder
360 self.sendBidTokens(vaultCap, sale:false)
361 }
362
363 destroy self.NFT
364 destroy self.bidVault
365 }
366 }
367
368 // AuctionPublic is a resource interface that restricts users to
369 // retreiving the auction price list and placing bids
370 pub resource interface AuctionPublic {
371 pub fun getAuctionKeys() : [UInt64]
372
373 pub fun getAuctionStatuses(): {UInt64: AuctionStatus}
374 pub fun getAuctionStatus(_ id:UInt64): AuctionStatus
375
376 pub fun placeBid(
377 id: UInt64,
378 bidTokens: @FungibleToken.Vault,
379 vaultCap: Capability<&{FungibleToken.Receiver}>,
380 collectionCap: Capability<&{DisruptArt.DisruptArtCollectionPublic}>
381 )
382
383 pub fun settleAuction(_ id: UInt64)
384 }
385
386 // AuctionCollection contains a dictionary of AuctionItems and provides
387 // methods for manipulating the AuctionItems
388 pub resource AuctionCollection: AuctionPublic {
389
390 // Auction Items
391 access(account) var auctionItems: @{UInt64: AuctionItem}
392
393 init() {
394 self.auctionItems <- {}
395 }
396
397 // addTokenToauctionItems adds an NFT to the auction items
398 pub fun addTokenToAuctionItems(token: @NonFungibleToken.NFT, minimumBidIncrement: UFix64, startPrice: UFix64, bidVault: @FungibleToken.Vault, collectionCap: Capability<&{DisruptArt.DisruptArtCollectionPublic}>, vaultCap: Capability<&{FungibleToken.Receiver}>, endTime : Fix64) {
399
400 pre {
401 Fix64(getCurrentBlock().timestamp) < endTime : "endtime should be greater than current time"
402 minimumBidIncrement > 0.0 : "minimumBidIncrement should be greater than 0.0"
403 }
404
405 let bidtoken <-token as! @DisruptArt.NFT
406
407 let tokenID = bidtoken.id
408
409 let resale = (bidtoken.creator == self.owner?.address) ? false : true
410
411 let creator = bidtoken.creator
412
413 let itemToken <- bidtoken as! @NonFungibleToken.NFT
414
415 DisruptArtAuction.totalAuctions = DisruptArtAuction.totalAuctions + UInt64(1)
416
417 let id = DisruptArtAuction.totalAuctions
418
419 let startBlock = getCurrentBlock().height
420
421 let startTime = Fix64(getCurrentBlock().timestamp)
422
423 // create a new auction items resource container
424 let item <- create AuctionItem(
425 NFT: <-itemToken,
426 bidVault: <-bidVault,
427 auctionID: id,
428 minimumBidIncrement: minimumBidIncrement,
429 startPrice: startPrice,
430 auctionStartBlock: startBlock,
431 startTime : startTime,
432 endTime : endTime,
433 resale: resale,
434 creator: creator,
435 ownerCollectionCap: collectionCap,
436 ownerVaultCap: vaultCap
437 )
438
439
440
441 // update the auction items dictionary with the new resources
442 let oldItem <- self.auctionItems[id] <- item
443 destroy oldItem
444
445 emit TokenAddedToAuctionItems(auctionID: id, startPrice: startPrice, minimumBidIncrement: minimumBidIncrement, auctionStartBlock: startBlock, tokenID:tokenID, endTime:endTime)
446 }
447
448 pub fun getAuctionStatuses(): {UInt64: AuctionStatus} {
449 pre {
450 self.auctionItems.keys.length > 0: "There are no auction items"
451 }
452
453 let auctionList: {UInt64: AuctionStatus} = {}
454
455 for id in self.auctionItems.keys {
456 let itemRef = (&self.auctionItems[id] as? &AuctionItem?)!
457 auctionList[id] = itemRef.getAuctionStatus()
458 }
459
460 return auctionList
461
462 }
463
464
465 pub fun getAuctionStatus(_ id:UInt64): AuctionStatus {
466 pre {
467 self.auctionItems[id] != nil:
468 "Auction doesn't exist"
469 }
470
471 // Get the auction item resources
472 let itemRef = (&self.auctionItems[id] as &AuctionItem?)!
473 let status = itemRef.getAuctionStatus()
474 return status
475 }
476
477 pub fun getAuctionKeys() : [UInt64] {
478
479 pre {
480 self.auctionItems.keys.length > 0: "There are no auction items"
481 }
482
483 return self.auctionItems.keys
484
485 }
486
487 // settleAuction sends the auction item to the highest bidder
488 // and deposits the FungibleTokens into the auction owner's account
489 pub fun settleAuction(_ id: UInt64) {
490 pre {
491 self.auctionItems[id] != nil:
492 "Auction doesn't exist"
493 }
494
495 let itemRef = (&self.auctionItems[id] as &AuctionItem?)!
496 itemRef.settleAuction()
497 }
498
499 pub fun cancelAuction(_ id: UInt64) {
500 pre {
501 self.auctionItems[id] != nil:
502 "Auction does not exist"
503 }
504
505 let itemRef = (&self.auctionItems[id] as &AuctionItem?)!
506 itemRef.cancelAuction()
507
508 }
509
510 // placeBid sends the bidder's tokens to the bid vault and updates the
511 // currentPrice of the current auction item
512 pub fun placeBid(id: UInt64, bidTokens: @FungibleToken.Vault, vaultCap: Capability<&{FungibleToken.Receiver}>, collectionCap: Capability<&{DisruptArt.DisruptArtCollectionPublic}>) {
513
514 pre {
515 self.auctionItems[id] != nil:
516 "Auction doesn't exist"
517 }
518
519 // Get the auction item resources
520 let itemRef = (&self.auctionItems[id] as &AuctionItem?)!
521
522 itemRef.placeBid(bidTokens: <- bidTokens, vaultCap: vaultCap, collectionCap: collectionCap)
523
524 }
525
526 destroy() {
527 // destroy the empty resources
528 destroy self.auctionItems
529 }
530 }
531
532 // createAuctionCollection returns a new AuctionCollection resource to the caller
533 pub fun createAuctionCollection(): @AuctionCollection {
534 let auctionCollection <- create AuctionCollection()
535 return <- auctionCollection
536 }
537
538 init() {
539 self.totalAuctions = UInt64(0)
540 self.auctionStoragePath= /storage/DisruptArtAuction
541 self.auctionPublicPath= /public/DisruptArtAuction
542 }
543}
544
545
546