Smart Contract
FanTopToken
A.86185fba578bc773.FanTopToken
1import NonFungibleToken from 0x1d7e57aa55817448
2import MetadataViews from 0x1d7e57aa55817448
3import FanTopSerial from 0x86185fba578bc773
4
5access(all) contract FanTopToken: NonFungibleToken {
6 access(all) var totalSupply: UInt64
7
8 access(all) event ContractInitialized()
9 access(all) event Deposit(id: UInt64, to: Address?)
10 access(all) event Withdraw(id: UInt64, from: Address?)
11
12 access(all) let collectionStoragePath: StoragePath
13 access(all) let collectionPublicPath: PublicPath
14
15 access(all) event TokenCreated(
16 id: UInt64,
17 refId: String,
18 serialNumber: UInt32,
19 itemId: String,
20 itemVersion: UInt32,
21 minter: Address
22 )
23
24 access(all) event TokenDestroyed(
25 id: UInt64,
26 refId: String,
27 serialNumber: UInt32,
28 itemId: String,
29 itemVersion: UInt32,
30 )
31
32 access(all) struct Item {
33 access(all) let itemId: String
34 access(all) var version: UInt32
35 access(all) var mintedCount: UInt32
36 access(all) var limit: UInt32
37 access(all) var active: Bool
38 access(self) let versions: { UInt32: ItemData }
39
40 init(itemId: String, version: UInt32, metadata: { String: String }, limit: UInt32, active: Bool) {
41 self.itemId = itemId
42
43 let data = ItemData(version: version, metadata: metadata, originSerialNumber: 1)
44 self.versions = { data.version: data }
45 self.version = data.version
46 self.mintedCount = 0
47 self.limit = limit
48 self.active = active
49 }
50
51 access(contract) fun setMetadata(version: UInt32, metadata: { String: String }) {
52 pre {
53 version >= self.version: "Version must be greater than or equal to the current version"
54 version > self.version || (version == self.version && !self.isVersionLocked()): "Locked version cannot be overwritten"
55 }
56 post {
57 self.version == version: "Must be the specified version"
58 self.versions[version] != nil: "ItemData must be in the specified version"
59 }
60 let data = ItemData(version: version, metadata: metadata, originSerialNumber: self.mintedCount + 1)
61 self.versions.insert(key: version, data)
62 self.version = version
63 }
64
65 access(contract) fun setLimit(limit: UInt32) {
66 pre {
67 self.mintedCount == 0: "Limit can be changed only if it has never been mint"
68 }
69 self.limit = limit
70 }
71
72 access(contract) fun setActive(active: Bool) {
73 self.active = active
74 }
75
76 access(contract) fun countUp(): UInt32 {
77 pre {
78 self.mintedCount < self.limit: "Item cannot be minted beyond the limit"
79 }
80 self.mintedCount = self.mintedCount + 1
81 return self.mintedCount
82 }
83
84 view access(all) fun getData(): ItemData {
85 return self.versions[self.version]!
86 }
87
88 view access(all) fun getVersions(): { UInt32: ItemData } {
89 return self.versions
90 }
91
92 view access(all) fun isVersionLocked(): Bool {
93 return self.mintedCount >= self.versions[self.version]!.originSerialNumber
94 }
95
96 view access(all) fun isLimitLocked(): Bool {
97 return self.mintedCount > 0
98 }
99
100 view access(all) fun isFulfilled(): Bool {
101 return self.mintedCount >= self.limit
102 }
103 }
104
105 access(all) struct ItemData {
106 access(all) let version: UInt32
107 access(all) let originSerialNumber: UInt32
108 access(self) let metadata: { String: String }
109
110 init(version: UInt32, metadata: { String: String }, originSerialNumber: UInt32) {
111 self.version = version
112 self.metadata = metadata
113 self.originSerialNumber = originSerialNumber
114 }
115
116 access(all) fun getMetadata(): { String: String } {
117 return self.metadata
118 }
119 }
120
121 access(all) resource NFT: NonFungibleToken.NFT {
122 access(all) let id: UInt64
123 access(all) let refId: String
124 access(self) let data: NFTData
125
126 access(all) event ResourceDestroyed(
127 id: UInt64 = self.id,
128 refId: String = self.refId,
129 serialNumber: UInt32 = self.data.serialNumber,
130 itemId: String = self.data.itemId,
131 itemVersion: UInt32 = self.data.itemVersion
132 )
133
134 init(refId: String, data: NFTData, minter: Address) {
135 FanTopToken.totalSupply = FanTopToken.totalSupply + (1 as UInt64)
136
137 self.id = FanTopToken.totalSupply
138 self.refId = refId
139 self.data = data
140
141 emit FanTopToken.TokenCreated(
142 id: self.id,
143 refId: refId,
144 serialNumber: data.serialNumber,
145 itemId: data.itemId,
146 itemVersion: data.itemVersion,
147 minter: minter
148 )
149 }
150
151 access(all) fun getData(): NFTData {
152 return self.data
153 }
154
155 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
156 return <- FanTopToken.createEmptyCollection(nftType: Type<@FanTopToken.NFT>())
157 }
158
159 view access(all) fun getViews(): [Type] {
160 return []
161 }
162
163 access(all) fun resolveView(_ view: Type): AnyStruct? {
164 return nil
165 }
166 }
167
168 access(all) struct NFTData {
169 access(all) let serialNumber: UInt32
170 access(all) let itemId: String
171 access(all) let itemVersion: UInt32
172 access(self) let metadata: { String: String }
173
174 init(
175 serialNumber: UInt32,
176 itemId: String,
177 itemVersion: UInt32,
178 metadata: { String: String }
179 ) {
180 self.serialNumber = serialNumber
181 self.itemId = itemId
182 self.itemVersion = itemVersion
183 self.metadata = metadata
184 }
185
186 access(all) fun getMetadata(): { String: String } {
187 return self.metadata
188 }
189 }
190
191 access(all) fun createEmptyDefaultTypeCollection(): @{NonFungibleToken.Collection} {
192 return <- FanTopToken.createEmptyCollection(nftType: Type<@FanTopToken.NFT>())
193 }
194
195 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
196 if nftType != Type<@FanTopToken.NFT>() {
197 panic("NFT type is not supported")
198 }
199 return <-create FanTopToken.Collection()
200 }
201
202 view access(all) fun getContractViews(resourceType: Type?): [Type] {
203 return []
204 }
205
206 view access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
207 return nil
208 }
209
210 access(all) resource interface CollectionPublic {
211 access(all) fun deposit(token: @{NonFungibleToken.NFT})
212 access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection})
213 access(all) fun getIDs(): [UInt64]
214 access(all) fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
215 access(all) fun borrowFanTopToken(id: UInt64): &FanTopToken.NFT? {
216 post {
217 result == nil || result!.id == id: "Invalid id"
218 }
219 }
220 }
221
222 access(all) resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.Collection, NonFungibleToken.CollectionPublic {
223 access(all) var ownedNFTs: @{ UInt64: {NonFungibleToken.NFT} }
224
225 init() {
226 self.ownedNFTs <- {}
227 }
228
229 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
230 pre {
231 self.ownedNFTs.containsKey(withdrawID): "That withdrawID does not exist"
232 }
233 let token <- self.ownedNFTs.remove(key: withdrawID)! as! @NFT
234
235 emit Withdraw(id: token.id, from: self.owner?.address)
236 return <-token
237 }
238
239 access(NonFungibleToken.Withdraw) fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} {
240 var batchCollection <- create Collection()
241
242 for id in ids {
243 batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
244 }
245
246 return <-batchCollection
247 }
248
249 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
250 pre {
251 !self.ownedNFTs.containsKey(token.id): "That id already exists"
252 }
253 let token <- token as! @FanTopToken.NFT
254 let id = token.id
255 self.ownedNFTs[id] <-! token
256
257 emit Deposit(id: id, to: self.owner?.address)
258 }
259
260 access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) {
261 let keys = tokens.getIDs()
262 for key in keys {
263 self.deposit(token: <-tokens.withdraw(withdrawID: key))
264 }
265 destroy tokens
266 }
267
268 view access(all) fun getIDs(): [UInt64] {
269 return self.ownedNFTs.keys
270 }
271
272 view access(all) fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
273 return &self.ownedNFTs[id]
274 }
275
276 view access(all) fun borrowFanTopToken(id: UInt64): &FanTopToken.NFT? {
277 return self.borrowNFT(id) as! &FanTopToken.NFT?
278 }
279
280 view access(all) fun getLength(): Int {
281 return self.ownedNFTs.keys.length
282 }
283
284 view access(all) fun getSupportedNFTTypes(): {Type: Bool} {
285 let supportedTypes: {Type: Bool} = {}
286 supportedTypes[Type<@FanTopToken.NFT>()] = true
287 return supportedTypes
288 }
289
290 view access(all) fun isSupportedNFTType(type: Type): Bool {
291 if type == Type<@FanTopToken.NFT>() {
292 return true
293 }
294 return false
295 }
296
297 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
298 return <- FanTopToken.createEmptyCollection(nftType: Type<@NFT>())
299 }
300 }
301
302 access(account) fun createItem(itemId: String, version: UInt32, limit: UInt32, metadata: { String: String }, active: Bool): Item {
303 pre {
304 !FanTopToken.items.containsKey(itemId): "Admin cannot create existing items"
305 }
306 post {
307 FanTopToken.items.containsKey(itemId): "items contains the created item"
308 }
309
310 let item = Item(itemId: itemId, version: version, metadata: metadata, limit: limit, active: active)
311 FanTopToken.items.insert(key: itemId, item)
312
313 return item
314 }
315
316 access(account) fun updateMetadata(itemId: String, version: UInt32, metadata: { String: String }) {
317 pre {
318 FanTopToken.items.containsKey(itemId): "Metadata of non-existent item cannot be updated"
319 }
320 FanTopToken.items[itemId]!.setMetadata(version: version, metadata: metadata)
321 }
322
323 access(account) fun updateLimit(itemId: String, limit: UInt32) {
324 pre {
325 FanTopToken.items.containsKey(itemId): "Limit of non-existent item cannot be updated"
326 }
327 FanTopToken.items[itemId]!.setLimit(limit: limit)
328 }
329
330 access(account) fun updateActive(itemId: String, active: Bool) {
331 pre {
332 FanTopToken.items.containsKey(itemId): "Limit of non-existent item cannot be updated"
333 FanTopToken.items[itemId]!.active != active: "Item cannot be updated with the same value"
334 }
335 FanTopToken.items[itemId]!.setActive(active: active)
336 }
337
338 access(account) fun mintToken(
339 refId: String,
340 itemId: String,
341 itemVersion: UInt32,
342 metadata: { String: String },
343 minter: Address
344 ): @NFT {
345 pre {
346 FanTopToken.items.containsKey(itemId): "That itemId does not exist"
347 itemVersion == FanTopToken.items[itemId]!.version : "That itemVersion did not match the latest version"
348 !FanTopToken.items[itemId]!.isFulfilled(): "Fulfilled items cannot be mint"
349 FanTopToken.items[itemId]!.active: "Only active items can be mint"
350 FanTopSerial.hasBox(itemId: itemId) == false: "Items with box cannot be mint without serial number"
351 }
352 post {
353 FanTopToken.totalSupply == before(FanTopToken.totalSupply) + 1: "totalSupply must be incremented"
354 FanTopToken.items[itemId]!.mintedCount == before(FanTopToken.items[itemId])!.mintedCount + 1: "mintedCount must be incremented"
355 FanTopToken.items[itemId]!.isVersionLocked(): "item must be locked once mint"
356 }
357
358 let serialNumber = FanTopToken.items[itemId]!.countUp()
359
360 let data = NFTData(
361 serialNumber: serialNumber,
362 itemId: itemId,
363 itemVersion: itemVersion,
364 metadata: metadata
365 )
366
367 return <- create NFT(refId: refId, data: data, minter: minter)
368 }
369
370 access(account) fun mintTokenWithSerialNumber(
371 refId: String,
372 itemId: String,
373 itemVersion: UInt32,
374 metadata: { String: String },
375 serialNumber: UInt32,
376 minter: Address
377 ): @NFT {
378 pre {
379 FanTopToken.items.containsKey(itemId): "That itemId does not exist"
380 itemVersion == FanTopToken.items[itemId]!.version : "That itemVersion did not match the latest version"
381 !FanTopToken.items[itemId]!.isFulfilled(): "Fulfilled items cannot be mint"
382 FanTopToken.items[itemId]!.active: "Only active items can be mint"
383 }
384 post {
385 FanTopToken.totalSupply == before(FanTopToken.totalSupply) + 1: "totalSupply must be incremented"
386 FanTopToken.items[itemId]!.mintedCount == before(FanTopToken.items[itemId])!.mintedCount + 1: "mintedCount must be incremented"
387 FanTopToken.items[itemId]!.isVersionLocked(): "item must be locked once mint"
388 }
389
390 if !FanTopSerial.hasBox(itemId: itemId) {
391 let item = self.items[itemId]!
392 let box = FanTopSerial.Box(size: item.limit, pickTo: item.mintedCount)
393 FanTopSerial.putBox(box, itemId: itemId)
394 }
395
396 let boxRef = FanTopSerial.getBoxRef(itemId: itemId)!
397 boxRef.pick(serialNumber)
398
399 FanTopToken.items[itemId]!.countUp()
400
401 let data = NFTData(
402 serialNumber: serialNumber,
403 itemId: itemId,
404 itemVersion: itemVersion,
405 metadata: metadata
406 )
407
408 return <- create NFT(refId: refId, data: data, minter: minter)
409 }
410
411 access(self) let items: { String: Item }
412
413 // Public
414
415 access(all) fun getItemIds(): [String] {
416 return self.items.keys
417 }
418
419 access(all) fun getItem(_ itemId: String): Item? {
420 return self.items[itemId]
421 }
422
423 init() {
424 self.collectionStoragePath = /storage/FanTopTokenCollection
425 self.collectionPublicPath = /public/FanTopTokenCollection
426
427 // For recovery of redeploy
428 self.totalSupply = 18652
429 self.items = {}
430 }
431}
432