Smart Contract
FindMarketAuctionEscrow
A.097bafa4e0b48eef.FindMarketAuctionEscrow
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3import MetadataViews from 0x1d7e57aa55817448
4import FindViews from 0x097bafa4e0b48eef
5import Clock from 0x097bafa4e0b48eef
6import FIND from 0x097bafa4e0b48eef
7import FindMarket from 0x097bafa4e0b48eef
8import Profile from 0x097bafa4e0b48eef
9
10// An auction saleItem contract that escrows the FT, does _not_ escrow the NFT
11access(all) contract FindMarketAuctionEscrow {
12
13 // A seller can list,delist and relist leases for auction
14 access(all) entitlement Seller
15
16 access(all) event EnglishAuction(tenant: String, id: UInt64, saleID: UInt64, seller: Address, sellerName:String?, amount: UFix64, auctionReservePrice: UFix64, status: String, vaultType:String, nft:FindMarket.NFTInfo?, buyer:Address?, buyerName:String?, buyerAvatar:String?, startsAt: UFix64?, endsAt: UFix64?, previousBuyer:Address?, previousBuyerName:String?)
17
18 access(all) resource SaleItem : FindMarket.SaleItem {
19 access(contract) var pointer: FindViews.AuthNFTPointer
20 access(contract) var vaultType: Type
21 access(contract) var auctionStartPrice: UFix64
22 access(contract) var auctionReservePrice: UFix64
23 access(contract) var auctionDuration: UFix64
24 access(contract) var auctionMinBidIncrement: UFix64
25 access(contract) var auctionExtensionOnLateBid: UFix64
26 access(contract) var auctionStartedAt: UFix64?
27 access(contract) var auctionValidUntil: UFix64?
28 access(contract) var auctionEndsAt: UFix64?
29 access(contract) var offerCallback: Capability<&MarketBidCollection>?
30 access(contract) let totalRoyalties: UFix64
31 access(contract) let saleItemExtraField: {String : AnyStruct}
32
33 init(pointer: FindViews.AuthNFTPointer, vaultType: Type, auctionStartPrice:UFix64, auctionReservePrice:UFix64, auctionDuration: UFix64, extentionOnLateBid:UFix64, minimumBidIncrement:UFix64, auctionValidUntil: UFix64?, saleItemExtraField: {String : AnyStruct}) {
34 self.vaultType=vaultType
35 self.pointer=pointer
36 self.auctionStartPrice=auctionStartPrice
37 self.auctionReservePrice=auctionReservePrice
38 self.auctionDuration=auctionDuration
39 self.auctionExtensionOnLateBid=extentionOnLateBid
40 self.auctionMinBidIncrement=minimumBidIncrement
41 self.offerCallback=nil
42 self.auctionStartedAt=nil
43 self.auctionEndsAt=nil
44 self.auctionValidUntil=auctionValidUntil
45 self.saleItemExtraField=saleItemExtraField
46 self.totalRoyalties=self.pointer.getTotalRoyaltiesCut()
47 }
48
49 access(all) fun getExtraFields() : {String:AnyStruct}{
50 return self.saleItemExtraField
51 }
52
53 access(all) fun getPointer() : FindViews.AuthNFTPointer {
54 return self.pointer
55 }
56 access(all) fun getId() : UInt64{
57 return self.pointer.getUUID()
58 }
59
60 access(contract) fun acceptEscrowedBid() : @{FungibleToken.Vault} {
61 if !self.offerCallback!.check() {
62 panic("bidder unlinked the bid collection capability. bidder address : ".concat(self.offerCallback!.address.toString()))
63 }
64 let path = self.pointer.getNFTCollectionData().publicPath
65 let vault <- self.offerCallback!.borrow()!.accept(<- self.pointer.withdraw(), path: path)
66 return <- vault
67 }
68
69 access(all) fun getRoyalty() : MetadataViews.Royalties {
70 return self.pointer.getRoyalty()
71 }
72
73 access(all) fun getBalance() : UFix64 {
74 if let cb= self.offerCallback {
75 if !cb.check() {
76 panic("Bidder unlinked the bid collection capability. bidder address : ".concat(cb.address.toString()))
77 }
78 return cb.borrow()!.getBalance(self.getId())
79 }
80 return self.auctionStartPrice
81 }
82
83 access(all) fun getSeller() : Address {
84 return self.pointer.owner()
85 }
86
87 access(all) fun getSellerName() : String? {
88 let address = self.pointer.owner()
89 return FIND.reverseLookup(address)
90 }
91
92 access(all) fun getBuyer() : Address? {
93 if let cb= self.offerCallback {
94 return cb.address
95 }
96 return nil
97 }
98
99 access(all) fun getBuyerName() : String? {
100 if let cb= self.offerCallback {
101 return FIND.reverseLookup(cb.address)
102 }
103 return nil
104 }
105
106 access(all) fun toNFTInfo(_ detail: Bool) : FindMarket.NFTInfo{
107 return FindMarket.NFTInfo(self.pointer.getViewResolver(), id: self.pointer.id, detail:detail)
108 }
109
110 access(contract) fun setAuctionStarted(_ startedAt: UFix64) {
111 self.auctionStartedAt=startedAt
112 }
113
114 access(contract) fun setAuctionEnds(_ endsAt: UFix64){
115 self.auctionEndsAt=endsAt
116 }
117
118 access(all) fun hasAuctionStarted() : Bool {
119 if let starts = self.auctionStartedAt {
120 return starts <= Clock.time()
121 }
122 return false
123 }
124
125 access(all) fun hasAuctionEnded() : Bool {
126 if let ends = self.auctionEndsAt {
127 return ends < Clock.time()
128 }
129 panic("Not a live auction")
130 }
131
132 access(all) fun hasAuctionMetReservePrice() : Bool {
133
134 let balance=self.getBalance()
135
136 if self.auctionReservePrice== nil {
137 return false
138 }
139
140 return balance >= self.auctionReservePrice
141 }
142
143 access(contract) fun setExtentionOnLateBid(_ time: UFix64) {
144 self.auctionExtensionOnLateBid=time
145 }
146
147 access(contract) fun setAuctionDuration(_ duration: UFix64) {
148 self.auctionDuration=duration
149 }
150
151 access(contract) fun setReservePrice(_ price: UFix64) {
152 self.auctionReservePrice=price
153 }
154
155 access(contract) fun setMinBidIncrement(_ price: UFix64) {
156 self.auctionMinBidIncrement=price
157 }
158
159 access(contract) fun setStartAuctionPrice(_ price: UFix64) {
160 self.auctionStartPrice=price
161 }
162
163 access(contract) fun setCallback(_ callback: Capability<&MarketBidCollection>?) {
164 self.offerCallback=callback
165 }
166
167 access(all) fun getSaleType(): String {
168 if self.auctionStartedAt != nil {
169 if self.hasAuctionEnded() {
170 if self.hasAuctionMetReservePrice() {
171 return "finished_completed"
172 }
173 return "finished_failed"
174 }
175 return "active_ongoing"
176 }
177 return "active_listed"
178 }
179
180 access(all) fun getListingType() : Type {
181 return Type<@SaleItem>()
182 }
183
184 access(all) fun getListingTypeIdentifier(): String {
185 return Type<@SaleItem>().identifier
186 }
187
188
189 access(all) fun getItemID() : UInt64 {
190 return self.pointer.id
191 }
192
193 access(all) fun getItemType() : Type {
194 return self.pointer.getItemType()
195 }
196
197 access(all) fun getAuction(): FindMarket.AuctionItem? {
198 return FindMarket.AuctionItem(startPrice: self.auctionStartPrice,
199 currentPrice: self.getBalance(),
200 minimumBidIncrement: self.auctionMinBidIncrement ,
201 reservePrice: self.auctionReservePrice,
202 extentionOnLateBid: self.auctionExtensionOnLateBid ,
203 auctionEndsAt: self.auctionEndsAt ,
204 timestamp: Clock.time())
205 }
206
207 access(all) fun getFtType() : Type {
208 return self.vaultType
209 }
210
211 access(contract) fun setValidUntil(_ time: UFix64?) {
212 self.auctionValidUntil=time
213 }
214
215 access(all) fun getValidUntil() : UFix64? {
216 if self.hasAuctionStarted() {
217 return self.auctionEndsAt
218 }
219 return self.auctionValidUntil
220 }
221
222 access(all) fun checkPointer() : Bool {
223 return self.pointer.valid()
224 }
225
226 access(all) fun checkSoulBound() : Bool {
227 return self.pointer.checkSoulBound()
228 }
229
230 access(all) fun getSaleItemExtraField() : {String : AnyStruct} {
231 return self.saleItemExtraField
232 }
233
234 access(all) fun getTotalRoyalties() : UFix64 {
235 return self.totalRoyalties
236 }
237
238 access(all) fun validateRoyalties() : Bool {
239 return self.totalRoyalties == self.pointer.getTotalRoyaltiesCut()
240 }
241
242 access(all) fun getDisplay() : MetadataViews.Display {
243 return self.pointer.getDisplay()
244 }
245
246 access(all) fun getNFTCollectionData() : MetadataViews.NFTCollectionData {
247 return self.pointer.getNFTCollectionData()
248 }
249 }
250
251
252 access(all) resource interface SaleItemCollectionPublic {
253 //fetch all the tokens in the collection
254 access(all) fun getIds(): [UInt64]
255 access(all) fun containsId(_ id: UInt64): Bool
256 access(contract) fun registerIncreasedBid(_ id: UInt64, oldBalance:UFix64)
257 //place a bid on a tokenÆ’
258 access(contract) fun registerBid(item: FindViews.ViewReadPointer, callback: Capability<&MarketBidCollection>, vaultType:Type)
259
260 //anybody should be able to fulfill an auction as long as it is done
261 access(all) fun fulfillAuction(_ id: UInt64)
262 }
263
264 access(all) resource SaleItemCollection: SaleItemCollectionPublic, FindMarket.SaleItemCollectionPublic {
265 //is this the best approach now or just put the NFT inside the saleItem?
266 access(contract) var items: @{UInt64: SaleItem}
267
268 access(contract) let tenantCapability: Capability<&FindMarket.Tenant>
269
270 init (_ tenantCapability: Capability<&FindMarket.Tenant>) {
271 self.items <- {}
272 self.tenantCapability=tenantCapability
273 }
274
275 access(self) fun getTenant() : &FindMarket.Tenant {
276 if !self.tenantCapability.check() {
277 panic("Tenant client is not linked anymore")
278 }
279 return self.tenantCapability.borrow()!
280 }
281
282 access(all) fun getListingType() : Type {
283 return Type<@SaleItem>()
284 }
285
286 access(self) fun addBid(id:UInt64, newOffer: Capability<&MarketBidCollection>, oldBalance:UFix64) {
287 let saleItem=self.borrowAuth(id)
288 let tenant=self.getTenant()
289 let nftType=saleItem.getItemType()
290 let ftType=saleItem.getFtType()
291
292 let actionResult=tenant.allowedAction(listingType: Type<@FindMarketAuctionEscrow.SaleItem>(), nftType: nftType, ftType: ftType, action: FindMarket.MarketAction(listing:false, name:"add bid in auction"), seller: self.owner!.address, buyer: newOffer.address)
293
294 if !actionResult.allowed {
295 panic(actionResult.message)
296 }
297
298 let timestamp=Clock.time()
299 let newOfferBalance=newOffer.borrow()?.getBalance(id) ?? panic("The new offer bid capability is invalid.")
300
301 let previousOffer = saleItem.offerCallback
302
303 var minBid=oldBalance + saleItem.auctionMinBidIncrement
304 if previousOffer != nil && newOffer.address != previousOffer!.address {
305 minBid = previousOffer!.borrow()!.getBalance(id) + saleItem.auctionMinBidIncrement
306 }
307
308 if newOfferBalance < minBid {
309 panic("bid ".concat(newOfferBalance.toString()).concat(" must be larger then previous bid+bidIncrement ").concat(minBid.toString()))
310 }
311
312 var previousBuyer:Address?=nil
313 if previousOffer != nil && newOffer.address != previousOffer!.address {
314 if !previousOffer!.check() {
315 panic("Previous bidder unlinked the bid collection capability. bidder address : ".concat(previousOffer!.address.toString()))
316 }
317 previousOffer!.borrow()!.cancelBidFromSaleItem(id)
318 previousBuyer=previousOffer!.address
319 }
320
321 saleItem.setCallback(newOffer)
322
323 let suggestedEndTime=timestamp+saleItem.auctionExtensionOnLateBid
324
325 if suggestedEndTime > saleItem.auctionEndsAt! {
326 saleItem.setAuctionEnds(suggestedEndTime)
327 }
328
329 let status="active_ongoing"
330
331 let seller=self.owner!.address
332
333 let nftInfo=saleItem.toNFTInfo(true)
334
335 var previousBuyerName : String?=nil
336 if let pb= previousBuyer {
337 previousBuyerName = FIND.reverseLookup(pb)
338 }
339
340 let buyer=newOffer.address
341
342 let buyerName=FIND.reverseLookup(buyer)
343 let profile = Profile.find(buyer)
344 emit EnglishAuction(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:seller, sellerName: FIND.reverseLookup(seller), amount: newOfferBalance, auctionReservePrice: saleItem.auctionReservePrice, status: status, vaultType:saleItem.vaultType.identifier, nft: nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(),startsAt: saleItem.auctionStartedAt, endsAt: saleItem.auctionEndsAt, previousBuyer: previousBuyer, previousBuyerName:previousBuyerName)
345
346
347 }
348
349 access(contract) fun registerIncreasedBid(_ id: UInt64, oldBalance:UFix64) {
350 if !self.items.containsKey(id) {
351 panic("Invalid id=".concat(id.toString()))
352 }
353
354 let saleItem=self.borrow(id)
355
356 if !saleItem.hasAuctionStarted() {
357 panic("Auction is not started")
358 }
359
360 if saleItem.hasAuctionEnded() {
361 panic("Auction has ended")
362 }
363
364 self.addBid(id: id, newOffer: saleItem.offerCallback!, oldBalance:oldBalance)
365 }
366
367 //This is a function that buyer will call (via his bid collection) to register the bicCallback with the seller
368 access(contract) fun registerBid(item: FindViews.ViewReadPointer, callback: Capability<&MarketBidCollection>, vaultType: Type) {
369
370 let timestamp=Clock.time()
371
372 let id = item.getUUID()
373
374 let saleItem=self.borrowAuth(id)
375
376 if saleItem.hasAuctionStarted() {
377 if saleItem.hasAuctionEnded() {
378 panic("Auction has ended")
379 }
380
381 if let cb = saleItem.offerCallback {
382 if cb.address == callback.address {
383 panic("You already have the latest bid on this item, use the incraseBid transaction")
384 }
385 }
386
387 self.addBid(id: id, newOffer: callback, oldBalance: saleItem.auctionStartPrice)
388 return
389 }
390
391 // If the auction is not started but the start time is set, it falls in here
392 if let startTime = saleItem.auctionStartedAt {
393 panic("Auction is not yet started, please place your bid after ".concat(startTime.toString()))
394 }
395
396 let tenant=self.getTenant()
397 let nftType= saleItem.getItemType()
398 let ftType= saleItem.getFtType()
399
400 let actionResult=tenant.allowedAction(listingType: Type<@FindMarketAuctionEscrow.SaleItem>(), nftType: nftType, ftType: ftType, action: FindMarket.MarketAction(listing:false, name: "bid in auction"), seller: self.owner!.address, buyer: callback.address)
401
402 if !actionResult.allowed {
403 panic(actionResult.message)
404 }
405
406 let balance=callback.borrow()?.getBalance(id) ?? panic("Bidder unlinked bid collection capability. bidder address : ".concat(callback.address.toString()))
407
408 if saleItem.auctionStartPrice > balance {
409 panic("You need to bid more then the starting price of ".concat(saleItem.auctionStartPrice.toString()))
410 }
411
412 if let valid = saleItem.getValidUntil() {
413 if valid < Clock.time() {
414 panic("This auction listing is already expired")
415 }
416 }
417 saleItem.setCallback(callback)
418 let duration=saleItem.auctionDuration
419 let endsAt=timestamp + duration
420 saleItem.setAuctionStarted(timestamp)
421 saleItem.setAuctionEnds(endsAt)
422
423 let status="active_ongoing"
424 let seller=self.owner!.address
425 let buyer=callback.address
426
427 let nftInfo=saleItem.toNFTInfo(true)
428
429 let buyerName=FIND.reverseLookup(buyer)
430 let profile = Profile.find(buyer)
431 emit EnglishAuction(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:seller, sellerName: FIND.reverseLookup(seller), amount: balance, auctionReservePrice: saleItem.auctionReservePrice, status: status, vaultType:saleItem.vaultType.identifier, nft: nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(),startsAt: saleItem.auctionStartedAt, endsAt: saleItem.auctionEndsAt, previousBuyer: nil, previousBuyerName:nil)
432
433 }
434
435 access(Seller) fun cancel(_ id: UInt64) {
436
437 if !self.items.containsKey(id) {
438 panic("Invalid id=".concat(id.toString()))
439 }
440
441 let saleItem=self.borrow(id)
442
443 var status = "cancel_listing"
444 if saleItem.checkPointer() {
445 if !saleItem.validateRoyalties() {
446 // this has to be here otherwise people cannot delist
447 status="cancel_royalties_changed"
448 } else if saleItem.hasAuctionStarted() && saleItem.hasAuctionEnded() {
449 if saleItem.hasAuctionMetReservePrice() {
450 panic("Cannot cancel finished auction, fulfill it instead")
451 }
452 status="cancel_reserved_not_met"
453 }
454 } else {
455 status = "cancel_ghostlisting"
456 }
457
458 self.internalCancelAuction(saleItem: saleItem, status: status)
459
460 }
461
462 access(Seller) fun relist(_ id: UInt64) {
463 let saleItem = self.borrow(id)
464 let pointer= saleItem.getPointer()
465 let vaultType= saleItem.vaultType
466 let auctionStartPrice= saleItem.auctionStartPrice
467 let auctionReservePrice= saleItem.auctionReservePrice
468 let auctionDuration = saleItem.auctionDuration
469 let auctionExtensionOnLateBid = saleItem.auctionExtensionOnLateBid
470 let minimumBidIncrement = saleItem.auctionMinBidIncrement
471 var auctionValidUntil= saleItem.auctionValidUntil
472 let currentTime = Clock.time()
473 if auctionValidUntil != nil && auctionValidUntil! <= currentTime {
474 auctionValidUntil = nil
475 }
476 var auctionStartedAt = saleItem.auctionStartedAt
477 if auctionStartedAt != nil && auctionStartedAt! <= currentTime {
478 auctionStartedAt = nil
479 }
480 let saleItemExtraField= saleItem.getExtraFields()
481
482 self.cancel(id)
483 self.listForAuction(pointer: pointer, vaultType: vaultType, auctionStartPrice: auctionStartPrice, auctionReservePrice: auctionReservePrice, auctionDuration: auctionDuration, auctionExtensionOnLateBid: auctionExtensionOnLateBid, minimumBidIncrement: minimumBidIncrement, auctionStartTime: auctionStartedAt, auctionValidUntil: auctionValidUntil, saleItemExtraField: saleItemExtraField)
484
485 }
486
487 access(self) fun internalCancelAuction(saleItem: &SaleItem, status:String) {
488
489 let status=status
490 let ftType=saleItem.getFtType()
491 let balance=saleItem.getBalance()
492 let seller=saleItem.getSeller()
493 let id=saleItem.getId()
494
495 let tenant=self.getTenant()
496
497 var nftInfo:FindMarket.NFTInfo?=nil
498 if saleItem.checkPointer() {
499 nftInfo=saleItem.toNFTInfo(false)
500 }
501
502 let buyer=saleItem.getBuyer()
503 if buyer != nil {
504 let buyerName=FIND.reverseLookup(buyer!)
505 let profile = Profile.find(buyer!)
506 emit EnglishAuction(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:seller, sellerName: FIND.reverseLookup(seller), amount: balance, auctionReservePrice: saleItem.auctionReservePrice, status: status, vaultType:saleItem.vaultType.identifier, nft: nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(),startsAt: saleItem.auctionStartedAt, endsAt: saleItem.auctionEndsAt, previousBuyer: nil, previousBuyerName:nil)
507 } else {
508 emit EnglishAuction(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:seller, sellerName: FIND.reverseLookup(seller), amount: balance, auctionReservePrice: saleItem.auctionReservePrice, status: status, vaultType:saleItem.vaultType.identifier, nft: nftInfo, buyer: nil, buyerName: nil, buyerAvatar:nil,startsAt: saleItem.auctionStartedAt, endsAt: saleItem.auctionEndsAt, previousBuyer:nil, previousBuyerName:nil)
509 }
510
511 if saleItem.offerCallback != nil && saleItem.offerCallback!.check() {
512 saleItem.offerCallback!.borrow()!.cancelBidFromSaleItem(id)
513 }
514 destroy <- self.items.remove(key: id)
515 }
516
517 /// fulfillAuction wraps the fulfill method and ensure that only a finished auction can be fulfilled by anybody
518 access(all) fun fulfillAuction(_ id: UInt64) {
519
520 if !self.items.containsKey(id) {
521 panic("Invalid id=".concat(id.toString()))
522 }
523 if self.borrow(id).auctionStartPrice == nil {
524 panic("Cannot fulfill sale that is not an auction=".concat(id.toString()))
525 }
526
527 let saleItem = self.borrow(id)
528
529 if saleItem.hasAuctionStarted() {
530 if !saleItem.hasAuctionEnded() {
531 panic("Auction has not ended yet")
532 }
533
534 let tenant=self.getTenant()
535 let nftType= saleItem.getItemType()
536 let ftType= saleItem.getFtType()
537
538 let actionResult=tenant.allowedAction(listingType: Type<@FindMarketAuctionEscrow.SaleItem>(), nftType: nftType, ftType: ftType, action: FindMarket.MarketAction(listing:false, name: "fulfill auction"), seller: self.owner!.address, buyer: saleItem.offerCallback!.address)
539
540 if !actionResult.allowed {
541 panic(actionResult.message)
542 }
543
544 let cuts= tenant.getCuts(name: actionResult.name, listingType: Type<@FindMarketAuctionEscrow.SaleItem>(), nftType: nftType, ftType: ftType)
545
546 if !saleItem.hasAuctionMetReservePrice() {
547 self.internalCancelAuction(saleItem: saleItem, status: "cancel_reserved_not_met")
548 return
549 }
550
551 let nftInfo= saleItem.toNFTInfo(true)
552 let royalty=saleItem.getRoyalty()
553
554 let status="sold"
555 let balance=saleItem.getBalance()
556 let seller=self.owner!.address
557
558 let buyer=saleItem.getBuyer()!
559
560 let buyerName=FIND.reverseLookup(buyer)
561 let sellerName = FIND.reverseLookup(seller)
562 let profile = Profile.find(buyer)
563 emit EnglishAuction(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:seller, sellerName: sellerName, amount: balance, auctionReservePrice: saleItem.auctionReservePrice, status: status, vaultType:ftType.identifier, nft: nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), startsAt: saleItem.auctionStartedAt, endsAt: saleItem.auctionEndsAt, previousBuyer: nil, previousBuyerName:nil)
564
565 let vault <- saleItem.acceptEscrowedBid()
566
567 let resolved : {Address : String} = {}
568 resolved[buyer] = buyerName ?? ""
569 resolved[seller] = sellerName ?? ""
570 resolved[FindMarketAuctionEscrow.account.address] = "find"
571 // Have to make sure the tenant always have the valid find name
572 resolved[FindMarket.tenantNameAddress[tenant.name]!] = tenant.name
573
574 FindMarket.pay(tenant:tenant.name, id:id, saleItem: saleItem, vault: <- vault, royalty:royalty, nftInfo:nftInfo, cuts:cuts, resolver: fun(address:Address): String? { return FIND.reverseLookup(address) }, resolvedAddress: resolved)
575
576 destroy <- self.items.remove(key: id)
577 return
578 }
579 panic("This auction is not live")
580
581 }
582
583 access(Seller) fun listForAuction(pointer: FindViews.AuthNFTPointer, vaultType: Type, auctionStartPrice: UFix64, auctionReservePrice: UFix64, auctionDuration: UFix64, auctionExtensionOnLateBid: UFix64, minimumBidIncrement: UFix64, auctionStartTime: UFix64?, auctionValidUntil: UFix64?, saleItemExtraField: {String : AnyStruct}) {
584
585 // ensure it is not a 0 dollar listing
586 if auctionStartPrice <= 0.0 {
587 panic("Auction start price should be greater than 0")
588 }
589
590 // ensure it is not a 0 dollar listing
591 if auctionReservePrice < auctionStartPrice {
592 panic("Auction reserve price should be greater than Auction start price")
593 }
594
595 let currentTime = Clock.time()
596 // ensure validUntil is valid
597 if auctionValidUntil != nil && auctionValidUntil! < currentTime {
598 panic("Valid until is before current time")
599 }
600
601 // if we do this, the auctionStartTime variable from arg is gone
602 var auctionStartTime = auctionStartTime
603 // ensure startTime is valid, if auctionStartTime is < currentTime, make it currentTIme (might not be easy to pass in exact time)
604 if auctionStartTime != nil && auctionStartTime! < currentTime {
605 auctionStartTime = currentTime
606 }
607
608 // check soul bound
609 if pointer.checkSoulBound() {
610 panic("This item is soul bounded and cannot be traded")
611 }
612
613 let saleItem <- create SaleItem(pointer: pointer, vaultType:vaultType, auctionStartPrice: auctionStartPrice, auctionReservePrice:auctionReservePrice, auctionDuration: auctionDuration, extentionOnLateBid: auctionExtensionOnLateBid, minimumBidIncrement:minimumBidIncrement, auctionValidUntil: auctionValidUntil, saleItemExtraField: saleItemExtraField)
614
615 // if startTime is set, start the auction at the specified time with intended auction duration
616 if auctionStartTime != nil {
617 saleItem.setAuctionStarted(auctionStartTime!)
618 let endTime = auctionStartTime! + auctionDuration
619 saleItem.setAuctionEnds(endTime)
620 }
621
622 let tenant=self.getTenant()
623
624 // Check if it is onefootball. If so, listing has to be at least $0.65 (DUC)
625 if tenant.name == "onefootball" {
626 // ensure it is not a 0 dollar listing
627 if auctionStartPrice <= 0.65 {
628 panic("Auction start price should be greater than 0.65")
629 }
630 }
631
632 let nftType= saleItem.getItemType()
633 let ftType= saleItem.getFtType()
634
635 let actionResult=tenant.allowedAction(listingType: Type<@FindMarketAuctionEscrow.SaleItem>(), nftType: nftType, ftType: ftType, action: FindMarket.MarketAction(listing:true, name: "list item for auction"), seller: self.owner!.address, buyer: nil)
636
637 if !actionResult.allowed {
638 panic(actionResult.message)
639 }
640 let id=pointer.getUUID()
641
642 if self.items[id] != nil {
643 panic("Auction listing for this item is already created.")
644 }
645
646 self.items[id] <-! saleItem
647 let saleItemRef = self.borrow(id)
648
649 var status = "active_listed"
650 let balance=auctionStartPrice
651 let seller=self.owner!.address
652 if auctionStartTime != nil {
653 if auctionStartTime == currentTime {
654 status = "active_ongoing"
655 } else {
656 status = "inactive_listed"
657 }
658 }
659
660 let nftInfo=saleItemRef.toNFTInfo(true)
661
662 emit EnglishAuction(tenant:tenant.name, id: id, saleID: saleItemRef.uuid, seller:seller, sellerName: FIND.reverseLookup(seller), amount: balance, auctionReservePrice: saleItemRef.auctionReservePrice, status: status, vaultType:ftType.identifier, nft: nftInfo, buyer: nil, buyerName: nil, buyerAvatar:nil, startsAt: saleItemRef.auctionStartedAt, endsAt: saleItemRef.auctionEndsAt, previousBuyer:nil, previousBuyerName:nil)
663
664 }
665
666 access(all) fun getIds(): [UInt64] {
667 return self.items.keys
668 }
669
670 access(all) fun getRoyaltyChangedIds(): [UInt64] {
671 let ids : [UInt64] = []
672 for id in self.getIds() {
673 let item = self.borrow(id)
674 if !item.validateRoyalties() {
675 ids.append(id)
676 }
677 }
678 return ids
679 }
680
681 access(all) fun containsId(_ id: UInt64): Bool {
682 return self.items.containsKey(id)
683 }
684
685 access(all) fun borrow(_ id: UInt64): &SaleItem {
686 if !self.items.containsKey(id) {
687 panic("This id does not exist.".concat(id.toString()))
688 }
689 return (&self.items[id])!
690 }
691
692 access(Seller) fun borrowAuth(_ id: UInt64): auth(Seller) &SaleItem {
693 if !self.items.containsKey(id) {
694 panic("This id does not exist.".concat(id.toString()))
695 }
696 return (&self.items[id])!
697 }
698
699 access(all) fun borrowSaleItem(_ id: UInt64) : &{FindMarket.SaleItem} {
700 if !self.items.containsKey(id) {
701 panic("This id does not exist.".concat(id.toString()))
702 }
703 return (&self.items[id])!
704 }
705 }
706
707 access(all) resource Bid : FindMarket.Bid {
708 access(contract) let from: Capability<&SaleItemCollection>
709 access(contract) let nftCap: Capability<&{NonFungibleToken.Receiver}>
710 access(contract) let itemUUID: UInt64
711
712 //this should reflect on what the above uuid is for
713 access(contract) let vault: @{FungibleToken.Vault}
714 access(contract) let vaultType: Type
715 access(contract) var bidAt: UFix64
716 access(contract) let bidExtraField: {String : AnyStruct}
717
718 init(from: Capability<&SaleItemCollection>, itemUUID: UInt64, vault: @{FungibleToken.Vault}, nftCap: Capability<&{NonFungibleToken.Receiver}> , bidExtraField: {String : AnyStruct}) {
719 self.vaultType= vault.getType()
720 self.vault <- vault
721 self.itemUUID=itemUUID
722 self.from=from
723 self.bidAt=Clock.time()
724 self.nftCap=nftCap
725 self.bidExtraField=bidExtraField
726 }
727
728 access(contract) fun setBidAt(_ time: UFix64) {
729 self.bidAt=time
730 }
731
732 access(all) fun getBalance() : UFix64 {
733 return self.vault.balance
734 }
735
736 access(all) fun getSellerAddress() : Address {
737 return self.from.address
738 }
739
740 access(all) fun getBidExtraField() : {String : AnyStruct} {
741 return self.bidExtraField
742 }
743 }
744
745 access(all) resource interface MarketBidCollectionPublic {
746 access(all) fun getBalance(_ id: UInt64) : UFix64
747 access(all) fun containsId(_ id: UInt64): Bool
748 access(contract) fun accept(_ nft: @{NonFungibleToken.NFT}, path:PublicPath) : @{FungibleToken.Vault}
749 access(contract) fun cancelBidFromSaleItem(_ id: UInt64)
750 }
751
752 // A Buyer can bid, increase bid and fulfill auctions
753 access(all) entitlement Buyer
754
755 //A collection stored for bidders/buyers
756 access(all) resource MarketBidCollection: MarketBidCollectionPublic, FindMarket.MarketBidCollectionPublic {
757
758 access(contract) var bids : @{UInt64: Bid}
759 access(contract) let receiver: Capability<&{FungibleToken.Receiver}>
760 access(contract) let tenantCapability: Capability<&FindMarket.Tenant>
761
762 //not sure we can store this here anymore. think it needs to be in every bid
763 init(receiver: Capability<&{FungibleToken.Receiver}>, tenantCapability: Capability<&FindMarket.Tenant>) {
764 self.bids <- {}
765 self.receiver=receiver
766 self.tenantCapability=tenantCapability
767 }
768
769 access(self) fun getTenant() : &FindMarket.Tenant {
770 if !self.tenantCapability.check() {
771 panic("Tenant client is not linked anymore")
772 }
773 return self.tenantCapability.borrow()!
774 }
775
776 //called from lease when auction is ended
777 access(contract) fun accept(_ nft: @{NonFungibleToken.NFT}, path:PublicPath) : @{FungibleToken.Vault} {
778 let id= nft.id
779 let bid <- self.bids.remove(key: nft.uuid) ?? panic("missing bid")
780 let vaultRef = &bid.vault as auth(FungibleToken.Withdraw) &{FungibleToken.Vault}
781 let nftCap = bid.nftCap
782 if !nftCap.check() {
783 let cpCap =getAccount(nftCap.address).capabilities.get<&{NonFungibleToken.Collection}>(path)!
784 if !cpCap.check() {
785 panic("Bidder unlinked the nft receiver capability. bidder address : ".concat(bid.nftCap.address.toString()))
786 } else {
787 bid.nftCap.borrow()!.deposit(token: <- nft)
788 }
789 } else {
790 bid.nftCap.borrow()!.deposit(token: <- nft)
791 }
792 let vault <- vaultRef.withdraw(amount: vaultRef.balance)
793 destroy bid
794 return <- vault
795 }
796
797 access(all) fun getIds() : [UInt64] {
798 return self.bids.keys
799 }
800
801 access(all) fun containsId(_ id: UInt64) : Bool {
802 return self.bids.containsKey(id)
803 }
804
805 access(all) fun getBidType() : Type {
806 return Type<@Bid>()
807 }
808
809 access(Buyer) fun bid(item: FindViews.ViewReadPointer, vault: @{FungibleToken.Vault}, nftCap: Capability<&{NonFungibleToken.Receiver}>, bidExtraField: {String : AnyStruct}) {
810
811 if self.owner!.address == item.owner() {
812 panic("You cannot bid on your own resource")
813 }
814
815 let uuid=item.getUUID()
816
817 if self.bids[uuid] != nil {
818 panic("You already have an bid for this item, use increaseBid on that bid")
819 }
820
821 let tenant=self.getTenant()
822 let from=getAccount(item.owner()).capabilities.get<&SaleItemCollection>(tenant.getPublicPath(Type<@SaleItemCollection>()))!
823 let vaultType=vault.getType()
824
825 let bid <- create Bid(from: from, itemUUID:uuid, vault: <- vault, nftCap: nftCap, bidExtraField: bidExtraField)
826 let saleItemCollection= from.borrow() ?? panic("Could not borrow sale item for id=".concat(uuid.toString()))
827
828 let callbackCapability =self.owner!.capabilities.get<&MarketBidCollection>(tenant.getPublicPath(Type<@MarketBidCollection>()))!
829 let oldToken <- self.bids[uuid] <- bid
830 saleItemCollection.registerBid(item: item, callback: callbackCapability, vaultType: vaultType)
831 destroy oldToken
832 }
833
834 access(all) fun fulfillAuction(_ id:UInt64) {
835 if self.bids[id] == nil {
836 panic("You need to have a bid here already")
837 }
838 let bid =self.borrowBid(id)
839 let saleItem=bid.from.borrow()!
840 saleItem.fulfillAuction(id)
841 }
842
843 access(Buyer) fun increaseBid(id: UInt64, vault: @{FungibleToken.Vault}) {
844 if self.bids[id] == nil {
845 panic("You need to have a bid here already")
846 }
847 let bid =self.borrowBid(id)
848
849 let oldBalance=bid.vault.balance
850
851 bid.setBidAt(Clock.time())
852 bid.vault.deposit(from: <- vault)
853 if !bid.from.check() {
854 panic("Seller unlinked SaleItem collection capability. seller address : ".concat(bid.from.address.toString()))
855 }
856 bid.from.borrow()!.registerIncreasedBid(id, oldBalance:oldBalance)
857 }
858
859 //called from saleItem when things are cancelled
860 //if the bid is canceled from seller then we move the vault tokens back into your vault
861 access(contract) fun cancelBidFromSaleItem(_ id: UInt64) {
862 let bid <- self.bids.remove(key: id) ?? panic("missing bid")
863 let vaultRef = &bid.vault as auth(FungibleToken.Withdraw) &{FungibleToken.Vault}
864 if !self.receiver.check() {
865 panic("Seller unlinked the SaleItem collection capability. seller address : ".concat(self.receiver.address.toString()))
866 }
867 self.receiver.borrow()!.deposit(from: <- vaultRef.withdraw(amount: vaultRef.balance))
868 destroy bid
869 }
870
871 access(all) fun borrowBid(_ id: UInt64): &Bid {
872 if !self.bids.containsKey(id) {
873 panic("This id does not exist.".concat(id.toString()))
874 }
875 return (&self.bids[id])!
876 }
877
878 access(all) fun borrowBidItem(_ id: UInt64): &{FindMarket.Bid} {
879 if !self.bids.containsKey(id) {
880 panic("This id does not exist.".concat(id.toString()))
881 }
882 return (&self.bids[id])!
883 }
884
885 access(all) fun getBalance(_ id: UInt64) : UFix64 {
886 let bid= self.borrowBid(id)
887 return bid.vault.balance
888 }
889 }
890
891 //Create an empty lease collection that store your leases to a name
892 access(all) fun createEmptySaleItemCollection(_ tenantCapability: Capability<&FindMarket.Tenant>) : @SaleItemCollection {
893 return <- create SaleItemCollection(tenantCapability)
894 }
895
896 access(all) fun createEmptyMarketBidCollection(receiver: Capability<&{FungibleToken.Receiver}>, tenantCapability: Capability<&FindMarket.Tenant>) : @MarketBidCollection {
897 return <- create MarketBidCollection(receiver: receiver, tenantCapability:tenantCapability)
898 }
899
900 access(all) fun getSaleItemCapability(marketplace:Address, user:Address) : Capability<&{SaleItemCollectionPublic, FindMarket.SaleItemCollectionPublic}>? {
901 if FindMarket.getTenantCapability(marketplace) == nil {
902 panic("Invalid tenant")
903 }
904 if let tenant=FindMarket.getTenantCapability(marketplace)!.borrow() {
905 return getAccount(user).capabilities.get<&{SaleItemCollectionPublic, FindMarket.SaleItemCollectionPublic}>(tenant.getPublicPath(Type<@SaleItemCollection>()))
906 }
907 return nil
908 }
909
910 access(all) fun getBidCapability( marketplace:Address, user:Address) : Capability<&{MarketBidCollectionPublic, FindMarket.MarketBidCollectionPublic}>? {
911 if FindMarket.getTenantCapability(marketplace) == nil {
912 panic("Invalid tenant")
913 }
914 if let tenant=FindMarket.getTenantCapability(marketplace)!.borrow() {
915 return getAccount(user).capabilities.get<&{MarketBidCollectionPublic, FindMarket.MarketBidCollectionPublic}>(tenant.getPublicPath(Type<@MarketBidCollection>()))
916 }
917 return nil
918 }
919
920 init() {
921 FindMarket.addSaleItemType(Type<@SaleItem>())
922 FindMarket.addSaleItemCollectionType(Type<@SaleItemCollection>())
923 FindMarket.addMarketBidType(Type<@Bid>())
924 FindMarket.addMarketBidCollectionType(Type<@MarketBidCollection>())
925 }
926}
927