Smart Contract
FlowtyListingCallback
A.3cdbb3d569211ff3.FlowtyListingCallback
1import MetadataViews from 0x1d7e57aa55817448
2import NonFungibleToken from 0x1d7e57aa55817448
3
4import FlowtyViews from 0x3cdbb3d569211ff3
5
6/*
7FlowtyListingCallback
8
9A contract to allow the injection of custom logic for the various
10lifecycle events of a listing. For now, these callbacks are limited purely to
11those created by the caller of the listing. In the future, it may be the case that
12an nft itself could define its own callback.
13*/
14access(all) contract FlowtyListingCallback {
15
16 access(all) entitlement Owner
17
18 access(all) entitlement Handle
19
20 access(all) let ContainerStoragePath: StoragePath
21
22 /*
23 The stage of a listing represents what part of a listing lifecycle a callback is being initiated into.
24 */
25 access(all) enum Stage: Int8 {
26 access(all) case Created // When a listing is made
27 access(all) case Filled // When a listing is filled (purchased, loan funded, rental rented)
28 access(all) case Completed // When a listing's life cycle completed (loan repaid, rental returned)
29 access(all) case Destroyed // When a listing is destroyed (this should only apply if the listing was not filled previously)
30 }
31
32 /*
33 So that we do not take in `AnyResource` as the input, a base resource interface type is defined
34 that other listings can extend. In the future, this listing type will also need to resolve information about
35 the listing such as what stage it's in, and details about the listing itself
36 */
37 access(all) resource interface Listing {
38 // There are no specific metadata views yet, and we cannot extend interfaces until
39 // Crescendo goes live, so for now we are making an interface that fills the same need
40 // as MetdataViews until we can extend them in the future.
41 access(all) fun getViews(): [Type] {
42 return []
43 }
44
45 access(all) fun resolveView(_ view: Type): AnyStruct? {
46 return nil
47 }
48 }
49
50 /*
51 The Handler is an interface with a single method that handles a listing each time the platform it origniated from
52 determines the callback is necessary.
53
54 **The handler resource is NOT in charge of the stage in a callback**
55 */
56 access(all) resource interface Handler {
57 access(Handle) fun handle(stage: Stage, listing: &{Listing}, nft: &{NonFungibleToken.NFT}?): Bool
58 access(all) fun validateListing(listing: &{Listing}, nft: &{NonFungibleToken.NFT}?): Bool
59 }
60
61 /*
62 Container is a general-purpose object that stores handlers.
63
64 There are type-specific handler mappings which might be required to handle details about special types of
65 nfts (like a Top Shot moment and whether it is locked). And there is a list of default handlers which will
66 always run such as a handler to record and compare the DNA of an NFT to ensure what is being bought is what
67 was listed (DNA is not changed).
68
69In the future, it may be possible for NFTs to define their own handlers, but this is not supported currently.
70 */
71 access(all) resource Container {
72 access(all) let nftTypeHandlers: @{Type: {Handler}}
73 access(all) var defaultHandlers: @[{Handler}]
74
75 access(all) let data: {String: AnyStruct}
76 access(all) let resources: @{String: AnyResource}
77
78 access(Owner) fun register(type: Type, handler: @{Handler}) {
79 pre {
80 type.isSubtype(of: Type<@{NonFungibleToken.NFT}>()): "registered type must be an NFT"
81 }
82
83 destroy self.nftTypeHandlers.insert(key: type, <- handler)
84 }
85
86 access(Handle) fun handle(stage: Stage, listing: &{Listing}, nft: &{NonFungibleToken.NFT}?): Bool {
87 let nftType = nft != nil ? nft!.getType() : nft.getType()
88
89 var res = true
90 // TODO: a custom metadata view for anyone to define their own callback
91 if let nftHandler = &self.nftTypeHandlers[nftType] as auth(Handle) &{Handler}? {
92 res = nftHandler.handle(stage: stage, listing: listing, nft: nft)
93 }
94
95 var index = 0
96 while index < self.defaultHandlers.length {
97 let ref = &self.defaultHandlers[index] as auth(Handle) &{Handler}
98 res = res && ref.handle(stage: stage, listing: listing, nft: nft)
99 index = index + 1
100 }
101
102 return res
103 }
104
105 access(all) fun validateListing(listing: &{FlowtyListingCallback.Listing}, nft: &{NonFungibleToken.NFT}?): Bool {
106 let nftType = nft != nil ? nft!.getType() : nft.getType()
107
108 var res = true
109 if let nftHandler = &self.nftTypeHandlers[nftType] as &{Handler}? {
110 res = nftHandler.validateListing(listing: listing, nft: nft)
111 }
112
113 var index = 0
114 while index < self.defaultHandlers.length {
115 let ref = &self.defaultHandlers[index] as &{Handler}
116 res = res && ref.validateListing(listing: listing, nft: nft)
117 index = index + 1
118 }
119
120 return res
121 }
122
123 access(Owner) fun addDefaultHandler(h: @{Handler}) {
124 self.defaultHandlers.append(<-h)
125 }
126
127 access(Owner) fun removeDefaultHandlerAt(index: Int): @{Handler}? {
128 if index >= self.defaultHandlers.length {
129 return nil
130 }
131
132 return <- self.defaultHandlers.remove(at: index)
133 }
134
135 init(defaultHandler: @{Handler}) {
136 self.defaultHandlers <- [ <-defaultHandler]
137 self.nftTypeHandlers <- {}
138
139 self.data = {}
140 self.resources <- {}
141 }
142 }
143
144 access(all) fun createContainer(defaultHandler: @{Handler}): @Container {
145 return <- create Container(defaultHandler: <- defaultHandler)
146 }
147
148 init() {
149 self.ContainerStoragePath = StoragePath(identifier: "FlowtyListingCallback_".concat(FlowtyListingCallback.account.address.toString()))!
150 }
151}