Smart Contract

FindMarketAuctionEscrow

A.097bafa4e0b48eef.FindMarketAuctionEscrow

Deployed

2d ago
Feb 26, 2026, 03:12:51 AM UTC

Dependents

0 imports
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