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