Smart Contract

TicalUniverse

A.fef48806337aabf1.TicalUniverse

Deployed

4d ago
Feb 23, 2026, 01:04:03 AM UTC

Dependents

3354 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import MetadataViews from 0x1d7e57aa55817448
3import ViewResolver from 0x1d7e57aa55817448
4
5access(all) contract TicalUniverse: NonFungibleToken {
6
7    // -----------------------------------------------------------------------
8    // TicalUniverse contract Paths
9    // -----------------------------------------------------------------------
10
11    access(all) let CollectionStoragePath: StoragePath
12    access(all) let CollectionPublicPath: PublicPath
13    access(all) let AdminPublicPath: PublicPath
14    access(all) let AdminStoragePath: StoragePath
15
16    // -----------------------------------------------------------------------
17    // TicalUniverse contract Events
18    // -----------------------------------------------------------------------
19
20    // Emitted when the TicalUniverse contract is created
21    access(all) event ContractInitialized()
22
23    // Emitted when a new Item struct is created
24    access(all) event ItemCreated(id: UInt32, metadata: {String:String})
25    // Emitted when a new series has been started
26    access(all) event NewSeriesStarted(newCurrentSeries: UInt32)
27
28    // Events for Set-Related actions
29    //
30    // Emitted when a new Set is created
31    access(all) event SetCreated(setId: UInt32, series: UInt32)
32    // Emitted when a new Item is added to a Set
33    access(all) event ItemAddedToSet(setId: UInt32, itemId: UInt32)
34    // Emitted when an Item is retired from a Set and cannot be used to mint
35    access(all) event ItemRetiredFromSet(setId: UInt32, itemId: UInt32, minted: UInt32)
36    // Emitted when a Set is locked, meaning collectibles cannot be added
37    access(all) event SetLocked(setId: UInt32)
38    // Emitted when a collectible is minted from a Set
39    access(all) event CollectibleMinted(id: UInt64, itemId: UInt32, setId: UInt32, serialNumber: UInt32)
40
41    // Events for Collection-related actions
42    //
43    // Emitted when a collectible is withdrawn from a Collection
44    access(all) event Withdraw(id: UInt64, from: Address?)
45    // Emitted when a collectible is deposited into a Collection
46    access(all) event Deposit(id: UInt64, to: Address?)
47
48    // -----------------------------------------------------------------------
49    // TicalUniverse contract-level fields.
50    // These contain actual values that are stored in the smart contract.
51    // -----------------------------------------------------------------------
52
53    // Series that this Set belongs to.
54    // Many Sets can exist at a time, but only one series.
55    access(all) var currentSeries: UInt32
56
57    // Variable size dictionary of Item structs
58    access(self) var itemDatas: {UInt32: Item}
59
60    // Variable size dictionary of SetData structs
61    access(self) var setDatas: {UInt32: SetData}
62
63    // Variable size dictionary of Set resources
64    access(self) var sets: @{UInt32: Set}
65
66    // The Id that is used to create Items.
67    // Every time an Item is created, nextItemId is assigned
68    // to the new Item's Id and then is incremented by one.
69    access(all) var nextItemId: UInt32
70
71    // The Id that is used to create Sets.
72    // Every time a Set is created, nextSetId is assigned
73    // to the new Set's Id and then is incremented by one.
74    access(all) var nextSetId: UInt32
75
76    // The total number of Collectible NFTs that have been created
77    // Because NFTs can be destroyed, it doesn't necessarily mean that this
78    // reflects the total number of NFTs in existence, just the number that
79    // have been minted to date.
80    access(all) var totalSupply: UInt64
81
82    // -----------------------------------------------------------------------
83    // TicalUniverse contract-level Composite Type definitions
84    // -----------------------------------------------------------------------
85    // These are just *definitions* for Types that this contract
86    // and other accounts can use. These definitions do not contain
87    // actual stored values, but an instance (or object) of one of these Types
88    // can be created by this contract that contains stored values.
89    // -----------------------------------------------------------------------
90
91    // Item is a Struct that holds metadata associated with a specific collectible item.
92    access(all) struct Item {
93
94        // The unique Id for the Item
95        access(all) let itemId: UInt32
96
97        // Stores all the metadata about the item as a string mapping.
98        access(all) let metadata: {String: String}
99
100        init(metadata: {String: String}) {
101            pre {
102                metadata.length != 0: "New Item metadata cannot be empty"
103            }
104            self.itemId = TicalUniverse.nextItemId
105            self.metadata = metadata
106
107            // Increment the Id so that it isn't used again
108            TicalUniverse.nextItemId = TicalUniverse.nextItemId + UInt32(1)
109
110            emit ItemCreated(id: self.itemId, metadata: metadata)
111        }
112    }
113
114    // A Set is a grouping of Items that make up a related group of collectibles,
115    // like sets of baseball cards.
116    // An Item can exist in multiple different sets.
117    //
118    // SetData is a struct that is stored in a field of the contract.
119    // Anyone can query the constant information
120    // about a set by calling various getters located
121    // at the end of the contract. Only the admin has the ability
122    // to modify any data in the private Set resource.
123    access(all) struct SetData {
124
125        // Unique Id for the Set
126        access(all) let setId: UInt32
127
128        // Name of the Set
129        access(all) let name: String
130
131        // Description of the Set
132        access(all) let description: String?
133
134        // Series that this Set belongs to
135        access(all) let series: UInt32
136
137        init(name: String, description: String?) {
138            pre {
139                name.length > 0: "New Set name cannot be empty"
140            }
141            self.setId = TicalUniverse.nextSetId
142            self.name = name
143            self.description = description
144            self.series = TicalUniverse.currentSeries
145
146            // Increment the setId so that it isn't used again
147            TicalUniverse.nextSetId = TicalUniverse.nextSetId + UInt32(1)
148
149            emit SetCreated(setId: self.setId, series: self.series)
150        }
151    }
152
153    // Set is a resource type that contains the functions to add and remove
154    // Items from a set and mint Collectibles.
155    //
156    // It is stored in a private field in the contract so that
157    // the admin resource can call its methods.
158    //
159    // The admin can add Items to a Set so that the set can mint Collectibles.
160    // The Collectible that is minted by a Set will be listed as belonging to
161    // the Set that minted it, as well as the Item it reference.
162    //
163    // Admin can also retire Items from the Set, meaning that the retired
164    // Item can no longer have Collectibles minted from it.
165    //
166    // If the admin locks the Set, no more Items can be added to it, but
167    // Collectibles can still be minted.
168    //
169    // If retireAll() and lock() are called back-to-back,
170    // the Set is closed off forever and nothing more can be done with it.
171    access(all) resource Set {
172
173        // Unique Id for the set
174        access(all) let setId: UInt32
175
176        // Array of items that are a part of this set.
177        // When an item is added to the set, its Id gets appended here.
178        // The Id does not get removed from this array when an Item is retired.
179        access(all) var items: [UInt32]
180
181        // Map of Item Ids that indicates if an Item in this Set can be minted.
182        // When an Item is added to a Set, it is mapped to false (not retired).
183        // When an Item is retired, this is set to true and cannot be changed.
184        access(all) var retired: {UInt32: Bool}
185
186        // Indicates if the Set is currently locked.
187        // When a Set is created, it is unlocked and Items are allowed to be added to it.
188        // When a set is locked, Items cannot be added to it.
189        // A Set can't transition from locked to unlocked. Locking is final.
190        // If a Set is locked, Items cannot be added, but Collectibles can still be minted
191        // from Items that exist in the Set.
192        access(all) var locked: Bool
193
194        // Mapping of Item Ids that indicates the number of Collectibles
195        // that have been minted for specific Items in this Set.
196        // When a Collectible is minted, this value is stored in the Collectible to
197        // show its place in the Set, eg. 42 of 100.
198        access(all) var numberMintedPerItem: {UInt32: UInt32}
199
200        init(name: String, description: String?) {
201            self.setId = TicalUniverse.nextSetId
202            self.items = []
203            self.retired = {}
204            self.locked = false
205            self.numberMintedPerItem = {}
206
207            // Create a new SetData for this Set and store it in contract storage
208            TicalUniverse.setDatas[self.setId] = SetData(name: name, description: description)
209        }
210
211        // Add an Item to the Set
212        //
213        // Pre-Conditions:
214        // The Item exists.
215        // The Set is unlocked.
216        // The Item is not present in the Set.
217        access(all) fun addItem(itemId: UInt32) {
218            pre {
219                TicalUniverse.itemDatas[itemId] != nil: "Cannot add the Item to Set: Item doesn't exist."
220                !self.locked: "Cannot add the Item to the Set after the set has been locked."
221                self.numberMintedPerItem[itemId] == nil: "The Item has already beed added to the set."
222            }
223
224            // Add the Item to the array of Items
225            self.items.append(itemId)
226
227            // Allow minting for Item
228            self.retired[itemId] = false
229
230            // Initialize the Collectible count to zero
231            self.numberMintedPerItem[itemId] = 0
232
233            emit ItemAddedToSet(setId: self.setId, itemId: itemId)
234        }
235
236        // Adds multiple Items to the Set
237        access(all) fun addItems(itemIds: [UInt32]) {
238            for id in itemIds {
239                self.addItem(itemId: id)
240            }
241        }
242
243        // Retire an Item from the Set. The Set can't mint new Collectibles for the Item.
244        // Pre-Conditions:
245        // The Item is part of the Set and not retired.
246        access(all) fun retireItem(itemId: UInt32) {
247            pre {
248                self.retired[itemId] != nil: "Cannot retire the Item: Item doesn't exist in this set!"
249            }
250
251            if !self.retired[itemId]! {
252                self.retired[itemId] = true
253
254                emit ItemRetiredFromSet(setId: self.setId, itemId: itemId, minted: self.numberMintedPerItem[itemId]!)
255            }
256        }
257
258        // Retire all the Items in the Set
259        access(all) fun retireAll() {
260            for id in self.items {
261                self.retireItem(itemId: id)
262            }
263        }
264
265        // Lock the Set so that no more Items can be added to it.
266        //
267        // Pre-Conditions:
268        // The Set is unlocked
269        access(all) fun lock() {
270            if !self.locked {
271                self.locked = true
272                emit SetLocked(setId: self.setId)
273            }
274        }
275
276        // Mint a new Collectible and returns the newly minted Collectible.
277        // Pre-Conditions:
278        // The Item must exist in the Set and be allowed to mint new Collectibles
279        access(all) fun mintCollectible(itemId: UInt32): @NFT {
280            pre {
281                self.retired[itemId] != nil: "Cannot mint the collectible: This item doesn't exist."
282                !self.retired[itemId]!: "Cannot mint the collectible from this item: This item has been retired."
283            }
284
285            // Gets the number of Collectibles that have been minted for this Item
286            // to use as this Collectibles's serial number
287            let minted = self.numberMintedPerItem[itemId]!
288
289            // Mint the new collectible
290            let newCollectible: @NFT <- create NFT(serialNumber: minted + UInt32(1),
291                                              itemId: itemId,
292                                              setId: self.setId)
293
294            // Increment the count of Collectibles minted for this Item
295            self.numberMintedPerItem[itemId] = minted + UInt32(1)
296
297            return <-newCollectible
298        }
299
300        // Mint an arbitrary quantity of Collectibles and return them as a Collection
301        access(all) fun batchMintCollectible(itemId: UInt32, quantity: UInt64): @Collection {
302            let newCollection <- create Collection()
303
304            var i: UInt64 = 0
305            while i < quantity {
306                newCollection.deposit(token: <-self.mintCollectible(itemId: itemId))
307                i = i + UInt64(1)
308            }
309
310            return <-newCollection
311        }
312    }
313
314    // Struct of Collectible metadata
315    access(all) struct CollectibleData {
316
317        // The Id of the Set that the Collectible comes from
318        access(all) let setId: UInt32
319
320        // The Id of the Item that the Collectible references
321        access(all) let itemId: UInt32
322
323        // The place in the edition that this Collectible was minted
324        access(all) let serialNumber: UInt32
325
326        init(setId: UInt32, itemId: UInt32, serialNumber: UInt32) {
327            self.setId = setId
328            self.itemId = itemId
329            self.serialNumber = serialNumber
330        }
331
332    }
333
334    // The resource that represents the Collectible NFT
335    access(all) resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver {
336
337        access(all) event ResourceDestroyed(id: UInt64 = self.id)
338
339        // Global unique Collectible Id
340        access(all) let id: UInt64
341
342        // Struct of Collectible metadata
343        access(all) let data: CollectibleData
344
345        init(serialNumber: UInt32, itemId: UInt32, setId: UInt32) {
346
347            // Increment the global Collectible Id
348            TicalUniverse.totalSupply = TicalUniverse.totalSupply + UInt64(1)
349
350            self.id = TicalUniverse.totalSupply
351
352            self.data = CollectibleData(setId: setId, itemId: itemId, serialNumber: serialNumber)
353
354            emit CollectibleMinted(id: self.id, itemId: itemId, setId: setId, serialNumber: self.data.serialNumber)
355        }
356
357
358        access(all) view fun getViews(): [Type] {
359            return [
360                Type<MetadataViews.Royalties>(),
361                Type<MetadataViews.Display>(),
362                Type<MetadataViews.Editions>(),
363                Type<MetadataViews.ExternalURL>(),
364                Type<MetadataViews.NFTCollectionData>(),
365                Type<MetadataViews.NFTCollectionDisplay>(),
366                Type<MetadataViews.Serial>(),
367                Type<MetadataViews.Traits>()
368            ]
369        }
370
371        access(all) fun resolveView(_ view: Type): AnyStruct? {
372            switch view {
373                case Type<MetadataViews.Royalties>():
374                    return MetadataViews.Royalties(
375                        []
376                    )
377                case Type<MetadataViews.Display>():
378                    let metadata = TicalUniverse.getItemMetadata(itemId: self.data.itemId)!
379                    let assetUrl = metadata["Asset"]!
380                    return MetadataViews.Display(
381                        name: metadata["Title"]!,
382                        description: metadata["Description"]!,
383                        thumbnail: MetadataViews.HTTPFile(
384                            url: assetUrl.slice(from: 0, upTo: assetUrl.length - 3).concat("gif")
385                        )
386                    )
387                case Type<MetadataViews.Editions>():
388                    let editionInfo = MetadataViews.Edition(name: "Tical Universe", number: self.id, max: nil)
389                    let editionList: [MetadataViews.Edition] = [editionInfo]
390                    return MetadataViews.Editions(
391                        editionList
392                    )
393                case Type<MetadataViews.Serial>():
394                    return MetadataViews.Serial(
395                        self.uuid
396                    )
397                case Type<MetadataViews.ExternalURL>():
398                    return MetadataViews.ExternalURL("https://www.tunegonft.com/collectible/".concat(self.uuid.toString()))
399                case Type<MetadataViews.NFTCollectionData>():
400                    return TicalUniverse.resolveContractView(resourceType: Type<@TicalUniverse.NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
401                case Type<MetadataViews.NFTCollectionDisplay>():
402                    return TicalUniverse.resolveContractView(resourceType: Type<@TicalUniverse.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
403                case Type<MetadataViews.Traits>():
404                    let metadata = TicalUniverse.getItemMetadata(itemId: self.data.itemId)
405                    let traitsView = metadata != nil ? MetadataViews.dictToTraits(dict: metadata!, excludedNames: []) : nil
406                    return traitsView
407            }
408            return nil
409        }
410
411        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
412            return <-TicalUniverse.createEmptyCollection(nftType: Type<@TicalUniverse.NFT>())
413        }
414    }
415
416    // Admin is an authorization resource that allows the owner to modify
417    // various aspects of the Items, Sets, and Collectibles
418    access(all) resource Admin {
419        // Create a new Item struct and store it in the Items dictionary in the contract
420        access(all) fun createItem(metadata: {String: String}): UInt32 {
421            var newItem = Item(metadata: metadata)
422            let newId = newItem.itemId
423
424            TicalUniverse.itemDatas[newId] = newItem
425
426            return newId
427        }
428
429        // Create a new Set resource and store it in the sets mapping in the contract
430        access(all) fun createSet(name: String, description: String?) {
431            var newSet <- create Set(name: name, description: description)
432            TicalUniverse.sets[newSet.setId] <-! newSet
433        }
434
435        // Return a reference to a set in the contract
436        access(all) fun borrowSet(setId: UInt32): &Set {
437            pre {
438                TicalUniverse.sets[setId] != nil: "Cannot borrow set: The set doesn't exist."
439            }
440
441            return (&TicalUniverse.sets[setId] as &Set?)!
442        }
443
444        // End the current series and start a new one
445        access(all) fun startNewSeries(): UInt32 {
446            TicalUniverse.currentSeries = TicalUniverse.currentSeries + UInt32(1)
447
448            emit NewSeriesStarted(newCurrentSeries: TicalUniverse.currentSeries)
449
450            return TicalUniverse.currentSeries
451        }
452
453        // Create a new Admin resource
454        access(all) fun createNewAdmin(): @Admin {
455            return <-create Admin()
456        }
457    }
458
459    // Interface that users can cast their TicalUniverse Collection as
460    // to allow others to deposit TicalUniverse Collectibles into their Collection.
461    access(all) resource interface TicalUniverseCollectionPublic: NonFungibleToken.Receiver, ViewResolver.ResolverCollection  {
462        access(all) fun deposit(token: @{NonFungibleToken.NFT})
463        access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection})
464        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
465        access(all) view fun borrowCollectible(_ id: UInt64): &TicalUniverse.NFT? {
466            // If the result isn't nil, the id of the returned reference
467            // should be the same as the argument to the function
468            post {
469                (result == nil) || (result?.id == id):
470                    "Cannot borrow collectible reference: The id of the returned reference is incorrect."
471            }
472        }
473        access(all) view fun getSupportedNFTTypes(): {Type: Bool}
474        access(all) view fun isSupportedNFTType(type: Type): Bool
475    }
476
477    // Collection is a resource that every user who owns NFTs
478    // will store in their account to manage their NFTS
479    access(all) resource Collection: NonFungibleToken.Collection, TicalUniverseCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, ViewResolver.ResolverCollection {
480        access(all) event ResourceDestroyed()
481        // Dictionary of Collectible conforming tokens
482        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
483
484        init() {
485            self.ownedNFTs <- {}
486        }
487
488        /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
489        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
490            let supportedTypes: {Type: Bool} = {}
491            supportedTypes[Type<@TicalUniverse.NFT>()] = true
492            return supportedTypes
493        }
494
495        /// Returns whether or not the given type is accepted by the collection
496        /// A collection that can accept any type should just return true by default
497        access(all) view fun isSupportedNFTType(type: Type): Bool {
498           if type == Type<@TicalUniverse.NFT>() {
499            return true
500           }
501           return false
502        }
503
504        // Remove a Collectible from the Collection and moves it to the caller
505        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
506
507            let token <- self.ownedNFTs.remove(key: withdrawID)
508                ?? panic("Cannot withdraw: Collectible does not exist in the collection.")
509
510            emit Withdraw(id: token.id, from: self.owner?.address)
511
512            return <-token
513        }
514
515        // Withdraw multiple tokens and returns them as a Collection
516        access(NonFungibleToken.Withdraw)
517        fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} {
518            var batchCollection <- create Collection()
519
520            for id in ids {
521                batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
522            }
523
524            return <-batchCollection
525        }
526
527        // Add a Collectible to the Collections dictionary
528        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
529
530            // Cast the deposited token as a Collectible NFT to make sure
531            // it is the correct type
532            let token <- token as! @TicalUniverse.NFT
533
534            let id = token.id
535
536            let oldToken <- self.ownedNFTs[id] <- token
537
538            emit Deposit(id: id, to: self.owner?.address)
539
540            // Destroy the empty removed token
541            destroy oldToken
542        }
543
544        // Deposit multiple NFTs into this Collection
545        access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) {
546
547            let keys = tokens.getIDs()
548
549            for key in keys {
550                self.deposit(token: <-tokens.withdraw(withdrawID: key))
551            }
552
553            // Destroy the empty Collection
554            destroy tokens
555        }
556
557        // Get the Ids that are in the Collection
558        access(all) view fun getIDs(): [UInt64] {
559            return self.ownedNFTs.keys
560        }
561
562        // Gets the amount of NFTs stored in the collection
563        access(all) view fun getLength(): Int {
564            return self.ownedNFTs.keys.length
565        }
566
567        // Return a borrowed reference to a Collectible in the Collection
568        // This only allows the caller to read the ID of the NFT,
569        // not any Collectible specific data.
570        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
571            return &self.ownedNFTs[id] as &{NonFungibleToken.NFT}?
572        }
573
574        // Return a borrowed reference to a Collectible
575        // This  allows the caller to read the setId, itemId, serialNumber,
576        // and use them to read the setData or Item data from the contract
577        access(all) view fun borrowCollectible(_ id: UInt64): &TicalUniverse.NFT? {
578            if self.ownedNFTs[id] != nil {
579                return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}? as! &TicalUniverse.NFT?)
580            } else {
581                return nil
582            }
583        }
584
585        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
586            if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
587                return nft as &{ViewResolver.Resolver}
588            }
589            return nil
590        }
591
592        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
593            return <-create TicalUniverse.Collection()
594        }
595    }
596
597    // -----------------------------------------------------------------------
598    // TicalUniverse contract-level function definitions
599    // -----------------------------------------------------------------------
600
601    // Create a new, empty Collection object so that a user can store it in their account storage
602    // and be able to receive Collectibles
603    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
604        assert(nftType == Type<@TicalUniverse.NFT>(), message: "I don't know how to create ".concat(nftType.identifier))
605        return <-create TicalUniverse.Collection()
606    }
607
608    access(all) view fun getContractViews(resourceType: Type?): [Type] {
609        return [
610            Type<MetadataViews.NFTCollectionData>(),
611            Type<MetadataViews.NFTCollectionDisplay>()
612        ]
613    }
614
615    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
616        switch viewType {
617            case Type<MetadataViews.NFTCollectionData>():
618                return MetadataViews.NFTCollectionData(
619                    storagePath: TicalUniverse.CollectionStoragePath,
620                    publicPath: TicalUniverse.CollectionPublicPath,
621                    publicCollection: Type<&TicalUniverse.Collection>(),
622                    publicLinkedType: Type<&{NonFungibleToken.Collection, TicalUniverse.TicalUniverseCollectionPublic,ViewResolver.ResolverCollection}>(),
623                    createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
624                        return <-TicalUniverse.createEmptyCollection(nftType: Type<@TicalUniverse.NFT>())
625                    })
626                )
627            case Type<MetadataViews.NFTCollectionDisplay>():
628                return TicalUniverse.getCollectionDisplay()
629        }
630        return nil
631    }
632
633
634    // Return all the Collectible Items
635    access(all) fun getAllItems(): [TicalUniverse.Item] {
636        return TicalUniverse.itemDatas.values
637    }
638
639    // Get all metadata of an Item
640    access(all) fun getItemMetadata(itemId: UInt32): {String: String}? {
641        return self.itemDatas[itemId]?.metadata
642    }
643
644    // Get a metadata field of an Item
645    access(all) fun getItemMetadataByField(itemId: UInt32, field: String): String? {
646        if let item = TicalUniverse.itemDatas[itemId] {
647            return item.metadata[field]
648        } else {
649            return nil
650        }
651    }
652
653    // Get the name of the Set
654    access(all) fun getSetName(setId: UInt32): String? {
655        return TicalUniverse.setDatas[setId]?.name
656    }
657
658    // Get the description of the Set
659    access(all) fun getSetDescription(setId: UInt32): String? {
660        return TicalUniverse.setDatas[setId]?.description
661    }
662
663    // Get the series that the specified Set is associated with
664    access(all) fun getSetSeries(setId: UInt32): UInt32? {
665        return TicalUniverse.setDatas[setId]?.series
666    }
667
668    // Get the Ids that the specified Set name is associated with
669    access(all) fun getSetIdsByName(setName: String): [UInt32]? {
670        var setIds: [UInt32] = []
671
672        for setData in TicalUniverse.setDatas.values {
673            if setName == setData.name {
674                setIds.append(setData.setId)
675            }
676        }
677
678        if setIds.length == 0 {
679            return nil
680        } else {
681            return setIds
682        }
683    }
684
685    // Get the list of Item Ids that are in the Set
686    access(all) fun getItemsInSet(setId: UInt32): [UInt32]? {
687        return TicalUniverse.sets[setId]?.items
688    }
689
690    // Indicates if a Set/Item combo (otherwise known as an edition) is retired
691    access(all) fun isEditionRetired(setId: UInt32, itemId: UInt32): Bool? {
692        if let setToRead <- TicalUniverse.sets.remove(key: setId) {
693            let retired = setToRead.retired[itemId]
694            TicalUniverse.sets[setId] <-! setToRead
695            return retired
696        } else {
697            return nil
698        }
699    }
700
701    // Indicates if the Set is locked or not
702    access(all) fun isSetLocked(setId: UInt32): Bool? {
703        return TicalUniverse.sets[setId]?.locked
704    }
705
706    // Total number of Collectibles that have been minted from an edition
707    access(all) fun getNumberCollectiblesInEdition(setId: UInt32, itemId: UInt32): UInt32? {
708        if let setToRead <- TicalUniverse.sets.remove(key: setId) {
709            let amount = setToRead.numberMintedPerItem[itemId]
710
711            // Put the Set back into the Sets dictionary
712            TicalUniverse.sets[setId] <-! setToRead
713
714            return amount
715        } else {
716            return nil
717        }
718    }
719
720    access(all) fun getCollectionDisplay(): MetadataViews.NFTCollectionDisplay {
721        let media = MetadataViews.Media(
722                file: MetadataViews.HTTPFile(
723                    url: "https://tunegonft.com/assets/images/collections-page/tical.png"
724                ),
725                mediaType: "image/png"
726            )
727        let socials: {String:MetadataViews.ExternalURL} = {}
728        return MetadataViews.NFTCollectionDisplay(
729            name: "Tical Universe",
730            description: "The Genesis NFT drop represents the dawn of Method Man’s Tical Universe and the birth and introduction of the original Tical Universe heroes and villains.",
731            externalURL: MetadataViews.ExternalURL("https://www.tunegonft.com/collection-details/95487a67-a66a-43d5-913d-46fa8a644f4c"),
732            squareImage: media,
733            bannerImage: media,
734            socials: socials
735        )
736    }
737
738    // -----------------------------------------------------------------------
739    // TicalUniverse initialization function
740    // -----------------------------------------------------------------------
741
742    init() {
743        // Paths
744        self.CollectionPublicPath= /public/TicalUniverseCollection
745        self.CollectionStoragePath= /storage/TicalUniverseCollection
746        self.AdminPublicPath= /public/TicalUniverseAdmin
747        self.AdminStoragePath=/storage/TicalUniverseAdmin
748
749        self.currentSeries = 0
750        self.itemDatas = {}
751        self.setDatas = {}
752        self.sets <- {}
753        self.nextItemId = 1
754        self.nextSetId = 1
755        self.totalSupply = 0
756
757        // Put a new Collection in storage
758        self.account.storage.save(<- create Collection(), to: TicalUniverse.CollectionStoragePath)
759
760        // create a public capability for the collection
761        let collectionCap = self.account.capabilities.storage.issue<&TicalUniverse.Collection>(TicalUniverse.CollectionStoragePath)
762        self.account.capabilities.publish(collectionCap, at: TicalUniverse.CollectionPublicPath)
763
764        // Put the Admin in storage
765        self.account.storage.save(<- create Admin(), to: TicalUniverse.AdminStoragePath)
766
767        emit ContractInitialized()
768    }
769}