Smart Contract

FindMarketDirectOfferEscrow

A.097bafa4e0b48eef.FindMarketDirectOfferEscrow

Deployed

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

Dependents

12 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3import MetadataViews from 0x1d7e57aa55817448
4import FindViews from 0x097bafa4e0b48eef
5import Clock from 0x097bafa4e0b48eef
6import Debug from 0x097bafa4e0b48eef
7import FIND from 0x097bafa4e0b48eef
8import FindMarket from 0x097bafa4e0b48eef
9import Profile from 0x097bafa4e0b48eef
10
11access(all) contract FindMarketDirectOfferEscrow {
12
13    access(all) event DirectOffer(tenant: String, id: UInt64, saleID: UInt64, seller: Address, sellerName: String?, amount: UFix64, status: String, vaultType:String, nft: FindMarket.NFTInfo?, buyer:Address?, buyerName:String?, buyerAvatar: String?, endsAt: UFix64?, previousBuyer:Address?, previousBuyerName:String?)
14
15    access(all) entitlement Seller
16
17    access(all) resource SaleItem : FindMarket.SaleItem {
18
19        access(contract) var pointer: {FindViews.Pointer}
20
21        access(contract) var offerCallback: Capability<&MarketBidCollection>
22        access(contract) var validUntil: UFix64?
23        access(contract) var saleItemExtraField: {String : AnyStruct}
24        access(contract) let totalRoyalties: UFix64
25
26        init(pointer: {FindViews.Pointer}, callback: Capability<&MarketBidCollection>, validUntil: UFix64?, saleItemExtraField: {String : AnyStruct}) {
27            self.pointer=pointer
28            self.offerCallback=callback
29            self.validUntil=validUntil
30            self.saleItemExtraField=saleItemExtraField
31            self.totalRoyalties=self.pointer.getTotalRoyaltiesCut()
32        }
33
34        access(all) fun getId() : UInt64{
35            return self.pointer.getUUID()
36        }
37
38        access(contract) fun acceptEscrowedBid() : @{FungibleToken.Vault} {
39            if !self.offerCallback.check() {
40                panic("Bidder unlinked bid collection capability. Bidder Address : ".concat(self.offerCallback.address.toString()))
41            }
42            let pointer= self.pointer as! FindViews.AuthNFTPointer
43            let publicPath = pointer.getNFTCollectionData().publicPath
44            let vault <- self.offerCallback.borrow()!.accept(<- pointer.withdraw(), path:publicPath)
45            return <- vault
46        }
47
48        access(all) fun getRoyalty() : MetadataViews.Royalties {
49            return self.pointer.getRoyalty()
50        }
51
52        access(all) fun getBalance() : UFix64 {
53            if !self.offerCallback.check() {
54                panic("Bidder unlinked bid collection capability. Bidder Address : ".concat(self.offerCallback.address.toString()))
55            }
56            return self.offerCallback.borrow()!.getBalance(self.getId())
57        }
58
59        access(all) fun getSeller() : Address {
60            return self.pointer.owner()
61        }
62
63        access(all) fun getSellerName() : String? {
64            let address = self.pointer.owner()
65            return FIND.reverseLookup(address)
66        }
67
68
69        access(all) fun getBuyer() : Address? {
70            return self.offerCallback.address
71        }
72
73        access(all) fun getBuyerName() : String? {
74            if let name = FIND.reverseLookup(self.offerCallback.address) {
75                return name
76            }
77            return nil
78        }
79
80        access(all) fun toNFTInfo(_ detail: Bool) : FindMarket.NFTInfo{
81            return FindMarket.NFTInfo(self.pointer.getViewResolver(), id: self.pointer.id, detail:detail)
82        }
83
84        access(all) fun getSaleType() : String {
85            return "active_ongoing"
86        }
87
88        access(all) fun getListingType() : Type {
89            return Type<@SaleItem>()
90        }
91
92        access(all) fun getListingTypeIdentifier() : String {
93            return Type<@SaleItem>().identifier
94        }
95
96        access(contract) fun setPointer(_ pointer: FindViews.AuthNFTPointer) {
97            self.pointer=pointer
98        }
99
100        access(all) fun getItemID() : UInt64 {
101            return self.pointer.id
102        }
103
104        access(all) fun getItemType() : Type {
105            return self.pointer.getItemType()
106        }
107
108        access(all) fun getAuction(): FindMarket.AuctionItem? {
109            return nil
110        }
111
112        access(all) fun getFtType() : Type  {
113            if !self.offerCallback.check() {
114                panic("Bidder unlinked bid collection capability. Bidder Address : ".concat(self.offerCallback.address.toString()))
115            }
116            return self.offerCallback.borrow()!.getVaultType(self.getId())
117        }
118
119        access(contract) fun setValidUntil(_ time: UFix64?) {
120            self.validUntil=time
121        }
122
123        access(all) fun getValidUntil() : UFix64? {
124            return self.validUntil
125        }
126
127        access(contract) fun setCallback(_ callback: Capability<&MarketBidCollection>) {
128            self.offerCallback=callback
129        }
130
131        access(all) fun checkPointer() : Bool {
132            return self.pointer.valid()
133        }
134
135        access(all) fun checkSoulBound() : Bool {
136            return self.pointer.checkSoulBound()
137        }
138
139        access(all) fun getSaleItemExtraField() : {String : AnyStruct} {
140            return self.saleItemExtraField
141        }
142
143        access(contract) fun setSaleItemExtraField(_ field: {String : AnyStruct}) {
144            self.saleItemExtraField = field
145        }
146
147        access(all) fun getTotalRoyalties() : UFix64 {
148            return self.totalRoyalties
149        }
150
151        access(all) fun validateRoyalties() : Bool {
152            return self.totalRoyalties == self.pointer.getTotalRoyaltiesCut()
153        }
154
155        access(all) fun getDisplay() : MetadataViews.Display {
156            return self.pointer.getDisplay()
157        }
158
159        access(all) fun getNFTCollectionData() : MetadataViews.NFTCollectionData {
160            return self.pointer.getNFTCollectionData()
161        }
162    }
163
164    access(all) resource interface SaleItemCollectionPublic {
165        //fetch all the tokens in the collection
166        access(all) fun getIds(): [UInt64]
167        access(all) fun containsId(_ id: UInt64): Bool
168        access(contract)fun cancelBid(_ id: UInt64)
169        access(contract) fun registerIncreasedBid(_ id: UInt64)
170
171        //place a bid on a token
172        access(contract) fun registerBid(item: FindViews.ViewReadPointer, callback: Capability<&MarketBidCollection>, validUntil: UFix64?, saleItemExtraField: {String : AnyStruct})
173    }
174
175    access(all) resource SaleItemCollection: SaleItemCollectionPublic, FindMarket.SaleItemCollectionPublic {
176        //is this the best approach now or just put the NFT inside the saleItem?
177        access(contract) var items: @{UInt64: SaleItem}
178
179        access(contract) let tenantCapability: Capability<&FindMarket.Tenant>
180
181        init (_ tenantCapability: Capability<&FindMarket.Tenant>) {
182            self.items <- {}
183            self.tenantCapability=tenantCapability
184        }
185
186        access(self) fun getTenant() : &FindMarket.Tenant {
187            if !self.tenantCapability.check() {
188                panic("Tenant client is not linked anymore")
189            }
190            return self.tenantCapability.borrow()!
191        }
192
193        access(all) fun getListingType() : Type {
194            return Type<@SaleItem>()
195        }
196
197        //this is called when a buyer cancel a direct offer
198        access(contract) fun cancelBid(_ id: UInt64) {
199            if !self.items.containsKey(id) {
200                panic("Invalid id=".concat(id.toString()))
201            }
202            let saleItem=self.borrow(id)
203
204            let tenant=self.getTenant()
205            let ftType= saleItem.getFtType()
206
207            let status="cancel"
208            let owner=self.owner!.address
209            let balance=saleItem.getBalance()
210            let buyer=saleItem.getBuyer()!
211            let buyerName=FIND.reverseLookup(buyer)
212            let profile = Profile.find(buyer)
213
214            var nftInfo:FindMarket.NFTInfo?=nil
215            if saleItem.checkPointer() {
216                nftInfo=saleItem.toNFTInfo(false)
217            }
218
219            emit DirectOffer(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:owner, sellerName: FIND.reverseLookup(owner), amount: balance, status:status, vaultType: ftType.identifier, nft:nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), endsAt: saleItem.validUntil, previousBuyer:nil, previousBuyerName:nil)
220
221            destroy <- self.items.remove(key: id)
222        }
223
224        //The only thing we do here is basically register an event
225        access(contract) fun registerIncreasedBid(_ id: UInt64) {
226
227            if !self.items.containsKey(id) {
228                panic("Invalid id=".concat(id.toString()))
229            }
230            let saleItem=self.borrow(id)
231
232            let tenant=self.getTenant()
233            let nftType= saleItem.getItemType()
234            let ftType= saleItem.getFtType()
235
236            let actionResult=tenant.allowedAction(listingType: Type<@FindMarketDirectOfferEscrow.SaleItem>(), nftType: nftType, ftType: ftType, action: FindMarket.MarketAction(listing:true, name: "add bid in direct offer"), seller: self.owner!.address, buyer: saleItem.offerCallback.address)
237
238            if !actionResult.allowed {
239                panic(actionResult.message)
240            }
241
242            let status="active_offered"
243            let owner=self.owner!.address
244            let balance=saleItem.getBalance()
245            let buyer=saleItem.getBuyer()!
246            let buyerName=FIND.reverseLookup(buyer)
247            let profile = Profile.find(buyer)
248
249            let nftInfo=saleItem.toNFTInfo(true)
250
251            emit DirectOffer(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:owner, sellerName: FIND.reverseLookup(owner), amount: balance, status:status, vaultType: ftType.identifier, nft:nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), endsAt: saleItem.validUntil, previousBuyer:nil, previousBuyerName:nil)
252
253        }
254
255        //This is a function that buyer will call (via his bid collection) to register the bicCallback with the seller
256        access(contract) fun registerBid(item: FindViews.ViewReadPointer, callback: Capability<&MarketBidCollection>, validUntil: UFix64?, saleItemExtraField: {String : AnyStruct}) {
257
258            let id = item.getUUID()
259
260            //If there are no bids from anybody else before we need to make the item
261            if !self.items.containsKey(id) {
262
263                let saleItem <- create SaleItem(pointer: item, callback: callback, validUntil: validUntil, saleItemExtraField: saleItemExtraField)
264
265                let tenant=self.getTenant()
266                let nftType= saleItem.getItemType()
267                let ftType= saleItem.getFtType()
268
269                let actionResult=tenant.allowedAction(listingType: Type<@FindMarketDirectOfferEscrow.SaleItem>(), nftType: nftType, ftType: ftType, action: FindMarket.MarketAction(listing:true, name: "bid in direct offer"), seller: self.owner!.address, buyer: callback.address)
270
271                if !actionResult.allowed {
272                    panic(actionResult.message)
273                }
274
275                self.items[id] <-! saleItem
276                let saleItemRef=self.borrow(id)
277
278                let status="active_offered"
279                let owner=self.owner!.address
280                let balance=saleItemRef.getBalance()
281                let buyer=callback.address
282                let buyerName=FIND.reverseLookup(buyer)
283                let profile = Profile.find(buyer)
284
285                let nftInfo=saleItemRef.toNFTInfo(true)
286
287                emit DirectOffer(tenant:tenant.name, id: id, saleID: saleItemRef.uuid, seller:owner, sellerName: FIND.reverseLookup(owner), amount: balance, status:status, vaultType: ftType.identifier, nft:nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), endsAt: saleItemRef.validUntil, previousBuyer:nil, previousBuyerName:nil)
288
289                return
290            }
291
292            let saleItem=self.borrow(id)
293
294            if self.borrow(id).getBuyer()! == callback.address {
295                panic("You already have the latest bid on this item, use the incraseBid transaction")
296            }
297
298            let tenant=self.getTenant()
299            let nftType= saleItem.getItemType()
300            let ftType= saleItem.getFtType()
301
302            let actionResult=tenant.allowedAction(listingType: Type<@FindMarketDirectOfferEscrow.SaleItem>(), nftType: nftType, ftType: ftType, action: FindMarket.MarketAction(listing:true, name: "bid in direct offer"), seller: self.owner!.address, buyer: callback.address)
303
304            if !actionResult.allowed {
305                panic(actionResult.message)
306            }
307
308            let balance=callback.borrow()?.getBalance(id) ?? panic("Bidder unlinked the bid collection capability. bidder address : ".concat(callback.address.toString()))
309
310            let currentBalance=saleItem.getBalance()
311            Debug.log("currentBalance=".concat(currentBalance.toString()).concat(" new bid is at=").concat(balance.toString()))
312            if currentBalance >= balance {
313                panic("There is already a higher bid on this item")
314            }
315            //somebody else has the highest item so we cancel it
316            let previousBuyer=saleItem.offerCallback.address
317            let previousCB = saleItem.offerCallback.borrow() ?? panic("Previous bidder unlinked the bid collection capability. bidder address : ".concat(previousBuyer.toString()))
318            previousCB.cancelBidFromSaleItem(id)
319            saleItem.setValidUntil(validUntil)
320            saleItem.setCallback(callback)
321
322            let status="active_offered"
323            let owner=self.owner!.address
324            let buyer=callback.address
325            let buyerName=FIND.reverseLookup(buyer)
326            let profile = Profile.find(buyer)
327
328            let nftInfo=saleItem.toNFTInfo(true)
329
330            let previousBuyerName = FIND.reverseLookup(previousBuyer)
331
332            emit DirectOffer(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:owner, sellerName: FIND.reverseLookup(owner), amount: balance, status:status, vaultType: ftType.identifier, nft:nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), endsAt: saleItem.validUntil, previousBuyer:previousBuyer, previousBuyerName:previousBuyerName)
333
334
335        }
336
337        //cancel will reject a direct offer
338        access(Seller) fun cancel(_ id: UInt64) {
339
340            if !self.items.containsKey(id) {
341                panic("Invalid id=".concat(id.toString()))
342            }
343
344            let saleItem=self.borrow(id)
345
346            let tenant=self.getTenant()
347            let ftType= saleItem.getFtType()
348
349
350            var status="rejected"
351            let owner=self.owner!.address
352            let balance=saleItem.getBalance()
353            let buyer=saleItem.getBuyer()!
354            let buyerName=FIND.reverseLookup(buyer)
355            let profile = Profile.find(buyer)
356
357            var nftInfo:FindMarket.NFTInfo?=nil
358            if saleItem.checkPointer() {
359                nftInfo=saleItem.toNFTInfo(false)
360            }
361
362            emit DirectOffer(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:owner, sellerName: FIND.reverseLookup(owner), amount: balance, status:status, vaultType: ftType.identifier, nft:nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), endsAt: saleItem.validUntil, previousBuyer:nil, previousBuyerName:nil)
363
364            saleItem.offerCallback.borrow()!.cancelBidFromSaleItem(id)
365            destroy <- self.items.remove(key: id)
366        }
367
368        access(Seller) fun acceptDirectOffer(_ pointer: FindViews.AuthNFTPointer) {
369
370            let id = pointer.getUUID()
371            if !self.items.containsKey(id) {
372                panic("Invalid id=".concat(pointer.getUUID().toString()))
373            }
374            let saleItem = self.borrow(id)
375
376            if saleItem.validUntil != nil && saleItem.validUntil! < Clock.time() {
377                panic("This direct offer is already expired")
378            }
379
380            let tenant=self.getTenant()
381            let nftType= saleItem.getItemType()
382            let ftType= saleItem.getFtType()
383
384            let actionResult=tenant.allowedAction(listingType: Type<@FindMarketDirectOfferEscrow.SaleItem>(), nftType: nftType, ftType: ftType, action: FindMarket.MarketAction(listing:false, name: "fulfill directOffer"), seller: self.owner!.address, buyer: saleItem.offerCallback.address)
385
386            if !actionResult.allowed {
387                panic(actionResult.message)
388            }
389
390            let cuts= tenant.getCuts(name: actionResult.name, listingType: Type<@FindMarketDirectOfferEscrow.SaleItem>(), nftType: nftType, ftType: ftType)
391
392            //Set the auth pointer in the saleItem so that it now can be fulfilled
393            saleItem.setPointer(pointer)
394
395            let royalty=saleItem.getRoyalty()
396            let nftInfo=saleItem.toNFTInfo(true)
397
398            let status="sold"
399            let owner=saleItem.getSeller()
400            let balance=saleItem.getBalance()
401            let buyer=saleItem.getBuyer()!
402            let buyerName=FIND.reverseLookup(buyer)
403            let sellerName=FIND.reverseLookup(owner)
404            let profile = Profile.find(buyer)
405
406            emit DirectOffer(tenant:tenant.name, id: id, saleID: saleItem.uuid, seller:owner, sellerName: sellerName , amount: balance, status:status, vaultType: ftType.identifier, nft:nftInfo, buyer: buyer, buyerName: buyerName, buyerAvatar: profile.getAvatar(), endsAt: saleItem.validUntil, previousBuyer:nil, previousBuyerName:nil)
407
408            let vault <- saleItem.acceptEscrowedBid()
409
410            let resolved : {Address : String} = {}
411            resolved[buyer] = buyerName ?? ""
412            resolved[owner] = sellerName ?? ""
413            resolved[FindMarketDirectOfferEscrow.account.address] =  "find"
414            // Have to make sure the tenant always have the valid find name
415            resolved[FindMarket.tenantNameAddress[tenant.name]!] =  tenant.name
416
417            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)
418            destroy <- self.items.remove(key: id)
419        }
420
421        access(all) fun getIds(): [UInt64] {
422            return self.items.keys
423        }
424
425        access(all) fun getRoyaltyChangedIds(): [UInt64] {
426            let ids : [UInt64] = []
427            for id in self.getIds() {
428                let item = self.borrow(id)
429                if !item.validateRoyalties() {
430                    ids.append(id)
431                }
432            }
433            return ids
434        }
435
436        access(all) fun containsId(_ id: UInt64): Bool {
437            return self.items.containsKey(id)
438        }
439
440        access(all) fun borrow(_ id: UInt64): &SaleItem {
441            return (&self.items[id])!
442        }
443
444        access(all) fun borrowSaleItem(_ id: UInt64) : &{FindMarket.SaleItem} {
445            if !self.items.containsKey(id) {
446                panic("This id does not exist : ".concat(id.toString()))
447            }
448            return (&self.items[id])!
449        }
450    }
451
452    access(all) resource Bid : FindMarket.Bid {
453        access(contract) let from: Capability<&SaleItemCollection>
454        access(contract) let nftCap: Capability<&{NonFungibleToken.Receiver}>
455        access(contract) let itemUUID: UInt64
456
457        //this should reflect on what the above uuid is for
458        access(contract) let vault: @{FungibleToken.Vault}
459        access(contract) let vaultType: Type
460        access(contract) var bidAt: UFix64
461        access(contract) let bidExtraField: {String : AnyStruct}
462
463        init(from: Capability<&SaleItemCollection>, itemUUID: UInt64, vault: @{FungibleToken.Vault}, nftCap: Capability<&{NonFungibleToken.Receiver}>, bidExtraField: {String : AnyStruct}) {
464            self.vaultType=vault.getType()
465            self.vault <- vault
466            self.itemUUID=itemUUID
467            self.from=from
468            self.bidAt=Clock.time()
469            self.nftCap=nftCap
470            self.bidExtraField=bidExtraField
471        }
472
473        access(contract) fun setBidAt(_ time: UFix64) {
474            self.bidAt=time
475        }
476
477        access(all) fun getBalance() : UFix64 {
478            return self.vault.balance
479        }
480
481        access(all) fun getSellerAddress() : Address {
482            return self.from.address
483        }
484
485        access(all) fun getBidExtraField() : {String : AnyStruct} {
486            return self.bidExtraField
487        }
488    }
489
490    access(all) resource interface MarketBidCollectionPublic {
491        access(all) fun getBalance(_ id: UInt64) : UFix64
492        access(all) fun getVaultType(_ id: UInt64) : Type
493        access(all) fun containsId(_ id: UInt64): Bool
494        access(contract) fun accept(_ nft: @{NonFungibleToken.NFT}, path:PublicPath) : @{FungibleToken.Vault}
495        access(contract) fun cancelBidFromSaleItem(_ id: UInt64)
496    }
497
498    access(all) entitlement Buyer
499
500    //A collection stored for bidders/buyers
501    access(all) resource MarketBidCollection: MarketBidCollectionPublic, FindMarket.MarketBidCollectionPublic {
502
503        access(contract) var bids : @{UInt64: Bid}
504        access(contract) let receiver: Capability<&{FungibleToken.Receiver}>
505        access(contract) let tenantCapability: Capability<&FindMarket.Tenant>
506
507        //not sure we can store this here anymore. think it needs to be in every bid
508        init(receiver: Capability<&{FungibleToken.Receiver}>, tenantCapability: Capability<&FindMarket.Tenant>) {
509            self.bids <- {}
510            self.receiver=receiver
511            self.tenantCapability=tenantCapability
512        }
513
514        access(self) fun getTenant() : &FindMarket.Tenant {
515            if !self.tenantCapability.check() {
516                panic("Tenant client is not linked anymore")
517            }
518            return self.tenantCapability.borrow()!
519        }
520
521        access(contract) fun accept(_ nft: @{NonFungibleToken.NFT}, path:PublicPath) : @{FungibleToken.Vault} {
522            let id= nft.id
523            let bid <- self.bids.remove(key: nft.uuid) ?? panic("missing bid")
524            let vaultRef = &bid.vault as auth(FungibleToken.Withdraw) &{FungibleToken.Vault}
525
526            let nftCap = bid.nftCap
527            if !nftCap.check() {
528                let cpCap =getAccount(nftCap.address).capabilities.get<&{NonFungibleToken.Collection}>(path)!
529                if !cpCap.check() {
530                    panic("Bidder unlinked the nft receiver capability. bidder address : ".concat(bid.nftCap.address.toString()))
531                } else {
532                    bid.nftCap.borrow()!.deposit(token: <- nft)
533                }
534            } else {
535                bid.nftCap.borrow()!.deposit(token: <- nft)
536            }
537
538            let vault  <- vaultRef.withdraw(amount: vaultRef.balance)
539            destroy bid
540            return <- vault
541        }
542
543        access(all) fun getVaultType(_ id:UInt64) : Type {
544            return self.borrowBid(id).vaultType
545        }
546
547        access(all) fun getIds() : [UInt64] {
548            return self.bids.keys
549        }
550
551        access(all) fun containsId(_ id: UInt64) : Bool {
552            return self.bids.containsKey(id)
553        }
554
555        access(all) fun getBidType() : Type {
556            return Type<@Bid>()
557        }
558
559        access(Buyer) fun bid(item: FindViews.ViewReadPointer, vault: @{FungibleToken.Vault}, nftCap: Capability<&{NonFungibleToken.Receiver}>, validUntil: UFix64?, saleItemExtraField: {String : AnyStruct}, bidExtraField: {String : AnyStruct}) {
560
561            // ensure it is not a 0 dollar listing
562            if vault.balance <= 0.0 {
563                panic("Offer price should be greater than 0")
564            }
565
566            // ensure validUntil is valid
567            if validUntil != nil && validUntil! < Clock.time() {
568                panic("Valid until is before current time")
569            }
570
571            // check soul bound
572            if item.checkSoulBound() {
573                panic("This item is soul bounded and cannot be traded")
574            }
575
576            if self.owner!.address == item.owner() {
577                panic("You cannot bid on your own resource")
578            }
579
580            let uuid=item.getUUID()
581
582            if self.bids[uuid] != nil {
583                panic("You already have an bid for this item, use increaseBid on that bid")
584            }
585            let tenant=self.getTenant()
586
587            // Check if it is onefootball. If so, listing has to be at least $0.65 (DUC)
588            if tenant.name == "onefootball" {
589                // ensure it is not a 0 dollar listing
590                if vault.balance <= 0.65 {
591                    panic("Offer price should be greater than 0.65")
592                }
593            }
594            let from=getAccount(item.owner()).capabilities.get<&SaleItemCollection>(tenant.getPublicPath(Type<@SaleItemCollection>()))!
595
596            let bid <- create Bid(from: from, itemUUID:item.getUUID(), vault: <- vault, nftCap: nftCap, bidExtraField: bidExtraField)
597            let saleItemCollection= from.borrow() ?? panic("Could not borrow sale item for id=".concat(uuid.toString()))
598            let callbackCapability =self.owner!.capabilities.get<&MarketBidCollection>(tenant.getPublicPath(Type<@MarketBidCollection>()))!
599            let oldToken <- self.bids[uuid] <- bid
600            saleItemCollection.registerBid(item: item, callback: callbackCapability, validUntil:validUntil, saleItemExtraField: saleItemExtraField)
601            destroy oldToken
602        }
603
604        access(Buyer) fun increaseBid(id: UInt64, vault: @{FungibleToken.Vault}) {
605            let bid =self.borrowBid(id)
606            bid.setBidAt(Clock.time())
607            bid.vault.deposit(from: <- vault)
608            if !bid.from.check() {
609                panic("Seller unlinked SaleItem collection capability. seller address : ".concat(bid.from.address.toString()))
610            }
611            bid.from.borrow()!.registerIncreasedBid(id)
612        }
613
614        /// The users cancel a bid himself
615        access(Buyer) fun cancelBid(_ id: UInt64) {
616            let bid= self.borrowBid(id)
617            bid.from.borrow()!.cancelBid(id)
618            self.cancelBidFromSaleItem(id)
619        }
620
621        access(contract) fun cancelBidFromSaleItem(_ id: UInt64) {
622            if !self.receiver.check() {
623                panic("This user does not have receiver vault set up. User: ".concat(self.receiver.address.toString()))
624            }
625            Debug.log("cancel bid")
626            let bid <- self.bids.remove(key: id) ?? panic("missing bid")
627            let vaultRef = &bid.vault as auth(FungibleToken.Withdraw) &{FungibleToken.Vault}
628            self.receiver.borrow()!.deposit(from: <- vaultRef.withdraw(amount: vaultRef.balance))
629            destroy bid
630        }
631
632        access(all) fun borrowBid(_ id: UInt64): &Bid {
633            if !self.bids.containsKey(id){
634                panic("This id does not exist : ".concat(id.toString()))
635            }
636            return (&self.bids[id])!
637        }
638
639        access(all) fun borrowBidItem(_ id: UInt64): &{FindMarket.Bid} {
640            if !self.bids.containsKey(id){
641                panic("This id does not exist : ".concat(id.toString()))
642            }
643            return (&self.bids[id])!
644        }
645
646        access(all) fun getBalance(_ id: UInt64) : UFix64 {
647            let bid= self.borrowBid(id)
648            return bid.vault.balance
649        }
650    }
651    //Create an empty lease collection that store your leases to a name
652    access(all)   fun createEmptySaleItemCollection(_ tenantCapability: Capability<&FindMarket.Tenant>): @SaleItemCollection {
653        return <- create SaleItemCollection(tenantCapability)
654    }
655
656    access(all) fun createEmptyMarketBidCollection(receiver: Capability<&{FungibleToken.Receiver}>, tenantCapability: Capability<&FindMarket.Tenant>) : @MarketBidCollection {
657        return <- create MarketBidCollection(receiver: receiver, tenantCapability:tenantCapability)
658    }
659
660    access(all) fun getSaleItemCapability(marketplace:Address, user:Address) : Capability<&SaleItemCollection>? {
661        if FindMarket.getTenantCapability(marketplace) == nil {
662            panic("Invalid tenant")
663        }
664        if let tenant=FindMarket.getTenantCapability(marketplace)!.borrow() {
665            return getAccount(user).capabilities.get<&SaleItemCollection>(tenant.getPublicPath(Type<@SaleItemCollection>()))
666        }
667        return nil
668    }
669
670    access(all) fun getSaleItemExtraField(marketplace:Address, user:Address, id:UInt64) : {String : AnyStruct}? {
671        if FindMarket.getTenantCapability(marketplace) == nil {
672            panic("Invalid tenant")
673        }
674        if let tenant=FindMarket.getTenantCapability(marketplace)!.borrow() {
675            if let saleItemCollection = getAccount(user).capabilities.get<&{FindMarket.SaleItemCollectionPublic}>(tenant.getPublicPath(Type<@SaleItemCollection>()))!.borrow() {
676                if let saleItem = saleItemCollection.borrowSaleItem(id) {
677                    return saleItem.getSaleItemExtraField()
678                }
679            }
680        }
681        return nil
682    }
683
684    access(all) fun getBidCapability( marketplace:Address, user:Address) : Capability<&MarketBidCollection>? {
685        if FindMarket.getTenantCapability(marketplace) == nil {
686            panic("Invalid tenant")
687        }
688        if let tenant=FindMarket.getTenantCapability(marketplace)!.borrow() {
689            return getAccount(user).capabilities.get<&MarketBidCollection>(tenant.getPublicPath(Type<@MarketBidCollection>()))
690        }
691        return nil
692    }
693
694    init() {
695        FindMarket.addSaleItemType(Type<@SaleItem>())
696        FindMarket.addSaleItemCollectionType(Type<@SaleItemCollection>())
697        FindMarket.addMarketBidType(Type<@Bid>())
698        FindMarket.addMarketBidCollectionType(Type<@MarketBidCollection>())
699    }
700}
701