Smart Contract

FlowverseTreasures

A.9212a87501a8a6a2.FlowverseTreasures

Deployed

1d ago
Feb 26, 2026, 04:22:09 AM UTC

Dependents

35 imports
1/*
2    FlowverseTreasures.cdc
3
4    Author: Brian Min brian@flowverse.co
5*/
6
7import NonFungibleToken from 0x1d7e57aa55817448
8import MetadataViews from 0x1d7e57aa55817448
9import FungibleToken from 0xf233dcee88fe0abe
10import ViewResolver from 0x1d7e57aa55817448
11
12// Flowverse Treasures is an NFT contract for artist collaborations
13access(all) contract FlowverseTreasures: NonFungibleToken {
14    // Events
15    access(all) event EntityCreated(id: UInt64, metadata: {String:String})
16    access(all) event EntityUpdated(id: UInt64, metadata: {String:String})
17    access(all) event SetCreated(setID: UInt64, name: String, description: String, externalURL: String, isPrivate: Bool, thumbnailURL: String, bannerURL: String, royaltyReceiverAddress: Address)
18    access(all) event SetUpdated(setID: UInt64, description: String?, externalURL: String?, thumbnailURL: String?, bannerURL: String?, royaltyReceiverAddress: Address?)
19    access(all) event EntityAddedToSet(setID: UInt64, entityID: UInt64)
20    access(all) event EntityRetiredFromSet(setID: UInt64, entityID: UInt64, numNFTs: UInt64)
21    access(all) event SetLocked(setID: UInt64)
22    access(all) event NFTMinted(nftID: UInt64, nftUUID: UInt64, entityID: UInt64, setID: UInt64, mintNumber: UInt64, minterAddress: Address)
23
24    // Named Paths
25    access(all) let CollectionStoragePath: StoragePath
26    access(all) let CollectionPublicPath: PublicPath
27    access(all) let AdminStoragePath: StoragePath
28
29    access(self) var entityDatas: {UInt64: Entity}
30    access(self) var setDatas: {UInt64: SetData}
31    access(self) var sets: @{UInt64: Set}
32
33    // Total number of FlowverseTreasures NFTs that have been minted
34    access(all) var totalSupply: UInt64
35
36    // Incremented ID used to create entities
37    access(all) var nextEntityID: UInt64
38
39    // Incremented ID used to create sets
40    access(all) var nextSetID: UInt64
41
42    // Entity is a Struct that holds metadata associated with an NFT
43    // NFTs reference a single entity. The entities are publicly accessible, so anyone can
44    // read the metadata associated with a specific entity ID.
45    // An entity metadata is immutable
46    access(all) struct Entity {
47        // Unique ID for the entity
48        access(all) let entityID: UInt64
49
50        // Stores all the metadata about the entity as a string mapping
51        access(contract) var metadata: {String: String}
52
53        init(metadata: {String: String}) {
54            pre {
55                metadata.length != 0: "New Entity metadata cannot be empty"
56            }
57            self.entityID = FlowverseTreasures.nextEntityID
58            self.metadata = metadata
59        }
60    }
61
62    // A Set is a group of Entities.
63    // An Entity can exist in multiple different sets. 
64    // SetData is a struct that contains information about the set.
65    access(all) struct SetData {
66        // Unique ID for the Set
67        access(all) let setID: UInt64
68
69        // Name of the Set
70        access(all) let name: String
71
72        // Description of the Set
73        access(all) let description: String
74
75        // External URL (website) of the Set
76        access(all) let externalURL: String
77
78        // Thumbnail image URL of the Set
79        access(all) let thumbnailURL: String
80
81        // Banner image URL of the Set
82        access(all) let bannerURL: String
83        
84        // Address of the royalty receiver
85        access(all) let royaltyReceiverAddress: Address
86
87        // Indicates if the Set is listed as a Drop
88        // e.g. admin may create a private collection for air dropping nfts
89        access(all) var isPrivate: Bool
90
91        init(setID: UInt64, name: String, description: String, externalURL: String, thumbnailURL: String, bannerURL: String, royaltyReceiverAddress: Address, isPrivate: Bool) {
92            pre {
93                name.length > 0: "Set name cannot be empty"
94                description.length > 0: "Set description cannot be empty"
95                thumbnailURL.length > 0: "Set thumbnailURL cannot be empty"
96                bannerURL.length > 0: "Set bannerURL cannot be empty"
97            }
98            
99            self.setID = setID
100            self.name = name
101            self.description = description
102            self.externalURL = externalURL
103            self.thumbnailURL = thumbnailURL
104            self.bannerURL = bannerURL
105            self.royaltyReceiverAddress = royaltyReceiverAddress
106            self.isPrivate = isPrivate
107        }
108    }
109
110    // Set is a resource type that contains the functions to add and remove
111    // entities from a set and mint NFTs.
112    //
113    // It is stored in a private field in the contract so that
114    // only the admin resource can call its methods.
115    //
116    // The admin can add entities to a Set so that the set can mint NFTs
117    // that reference that entity data.
118    // The NFTs that are minted by a Set will be listed as belonging to
119    // the Set that minted it, as well as the Entity it references.
120    // 
121    // Admin can also lock entities from the Set, meaning that the locked
122    // Entity can no longer have NFTs minted from it.
123    //
124    // If the admin locks the Set, no more entities can be added to it, but 
125    // NFTs can still be minted from the set.
126    access(all) resource Set {
127        // Unique ID for the set
128        access(all) let setID: UInt64
129
130        // Array of entities that are a part of this set.
131        // When an entity is added to the set, its ID gets appended here.
132        access(self) var entities: [UInt64]
133
134        // Map of entity IDs that indicates whether an entity in this Set can be minted.
135        // When an entity is added to a Set, it is mapped to false.
136        // When an entity is retired, the mapping is updated to true and cannot be changed.
137        access(self) var retired: {UInt64: Bool}
138
139        // Indicates whether the Set is currently locked.
140        // When a Set is created, it is unlocked 
141        // and entities can be added to it.
142        // When a set is locked, entities cannot be added.
143        // A Set can never be changed from locked to unlocked.
144        // The decision to lock a Set it is final and irreversible.
145        // If a Set is locked, entities cannot be added, but
146        // NFTs can still be minted from entities that exist in the Set.
147        access(all) var locked: Bool
148
149        // Mapping of Entity IDs that indicates the number of NFTs 
150        // that have been minted for specific entities in this Set.
151        // When an NFT is minted, this value is stored in the NFT to
152        // show its position / mint number (serial number) 
153        // in the Set, eg. 13 of 60.
154        access(self) var numMintedPerEntity: {UInt64: UInt64}
155
156        access(all) var totalMinted: UInt64
157
158        init()
159         {
160            self.setID = FlowverseTreasures.nextSetID
161            self.entities = []
162            self.retired = {}
163            self.locked = false
164            self.numMintedPerEntity = {}
165            self.totalMinted = 0
166        }
167
168        // addEntity adds an entity to the set
169        //
170        // Parameters: entityID: The ID of the entity that is being added
171        //
172        // Pre-Conditions:
173        // The entity needs to be an existing entity
174        // The Set must not be locked
175        // The entity cannot already exist in the Set
176        //
177        access(all) fun addEntity(entityID: UInt64) {
178            pre {
179                FlowverseTreasures.entityDatas[entityID] != nil: "Cannot add the Entity to Set: Entity doesn't exist."
180                !self.locked: "Cannot add the entity to the Set after the set has been locked."
181                self.numMintedPerEntity[entityID] == nil: "The entity has already been added to the set."
182            }
183
184            // Add the entity to the array of entities
185            self.entities.append(entityID)
186
187            // Allow the entity to be minted
188            self.retired[entityID] = false
189
190            // Initialize the entity minted count to zero
191            self.numMintedPerEntity[entityID] = 0
192
193            emit EntityAddedToSet(setID: self.setID, entityID: entityID)
194        }
195
196        // addEntities adds multiple entities to the Set
197        //
198        // Parameters: entityIDs: The entity IDs that are being added
199        //
200        access(all) fun addEntities(entityIDs: [UInt64]) {
201            for entity in entityIDs {
202                self.addEntity(entityID: entity)
203            }
204        }
205
206        // retireEntity retires an entity from the Set so that it cannot mint new NFTs
207        //
208        // Parameters: entityID: The ID of the entity that is being retired
209        //
210        // Pre-Conditions:
211        // The entity exists in the Set and is not already retired
212        // 
213        access(all) fun retireEntity(entityID: UInt64) {
214            pre {
215                self.retired[entityID] == false: "Cannot retire the entity: Entity must exist in the Set and not be retired."
216            }
217            self.retired[entityID] = true
218            emit EntityRetiredFromSet(setID: self.setID, entityID: entityID, numNFTs: self.numMintedPerEntity[entityID]!)
219        }
220
221        // retireAll retires all the entities in the Set
222        //
223        access(all) fun retireAll() {
224            for entity in self.entities {
225                self.retireEntity(entityID: entity)
226            }
227        }
228
229        // lock() locks the Set so that no more entities can be added to it
230        //
231        // Pre-Conditions:
232        // The Set should not already be locked
233        access(all) fun lock() {
234            pre {
235                self.locked == false: "Cannot lock the set: Set is already locked."
236            }
237            self.locked = true
238            emit SetLocked(setID: self.setID)
239        }
240
241        // mint mints a new entity instance and returns the newly minted instance of an entity
242        // 
243        // Parameters: 
244        // entityID: The ID of the entity that the NFT references
245        // minterAddress: The address of the minter
246        //
247        // Pre-Conditions:
248        // The entity must exist in the Set and be allowed to mint new NFTs
249        //
250        // Returns: The NFT that was minted
251        // 
252        access(all) fun mint(entityID: UInt64, minterAddress: Address): @NFT {
253            pre {
254                self.retired[entityID] == false: "Cannot mint: the entity doesn't exist or has been retired."
255            }
256
257            // Gets the number of NFTs that have been minted for this Entity
258            // to use as this NFT's serial number
259            let mintNumber = self.numMintedPerEntity[entityID]!
260
261            // Mint the new NFT
262            let nft: @NFT <- create NFT(mintNumber: mintNumber + UInt64(1),
263                                              entityID: entityID,
264                                              setID: self.setID,
265                                              minterAddress: minterAddress)
266
267            // Increment the number of copies minted for this NFT
268            self.numMintedPerEntity[entityID] = mintNumber + UInt64(1)
269
270            // Increment the total number of NFTs minted for this Set
271            self.totalMinted = self.totalMinted + UInt64(1)
272
273            return <-nft
274        }
275
276        access(all) view fun getEntities(): [UInt64] {
277            return self.entities
278        }
279
280        access(all) view fun getRetired(): {UInt64: Bool} {
281            return self.retired
282        }
283
284        access(all) view fun getNumMintedPerEntity(): {UInt64: UInt64} {
285            return self.numMintedPerEntity
286        }
287
288        access(all) view fun getTotalMinted(): UInt64 {
289            return self.totalMinted
290        }
291    }
292
293    // Struct that contains all of the important data about a set
294    // Can be easily queried by instantiating the `QuerySetData` object
295    // with the desired set ID
296    // let setData = FlowverseTreasures.QuerySetData(setID: 12)
297    //
298    access(all) struct QuerySetData {
299        access(all) let setID: UInt64
300        access(all) let name: String
301        access(all) let description: String
302        access(all) let externalURL: String
303        access(all) let thumbnailURL: String
304        access(all) let bannerURL: String
305        access(all) let royaltyReceiverAddress: Address
306        access(all) let isPrivate: Bool
307        access(all) var locked: Bool
308        access(all) var totalMinted: UInt64
309
310        access(self) var entities: [UInt64]
311        access(self) var retired: {UInt64: Bool}
312        access(self) var numMintedPerEntity: {UInt64: UInt64}
313
314        init(setID: UInt64) {
315            pre {
316                FlowverseTreasures.sets[setID] != nil: "The set with the given ID does not exist"
317            }
318
319            let set = (&FlowverseTreasures.sets[setID] as &Set?)!
320            let setData = FlowverseTreasures.setDatas[setID]!
321
322            self.setID = setID
323            self.name = setData.name
324            self.description = setData.description
325            self.externalURL = setData.externalURL
326            self.thumbnailURL = setData.thumbnailURL
327            self.bannerURL = setData.bannerURL
328            self.royaltyReceiverAddress = setData.royaltyReceiverAddress
329            self.locked = set.locked
330            self.isPrivate = setData.isPrivate
331            self.totalMinted = set.getTotalMinted()
332            self.entities = set.getEntities()
333            self.retired = set.getRetired()
334            self.numMintedPerEntity = set.getNumMintedPerEntity()
335        }
336
337        // getEntities returns the IDs of all the entities in the Set
338        access(all) view fun getEntities(): [UInt64] {
339            return self.entities
340        }
341
342        // getRetired returns a mapping of entity IDs to retired state
343        access(all) view fun getRetired(): {UInt64: Bool} {
344            return self.retired
345        }
346
347        // getNumMintedPerEntity returns a mapping of entity IDs to the number of NFTs minted for that entity
348        access(all) view fun getNumMintedPerEntity(): {UInt64: UInt64} {
349            return self.numMintedPerEntity
350        }
351    }
352
353    // NFT Resource that represents an instance of an entity in a set
354    access(all) resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver {
355        // Global unique NFT ID
356        access(all) let id: UInt64
357
358        // The ID of the Set that the NFT comes from
359        access(all) let setID: UInt64
360
361        // The ID of the Entity that the NFT references
362        access(all) let entityID: UInt64
363
364        // The minterAddress of the NFT
365        access(all) let minterAddress: Address
366
367        // The serial number of the NFT, number minted for this entity in the set
368        access(all) let mintNumber: UInt64
369
370        init(mintNumber: UInt64, entityID: UInt64, setID: UInt64, minterAddress: Address) {
371            FlowverseTreasures.totalSupply = FlowverseTreasures.totalSupply + UInt64(1)
372
373            self.id = FlowverseTreasures.totalSupply
374
375            self.mintNumber = mintNumber
376            self.entityID = entityID
377            self.setID = setID
378            self.minterAddress = minterAddress
379
380            emit NFTMinted(nftID: self.id, nftUUID: self.uuid, entityID: entityID, setID: self.setID, mintNumber: self.mintNumber, minterAddress: self.minterAddress)
381        }
382
383        access(all) view fun name(): String {
384            let name: String = FlowverseTreasures.getEntityMetaDataByField(entityID: self.entityID, field: "name") ?? ""
385            return name.concat(" #").concat(self.mintNumber.toString())
386        }
387
388        access(all) view fun getViews(): [Type] {
389            return [
390                Type<MetadataViews.Display>(),
391                Type<MetadataViews.Royalties>(),
392                Type<MetadataViews.Serial>(),
393                Type<MetadataViews.Edition>(),
394                Type<MetadataViews.Traits>(),
395                Type<MetadataViews.ExternalURL>(),
396                Type<MetadataViews.Medias>()
397            ]
398        }
399
400        access(all) fun resolveView(_ view: Type): AnyStruct? {
401            let querySetData = FlowverseTreasures.getSetData(setID: self.setID)!
402            switch view {
403                case Type<MetadataViews.Display>():
404                    return MetadataViews.Display(
405                        name: self.name(),
406                        description: FlowverseTreasures.getEntityMetaDataByField(entityID: self.entityID, field: "description") ?? "",
407                        thumbnail: MetadataViews.HTTPFile(url: FlowverseTreasures.getEntityMetaDataByField(entityID: self.entityID, field: "thumbnailURL") ?? "")
408                    )
409                case Type<MetadataViews.Royalties>():
410                    let feeCut: UFix64 = 0.05
411                    let royalties : [MetadataViews.Royalty] = [
412                        MetadataViews.Royalty(
413                            receiver: getAccount(querySetData.royaltyReceiverAddress).capabilities.get<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!,
414                            cut: feeCut,
415                            description: "Creator Royalty Fee")
416                    ]
417                    return MetadataViews.Royalties(royalties)
418                case Type<MetadataViews.Serial>():
419                    return MetadataViews.Serial(self.mintNumber)
420                case Type<MetadataViews.Edition>():
421                    return MetadataViews.Edition(
422                        name: FlowverseTreasures.getEntityMetaDataByField(entityID: self.entityID, field: "name") ?? "",
423                        number: self.mintNumber,
424                        max: nil
425                    )
426                case Type<MetadataViews.Traits>():
427                    let traits: [MetadataViews.Trait] = []
428                    if let artist = FlowverseTreasures.getEntityMetaDataByField(entityID: self.entityID, field: "artist") {
429                        traits.append(MetadataViews.Trait(
430                            name: "Artist",
431                            value: artist,
432                            displayType: nil,
433                            rarity: nil
434                        ))
435                    }
436                    if let edition = FlowverseTreasures.getEntityMetaDataByField(entityID: self.entityID, field: "edition") {
437                        traits.append(MetadataViews.Trait(
438                            name: "Edition",
439                            value: edition,
440                            displayType: nil,
441                            rarity: nil
442                        ))
443                    }
444                    return MetadataViews.Traits(traits)
445                case Type<MetadataViews.ExternalURL>():
446                    let baseURL = "https://nft.flowverse.co/collections/FlowverseTreasures/"
447                    return MetadataViews.ExternalURL(baseURL.concat(self.owner!.address.toString()).concat("/".concat(self.id.toString())))
448                case Type<MetadataViews.Medias>():
449                    let medias: [MetadataViews.Media] = []
450                    let mediaURL = FlowverseTreasures.getEntityMetaDataByField(entityID: self.entityID, field: "mediaURL")
451                    let mediaType = FlowverseTreasures.getEntityMetaDataByField(entityID: self.entityID, field: "mediaType")
452                    if mediaURL != nil && mediaType != nil {
453                        let media = MetadataViews.Media(
454                            file: MetadataViews.HTTPFile(
455                                url: mediaURL!
456                            ),
457                            mediaType: mediaType!
458                        )
459                        medias.append(media)
460                    }
461                    return MetadataViews.Medias(medias)
462            }
463            return nil
464        }
465        
466        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
467            return <- FlowverseTreasures.createEmptyCollection(nftType: Type<@FlowverseTreasures.NFT>())
468        }
469    }
470
471    // SetMinter is a special authorization resource that
472    // allows the owner to mint new NFTs
473    access(all) resource SetMinter {
474        access(all) let setID: UInt64
475
476        init(setID: UInt64) {
477            self.setID = setID
478        }
479
480        access(all) fun mint(entityID: UInt64, minterAddress: Address): @NFT {
481            let setRef = (&FlowverseTreasures.sets[self.setID] as &Set?)!
482            return <- setRef.mint(entityID: entityID, minterAddress: minterAddress)
483        }
484    }
485
486    // Admin is a special authorization resource that 
487    // allows the owner to perform important functions to modify the 
488    // various aspects of the entities and sets
489    //
490    access(all) resource Admin {
491
492        // createEntity creates a new Entity struct 
493        // and stores it in the Entities dictionary in the FlowverseTreasures smart contract
494        access(all) fun createEntity(metadata: {String: String}): UInt64 {
495            // Create the new Entity
496            var entity = Entity(metadata: metadata)
497            let entityID = entity.entityID
498
499            emit EntityCreated(id: entityID, metadata: metadata)
500
501            // Increment the ID so that it isn't used again
502            FlowverseTreasures.nextEntityID = FlowverseTreasures.nextEntityID + UInt64(1)
503
504            // Store it in the contract
505            FlowverseTreasures.entityDatas[entityID] = entity
506
507            return entityID
508        }
509        
510        // createSet creates a new Set resource and stores it
511        // in the sets mapping in the contract
512        access(all) fun createSet(name: String, description: String, externalURL: String, thumbnailURL: String, bannerURL: String, royaltyReceiverAddress: Address, isPrivate: Bool): UInt64 {
513            // Create a new SetData for this Set
514            let setData = SetData(
515                setID: FlowverseTreasures.nextSetID,
516                name: name, 
517                description: description, 
518                externalURL: externalURL, 
519                thumbnailURL: thumbnailURL, 
520                bannerURL: bannerURL, 
521                royaltyReceiverAddress: royaltyReceiverAddress, 
522                isPrivate: isPrivate
523            )
524
525            // Create the new Set
526            var set <- create Set()
527
528             // Increment the setID so that it isn't used again
529            FlowverseTreasures.nextSetID = FlowverseTreasures.nextSetID + UInt64(1)
530            
531            let setID = set.setID
532
533            emit SetCreated(setID: setID, name: name, description: description, externalURL: externalURL, isPrivate: isPrivate, thumbnailURL: thumbnailURL, bannerURL: bannerURL, royaltyReceiverAddress: royaltyReceiverAddress)
534
535            // Store it in the contract
536            FlowverseTreasures.setDatas[setID] = setData
537            FlowverseTreasures.sets[setID] <-! set
538
539            return setID
540        }
541        
542        // updateSetData updates set info including: description, externalURL, thumbnailURL, bannerURL
543        access(all) fun updateSetData(setID: UInt64, description: String?, externalURL: String?, thumbnailURL: String?, bannerURL: String?, royaltyReceiverAddress: Address?) {
544            pre {
545                FlowverseTreasures.setDatas.containsKey(setID): "Set data does not exist"
546                FlowverseTreasures.sets.containsKey(setID): "Set data does not exist"
547                FlowverseTreasures.sets[setID]?.locked == false: "Locked set data cannot be updated"
548            }
549            var setData = FlowverseTreasures.setDatas[setID]!
550            let updatedSetData = SetData(
551                setID: setID,
552                name: setData.name,
553                description: description ?? setData.description,
554                externalURL: externalURL ?? setData.externalURL,
555                thumbnailURL: thumbnailURL ?? setData.thumbnailURL,
556                bannerURL: bannerURL ?? setData.bannerURL,
557                royaltyReceiverAddress: royaltyReceiverAddress ?? setData.royaltyReceiverAddress,
558                isPrivate: setData.isPrivate
559            )
560            FlowverseTreasures.setDatas[setID] = updatedSetData
561            emit SetUpdated(setID: setID, description: description, externalURL: externalURL, thumbnailURL: thumbnailURL, bannerURL: bannerURL, royaltyReceiverAddress: royaltyReceiverAddress)
562        }
563
564        // borrowSet returns a reference to a set in the contract
565        // so that the admin can call methods on it
566        access(all) view fun borrowSet(setID: UInt64): &Set {
567            pre {
568                FlowverseTreasures.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
569            }
570            
571            // Get a reference to the Set and return it
572            // use `&` to indicate the reference to the object and type
573            return (&FlowverseTreasures.sets[setID] as &Set?)!
574        }
575
576        access(all) fun createSetMinter(setID: UInt64): @SetMinter {
577            return <- create SetMinter(setID: setID)
578        }
579
580        // createNewAdmin creates a new Admin resource
581        access(all) fun createNewAdmin(): @Admin {
582            return <-create Admin()
583        }
584    }
585    
586    // Public interface for the FlowverseTreasures Collection that allows users access to certain functionalities
587    access(all) resource interface CollectionPublic {
588        access(all) fun deposit(token: @{NonFungibleToken.NFT})
589        access(all) view fun getIDs(): [UInt64]
590        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
591        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?
592    }
593
594    
595    // Collection of FlowverseShirt NFTs owned by an account
596    access(all) resource Collection: CollectionPublic, NonFungibleToken.Collection {
597        // Dictionary of entity instances conforming tokens
598        // NFT is a resource type with a UInt64 ID field
599        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
600
601        init () {
602            self.ownedNFTs <- {}
603        }
604
605        /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
606        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
607            let supportedTypes: {Type: Bool} = {}
608            supportedTypes[Type<@FlowverseTreasures.NFT>()] = true
609            return supportedTypes
610        }
611
612        /// Returns whether or not the given type is accepted by the collection
613        /// A collection that can accept any type should just return true by default
614        access(all) view fun isSupportedNFTType(type: Type): Bool {
615           if type == Type<@FlowverseTreasures.NFT>() {
616            return true
617           } else {
618            return false
619           }
620        }
621
622        /// Gets the amount of NFTs stored in the collection
623        access(all) view fun getLength(): Int {
624            return self.ownedNFTs.keys.length
625        }
626
627        /// withdraw removes an NFT from the collection and moves it to the caller
628        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
629            let token <- self.ownedNFTs.remove(key: withdrawID)
630                ?? panic("Could not withdraw an NFT with the provided ID from the collection")
631
632            return <-token
633        }
634
635        /// deposit takes a NFT and adds it to the collections dictionary
636        /// and adds the ID to the id array
637        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
638            let token <- token as! @FlowverseTreasures.NFT
639            let id = token.id
640
641            // add the new token to the dictionary which removes the old one
642            let oldToken <- self.ownedNFTs[token.id] <- token
643
644            destroy oldToken
645        }
646
647        access(all) view fun getIDs(): [UInt64] {
648            return self.ownedNFTs.keys
649        }
650
651        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
652            return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
653        }
654
655        /// Borrow the view resolver for the specified NFT ID
656        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
657            if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
658                return nft as &{ViewResolver.Resolver}
659            }
660            return nil
661        }
662
663        /// createEmptyCollection creates an empty Collection of the same type
664        /// and returns it to the caller
665        /// @return A an empty collection of the same type
666        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
667            return <-FlowverseTreasures.createEmptyCollection(nftType: Type<@FlowverseTreasures.NFT>())
668        }
669    }
670
671    // -----------------------------------------------------------------------
672    // FlowverseTreasures contract-level function definitions
673    // -----------------------------------------------------------------------
674
675    /// createEmptyCollection creates an empty Collection for the specified NFT type
676    /// and returns it to the caller so that they can own NFTs
677    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
678        return <- create Collection()
679    }
680
681    /// Function that returns all the Metadata Views implemented by a Non Fungible Token
682    ///
683    /// @return An array of Types defining the implemented views. This value will be used by
684    ///         developers to know which parameter to pass to the resolveView() method.
685    ///
686    access(all) view fun getContractViews(resourceType: Type?): [Type] {
687        return [
688            Type<MetadataViews.NFTCollectionData>(),
689            Type<MetadataViews.NFTCollectionDisplay>()
690        ]
691    }
692
693    /// Function that resolves a metadata view for this contract.
694    ///
695    /// @param view: The Type of the desired view.
696    /// @return A structure representing the requested view.
697    ///
698    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
699        switch viewType {
700           case Type<MetadataViews.NFTCollectionData>():
701                return MetadataViews.NFTCollectionData(
702                    storagePath: self.CollectionStoragePath,
703                    publicPath: self.CollectionPublicPath,
704                    publicCollection: Type<&FlowverseTreasures.Collection>(),
705                    publicLinkedType: Type<&FlowverseTreasures.Collection>(),
706                    createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {return <- FlowverseTreasures.createEmptyCollection(nftType: Type<@FlowverseTreasures.NFT>())}),
707                )
708            case Type<MetadataViews.NFTCollectionDisplay>():
709                let squareImage = MetadataViews.Media(
710                    file: MetadataViews.HTTPFile(
711                        url: "https://flowverse-treasures.s3.filebase.com/mainnet/collectionSquareImage.png"
712                    ),
713                    mediaType: "image"
714                )
715                let bannerImage = MetadataViews.Media(
716                    file: MetadataViews.HTTPFile(
717                        url: "https://flowverse-treasures.s3.filebase.com/mainnet/collectionBannerImage.png"
718                    ),
719                    mediaType: "image"
720                )
721                return MetadataViews.NFTCollectionDisplay(
722                    name: "Treasures by Flowverse",
723                    description: "Treasures is Flowverse's iconic art collector experience. These are ongoing, free digital artworks exclusively available for Flowverse Mystery Pass and Sock holders",
724                    externalURL: MetadataViews.ExternalURL("https://nft.flowverse.co/treasures"),
725                    squareImage: squareImage,
726                    bannerImage: bannerImage,
727                    socials: {
728                        "discord": MetadataViews.ExternalURL("https://discord.gg/flowverse"),
729                        "twitter": MetadataViews.ExternalURL("https://twitter.com/flowverse_"),
730                        "instagram": MetadataViews.ExternalURL("https://www.instagram.com/flowverseofficial")
731                    }
732                )
733        }
734        return nil
735    }
736
737    // getAllEntities returns all the entities available
738    access(all) view fun getAllEntities(): [FlowverseTreasures.Entity] {
739        return FlowverseTreasures.entityDatas.values
740    }
741
742    // getEntity returns an entity by ID
743    access(all) view fun getEntity(entityID: UInt64): FlowverseTreasures.Entity? {
744        return self.entityDatas[entityID]
745    }
746
747    // getEntityMetaData returns all the metadata associated with a specific entity
748    access(all) view fun getEntityMetaData(entityID: UInt64): {String: String}? {
749        return self.entityDatas[entityID]?.metadata
750    }
751    
752    access(all) view fun getEntityMetaDataByField(entityID: UInt64, field: String): String? {
753        if let entity = FlowverseTreasures.entityDatas[entityID] {
754            return entity.metadata[field]
755        } else {
756            return nil
757        }
758    }
759
760    // getSetData returns the data that the specified Set
761    //            is associated with.
762    // 
763    // Parameters: setID: The id of the Set that is being searched
764    //
765    // Returns: The QuerySetData struct that has all the important information about the set
766    access(all) fun getSetData(setID: UInt64): QuerySetData? {
767        if FlowverseTreasures.sets[setID] == nil {
768            return nil
769        } else {
770            return QuerySetData(setID: setID)
771        }
772    }
773    
774    // getSetName returns the name that the specified Set
775    //            is associated with.
776    // 
777    // Parameters: setID: The id of the Set that is being searched
778    //
779    // Returns: The name of the Set
780    access(all) view fun getSetName(setID: UInt64): String? {
781        // Don't force a revert if the setID is invalid
782        return FlowverseTreasures.setDatas[setID]?.name
783    }
784
785    // getSetIDsByName returns the IDs that the specified Set name
786    //                 is associated with.
787    access(all) fun getSetIDsByName(setName: String): [UInt64]? {
788        var setIDs: [UInt64] = []
789
790        for setData in FlowverseTreasures.setDatas.values {
791            if setName == setData.name {
792                setIDs.append(setData.setID)
793            }
794        }
795
796        if setIDs.length == 0 {
797            return nil
798        } else {
799            return setIDs
800        }
801    }
802
803    // getAllSetDatas returns all the set datas available
804    access(all) view fun getAllSetDatas(): [SetData] {
805        return FlowverseTreasures.setDatas.values
806    }
807
808    // getEntitiesInSet returns the list of Entity IDs that are in the Set
809    access(all) view fun getEntitiesInSet(setID: UInt64): [UInt64]? {
810        return FlowverseTreasures.sets[setID]?.getEntities()
811    }
812
813    // isSetEntityRetired returns a boolean that indicates if a Set/Entity combination
814    //                  is retired.
815    //                  If an entity is retired, it still remains in the Set,
816    //                  but NFTs can no longer be minted from it.
817    access(all) fun isSetEntityRetired(setID: UInt64, entityID: UInt64): Bool? {
818        if let setdata = self.getSetData(setID: setID) {
819            // See if the Entity is retired from this Set
820            let retired = setdata.getRetired()[entityID]
821
822            // Return the retired status
823            return retired
824        } else {
825            // If the Set wasn't found, return nil
826            return nil
827        }
828    }
829
830    access(all) view fun isSetLocked(setID: UInt64): Bool? {
831        return FlowverseTreasures.sets[setID]?.locked
832    }
833
834    // getNumInstancesOfEntity return the number of entity instances that have been 
835    //                        minted in a set.
836    //
837    // Parameters: setID: The id of the Set that is being searched
838    //             entityID: The id of the Entity that is being searched
839    //
840    // Returns: The total number of entity instances (NFTs) 
841    //          that have been minted in a set
842    access(all) fun getNumInstancesOfEntity(setID: UInt64, entityID: UInt64): UInt64? {
843        if let setdata = self.getSetData(setID: setID) {
844            return setdata.getNumMintedPerEntity()[entityID]
845        } else {
846            // If the set wasn't found return nil
847            return nil
848        }
849    }
850
851    // -----------------------------------------------------------------------
852    // FlowverseTreasures initialization function
853    // -----------------------------------------------------------------------
854    //
855    init() {
856        self.CollectionStoragePath = /storage/FlowverseTreasuresCollection
857        self.CollectionPublicPath = /public/FlowverseTreasuresCollection
858        self.AdminStoragePath = /storage/FlowverseTreasuresAdmin
859
860        // Initialize contract fields
861        self.entityDatas = {}
862        self.setDatas = {}
863        self.sets <- {}
864        self.nextEntityID = 1
865        self.nextSetID = 1
866        self.totalSupply = 0
867
868        // Create and save a new Collection in storage
869        let collection <- create Collection()
870        self.account.storage.save(<-collection, to: self.CollectionStoragePath)
871
872        // Issue a public capability for the Collection
873        let collectionCap = self.account.capabilities.storage.issue<&FlowverseTreasures.Collection>(self.CollectionStoragePath)
874        self.account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
875
876        // Create and save Admin resource in storage
877        self.account.storage.save<@Admin>(<- create Admin(), to: self.AdminStoragePath)
878    }
879}
880