Smart Contract

FindMarket

A.097bafa4e0b48eef.FindMarket

Deployed

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

Dependents

30 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import MetadataViews from 0x1d7e57aa55817448
3import Profile from 0x097bafa4e0b48eef
4import Clock from 0x097bafa4e0b48eef
5import FTRegistry from 0x097bafa4e0b48eef
6import FindRulesCache from 0x097bafa4e0b48eef
7import FindMarketCut from 0x097bafa4e0b48eef
8import FindMarketCutStruct from 0x097bafa4e0b48eef
9import FindUtils from 0x097bafa4e0b48eef
10import ViewResolver from 0x1d7e57aa55817448
11import FungibleTokenSwitchboard from 0xf233dcee88fe0abe
12import TokenForwarding from 0xe544175ee0461c4b
13
14access(all) contract FindMarket {
15    // Variables
16    access(account) let  pathMap : {String: String}
17    access(account) let  listingName : {String: String}
18    access(contract) let  saleItemTypes : [Type]
19    access(contract) let  saleItemCollectionTypes : [Type]
20    access(contract) let  marketBidTypes : [Type]
21    access(contract) let  marketBidCollectionTypes : [Type]
22
23    // Events
24    access(all) event RoyaltyPaid(tenant:String, id: UInt64, saleID: UInt64, address:Address, findName:String?, royaltyName:String, amount: UFix64, vaultType:String, nft:NFTInfo)
25    access(all) event RoyaltyCouldNotBePaid(tenant:String, id: UInt64, saleID: UInt64, address:Address, findName:String?, royaltyName:String, amount: UFix64, vaultType:String, nft:NFTInfo, residualAddress: Address)
26    access(all) event FindBlockRules(tenant: String, ruleName: String, ftTypes:[String], nftTypes:[String], listingTypes:[String], status:String)
27    access(all) event TenantAllowRules(tenant: String, ruleName: String, ftTypes:[String], nftTypes:[String], listingTypes:[String], status:String)
28    access(all) event FindCutRules(tenant: String, ruleName: String, cut:UFix64, ftTypes:[String], nftTypes:[String], listingTypes:[String], status:String)
29    access(all) event FindTenantRemoved(tenant: String, address: Address)
30
31    // Residual Royalty
32    access(all) var residualAddress : Address
33
34    // Tenant information
35    access(all) let TenantClientPublicPath: PublicPath
36    access(all) let TenantClientStoragePath: StoragePath
37    access(contract) let tenantPathPrefix :String
38    access(account) let tenantNameAddress : {String:Address}
39    access(account) let tenantAddressName : {Address:String}
40
41    // Deprecated in testnet
42    access(all) struct TenantCuts {
43        access(all) let findCut:MetadataViews.Royalty?
44        access(all) let tenantCut:MetadataViews.Royalty?
45
46        init(findCut:MetadataViews.Royalty?, tenantCut:MetadataViews.Royalty?) {
47            self.findCut=findCut
48            self.tenantCut=tenantCut
49        }
50    }
51
52    // Deprecated in testnet
53    access(all) struct ActionResult {
54        access(all) let allowed:Bool
55        access(all) let message:String
56        access(all) let name:String
57
58        init(allowed:Bool, message:String, name:String) {
59            self.allowed=allowed
60            self.message=message
61            self.name =name
62        }
63    }
64
65    // ========================================
66    access(all) fun getPublicPath(_ type: Type, name:String) : PublicPath {
67
68        let pathPrefix=self.pathMap[type.identifier]!
69        let path=pathPrefix.concat("_").concat(name)
70
71        return PublicPath(identifier: path) ?? panic("Cannot find public path for type ".concat(type.identifier))
72    }
73
74    access(all) fun getStoragePath(_ type: Type, name:String) : StoragePath {
75
76        let pathPrefix=self.pathMap[type.identifier]!
77        let path=pathPrefix.concat("_").concat(name)
78
79        return StoragePath(identifier: path) ?? panic("Cannot find public path for type ".concat(type.identifier))
80    }
81    access(all) fun getFindTenantAddress() : Address {
82        return FindMarket.account.address
83    }
84
85    /* Get Tenant */
86    access(all) fun getTenant(_ tenant: Address) : &{FindMarket.TenantPublic} {
87        return FindMarket.getTenantCapability(tenant)!.borrow()!
88    }
89
90    access(all) fun getSaleItemTypes() : [Type] {
91        return self.saleItemTypes
92    }
93
94    /* Get SaleItemCollections */
95    access(all) fun getSaleItemCollectionTypes() : [Type] {
96        return self.saleItemCollectionTypes
97    }
98
99    access(all) fun getSaleItemCollectionCapabilities(tenantRef: &{FindMarket.TenantPublic}, address: Address) : [Capability<&{FindMarket.SaleItemCollectionPublic}>] {
100        var caps : [Capability<&{FindMarket.SaleItemCollectionPublic}>] = []
101        for type in self.getSaleItemCollectionTypes() {
102            if type != nil {
103                let cap = getAccount(address).capabilities.get<&{FindMarket.SaleItemCollectionPublic}>(tenantRef.getPublicPath(type))
104                if cap.check() {
105                    caps.append(cap)
106                }
107            }
108        }
109        return caps
110    }
111
112    access(all) fun getSaleItemCollectionCapability(tenantRef: &{FindMarket.TenantPublic}, marketOption: String, address: Address) : Capability<&{FindMarket.SaleItemCollectionPublic}>? {
113        for type in self.getSaleItemCollectionTypes() {
114            if self.getMarketOptionFromType(type) == marketOption{
115                let path=tenantRef.getPublicPath(type)
116                return getAccount(address).capabilities.get<&{FindMarket.SaleItemCollectionPublic}>(path)
117            }
118        }
119
120        panic("Cannot find market option : ".concat(marketOption))
121    }
122
123
124
125    /* Get Sale Reports and Sale Item */
126    access(all) fun assertOperationValid(tenant: Address, address: Address, marketOption: String, id:UInt64) : &{SaleItem} {
127
128        let tenantRef=self.getTenant(tenant)
129
130        let collectionCap = self.getSaleItemCollectionCapability(tenantRef: tenantRef, marketOption: marketOption, address: address)
131
132        let optRef = collectionCap?.borrow()
133        if optRef == nil {
134            panic("Account not properly set up, cannot borrow sale item collection")
135        }
136        let ref=optRef!
137        let item=ref!.borrowSaleItem(id)!
138        if !item.checkPointer() {
139            panic("this is a ghost listing. SaleItem id : ".concat(id.toString()))
140        }
141
142        return item
143    }
144
145    // Get Royalties Changed Items
146
147    access(all) fun getRoyaltiesChangedIds(tenant:Address, address: Address) : {String : [UInt64]} {
148        let tenantRef = self.getTenant(tenant)
149        var report : {String : [UInt64]} = {}
150        for type in self.getSaleItemCollectionTypes() {
151            let marketOption = self.getMarketOptionFromType(type)
152
153            let collectionCap = self.getSaleItemCollectionCapability(tenantRef: tenantRef, marketOption: marketOption, address: address)
154            if let optRef = collectionCap?.borrow() {
155                let ids = optRef!.getRoyaltyChangedIds()
156                if ids.length > 0 {
157                    report[marketOption] = ids
158                }
159            }
160        }
161        return report
162    }
163
164    access(all) fun getRoyaltiesChangedItems(tenant:Address, address: Address) : {String : FindMarket.SaleItemCollectionReport} {
165        let tenantRef = self.getTenant(tenant)
166        var report : {String : FindMarket.SaleItemCollectionReport} = {}
167        for type in self.getSaleItemCollectionTypes() {
168            let marketOption = self.getMarketOptionFromType(type)
169            let returnedReport = self.checkSaleInformation(tenantRef: tenantRef, marketOption:marketOption, address: address, itemId: nil, getGhost: true, getNFTInfo: true, getRoyaltyChanged: true )
170            if returnedReport.items.length > 0 || returnedReport.ghosts.length > 0 {
171                report[marketOption] = returnedReport
172            }
173        }
174        return report
175    }
176
177    /* Get Sale Reports and Sale Item */
178    access(all) fun getSaleInformation(tenant: Address, address: Address, marketOption: String, id:UInt64, getNFTInfo: Bool) : FindMarket.SaleItemInformation? {
179        let tenantRef=self.getTenant(tenant)
180        let info = self.checkSaleInformation(tenantRef: tenantRef, marketOption:marketOption, address: address, itemId: id, getGhost: false, getNFTInfo: getNFTInfo, getRoyaltyChanged: true )
181        if info.items.length > 0 {
182            return info.items[0]
183        }
184        return nil
185    }
186
187    access(all) fun getSaleItemReport(tenant:Address, address: Address, getNFTInfo: Bool) : {String : FindMarket.SaleItemCollectionReport} {
188        let tenantRef = self.getTenant(tenant)
189        var report : {String : FindMarket.SaleItemCollectionReport} = {}
190        for type in self.getSaleItemCollectionTypes() {
191            let marketOption = self.getMarketOptionFromType(type)
192            let returnedReport = self.checkSaleInformation(tenantRef: tenantRef, marketOption:marketOption, address: address, itemId: nil, getGhost: true, getNFTInfo: getNFTInfo, getRoyaltyChanged: true )
193            if returnedReport.items.length > 0 || returnedReport.ghosts.length > 0 {
194                report[marketOption] = returnedReport
195            }
196        }
197        return report
198    }
199
200    access(all) fun getSaleItems(tenant:Address, address: Address, id: UInt64, getNFTInfo: Bool) : {String : FindMarket.SaleItemCollectionReport} {
201        let tenantRef = self.getTenant(tenant)
202        var report : {String : FindMarket.SaleItemCollectionReport} = {}
203        for type in self.getSaleItemCollectionTypes() {
204            let marketOption = self.getMarketOptionFromType(type)
205            let returnedReport = self.checkSaleInformation(tenantRef: tenantRef, marketOption:marketOption, address: address, itemId: id, getGhost: true, getNFTInfo: getNFTInfo, getRoyaltyChanged: true )
206            if returnedReport.items.length > 0 || returnedReport.ghosts.length > 0 {
207                report[marketOption] = returnedReport
208            }
209        }
210        return report
211    }
212
213    access(all) fun getNFTListing(tenant:Address, address: Address, id: UInt64, getNFTInfo: Bool) : {String : FindMarket.SaleItemInformation} {
214        let tenantRef = self.getTenant(tenant)
215        var report : {String : FindMarket.SaleItemInformation} = {}
216        for type in self.getSaleItemCollectionTypes() {
217            let marketOption = self.getMarketOptionFromType(type)
218            let returnedReport = self.checkSaleInformation(tenantRef: tenantRef, marketOption:marketOption, address: address, itemId: id, getGhost: true, getNFTInfo: getNFTInfo, getRoyaltyChanged: true )
219            if returnedReport.items.length > 0 {
220                report[marketOption] = returnedReport.items[0]
221            }
222        }
223        return report
224    }
225
226    access(contract) fun checkSaleInformation(tenantRef: &{FindMarket.TenantPublic}, marketOption: String, address: Address, itemId: UInt64?, getGhost: Bool, getNFTInfo: Bool, getRoyaltyChanged: Bool ) : FindMarket.SaleItemCollectionReport {
227        let ghost: [FindMarket.GhostListing] =[]
228        let info: [FindMarket.SaleItemInformation] =[]
229        let collectionCap = self.getSaleItemCollectionCapability(tenantRef: tenantRef, marketOption: marketOption, address: address)
230        let optRef = collectionCap?.borrow()
231        if optRef == nil {
232            return FindMarket.SaleItemCollectionReport(items: info, ghosts: ghost)
233        }
234        let ref=optRef!!
235
236        var listID : [UInt64]= []
237        if let id = itemId{
238            if !ref.containsId(id) {
239                return FindMarket.SaleItemCollectionReport(items: info, ghosts: ghost)
240            }
241            listID=[id]
242        } else {
243            listID = ref.getIds()
244        }
245
246        let listingType = ref.getListingType()
247
248        for id in listID {
249            //if this id is not present in this Market option then we just skip it
250            let item=ref.borrowSaleItem(id)!
251            if !item.checkPointer() {
252                if getGhost {
253                    ghost.append(FindMarket.GhostListing(listingType: listingType, id:id))
254                }
255                continue
256            }
257
258            // check soulBound Items
259            if item.checkSoulBound() {
260                ghost.append(FindMarket.GhostListing(listingType: listingType, id:id))
261                continue
262            }
263
264            if getRoyaltyChanged && !item.validateRoyalties() {
265                ghost.append(FindMarket.GhostListing(listingType: listingType, id:id))
266                continue
267            }
268
269            let stopped=tenantRef.allowedAction(listingType: listingType, nftType: item.getItemType(), ftType: item.getFtType(), action: FindMarket.MarketAction(listing:false, name: "delist item for sale"), seller: address, buyer: nil)
270            var status="active"
271
272            if !stopped.allowed && stopped.message == "Seller banned by Tenant" {
273                status="banned"
274                info.append(FindMarket.SaleItemInformation(item:item, status:status, nftInfo:false))
275                continue
276            }
277
278            if !stopped.allowed {
279                status="stopped"
280                info.append(FindMarket.SaleItemInformation(item:item, status:status, nftInfo:false))
281                continue
282            }
283
284            let deprecated=tenantRef.allowedAction(listingType: listingType, nftType: item.getItemType(), ftType: item.getFtType(), action: FindMarket.MarketAction(listing:true, name: "delist item for sale"), seller: address, buyer: nil)
285
286            if !deprecated.allowed {
287                status="deprecated"
288                info.append(FindMarket.SaleItemInformation(item:item, status:status, nftInfo:false))
289                continue
290            }
291
292            if let validTime = item.getValidUntil() {
293                if validTime <= Clock.time() {
294                    status="ended"
295                }
296            }
297            info.append(FindMarket.SaleItemInformation(item:item, status:status, nftInfo:getNFTInfo))
298        }
299
300        return FindMarket.SaleItemCollectionReport(items: info, ghosts: ghost)
301    }
302
303    /* Get Bid Collections */
304    access(all) fun getMarketBidTypes() : [Type] {
305        return self.marketBidTypes
306    }
307
308    access(all) fun getMarketBidCollectionTypes() : [Type] {
309        return self.marketBidCollectionTypes
310    }
311
312    access(all) fun getMarketBidCollectionCapabilities(tenantRef: &{FindMarket.TenantPublic}, address: Address) : [Capability<&{FindMarket.MarketBidCollectionPublic}>] {
313        var caps : [Capability<&{FindMarket.MarketBidCollectionPublic}>] = []
314        for type in self.getMarketBidCollectionTypes() {
315            let cap = getAccount(address).capabilities.get<&{FindMarket.MarketBidCollectionPublic}>(tenantRef.getPublicPath(type))
316            if cap.check() {
317                caps.append(cap)
318            }
319        }
320        return caps
321    }
322
323    access(all) fun getMarketBidCollectionCapability(tenantRef: &{FindMarket.TenantPublic}, marketOption: String, address: Address) : Capability<&{FindMarket.MarketBidCollectionPublic}>? {
324        for type in self.getMarketBidCollectionTypes() {
325            if self.getMarketOptionFromType(type) == marketOption{
326                return  getAccount(address).capabilities.get<&{FindMarket.MarketBidCollectionPublic}>(tenantRef.getPublicPath(type))
327            }
328        }
329        panic("Cannot find market option : ".concat(marketOption))
330    }
331
332    access(all) fun getBid(tenant: Address, address: Address, marketOption: String, id:UInt64, getNFTInfo: Bool) : FindMarket.BidInfo? {
333        let tenantRef=self.getTenant(tenant)
334        let bidInfo = self.checkBidInformation(tenantRef: tenantRef, marketOption: marketOption, address: address, itemId: id, getGhost: false, getNFTInfo: getNFTInfo)
335        if bidInfo.items.length > 0 {
336            return bidInfo.items[0]
337        }
338        return nil
339    }
340
341    access(all) fun getBidsReport(tenant:Address, address: Address, getNFTInfo: Bool) : {String : FindMarket.BidItemCollectionReport} {
342        let tenantRef = self.getTenant(tenant)
343        var report : {String : FindMarket.BidItemCollectionReport} = {}
344        for type in self.getMarketBidCollectionTypes() {
345            let marketOption = self.getMarketOptionFromType(type)
346            let returnedReport = self.checkBidInformation(tenantRef: tenantRef, marketOption: marketOption, address: address, itemId: nil, getGhost: true, getNFTInfo: getNFTInfo)
347            if returnedReport.items.length > 0 || returnedReport.ghosts.length > 0 {
348                report[marketOption] = returnedReport
349            }
350        }
351        return report
352    }
353
354    access(contract) fun checkBidInformation(tenantRef: &{FindMarket.TenantPublic}, marketOption: String, address: Address, itemId: UInt64?, getGhost:Bool, getNFTInfo: Bool) : FindMarket.BidItemCollectionReport {
355        let ghost: [FindMarket.GhostListing] =[]
356        let info: [FindMarket.BidInfo] =[]
357        let collectionCap = self.getMarketBidCollectionCapability(tenantRef: tenantRef, marketOption: marketOption, address: address)
358
359        let optRef = collectionCap?.borrow()
360        if optRef==nil {
361            return FindMarket.BidItemCollectionReport(items: info, ghosts: ghost)
362        }
363
364        let ref=optRef!!
365
366        let listingType = ref.getBidType()
367        var listID : [UInt64]= []
368        if let id = itemId{
369            if !ref.containsId(id) {
370                return FindMarket.BidItemCollectionReport(items: info, ghosts: ghost)
371            }
372            listID=[id]
373        } else {
374            listID = ref.getIds()
375        }
376
377        for id in listID {
378
379            let bid=ref.borrowBidItem(id)
380            let item=self.getSaleInformation(tenant: tenantRef.owner!.address, address: bid.getSellerAddress(), marketOption: marketOption, id: id, getNFTInfo: getNFTInfo)
381            if item == nil {
382                if getGhost {
383                    ghost.append(FindMarket.GhostListing(listingType: listingType, id:id))
384                }
385                continue
386            }
387            let bidInfo = FindMarket.BidInfo(id: id, bidTypeIdentifier: listingType.identifier,  bidAmount: bid.getBalance(), timestamp: Clock.time(), item:item!)
388            info.append(bidInfo)
389
390        }
391        return FindMarket.BidItemCollectionReport(items: info, ghosts: ghost)
392    }
393
394    access(all) fun assertBidOperationValid(tenant: Address, address: Address, marketOption: String, id:UInt64) : &{SaleItem} {
395
396        let tenantRef=self.getTenant(tenant)
397        let collectionCap = self.getMarketBidCollectionCapability(tenantRef: tenantRef, marketOption: marketOption, address: address)
398        let optRef = collectionCap?.borrow()
399        if optRef == nil {
400            panic("Account not properly set up, cannot borrow bid item collection")
401        }
402        let ref=optRef!!
403        let bidItem=ref.borrowBidItem(id)
404
405        let saleItemCollectionCap = self.getSaleItemCollectionCapability(tenantRef: tenantRef, marketOption: marketOption, address: bidItem.getSellerAddress())
406        let saleRef = saleItemCollectionCap?.borrow()
407        if saleRef == nil {
408            panic("Seller account is not properly set up, cannot borrow sale item collection")
409        }
410        let sale=saleRef!
411        let item=sale!.borrowSaleItem(id)!
412        if !item.checkPointer() {
413            panic("this is a ghost listing. SaleItem id : ".concat(id.toString()))
414        }
415
416        return item
417    }
418
419    /* Helper Function */
420    access(all) fun getMarketOptionFromType(_ type:Type) : String {
421        return self.listingName[type.identifier]!
422    }
423
424    access(all) fun typeToListingName(_ type: Type) : String {
425        let identifier = type.identifier
426        var dots = 0
427        var start = 0
428        var end = 0
429        var counter = 0
430        while counter < identifier.length {
431            if identifier[counter] == "." {
432                dots = dots + 1
433            }
434            if start == 0 && dots == 2 {
435                start = counter
436            }
437            if end == 0 && dots == 3 {
438                end = counter
439            }
440            counter = counter + 1
441        }
442        return identifier.slice(from: start + 1, upTo: end)
443    }
444
445    /* Admin Function */
446    access(account) fun addPathMap(_ type: Type) {
447        self.pathMap[type.identifier]= self.typeToPathIdentifier(type)
448    }
449
450    access(account) fun addListingName(_ type: Type) {
451        self.listingName[type.identifier] =self.typeToListingName(type)
452    }
453
454    access(account) fun removePathMap(_ type: Type) {
455        self.pathMap.remove(key: type.identifier)
456    }
457
458    access(account) fun removeListingName(_ type: Type) {
459        self.listingName.remove(key: type.identifier)
460    }
461
462    access(account) fun addSaleItemType(_ type: Type) {
463        self.saleItemTypes.append(type)
464        self.pathMap[type.identifier]= self.typeToPathIdentifier(type)
465        self.listingName[type.identifier] =self.typeToListingName(type)
466    }
467
468    access(account) fun addMarketBidType(_ type: Type) {
469        self.marketBidTypes.append(type)
470        self.pathMap[type.identifier]= self.typeToPathIdentifier(type)
471        self.listingName[type.identifier] =self.typeToListingName(type)
472    }
473
474    access(account) fun addSaleItemCollectionType(_ type: Type) {
475        self.saleItemCollectionTypes.append(type)
476        self.pathMap[type.identifier]= self.typeToPathIdentifier(type)
477        self.listingName[type.identifier] =self.typeToListingName(type)
478    }
479
480    access(account) fun addMarketBidCollectionType(_ type: Type) {
481        self.marketBidCollectionTypes.append(type)
482        self.pathMap[type.identifier]= self.typeToPathIdentifier(type)
483        self.listingName[type.identifier] =self.typeToListingName(type)
484    }
485
486    access(account) fun removeSaleItemType(_ type: Type) {
487        var counter = 0
488        while counter < self.saleItemTypes.length {
489            if type == self.saleItemTypes[counter] {
490                self.saleItemTypes.remove(at: counter)
491            }
492            counter = counter + 1
493        }
494    }
495
496    access(account) fun removeMarketBidType(_ type: Type) {
497        var counter = 0
498        while counter < self.marketBidTypes.length {
499            if type == self.marketBidTypes[counter] {
500                self.marketBidTypes.remove(at: counter)
501            }
502            counter = counter + 1
503        }
504    }
505
506    access(account) fun removeSaleItemCollectionType(_ type: Type) {
507        var counter = 0
508        while counter < self.saleItemCollectionTypes.length {
509            if type == self.saleItemCollectionTypes[counter] {
510                self.saleItemCollectionTypes.remove(at: counter)
511            }
512            counter = counter + 1
513        }
514    }
515
516    access(account) fun removeMarketBidCollectionType(_ type: Type) {
517        var counter = 0
518        while counter < self.marketBidCollectionTypes.length {
519            if type == self.marketBidCollectionTypes[counter] {
520                self.marketBidCollectionTypes.remove(at: counter)
521            }
522            counter = counter + 1
523        }
524    }
525
526    access(all) fun typeToPathIdentifier(_ type:Type) : String {
527        let identifier=type.identifier
528
529        var i=0
530        var newIdentifier=""
531        while i < identifier.length {
532
533            let item= identifier.slice(from: i, upTo: i+1)
534            if item=="." {
535                newIdentifier=newIdentifier.concat("_")
536            } else {
537                newIdentifier=newIdentifier.concat(item)
538            }
539            i=i+1
540        }
541        return newIdentifier
542    }
543    // ========================================
544
545    /// A struct to return what action an NFT can execute
546    access(all) struct AllowedListing {
547        access(all) let listingType: Type
548        access(all) let ftTypes: [Type]
549        access(all) let status: String
550
551        init(listingType: Type, ftTypes: [Type], status: String) {
552            self.listingType=listingType
553            self.ftTypes=ftTypes
554            self.status=status
555        }
556    }
557
558    access(all) struct MarketAction{
559        access(all) let listing:Bool
560        access(all) let name:String
561
562        init(listing:Bool, name:String){
563            self.listing=listing
564            self.name=name
565        }
566    }
567
568    access(all) struct TenantRule{
569        access(all) let name:String
570        access(all) let types:[Type]
571        access(all) let ruleType:String
572        access(all) let allow:Bool
573
574        init(name:String, types:[Type], ruleType:String, allow:Bool){
575
576            if !(ruleType == "nft" || ruleType == "ft" || ruleType == "listing") {
577                panic("Must be nft/ft/listing")
578            }
579            self.name=name
580            self.types=types
581            self.ruleType=ruleType
582            self.allow=allow
583        }
584
585
586        access(all) fun accept(_ relevantType: Type): Bool {
587            let contains=self.types.contains(relevantType)
588
589            if self.allow && contains{
590                return true
591            }
592
593            if !self.allow && !contains {
594                return true
595            }
596            return false
597        }
598    }
599
600    access(all) struct TenantSaleItem {
601        access(all) let name:String
602        access(all) let cut:MetadataViews.Royalty?
603        access(all) let rules:[TenantRule]
604        access(all) var status:String
605
606        init(name:String, cut:MetadataViews.Royalty?, rules:[TenantRule], status:String){
607            self.name=name
608            self.cut=cut
609            self.rules=rules
610            self.status=status
611        }
612
613        access(contract) fun removeRules(_ index: Int) : TenantRule {
614            return self.rules.remove(at: index)
615        }
616
617        access(contract) fun addRules(_ rule: TenantRule) {
618            self.rules.append(rule)
619        }
620
621        access(contract) fun alterStatus(_ status : String) {
622            self.status = status
623        }
624
625        access(contract) fun isValid(nftType: Type, ftType: Type, listingType: Type) : Bool {
626            for rule in self.rules {
627
628                var relevantType=nftType
629                if rule.ruleType == "listing" {
630                    relevantType=listingType
631                } else if rule.ruleType=="ft" {
632                    relevantType=ftType
633                }
634
635                if !rule.accept(relevantType) {
636                    return false
637                }
638            }
639            return true
640        }
641    }
642
643    access(all) resource interface TenantPublic {
644        access(all) fun getStoragePath(_ type: Type) : StoragePath
645        access(all) fun getPublicPath(_ type: Type) : PublicPath
646        access(all) fun allowedAction(listingType: Type, nftType:Type, ftType:Type, action: MarketAction, seller: Address? , buyer: Address?) : FindRulesCache.ActionResult
647        access(all) fun getCuts(name:String, listingType: Type, nftType:Type, ftType:Type) : {String : FindMarketCutStruct.Cuts}
648        access(all) fun getAllowedListings(nftType: Type, marketType: Type) : AllowedListing?
649        access(all) fun getBlockedNFT(marketType: Type) : [Type]
650        access(all) let name:String
651    }
652
653    //this needs to be a resource so that nobody else can make it.
654    access(all) resource Tenant : TenantPublic{
655
656        access(self) let findSaleItems : {String : TenantSaleItem}
657        access(self) let tenantSaleItems : {String : TenantSaleItem}
658        access(self) let findCuts : {String : TenantSaleItem}
659
660        access(all) let name: String
661
662        init(_ name:String) {
663            self.name=name
664            self.tenantSaleItems={}
665            self.findSaleItems={}
666            self.findCuts= {}
667        }
668
669        // This is an one-off temporary function to switch all receiver of rules / cuts to Switchboard cut
670        // This requires all tenant and find to have the switchboard set up, but this is very powerful and can enable all sorts of FT listings
671
672        access(account) fun setupSwitchboardCut() {
673            for key in self.findSaleItems.keys {
674                let val = self.findSaleItems[key]!
675                if val.cut != nil {
676                    let newReceiver = getAccount(val.cut!.receiver.address).capabilities.get<&{FungibleToken.Receiver}>(FungibleTokenSwitchboard.ReceiverPublicPath)
677                    let newCut = MetadataViews.Royalty(
678                        receiver: newReceiver,
679                        cut: val.cut!.cut,
680                        description: val.cut!.description
681                    )
682                    let newVal = FindMarket.TenantSaleItem(
683                        name : val.name,
684                        cut : newCut,
685                        rules : val.rules,
686                        status : val.status
687                    )
688                    self.findSaleItems[key] = newVal
689                }
690            }
691
692            for key in self.tenantSaleItems.keys {
693                let val = self.tenantSaleItems[key]!
694                if val.cut != nil {
695                    let newReceiver = getAccount(val.cut!.receiver.address).capabilities.get<&{FungibleToken.Receiver}>(FungibleTokenSwitchboard.ReceiverPublicPath)
696                    let newCut = MetadataViews.Royalty(
697                        receiver: newReceiver,
698                        cut: val.cut!.cut,
699                        description: val.cut!.description
700                    )
701                    let newVal = FindMarket.TenantSaleItem(
702                        name : val.name,
703                        cut : newCut,
704                        rules : val.rules,
705                        status : val.status
706                    )
707                    self.tenantSaleItems[key] = newVal
708                }
709            }
710
711            for key in self.findCuts.keys {
712                let val = self.findCuts[key]!
713                if val.cut != nil {
714                    let newReceiver = getAccount(val.cut!.receiver.address).capabilities.get<&{FungibleToken.Receiver}>(FungibleTokenSwitchboard.ReceiverPublicPath)
715                    let newCut = MetadataViews.Royalty(
716                        receiver: newReceiver,
717                        cut: val.cut!.cut,
718                        description: val.cut!.description
719                    )
720                    let newVal = FindMarket.TenantSaleItem(
721                        name : val.name,
722                        cut : newCut,
723                        rules : val.rules,
724                        status : val.status
725                    )
726                    self.findCuts[key] = newVal
727                }
728            }
729            FindRulesCache.resetTenantCutCache(self.name)
730            FindRulesCache.resetTenantFindRulesCache(self.name)
731            FindRulesCache.resetTenantTenantRulesCache(self.name)
732        }
733
734        access(account) fun checkFindCuts(_ cutName: String) : Bool {
735            return self.findCuts.containsKey(cutName)
736        }
737
738        access(account) fun alterMarketOption(name: String, status: String) {
739            pre{
740                self.tenantSaleItems[name] != nil : "This saleItem does not exist. Item : ".concat(name)
741            }
742            self.tenantSaleItems[name]!.alterStatus(status)
743            FindRulesCache.resetTenantTenantRulesCache(self.name)
744            FindRulesCache.resetTenantCutCache(self.name)
745            self.emitRulesEvent(item: self.tenantSaleItems[name]!, type: "tenant", status: status)
746        }
747
748        access(account) fun setExtraCut(types: [Type], category: String, cuts: FindMarketCutStruct.Cuts) {
749            FindMarketCut.setTenantCuts(tenant: self.name, types: types, category: category, cuts: cuts)
750        }
751
752        access(all) fun getCuts(name:String, listingType: Type, nftType:Type, ftType:Type) : {String : FindMarketCutStruct.Cuts} {
753
754            let cuts = FindMarketCut.getCuts(tenant: self.name, listingType: listingType, nftType: nftType, ftType: ftType)
755
756            cuts["find"] = self.getFindCut(name: name, listingType: listingType, nftType: nftType, ftType: ftType)
757
758            cuts["tenant"] = self.getTenantCut(name: name, listingType: listingType, nftType: nftType, ftType: ftType)
759
760            return cuts
761        }
762
763        access(all) fun getFindCut(name:String, listingType: Type, nftType:Type, ftType:Type) : FindMarketCutStruct.Cuts? {
764            let ruleId = FindMarketCut.getRuleId(listingType: listingType, nftType: nftType, ftType: ftType)
765            let findRuleId = ruleId.concat("-find")
766            if let cache = FindRulesCache.getTenantCut(tenant: self.name, ruleId: findRuleId) {
767                var returningCut : FindMarketCutStruct.Cuts? = nil
768                if let findCut = cache.findCut {
769                    returningCut = FindMarketCutStruct.Cuts(cuts:[
770                    FindMarketCutStruct.GeneralCut(
771                        name : findCut.description,
772                        cap: findCut.receiver,
773                        cut: findCut.cut,
774                        description: findCut.description
775                    )])
776                }
777                return returningCut
778            }
779
780            var cacheFindCut : MetadataViews.Royalty? = nil
781            var returningCut : FindMarketCutStruct.Cuts? = nil
782            for findCut in self.findCuts.values {
783                let valid = findCut.isValid(nftType: nftType, ftType: ftType, listingType: listingType)
784                if valid && findCut.cut != nil {
785                    cacheFindCut = findCut.cut
786                    returningCut = FindMarketCutStruct.Cuts(cuts:[
787                    FindMarketCutStruct.GeneralCut(
788                        name : findCut.cut!.description,
789                        cap: findCut.cut!.receiver,
790                        cut: findCut.cut!.cut,
791                        description: findCut.cut!.description
792                    )])
793                    break
794                }
795            }
796
797            // store that to cache
798            let cacheCut = FindRulesCache.TenantCuts(
799                findCut: cacheFindCut,
800                tenantCut: nil,
801            )
802            FindRulesCache.setTenantCutCache(tenant: self.name, ruleId: findRuleId, cut: cacheCut)
803
804            return returningCut
805        }
806
807        access(all) fun getTenantCut(name:String, listingType: Type, nftType:Type, ftType:Type) : FindMarketCutStruct.Cuts? {
808            let ruleId = FindMarketCut.getRuleId(listingType: listingType, nftType: nftType, ftType: ftType)
809            let tenantRuleId = ruleId.concat("-tenant")
810            if let cache = FindRulesCache.getTenantCut(tenant: self.name, ruleId: tenantRuleId) {
811                var returningCut : FindMarketCutStruct.Cuts? = nil
812                if let tenantCut = cache.tenantCut {
813                    returningCut = FindMarketCutStruct.Cuts(cuts: [
814                    FindMarketCutStruct.GeneralCut(
815                        name : tenantCut.description,
816                        cap: tenantCut.receiver,
817                        cut: tenantCut.cut,
818                        description: tenantCut.description
819                    )])
820                }
821                return returningCut
822            }
823
824            var cacheTenantCut : MetadataViews.Royalty? = nil
825            var returningCut : FindMarketCutStruct.Cuts? = nil
826            for item in self.tenantSaleItems.values {
827                let valid = item.isValid(nftType: nftType, ftType: ftType, listingType: listingType)
828
829                if valid && item.cut != nil{
830                    cacheTenantCut = item.cut
831                    returningCut = FindMarketCutStruct.Cuts(cuts: [
832                    FindMarketCutStruct.GeneralCut(
833                        name : item.cut!.description,
834                        cap: item.cut!.receiver,
835                        cut: item.cut!.cut,
836                        description: item.cut!.description
837                    )]) 
838                    break
839                }
840            }
841
842            // store that to cache
843            let cacheCut = FindRulesCache.TenantCuts(
844                findCut: nil,
845                tenantCut: cacheTenantCut,
846            )
847            FindRulesCache.setTenantCutCache(tenant: self.name, ruleId: tenantRuleId, cut: cacheCut)
848
849            return returningCut
850        }
851
852        access(account) fun addSaleItem(_ item: TenantSaleItem, type:String) {
853            if type=="find" {
854                var status : String? = nil
855                if self.findSaleItems[item.name] != nil {
856                    status = "update"
857                }
858                self.findSaleItems[item.name]=item
859                FindRulesCache.resetTenantFindRulesCache(self.name)
860                self.emitRulesEvent(item: item, type: "find", status: status)
861            } else if type=="tenant" {
862                var status : String? = nil
863                if self.findSaleItems[item.name] != nil {
864                    status = "update"
865                }
866                self.tenantSaleItems[item.name]=item
867                FindRulesCache.resetTenantTenantRulesCache(self.name)
868                FindRulesCache.resetTenantCutCache(self.name)
869                self.emitRulesEvent(item: item, type: "tenant", status: status)
870            } else if type=="cut" {
871                var status : String? = nil
872                if self.findSaleItems[item.name] != nil {
873                    status = "update"
874                }
875                self.findCuts[item.name]=item
876                FindRulesCache.resetTenantCutCache(self.name)
877                self.emitRulesEvent(item: item, type: "cut", status: status)
878            } else{
879                panic("Not valid type to add sale item for")
880            }
881        }
882
883        access(account) fun removeSaleItem(_ name:String, type:String) : TenantSaleItem {
884            if type=="find" {
885                let item = self.findSaleItems.remove(key: name) ?? panic("This Find Sale Item does not exist. SaleItem : ".concat(name))
886                FindRulesCache.resetTenantFindRulesCache(self.name)
887                self.emitRulesEvent(item: item, type: "find", status: "remove")
888                return item
889            } else if type=="tenant" {
890                let item = self.tenantSaleItems.remove(key: name)?? panic("This Tenant Sale Item does not exist. SaleItem : ".concat(name))
891                FindRulesCache.resetTenantTenantRulesCache(self.name)
892                FindRulesCache.resetTenantCutCache(self.name)
893                self.emitRulesEvent(item: item, type: "tenant", status: "remove")
894                return item
895            } else if type=="cut" {
896                let item = self.findCuts.remove(key: name)?? panic("This Find Cut does not exist. Cut : ".concat(name))
897                FindRulesCache.resetTenantCutCache(self.name)
898                self.emitRulesEvent(item: item, type: "cut", status: "remove")
899                return item
900            }
901            panic("Not valid type to add sale item for")
902
903        }
904
905        // if status is nil, will fetch the original rule status (use for removal of rules)
906        access(contract) fun emitRulesEvent(item: TenantSaleItem, type: String, status: String?) {
907            let tenant = self.name
908            let ruleName = item.name
909            var ftTypes : [String] = []
910            var nftTypes : [String] = []
911            var listingTypes : [String] = []
912            var ruleStatus = status
913            for rule in item.rules {
914                var array : [String] = []
915                for t in rule.types {
916                    array.append(t.identifier)
917                }
918                if rule.ruleType == "ft" {
919                    ftTypes = array
920                } else if rule.ruleType == "nft" {
921                    nftTypes = array
922                } else if rule.ruleType == "listing" {
923                    listingTypes = array
924                }
925            }
926            if ruleStatus == nil {
927                ruleStatus = item.status
928            }
929
930            if type == "find" {
931                emit FindBlockRules(tenant: tenant, ruleName: ruleName, ftTypes:ftTypes, nftTypes:nftTypes, listingTypes:listingTypes, status:ruleStatus!)
932                return
933            } else if type == "tenant" {
934                emit TenantAllowRules(tenant: tenant, ruleName: ruleName, ftTypes:ftTypes, nftTypes:nftTypes, listingTypes:listingTypes, status:ruleStatus!)
935                return
936            } else if type == "cut" {
937                emit FindCutRules(tenant: tenant, ruleName: ruleName, cut:item.cut?.cut ?? 0.0, ftTypes:ftTypes, nftTypes:nftTypes, listingTypes:listingTypes, status:ruleStatus!)
938                return
939            }
940            panic("Panic executing emitRulesEvent, Must be nft/ft/listing")
941        }
942
943        access(all) fun allowedAction(listingType: Type, nftType:Type, ftType:Type, action: MarketAction, seller: Address?, buyer: Address?) : FindRulesCache.ActionResult{
944            /* Check for Honour Banning */
945            let profile = getAccount(FindMarket.tenantNameAddress[self.name]!).capabilities.borrow<&{Profile.Public}>(Profile.publicPath) ?? panic("Cannot get reference to Profile to check honour banning. Tenant Name : ".concat(self.name))
946            if seller != nil && profile.isBanned(seller!) {
947                return FindRulesCache.ActionResult(allowed:false, message: "Seller banned by Tenant", name: "Profile Ban")
948            }
949            if buyer != nil && profile.isBanned(buyer!) {
950                return FindRulesCache.ActionResult(allowed:false, message: "Buyer banned by Tenant", name: "Profile Ban")
951            }
952
953            let ruleId = listingType.identifier.concat(nftType.identifier).concat(ftType.identifier)
954            let findRulesCache = FindRulesCache.getTenantFindRules(tenant: self.name, ruleId: ruleId)
955
956            if findRulesCache == nil {
957                // if the cache returns nil, go thru the logic once to save the result to the cache
958                for item in self.findSaleItems.values {
959                    for rule in item.rules {
960                        var relevantType=nftType
961                        if rule.ruleType == "listing" {
962                            relevantType=listingType
963                        } else if rule.ruleType=="ft" {
964                            relevantType=ftType
965                        }
966
967                        if rule.accept(relevantType) {
968                            continue
969                        }
970                        let result = FindRulesCache.ActionResult(allowed:false, message: rule.name, name: item.name)
971                        FindRulesCache.setTenantFindRulesCache(tenant: self.name, ruleId: ruleId, result: result)
972                        return result
973                    }
974                    if item.status=="stopped" {
975                        let result = FindRulesCache.ActionResult(allowed:false, message: "Find has stopped this item", name:item.name)
976                        FindRulesCache.setTenantFindRulesCache(tenant: self.name, ruleId: ruleId, result: result)
977                        return result
978                    }
979
980                    if item.status=="deprecated" && action.listing{
981                        let result = FindRulesCache.ActionResult(allowed:false, message: "Find has deprected mutation options on this item", name:item.name)
982                        FindRulesCache.setTenantFindRulesCache(tenant: self.name, ruleId: ruleId, result: result)
983                        return result
984                    }
985                }
986                FindRulesCache.setTenantFindRulesCache(tenant: self.name, ruleId: ruleId, result: FindRulesCache.ActionResult(allowed:true, message: "No Find deny rules hit", name:""))
987
988            } else if !findRulesCache!.allowed {
989                return findRulesCache!
990            }
991
992
993            let tenantRulesCache = FindRulesCache.getTenantTenantRules(tenant: self.name, ruleId: ruleId)
994
995            if tenantRulesCache == nil {
996                // if the cache returns nil, go thru the logic once to save the result to the cache
997                for item in self.tenantSaleItems.values {
998                    let valid = item.isValid(nftType: nftType, ftType: ftType, listingType: listingType)
999
1000                    if !valid {
1001                        continue
1002                    }
1003
1004                    if item.status=="stopped" {
1005                        let result = FindRulesCache.ActionResult(allowed:false, message: "Tenant has stopped this item", name:item.name)
1006                        FindRulesCache.setTenantTenantRulesCache(tenant: self.name, ruleId: ruleId, result: result)
1007                        return result
1008                    }
1009
1010                    if item.status=="deprecated" && action.listing{
1011                        let result = FindRulesCache.ActionResult(allowed:false, message: "Tenant has deprected mutation options on this item", name:item.name)
1012                        FindRulesCache.setTenantTenantRulesCache(tenant: self.name, ruleId: ruleId, result: result)
1013                        return result
1014                    }
1015                    let result = FindRulesCache.ActionResult(allowed:true, message:"OK!", name:item.name)
1016                    FindRulesCache.setTenantTenantRulesCache(tenant: self.name, ruleId: ruleId, result: result)
1017                    return result
1018                }
1019
1020                let result = FindRulesCache.ActionResult(allowed:false, message:"Nothing matches", name:"")
1021                FindRulesCache.setTenantTenantRulesCache(tenant: self.name, ruleId: ruleId, result: result)
1022                return result
1023
1024            }
1025            return tenantRulesCache!
1026
1027        }
1028
1029        access(all) fun getPublicPath(_ type: Type) : PublicPath {
1030            return FindMarket.getPublicPath(type, name: self.name)
1031        }
1032
1033        access(all) fun getStoragePath(_ type: Type) : StoragePath {
1034            return FindMarket.getStoragePath(type, name: self.name)
1035        }
1036
1037        access(all) fun getAllowedListings(nftType: Type, marketType: Type) : AllowedListing? {
1038
1039            // Find Rules have to be deny rules
1040            var containsNFTType = false
1041            var containsListingType = true
1042            for item in self.findSaleItems.values {
1043                for rule in item.rules {
1044                    if rule.types.contains(nftType){
1045                        containsNFTType = true
1046                    }
1047                    if rule.ruleType == "listing" && !rule.types.contains(marketType) {
1048                        containsListingType = false
1049                    }
1050                }
1051                if containsListingType && containsNFTType {
1052                    return nil
1053                }
1054                containsNFTType = false
1055                containsListingType = true
1056            }
1057
1058            // Tenant Rules have to be allow rules
1059            var returningFTTypes : [Type] = []
1060            for item in self.tenantSaleItems.values {
1061                var allowedFTTypes : [Type] = []
1062                if item.status != "active" {
1063                    continue
1064                }
1065                for rule in item.rules {
1066                    if rule.ruleType == "ft"{
1067                        allowedFTTypes = rule.types
1068                    }
1069                    if rule.types.contains(nftType) && rule.allow {
1070                        containsNFTType = true
1071                    }
1072                    if rule.ruleType == "listing" && !rule.types.contains(marketType) && rule.allow {
1073                        containsListingType = false
1074                    }
1075                }
1076                if containsListingType && containsNFTType {
1077                    returningFTTypes.appendAll(allowedFTTypes)
1078                }
1079
1080                containsNFTType = false
1081                containsListingType = true
1082            }
1083
1084            if returningFTTypes.length == 0 {
1085                return nil
1086            }
1087            returningFTTypes = FindUtils.deDupTypeArray(returningFTTypes)
1088            return AllowedListing(listingType: marketType, ftTypes: returningFTTypes, status: "active")
1089        }
1090
1091        access(all) fun getBlockedNFT(marketType: Type) : [Type] {
1092            // Find Rules have to be deny rules
1093            let list : [Type] = []
1094            var containsListingType = true
1095            var l : [Type] = []
1096            for item in self.findSaleItems.values {
1097                for rule in item.rules {
1098                    if rule.ruleType == "listing" && !rule.types.contains(marketType){
1099                        containsListingType = false
1100                    }
1101                    if rule.ruleType == "nft" {
1102                        l.appendAll(rule.types)
1103                    }
1104                }
1105                if containsListingType {
1106                    for type in l {
1107                        if list.contains(type) {
1108                            continue
1109                        }
1110                        list.append(type)
1111                    }
1112                }
1113                l = []
1114                containsListingType = true
1115            }
1116
1117            containsListingType = false
1118            // Tenant Rules have to be allow rules
1119            for item in self.tenantSaleItems.values {
1120                for rule in item.rules {
1121                    if item.status != "stopped" {
1122                        continue
1123                    }
1124                    if rule.ruleType == "nft"{
1125                        l.appendAll(rule.types)
1126                    }
1127                    if rule.types.contains(marketType) && rule.allow {
1128                        containsListingType = true
1129                    }
1130
1131                }
1132                if containsListingType {
1133                    for type in l {
1134                        if list.contains(type) {
1135                            continue
1136                        }
1137                        list.append(type)
1138                    }
1139                }
1140                l = []
1141                containsListingType = false
1142            }
1143            return list
1144        }
1145    }
1146
1147    // Tenant admin stuff
1148    //Admin client to use for capability receiver pattern
1149    access(all) fun createTenantClient() : @TenantClient {
1150        return <- create TenantClient()
1151    }
1152
1153
1154    //interface to use for capability receiver pattern
1155    access(all) resource interface TenantClientPublic  {
1156        access(all) fun addCapability(_ cap: Capability<&Tenant>)
1157    }
1158
1159    access(all) entitlement TenantClientOwner
1160
1161    /*
1162
1163    A tenantClient should be able to:
1164    - deprecte a certain market type: No new listings can be made
1165
1166    */
1167    //admin proxy with capability receiver
1168    access(all) resource TenantClient: TenantClientPublic {
1169
1170        access(self) var capability: Capability<&Tenant>?
1171
1172        access(all) fun  addCapability(_ cap: Capability<&Tenant>) {
1173
1174            if !cap.check() {
1175                panic("Invalid tenant")
1176            }
1177            if self.capability != nil {
1178                panic("Server already set")
1179            }
1180            self.capability = cap
1181        }
1182
1183        init() {
1184            self.capability = nil
1185        }
1186
1187        access(TenantClientOwner) fun setMarketOption(saleItem: TenantSaleItem) {
1188            let tenant = self.getTenantRef()
1189            tenant.addSaleItem(saleItem, type: "tenant")
1190        }
1191
1192        access(TenantClientOwner) fun removeMarketOption(name: String) {
1193            let tenant = self.getTenantRef()
1194            tenant.removeSaleItem(name, type: "tenant")
1195        }
1196
1197        access(TenantClientOwner) fun enableMarketOption(_ name: String) {
1198            let tenant = self.getTenantRef()
1199            tenant.alterMarketOption(name: name, status: "active")
1200        }
1201
1202        access(TenantClientOwner) fun deprecateMarketOption(_ name: String) {
1203            let tenant = self.getTenantRef()
1204            tenant.alterMarketOption(name: name, status: "deprecated")
1205        }
1206
1207        access(TenantClientOwner) fun stopMarketOption(_ name: String) {
1208            let tenant = self.getTenantRef()
1209            tenant.alterMarketOption(name: name, status: "stopped")
1210        }
1211
1212        access(TenantClientOwner) fun getTenantRef() : &Tenant {
1213            if self.capability == nil {
1214                panic("TenantClient is not present")
1215            }
1216            if !self.capability!.check() {
1217                panic("Tenant client is not linked anymore")
1218            }
1219
1220            return self.capability!.borrow()!
1221        }
1222
1223        access(TenantClientOwner) fun setExtraCut(types: [Type], category: String, cuts: FindMarketCutStruct.Cuts) {
1224            let tenant = self.getTenantRef()
1225            tenant.setExtraCut(types: types, category: category, cuts: cuts)
1226        }
1227    }
1228
1229    access(account) fun removeFindMarketTenant(tenant: Address) {
1230
1231        if let name = self.tenantAddressName[tenant]  {
1232            FindRulesCache.resetTenantFindRulesCache(name)
1233            FindRulesCache.resetTenantTenantRulesCache(name)
1234            FindRulesCache.resetTenantCutCache(name)
1235
1236            let account=FindMarket.account
1237            let tenantPath=self.getTenantPathForName(name)
1238            let sp=StoragePath(identifier: tenantPath)!
1239            let pubp=PublicPath(identifier:tenantPath)!
1240            destroy account.storage.load<@Tenant>(from: sp)
1241            account.capabilities.unpublish(pubp)
1242            self.tenantAddressName.remove(key: tenant)
1243            self.tenantNameAddress.remove(key: name)
1244            emit FindTenantRemoved(tenant: name, address: tenant)
1245        }
1246
1247    }
1248
1249    access(account) fun createFindMarket(name: String, address:Address, findCutSaleItem: TenantSaleItem?) : Capability<&Tenant> {
1250        let account=FindMarket.account
1251
1252        let tenant <- create Tenant(name)
1253        //fetch the TenentRegistry from our storage path and add the new tenant with the given name and address
1254
1255        //add to registry
1256        self.tenantAddressName[address]=name
1257        self.tenantNameAddress[name]=address
1258
1259        if findCutSaleItem != nil {
1260            tenant.addSaleItem(findCutSaleItem!, type: "cut")
1261        }
1262
1263        //end do on outside
1264
1265        let tenantPath=self.getTenantPathForName(name)
1266        let sp=StoragePath(identifier: tenantPath)!
1267        let pubp=PublicPath(identifier:tenantPath)!
1268        account.storage.save(<- tenant, to: sp)
1269        //TODO:: What path to store here...
1270        let cap = account.capabilities.storage.issue<&Tenant>(sp)
1271        account.capabilities.publish(cap, at: pubp)
1272        let tenantCap = account.capabilities.storage.issue<&Tenant>(sp)
1273        return tenantCap
1274    }
1275
1276    access(all) fun getTenantPathForName(_ name:String) : String {
1277        if !self.tenantNameAddress.containsKey(name) {
1278            panic("tenant is not registered in registry")
1279        }
1280
1281        return self.tenantPathPrefix.concat("_").concat(name)
1282    }
1283
1284    access(all) fun getTenantPathForAddress(_ address:Address) : String {
1285        if !self.tenantAddressName.containsKey(address) {
1286            panic("tenant is not registered in registry")
1287        }
1288
1289        return self.getTenantPathForName(self.tenantAddressName[address]!)
1290    }
1291
1292    access(all) fun getTenantCapability(_ marketplace:Address) : Capability<&Tenant>? {
1293
1294        if !self.tenantAddressName.containsKey(marketplace)  {
1295            "tenant is not registered in registry"
1296        }
1297
1298        let cap= FindMarket.account.capabilities.get<&Tenant>(PublicPath(identifier:self.getTenantPathForAddress(marketplace))!)
1299        if !cap.check() {
1300            return nil
1301        }
1302        return cap
1303    }
1304
1305
1306    access(account) fun pay(tenant: String, id: UInt64, saleItem: &{SaleItem}, vault: @{FungibleToken.Vault}, royalty: MetadataViews.Royalties, nftInfo:NFTInfo, cuts:{String : FindMarketCutStruct.Cuts}, resolver: (fun (Address) : String?), resolvedAddress: {Address: String}) {
1307        let resolved : {Address : String} = resolvedAddress
1308
1309        fun resolveName(_ addr: Address ) : String? {
1310            if !resolved.containsKey(addr) {
1311                let name = resolver(addr)
1312                if name != nil {
1313                    resolved[addr] = name
1314                    return name
1315                } else {
1316                    resolved[addr] = ""
1317                    return nil
1318                }
1319            }
1320
1321            let name = resolved[addr]!
1322            if name == "" {
1323                return nil
1324            }
1325            return name
1326        }
1327
1328        let buyer=saleItem.getBuyer()
1329        let seller=saleItem.getSeller()
1330        let soldFor=vault.balance
1331        let ftType=vault.getType()
1332
1333        /* Residual Royalty */
1334        var payInFUT = false
1335        var payInDUC = false
1336        let ftInfo = FTRegistry.getFTInfoByTypeIdentifier(ftType.identifier)! // If this panic, there is sth wrong in FT set up
1337        let oldProfileCap= getAccount(seller).capabilities.get<&{FungibleToken.Receiver}>(Profile.publicReceiverPath)
1338        let oldProfile = self.getPaymentWallet(oldProfileCap, ftInfo, panicOnFailCheck: true)
1339
1340        /* Check the total royalty to prevent changing of royalties */
1341        let royalties = royalty.getRoyalties()
1342
1343        if royalties.length != 0 {
1344            var totalRoyalties : UFix64 = 0.0
1345
1346            for royaltyItem in royalties {
1347                totalRoyalties = totalRoyalties + royaltyItem.cut
1348                let description=royaltyItem.description
1349
1350                var cutAmount= soldFor * royaltyItem.cut
1351                if tenant == "onefootball" {
1352                    //{"onefootball largest of 6% or 0.65": 0.65)}
1353                    let minAmount = 0.65
1354
1355                    if minAmount > cutAmount {
1356                        cutAmount = minAmount
1357                    }
1358                }
1359
1360                var receiver = royaltyItem.receiver.address
1361                let name = resolveName(royaltyItem.receiver.address)
1362                let wallet = self.getPaymentWallet(royaltyItem.receiver, ftInfo, panicOnFailCheck: false)
1363
1364                /* If the royalty receiver check failed */
1365                if wallet.owner!.address == FindMarket.residualAddress {
1366                    emit RoyaltyCouldNotBePaid(tenant:tenant, id: id, saleID: saleItem.uuid, address:receiver, findName: name, royaltyName: description, amount: cutAmount,  vaultType: ftType.identifier, nft:nftInfo, residualAddress: wallet.owner!.address)
1367                    wallet.deposit(from: <- vault.withdraw(amount: cutAmount))
1368                    continue
1369                }
1370                emit RoyaltyPaid(tenant:tenant, id: id, saleID: saleItem.uuid, address:receiver, findName: name, royaltyName: description, amount: cutAmount,  vaultType: ftType.identifier, nft:nftInfo)
1371                wallet.deposit(from: <- vault.withdraw(amount: cutAmount))
1372            }
1373            if totalRoyalties != saleItem.getTotalRoyalties() {
1374                panic("The total Royalties to be paid is changed after listing.")
1375            }
1376        }
1377
1378        for key in cuts.keys {
1379            let allCuts = cuts[key]!
1380            for cut in allCuts.cuts {
1381                if var cutAmount= cut.getAmountPayable(soldFor) {
1382                    let findName = resolveName(cut.getAddress())
1383                    emit RoyaltyPaid(tenant: tenant, id: id, saleID: saleItem.uuid, address:cut.getAddress(), findName: findName , royaltyName: cut.getName(), amount: cutAmount,  vaultType: ftType.identifier, nft:nftInfo)
1384                    let vaultRef = cut.getReceiverCap().borrow() ?? panic("Royalty receiving account is not set up properly. Royalty account address : ".concat(cut.getAddress().toString()).concat(" Royalty Name : ").concat(cut.getName()))
1385                    vaultRef.deposit(from: <- vault.withdraw(amount: cutAmount))
1386                }
1387            }
1388        }
1389
1390        oldProfile.deposit(from: <- vault)
1391    }
1392
1393    access(account) fun getPaymentWallet(_ cap: Capability<&{FungibleToken.Receiver}>, _ ftInfo: FTRegistry.FTInfo, panicOnFailCheck: Bool) : &{FungibleToken.Receiver} {
1394        var tempCap = cap
1395
1396        // If capability is valid, we do not trust it but will do the below checks
1397        if tempCap.check() {
1398            let ref = cap.borrow()!
1399            let underlyingType = ref.getType()
1400            switch underlyingType {
1401                // If the underlying matches with the token type, we return the ref
1402            case ftInfo.type :
1403                return ref
1404                // If the underlying is a profile, we check if the wallet type is registered in profile wallet and then return
1405                // If it is not registered, it falls through and be handled by residual
1406            case Type<@Profile.User>():
1407                if let ProfileRef = getAccount(cap.address).capabilities.borrow<&{Profile.Public}>(Profile.publicPath) {
1408                    if ProfileRef.hasWallet(ftInfo.type.identifier) {
1409                        return ref
1410                    }
1411                }
1412                // If the underlying is a switchboard, we check if the wallet type is registered in switchboard wallet and then return
1413                // If it is not registered, it falls through and be handled by residual
1414            case Type<@FungibleTokenSwitchboard.Switchboard>() :
1415                if let sbRef = getAccount(cap.address).capabilities.borrow<&{FungibleTokenSwitchboard.SwitchboardPublic}>(FungibleTokenSwitchboard.PublicPath) {
1416                    if sbRef.isSupportedVaultType(type: ftInfo.type) {
1417                        return ref
1418                    }
1419                }
1420                // If the underlying is a tokenforwarder, we cannot verify if it is pointing to the right vault type.
1421                // The best we can do is to try borrow from the standard path and TRY deposit
1422            case Type<@TokenForwarding.Forwarder>() :
1423                // This might break FindMarket with NFT with "Any" kind of forwarder.
1424                // We might have to restrict this to only DUC FUT at the moment and fix it after.
1425                if !ftInfo.tag.contains("dapper"){
1426                    return ref
1427                }
1428            }
1429        }
1430
1431        // if capability is not valid, or if the above cases are fell through, we will try to get one with "standard" path
1432        if let tempCap = getAccount(cap.address).capabilities.borrow<&{FungibleToken.Receiver}>(ftInfo.receiverPath) {
1433            return tempCap
1434        }
1435
1436        if !panicOnFailCheck {
1437            // If it all falls throught, these edge cases will be handled by a residual account that has switchboard set up
1438            return getAccount(FindMarket.residualAddress).capabilities.borrow<&{FungibleToken.Receiver}>(FungibleTokenSwitchboard.ReceiverPublicPath) ?? panic("Cannot borrow residual vault in address : ".concat(FindMarket.residualAddress.toString()).concat(" type : ").concat(ftInfo.typeIdentifier))
1439        }
1440
1441        let msg = "User ".concat(cap.address.toString()).concat(" does not have any usable links set up for vault type ").concat(ftInfo.typeIdentifier)
1442        panic(msg)
1443    }
1444
1445    access(all) struct NFTInfo {
1446        access(all) let id: UInt64
1447        access(all) let name:String
1448        access(all) let thumbnail:String
1449        access(all) let type: String
1450        access(all) var rarity:String?
1451        access(all) var editionNumber: UInt64?
1452        access(all) var totalInEdition: UInt64?
1453        access(all) var scalars: {String:UFix64}
1454        access(all) var tags : {String:String}
1455        access(all) var collectionName: String?
1456        access(all) var collectionDescription: String?
1457
1458        init(_ item: &{ViewResolver.Resolver}, id: UInt64, detail: Bool){
1459
1460            self.tags = { "uuid" : item.uuid.toString()}
1461
1462            self.collectionName=nil
1463            self.collectionDescription=nil
1464            //uuid is not here anymore
1465            self.scalars = { }
1466            self.rarity= nil
1467            self.editionNumber=nil
1468            self.totalInEdition=nil
1469            let display = MetadataViews.getDisplay(item) ?? panic("cannot get MetadataViews.Display View")
1470            self.name=display.name
1471            self.thumbnail=display.thumbnail.uri()
1472            self.type=item.getType().identifier
1473            self.id=id
1474
1475            if detail {
1476                if let ncd = MetadataViews.getNFTCollectionDisplay(item) {
1477                    self.collectionName=ncd.name
1478                    self.collectionDescription=ncd.description
1479                }
1480
1481                if let rarity = MetadataViews.getRarity(item) {
1482                    if rarity.description != nil {
1483                        self.rarity=rarity.description!
1484                    }
1485
1486                    if rarity.score != nil {
1487                        self.scalars["rarity_score"] = rarity.score!
1488                    }
1489                    if rarity.max != nil {
1490                        self.scalars["rarity_max"] = rarity.max!
1491                    }
1492                }
1493
1494                let numericValues  = {"Date" : true, "Numeric":true, "Number":true, "date":true, "numeric":true, "number":true}
1495
1496                var singleTrait : MetadataViews.Trait? = nil
1497                let traits : [MetadataViews.Trait] = []
1498                if let view = item.resolveView(Type<MetadataViews.Trait>()) {
1499                    if let t = view as? MetadataViews.Trait {
1500                        singleTrait = t
1501                        traits.append(t)
1502                    }
1503                }
1504
1505                if let t =  MetadataViews.getTraits(item) {
1506                    traits.appendAll(t.traits)
1507                }
1508
1509                for trait in traits {
1510
1511                    let name = trait.name
1512                    let display = trait.displayType ?? "String"
1513
1514                    var traitName = name
1515
1516                    if numericValues[display] != nil {
1517
1518                        if display == "Date" || display == "date" {
1519                            traitName = "date.".concat(traitName)
1520                        }
1521
1522                        if let value = trait.value as? Number {
1523                            self.scalars[traitName]  = UFix64(value)
1524                        }
1525                    } else {
1526                        if let value = trait.value as? String {
1527                            self.tags[traitName]  = value
1528                        }
1529                        if let value = trait.value as? Bool {
1530                            if value {
1531                                self.tags[traitName]  = "true"
1532                            }else {
1533                                self.tags[traitName]  = "false"
1534                            }
1535                        }
1536
1537                    }
1538                    if let rarity = trait.rarity {
1539                        if rarity.description != nil {
1540                            self.tags[traitName.concat("_rarity")] = rarity.description!
1541                        }
1542
1543                        if rarity.score != nil {
1544                            self.scalars[traitName.concat("_rarity_score")] = rarity.score!
1545                        }
1546                        if rarity.max != nil {
1547                            self.scalars[traitName.concat("_rarity_max")] = rarity.max!
1548                        }
1549                    }
1550                }
1551
1552                var singleEdition : MetadataViews.Edition? = nil
1553                let editions : [MetadataViews.Edition] = []
1554                if let view = item.resolveView(Type<MetadataViews.Edition>()) {
1555                    if let e = view as? MetadataViews.Edition {
1556                        singleEdition = e
1557                        editions.append(e)
1558                    }
1559                }
1560
1561                if let e = MetadataViews.getEditions(item) {
1562                    editions.appendAll(e.infoList)
1563                }
1564
1565                for edition in editions {
1566
1567                    if edition.name == nil {
1568                        self.editionNumber=edition.number
1569                        self.totalInEdition=edition.max
1570                    } else {
1571                        self.scalars["edition_".concat(edition.name!).concat("_number")] = UFix64(edition.number)
1572                        if edition.max != nil {
1573                            self.scalars["edition_".concat(edition.name!).concat("_max")] = UFix64(edition.max!)
1574                        }
1575                    }
1576                }
1577
1578                if let serial = MetadataViews.getSerial(item) {
1579                    self.scalars["serial_number"] = UFix64(serial.number)
1580                }
1581
1582                if let url = MetadataViews.getExternalURL(item) {
1583                    self.tags["external_url"] = url.url
1584                }
1585            }
1586        }
1587    }
1588
1589    access(all) struct GhostListing{
1590        //		access(all) let listingType: Type
1591        access(all) let listingTypeIdentifier: String
1592        access(all) let id: UInt64
1593
1594        init(listingType:Type, id:UInt64) {
1595            //			self.listingType=listingType
1596            self.listingTypeIdentifier=listingType.identifier
1597            self.id=id
1598        }
1599    }
1600
1601    access(all) struct AuctionItem {
1602        //end time
1603        //current time
1604        access(all) let startPrice: UFix64
1605        access(all) let currentPrice: UFix64
1606        access(all) let minimumBidIncrement: UFix64
1607        access(all) let reservePrice: UFix64
1608        access(all) let extentionOnLateBid: UFix64
1609        access(all) let auctionEndsAt: UFix64?
1610        access(all) let timestamp: UFix64
1611
1612        init(startPrice: UFix64, currentPrice: UFix64, minimumBidIncrement: UFix64, reservePrice: UFix64, extentionOnLateBid: UFix64, auctionEndsAt: UFix64? , timestamp: UFix64){
1613            self.startPrice = startPrice
1614            self.currentPrice = currentPrice
1615            self.minimumBidIncrement = minimumBidIncrement
1616            self.reservePrice = reservePrice
1617            self.extentionOnLateBid = extentionOnLateBid
1618            self.auctionEndsAt = auctionEndsAt
1619            self.timestamp = timestamp
1620        }
1621    }
1622
1623    access(all) resource interface SaleItemCollectionPublic {
1624        access(all) fun getIds(): [UInt64]
1625        access(all) fun getRoyaltyChangedIds(): [UInt64]
1626        access(all) fun containsId(_ id: UInt64): Bool
1627        access(all) fun borrowSaleItem(_ id: UInt64) : &{SaleItem}?
1628        access(all) fun getListingType() : Type
1629    }
1630
1631    access(all) struct SaleItemCollectionReport {
1632        access(all) let items : [FindMarket.SaleItemInformation]
1633        access(all) let ghosts: [FindMarket.GhostListing]
1634
1635        init(items: [SaleItemInformation], ghosts: [GhostListing]) {
1636            self.items=items
1637            self.ghosts=ghosts
1638        }
1639    }
1640
1641    access(all) resource interface MarketBidCollectionPublic {
1642        access(all) fun getIds() : [UInt64]
1643        access(all) fun containsId(_ id: UInt64): Bool
1644        access(all) fun getBidType() : Type
1645        //TODO: this used to be access account, look ino this, because now we can downcast
1646        access(all) fun borrowBidItem(_ id: UInt64) : &{Bid}
1647    }
1648
1649    access(all) struct BidItemCollectionReport {
1650        access(all) let items : [FindMarket.BidInfo]
1651        access(all) let ghosts: [FindMarket.GhostListing]
1652
1653        init(items: [BidInfo], ghosts: [GhostListing]) {
1654            self.items=items
1655            self.ghosts=ghosts
1656        }
1657    }
1658
1659    access(all) resource interface Bid {
1660        access(all) fun getBalance() : UFix64
1661        access(all) fun getSellerAddress() : Address
1662        access(all) fun getBidExtraField() : {String : AnyStruct}
1663    }
1664
1665    access(all) resource interface SaleItem {
1666        //this is the type of sale this is, auction, direct offer etc
1667        access(all) fun getSaleType(): String
1668        access(all) fun getListingTypeIdentifier(): String
1669
1670        access(all) fun getSeller(): Address
1671        access(all) fun getBuyer(): Address?
1672
1673        access(all) fun getSellerName() : String?
1674        access(all) fun getBuyerName() : String?
1675
1676        access(all) fun toNFTInfo(_ detail: Bool) : FindMarket.NFTInfo
1677        access(all) fun checkPointer() : Bool
1678        access(all) fun checkSoulBound() : Bool
1679        access(all) fun getListingType() : Type
1680
1681        // access(all) getFtAlias(): String
1682        //the Type of the item for sale
1683        access(all) fun getItemType(): Type
1684        //The id of fun the nft for sale
1685        access(all) fun getItemID() : UInt64
1686        //The id of fun this sale item, ie the UUID of the item listed for sale
1687        access(all) fun getId() : UInt64
1688
1689        access(all) fun getBalance(): UFix64
1690        access(all) fun getAuction(): AuctionItem?
1691        access(all) fun getFtType() : Type //The type of FT used for this sale item
1692        access(all) fun getValidUntil() : UFix64? //A timestamp that says when this item is valid until
1693
1694        access(all) fun getSaleItemExtraField() : {String : AnyStruct}
1695
1696        access(all) fun getTotalRoyalties() : UFix64
1697        access(all) fun validateRoyalties() : Bool
1698        access(all) fun getDisplay() : MetadataViews.Display
1699        access(all) fun getNFTCollectionData() : MetadataViews.NFTCollectionData
1700    }
1701
1702    access(all) struct SaleItemInformation {
1703        access(all) let nftIdentifier: String
1704        access(all) let nftId: UInt64
1705        access(all) let seller: Address
1706        access(all) let sellerName: String?
1707        access(all) let amount: UFix64?
1708        access(all) let bidder: Address?
1709        access(all) var bidderName: String?
1710        access(all) let listingId: UInt64
1711
1712        access(all) let saleType: String
1713        access(all) let listingTypeIdentifier: String
1714        access(all) let ftAlias: String
1715        access(all) let ftTypeIdentifier: String
1716        access(all) let listingValidUntil: UFix64?
1717
1718        access(all) var nft: NFTInfo?
1719        access(all) let auction: AuctionItem?
1720        access(all) let listingStatus:String
1721        access(all) let saleItemExtraField: {String : AnyStruct}
1722
1723        init(item: &{SaleItem}, status:String, nftInfo: Bool) {
1724            self.nftIdentifier= item.getItemType().identifier
1725            self.nftId=item.getItemID()
1726            self.listingStatus=status
1727            self.saleType=item.getSaleType()
1728            self.listingTypeIdentifier=item.getListingTypeIdentifier()
1729            self.listingId=item.getId()
1730            self.amount=item.getBalance()
1731            self.bidder=item.getBuyer()
1732            self.bidderName=item.getBuyerName()
1733            self.seller=item.getSeller()
1734            self.sellerName=item.getSellerName()
1735            self.listingValidUntil=item.getValidUntil()
1736            self.nft=nil
1737            if nftInfo {
1738                if status != "stopped" {
1739                    self.nft=item.toNFTInfo(true)
1740                }
1741            }
1742            let ftIdentifier=item.getFtType().identifier
1743            self.ftTypeIdentifier=ftIdentifier
1744            let ftInfo=FTRegistry.getFTInfoByTypeIdentifier(ftIdentifier)
1745            self.ftAlias=ftInfo?.alias ?? ""
1746
1747            self.auction=item.getAuction()
1748            self.saleItemExtraField=item.getSaleItemExtraField()
1749        }
1750    }
1751
1752    access(all) struct BidInfo {
1753        access(all) let id: UInt64
1754        access(all) let bidAmount: UFix64
1755        access(all) let bidTypeIdentifier: String
1756        access(all) let timestamp: UFix64
1757        access(all) let item: SaleItemInformation
1758
1759        init(id: UInt64, bidTypeIdentifier: String, bidAmount: UFix64, timestamp: UFix64, item:SaleItemInformation) {
1760            self.id=id
1761            self.bidAmount=bidAmount
1762            self.bidTypeIdentifier=bidTypeIdentifier
1763            self.timestamp=timestamp
1764            self.item=item
1765        }
1766    }
1767
1768    access(all) fun getTenantAddress(_ name: String) : Address? {
1769        return FindMarket.tenantNameAddress[name]
1770    }
1771
1772    access(account) fun setResidualAddress(_ address: Address) {
1773        FindMarket.residualAddress = address
1774    }
1775
1776    init() {
1777        self.tenantAddressName={}
1778        self.tenantNameAddress={}
1779
1780        self.TenantClientPublicPath=/public/findMarketClient
1781        self.TenantClientStoragePath=/storage/findMarketClient
1782
1783        self.tenantPathPrefix=  FindMarket.typeToPathIdentifier(Type<@Tenant>())
1784
1785        self.saleItemTypes = []
1786        self.saleItemCollectionTypes = []
1787        self.pathMap = {}
1788        self.listingName={}
1789        self.marketBidTypes = []
1790        self.marketBidCollectionTypes = []
1791        self.residualAddress = self.account.address // This has to be changed
1792    }
1793}
1794