Smart Contract

FindMarketDirectOfferSoft

A.097bafa4e0b48eef.FindMarketDirectOfferSoft

Deployed

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

Dependents

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