Smart Contract

TopShotLocking

A.0b2a3299cc857e29.TopShotLocking

Deployed

1w ago
Feb 17, 2026, 04:29:37 PM UTC

Dependents

37979 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2
3access(all) contract TopShotLocking {
4
5    // -----------------------------------------------------------------------
6    // TopShotLocking contract Events
7    // -----------------------------------------------------------------------
8
9    // Emitted when a Moment is locked
10    access(all) event MomentLocked(id: UInt64, duration: UFix64, expiryTimestamp: UFix64)
11
12    // Emitted when a Moment is unlocked
13    access(all) event MomentUnlocked(id: UInt64)
14
15    // Dictionary of locked NFTs
16    // TopShot nft resource id is the key
17    // locked until timestamp is the value
18    access(self) var lockedNFTs: {UInt64: UFix64}
19
20    // Dictionary of NFTs overridden to be unlocked
21    access(self) var unlockableNFTs: {UInt64: Bool} // nft resource id is the key
22
23    // isLocked Returns a boolean indicating if an nft exists in the lockedNFTs dictionary
24    //
25    // Parameters: nftRef: A reference to the NFT resource
26    //
27    // Returns: true if NFT is locked
28    access(all) view fun isLocked(nftRef: &{NonFungibleToken.NFT}): Bool {
29        return self.lockedNFTs.containsKey(nftRef.id)
30    }
31
32    // getLockExpiry Returns the unix timestamp when an nft is unlockable
33    //
34    // Parameters: nftRef: A reference to the NFT resource
35    //
36    // Returns: unix timestamp
37    access(all) view fun getLockExpiry(nftRef: &{NonFungibleToken.NFT}): UFix64 {
38        if !self.lockedNFTs.containsKey(nftRef.id) {
39            panic("NFT is not locked")
40        }
41        return self.lockedNFTs[nftRef.id]!
42    }
43
44    // lockNFT Takes an NFT resource and adds its unique identifier to the lockedNFTs dictionary
45    //
46    // Parameters: nft: NFT resource
47    //             duration: number of seconds the NFT will be locked for
48    //
49    // Returns: the NFT resource
50    access(all) fun lockNFT(nft: @{NonFungibleToken.NFT}, duration: UFix64): @{NonFungibleToken.NFT} {
51        let TopShotNFTType: Type = CompositeType("A.0b2a3299cc857e29.TopShot.NFT")!
52        if !nft.isInstance(TopShotNFTType) {
53            panic("NFT is not a TopShot NFT")
54        }
55
56        if self.lockedNFTs.containsKey(nft.id) {
57            // already locked - short circuit and return the nft
58            return <- nft
59        }
60
61        let expiryTimestamp = getCurrentBlock().timestamp + duration
62
63        self.lockedNFTs[nft.id] = expiryTimestamp
64
65        emit MomentLocked(id: nft.id, duration: duration, expiryTimestamp: expiryTimestamp)
66
67        return <- nft
68    }
69
70    // unlockNFT Takes an NFT resource and removes it from the lockedNFTs dictionary
71    //
72    // Parameters: nft: NFT resource
73    //
74    // Returns: the NFT resource
75    //
76    // NFT must be eligible for unlocking by an admin
77    access(all) fun unlockNFT(nft: @{NonFungibleToken.NFT}): @{NonFungibleToken.NFT} {
78        if !self.lockedNFTs.containsKey(nft.id) {
79            // nft is not locked, short circuit and return the nft
80            return <- nft
81        }
82
83        let lockExpiryTimestamp: UFix64 = self.lockedNFTs[nft.id]!
84        let isPastExpiry: Bool = getCurrentBlock().timestamp >= lockExpiryTimestamp
85
86        let isUnlockableOverridden: Bool = self.unlockableNFTs.containsKey(nft.id)
87
88        if !(isPastExpiry || isUnlockableOverridden) {
89            panic("NFT is not eligible to be unlocked, expires at ".concat(lockExpiryTimestamp.toString()))
90        }
91
92        self.unlockableNFTs.remove(key: nft.id)
93        self.lockedNFTs.remove(key: nft.id)
94
95        emit MomentUnlocked(id: nft.id)
96
97        return <- nft
98    }
99
100    // getIDs Returns the ids of all locked Top Shot NFT tokens
101    //
102    // Returns: array of ids
103    //
104    access(all) view fun getIDs(): [UInt64] {
105        return self.lockedNFTs.keys
106    }
107
108    // getExpiry Returns the timestamp when a locked token is eligible for unlock
109    //
110    // Parameters: tokenID: the nft id of the locked token
111    //
112    // Returns: a unix timestamp in seconds
113    //
114    access(all) view fun getExpiry(tokenID: UInt64): UFix64? {
115        return self.lockedNFTs[tokenID]
116    }
117
118    // getLockedNFTsLength Returns the count of locked tokens
119    //
120    // Returns: an integer containing the number of locked tokens
121    //
122    access(all) view fun getLockedNFTsLength(): Int {
123        return self.lockedNFTs.length
124    }
125
126    // The path to the TopShotLocking Admin resource belonging to the Account
127    // which the contract is deployed on
128    access(all) view fun AdminStoragePath() : StoragePath { return /storage/TopShotLockingAdmin}
129
130    // Admin is a special authorization resource that 
131    // allows the owner to override the lock on a moment
132    //
133    access(all) resource Admin {
134        // createNewAdmin creates a new Admin resource
135        //
136        access(all) fun createNewAdmin(): @Admin {
137            return <-create Admin()
138        }
139
140        // markNFTUnlockable marks a given nft as being
141        // unlockable, overridding the expiry timestamp
142        // the nft owner will still need to send an unlock transaction to unlock
143        //
144        access(all) fun markNFTUnlockable(nftRef: &{NonFungibleToken.NFT}) {
145            TopShotLocking.unlockableNFTs[nftRef.id] = true
146        }
147
148        access(all) fun unlockByID(id: UInt64) {
149            if !TopShotLocking.lockedNFTs.containsKey(id) {
150                // nft is not locked, do nothing
151                return
152            }
153            TopShotLocking.lockedNFTs.remove(key: id)
154            emit MomentUnlocked(id: id)
155        }
156
157        // admin may alter the expiry of a lock on an NFT
158        access(all) fun setLockExpiryByID(id: UInt64, expiryTimestamp: UFix64) {
159            if expiryTimestamp < getCurrentBlock().timestamp {
160                panic("cannot set expiry in the past")
161            }
162
163            let duration = expiryTimestamp - getCurrentBlock().timestamp
164
165            TopShotLocking.lockedNFTs[id] = expiryTimestamp
166
167            emit MomentLocked(id: id, duration: duration, expiryTimestamp: expiryTimestamp)
168        }
169
170        // unlocks all NFTs
171        access(all) fun unlockAll() {
172            TopShotLocking.lockedNFTs = {}
173            TopShotLocking.unlockableNFTs = {}
174        }
175    }
176
177    // -----------------------------------------------------------------------
178    // TopShotLocking initialization function
179    // -----------------------------------------------------------------------
180    //
181    init() {
182        self.lockedNFTs = {}
183        self.unlockableNFTs = {}
184
185        // Create a single admin resource
186        let admin <- create Admin()
187
188        // Store it in private account storage in `init` so only the admin can use it
189        self.account.storage.save(<-admin, to: TopShotLocking.AdminStoragePath())
190    }
191}
192