Smart Contract

FanTopToken

A.86185fba578bc773.FanTopToken

Deployed

2w ago
Feb 11, 2026, 06:35:26 PM UTC

Dependents

11 imports
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