Smart Contract

Escrow

A.4da127056dc9ba3f.Escrow

Valid From

86,719,009

Deployed

1d ago
Feb 24, 2026, 11:43:45 PM UTC

Dependents

81799 imports
1/*
2    Escrow Contract for managing NFTs in a Leaderboard Context.
3    Holds NFTs in Escrow account awaiting transfer or burn.
4
5    Authors:
6        Corey Humeston: corey.humeston@dapperlabs.com
7        Deewai Abdullahi: innocent.abdullahi@dapperlabs.com
8*/
9
10import NonFungibleToken from 0x1d7e57aa55817448
11import NFTLocker from 0xb6f2481eba4df97b
12
13access(all) contract Escrow {
14    // Event emitted when a new leaderboard is created.
15    access(all) event LeaderboardCreated(name: String, nftType: Type)
16
17    // Event emitted when an NFT is deposited to a leaderboard.
18    access(all) event EntryDeposited(leaderboardName: String, nftID: UInt64, owner: Address)
19
20    // Event emitted when an NFT is returned to the original collection from a leaderboard.
21    access(all) event EntryReturnedToCollection(leaderboardName: String, nftID: UInt64, owner: Address)
22
23    // Event emitted when an NFT is burned from a leaderboard.
24    access(all) event EntryBurned(leaderboardName: String, nftID: UInt64)
25
26    // Named Paths
27    access(all) let CollectionStoragePath: StoragePath
28    access(all) let CollectionPublicPath: PublicPath
29
30    // The resource representing an NFT leaderboard's info.
31    access(all) struct LeaderboardInfo {
32        access(all) let name: String
33        access(all) let nftType: Type
34        access(all) let entriesLength: Int
35
36        // LeaderboardInfo struct initializer.
37        init(name: String, nftType: Type, entriesLength: Int) {
38            self.name = name
39            self.nftType = nftType
40            self.entriesLength = entriesLength
41        }
42    }
43
44    // The resource representing a leaderboard.
45    access(all) resource Leaderboard {
46        access(all) var collection: @{NonFungibleToken.Collection}
47        access(all) var entriesData: {UInt64: LeaderboardEntry}
48        access(all) let name: String
49        access(all) let nftType: Type
50        access(all) var entriesLength: Int
51        access(all) var metadata: {String: AnyStruct}
52
53        // Adds the provided NFT to the leaderboard and creates the corresponding entry with the provided ownerAddress, which is
54        // used to validate the deposit capability when calling transferNftToCollection.
55        access(contract) fun addEntryToLeaderboard(nft: @{NonFungibleToken.NFT}, ownerAddress: Address) {
56            pre {
57                nft.isInstance(self.nftType): "This NFT cannot be used for leaderboard. NFT is not of the correct type."
58            }
59
60            let nftID = nft.id
61
62            // Create the entry and add it to the Leaderboard's entries map.
63            self.entriesData[nftID] = LeaderboardEntry(
64                nftID: nftID,
65                ownerAddress: ownerAddress,
66                metadata: {},
67            )
68
69            // Deposit the NFT into the leaderboard's NFT collection.
70            self.collection.deposit(token: <-nft)
71
72            // Increment entries length.
73            self.entriesLength = self.entriesLength + 1
74
75            emit EntryDeposited(leaderboardName: self.name, nftID: nftID, owner: ownerAddress)
76        }
77
78        // Transfers an NFT entry from the leaderboard to an account.
79        access(contract) fun transferNftToCollection(nftID: UInt64, depositCap: Capability<&{NonFungibleToken.Collection}>) {
80        pre {
81            depositCap.check() : "Deposit capability is not valid"
82        }
83            // Remove the NFT entry's data from the leaderboard.
84            self.entriesData.remove(key: nftID)!
85
86            // Transfer the NFT to the receiver's collection.
87            let receiverCollection = depositCap.borrow()
88                ?? panic("Could not borrow the NFT receiver from the capability")
89            receiverCollection.deposit(token: <- self.collection.withdraw(withdrawID: nftID))
90            emit EntryReturnedToCollection(leaderboardName: self.name, nftID: nftID, owner: depositCap.address)
91
92            // Decrement entries length.
93            self.entriesLength = self.entriesLength - 1
94        }
95
96        // Burns an NFT entry from the leaderboard.
97        access(contract) fun burn(nftID: UInt64) {
98            if(self.entriesData[nftID] == nil) {
99                return
100            }
101
102            // Remove the NFT entry's data from the leaderboard.
103            self.entriesData.remove(key: nftID)!
104
105            // Burn the NFT.
106            destroy <- self.collection.withdraw(withdrawID: nftID)
107            emit EntryBurned(leaderboardName: self.name, nftID: nftID)
108
109            // Decrement entries length.
110            self.entriesLength = self.entriesLength - 1
111        }
112
113        // Leaderboard resource initializer.
114        init(name: String, nftType: Type, collection: @{NonFungibleToken.Collection}) {
115            self.name = name
116            self.nftType = nftType
117            self.collection <- collection
118            self.entriesLength = 0
119            self.metadata = {}
120            self.entriesData = {}
121        }
122    }
123
124    // The resource representing an NFT entry in a leaderboard.
125    access(all) struct LeaderboardEntry {
126        access(all) let nftID: UInt64
127        access(all) let ownerAddress: Address
128        access(all) var metadata: {String: AnyStruct}
129
130        // LeaderboardEntry struct initializer.
131        init(nftID: UInt64, ownerAddress: Address, metadata: {String: AnyStruct}) {
132            self.nftID = nftID
133            self.ownerAddress = ownerAddress
134            self.metadata = metadata
135        }
136    }
137
138    // An interface containing the Collection function that gets leaderboards by name.
139    access(all) resource interface ICollectionPublic {
140        access(all) fun getLeaderboardInfo(name: String): LeaderboardInfo?
141        access(all) fun addEntryToLeaderboard(nft: @{NonFungibleToken.NFT}, leaderboardName: String, ownerAddress: Address)
142        access(Operate) fun createLeaderboard(name: String, nftType: Type, collection: @{NonFungibleToken.Collection})
143        access(Operate) fun transferNftToCollection(leaderboardName: String, nftID: UInt64, depositCap: Capability<&{NonFungibleToken.Collection}>)
144        access(Operate) fun burn(leaderboardName: String, nftID: UInt64)
145    }
146
147    // Entitlement that grants the ability to operate the Escrow Collection
148    access(all) entitlement Operate
149
150    // Deprecated in favor of Operate entitlement
151    access(all) resource interface ICollectionPrivate: ICollectionPublic {}
152
153    // The resource representing a collection.
154    access(all) resource Collection: ICollectionPublic, ICollectionPrivate {
155        // A dictionary holding leaderboards.
156        access(self) var leaderboards: @{String: Leaderboard}
157
158        // Creates a new leaderboard and stores it.
159        access(Operate) fun createLeaderboard(name: String, nftType: Type, collection: @{NonFungibleToken.Collection}) {
160            pre{
161                self.leaderboards.containsKey(name) == false: "Leaderboard already exists with this name"
162            }
163
164            // Create and store leaderboard resource in the leaderboards dictionary.
165            self.leaderboards[name] <-! create Leaderboard(name: name, nftType: nftType, collection: <-collection)
166
167            // Emit the event.
168            emit LeaderboardCreated(name: name, nftType: nftType)
169        }
170
171        // Returns leaderboard info with the given name.
172        access(all) fun getLeaderboardInfo(name: String): LeaderboardInfo? {
173            if let leaderboard = &self.leaderboards[name] as &Leaderboard? {
174                return LeaderboardInfo(
175                    name: leaderboard.name,
176                    nftType: leaderboard.nftType,
177                    entriesLength: leaderboard.entriesLength
178                )
179            }
180            return nil
181        }
182
183        // Adds the provided NFT to the leaderboard and creates the corresponding entry with the provided ownerAddress, which is
184        // used to validate the deposit capability when calling transferNftToCollection.
185        access(all) fun addEntryToLeaderboard(nft: @{NonFungibleToken.NFT}, leaderboardName: String, ownerAddress: Address) {
186            let leaderboard = &self.leaderboards[leaderboardName] as &Leaderboard?
187                ?? panic("Leaderboard does not exist with this name")
188            leaderboard.addEntryToLeaderboard(nft: <-nft, ownerAddress: ownerAddress)
189        }
190
191        // transferNftToCollection transfers the moment back to the owner's collection once the leaderboard is completed.
192        // This is used for entries of users who didn't win the leaderboard
193        access(Operate) fun transferNftToCollection(leaderboardName: String, nftID: UInt64, depositCap: Capability<&{NonFungibleToken.Collection}>) {
194            let leaderboard= &self.leaderboards[leaderboardName] as &Leaderboard?
195                ?? panic("Leaderboard does not exist with this name")
196
197            if(leaderboard.entriesData[nftID] == nil) {
198                return
199            }
200
201            if(depositCap.address != leaderboard.entriesData[nftID]!.ownerAddress){
202                panic("Only the owner of the entry can withdraw it")
203            }
204
205            leaderboard.transferNftToCollection(nftID: nftID, depositCap: depositCap)
206        }
207
208        // adminTransferNftToCollection transfers the moment back to an admin collection once the leaderboard is completed.
209        // This is used for entries of users who won the leaderboard
210        access(Operate) fun adminTransferNftToCollection(leaderboardName: String, nftID: UInt64, depositCap: Capability<&{NonFungibleToken.Collection}>) {
211            let leaderboard= &self.leaderboards[leaderboardName] as &Leaderboard?
212                ?? panic("Leaderboard does not exist with this name")
213
214            if(leaderboard.entriesData[nftID] == nil) {
215                return
216            }
217
218            leaderboard.transferNftToCollection(nftID: nftID, depositCap: depositCap)
219        }
220
221        // burn destroys the NFT from the leaderboard.
222        // this is used for entries of users who won the leaderboard
223        access(Operate) fun burn(leaderboardName: String, nftID: UInt64) {
224            let leaderboard = &self.leaderboards[leaderboardName] as &Leaderboard?
225                ?? panic("Leaderboard does not exist with this name")
226            leaderboard.burn(nftID: nftID)
227        }
228
229        // Collection resource initializer.
230        init() {
231            self.leaderboards <- {}
232        }
233    }
234
235    // Handler for depositing NFTs to the Escrow Collection, used by the NFTLocker contract.
236    access(all) struct DepositHandler: NFTLocker.IAuthorizedDepositHandler {
237        access(all) fun deposit(nft: @{NonFungibleToken.NFT}, ownerAddress: Address, passThruParams: {String: AnyStruct}) {
238            // Get leaderboard name from pass-thru parameters
239            let leaderboardName = passThruParams["leaderboardName"] as! String?
240                ?? panic("Missing or invalid 'leaderboardName' entry in pass-thru parameters map")
241
242            // Get the Escrow Collection public reference
243            let escrowCollectionPublic = Escrow.account.capabilities.borrow<&Escrow.Collection>(Escrow.CollectionPublicPath)
244                ?? panic("Could not borrow a reference to the public leaderboard collection")
245
246            // Add the NFT to the escrow leaderboard
247            escrowCollectionPublic.addEntryToLeaderboard(nft: <-nft, leaderboardName: leaderboardName, ownerAddress: ownerAddress)
248        }
249    }
250
251    // Escrow contract initializer.
252    init() {
253        // Initialize paths.
254        self.CollectionStoragePath = /storage/EscrowLeaderboardCollection
255        self.CollectionPublicPath = /public/EscrowLeaderboardCollectionInfo
256
257        // Create and save a Collection resource to account storage.
258        self.account.storage.save(<- create Collection(), to: self.CollectionStoragePath)
259
260        // Create a public capability to the Collection resource and publish it publicly.
261        self.account.capabilities.publish(
262            self.account.capabilities.storage.issue<&Collection>(self.CollectionStoragePath),
263            at: self.CollectionPublicPath
264        )
265    }
266}
267