Smart Contract
TopShotLocking
A.0b2a3299cc857e29.TopShotLocking
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