Smart Contract
FindMarket
A.097bafa4e0b48eef.FindMarket
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