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