Smart Contract

FlowtyListingCallback

A.3cdbb3d569211ff3.FlowtyListingCallback

Deployed

1w ago
Feb 20, 2026, 04:31:30 AM UTC

Dependents

0 imports
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}