Smart Contract

Gaia

A.8b148183c28ff88f.Gaia

Valid From

86,170,821

Deployed

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

Dependents

3463 imports
1import NonFungibleToken from 0x1d7e57aa55817448
2import FungibleToken from 0xf233dcee88fe0abe 
3import MetadataViews from 0x1d7e57aa55817448
4import ViewResolver from 0x1d7e57aa55817448
5
6// Gaia
7// NFT an open NFT standard!
8//
9access(all) contract Gaia: ViewResolver, NonFungibleToken {
10
11    // entitlement for all previous Admin resources/functions
12    access(all) entitlement Owner
13
14    access(all) entitlement Mint
15
16    // Events
17    //
18    access(all) event ContractInitialized()
19    access(all) event Withdraw(id: UInt64, from: Address?)
20    access(all) event Deposit(id: UInt64, to: Address?)
21    access(all) event TemplateCreated(id: UInt64, metadata: {String:String})
22    access(all) event SetCreated(setID: UInt64, name: String, description: String, website: String, imageURI: String, creator: Address, marketFee: UFix64)
23    access(all) event SetAddedAllowedAccount(setID: UInt64, allowedAccount: Address)
24    access(all) event SetRemovedAllowedAccount(setID: UInt64, allowedAccount: Address)
25    access(all) event TemplateAddedToSet(setID: UInt64, templateID: UInt64)
26    access(all) event TemplateLockedFromSet(setID: UInt64, templateID: UInt64, numNFTs: UInt64)
27    access(all) event SetLocked(setID: UInt64)
28    access(all) event Minted(assetID: UInt64, templateID: UInt64, setID: UInt64, mintNumber: UInt64)
29
30    /// Storage and Public Paths
31    access(all) let CollectionStoragePath: StoragePath
32    access(all) let CollectionPublicPath: PublicPath
33
34    // Variable size dictionary of Play structs
35    access(self) var templateDatas: {UInt64: Template}
36
37    // Variable size dictionary of SetData structs
38    access(self) var setDatas: {UInt64: SetData}
39
40    // Variable size dictionary of Set resources
41    access(self) var sets: @{UInt64: Set}
42
43    // totalSupply
44    // The total number of Gaia that have been minted
45    //
46    access(all) var totalSupply: UInt64
47
48    // The ID that is used to create Templates.
49    // Every time a Template is created, templateID is assigned
50    // to the new Template's ID and then is incremented by 1.
51    access(all)  var nextTemplateID: UInt64
52
53    // The ID that is used to create Sets. Every time a Set is created
54    // setID is assigned to the new set's ID and then is incremented by 1.
55    access(all)  var nextSetID: UInt64
56
57    access(all) fun royaltyAddress(setName: String): Address {
58        return setName == "Ballerz" || setName == "Sneakerz" ? 0x01 : 0x9eef2e4511390ce4
59    }
60
61    // -----------------------------------------------------------------------
62    // Gaia contract-level Composite Type definitions
63    // -----------------------------------------------------------------------
64    // These are just *definitions* for Types that this contract
65    // and other accounts can use. These definitions do not contain
66    // actual stored values, but an instance (or object) of one of these Types
67    // can be created by this contract that contains stored values.
68    // -----------------------------------------------------------------------
69
70    // Template is a Struct that holds metadata associated
71    // with a specific NFT template
72    // NFTs will all reference a single template as the owner of
73    // its metadata. The templates are publicly accessible, so anyone can
74    // read the metadata associated with a specific template ID
75    //
76    access(all) struct Template {
77
78        // The unique ID for the template
79        access(all) let templateID: UInt64
80
81        // Stores all the metadata about the template as a string mapping
82        // This is not the long term way NFT metadata will be stored.
83        access(all) let metadata: {String: String}
84
85        init(metadata: {String: String}) {
86            pre {
87                metadata.length != 0: "New Template metadata cannot be empty"
88            }
89            self.templateID = Gaia.nextTemplateID
90            self.metadata = metadata
91
92            // Increment the ID so that it isn't used again
93            Gaia.nextTemplateID = Gaia.nextTemplateID + 1
94
95            emit TemplateCreated(id: self.templateID, metadata: metadata)
96        }
97    }
98
99    // A Set is a grouping of Templates that have occured in the real world
100    // that make up a related group of collectibles, like sets of Magic cards.
101    // A Template can exist in multiple different sets.
102    // SetData is a struct that is stored in a field of the contract.
103    // Anyone can query the constant information
104    // about a set by calling various getters located
105    // at the end of the contract. Only the admin has the ability
106    // to modify any data in the private Set resource.
107    //
108    access(all) struct SetData {
109
110        // Unique ID for the Set
111        access(all) let setID: UInt64
112
113        // Name of the Set
114        access(all) let name: String
115
116        // Brief description of the Set
117        access(all) let description: String
118
119        // Set cover image
120        access(all) let imageURI: String
121
122        // Set website url
123        access(all) let website: String
124
125        // Set creator account address
126        access(all) let creator: Address
127
128        // Accounts allowed to mint
129        access(self) let allowedAccounts: [Address]
130
131        access(all) view fun returnAllowedAccounts(): [Address] {
132            return self.allowedAccounts
133        }
134
135        // Set marketplace fee
136        access(all) let marketFee: UFix64
137
138        init(name: String, description: String, website: String, imageURI: String, creator: Address, marketFee: UFix64) {
139            pre {
140                name.length > 0: "New set name cannot be empty"
141                description.length > 0: "New set description cannot be empty"
142                imageURI.length > 0: "New set imageURI cannot be empty"
143                creator != nil: "Creator must not be nil"
144                marketFee >= 0.0 && marketFee <= 0.15: "Market fee must be a number between 0.00 and 0.15"
145            }
146
147            self.setID = Gaia.nextSetID
148            self.name = name
149            self.description = description
150            self.website = website
151            self.imageURI = imageURI
152            self.creator = creator
153            self.allowedAccounts = [creator, Gaia.account.address]
154            self.marketFee = marketFee
155
156            // Increment the setID so that it isn't used again
157            Gaia.nextSetID = Gaia.nextSetID + 1
158            emit SetCreated(setID: self.setID, name: name, description: description, website: website, imageURI: imageURI, creator: creator, marketFee: marketFee)
159        }
160
161        access(Owner) fun addAllowedAccount(account: Address) {
162            pre {
163                !self.allowedAccounts.contains(account): "Account already allowed"
164            }
165
166            self.allowedAccounts.append(account)
167
168            emit SetAddedAllowedAccount(setID: self.setID, allowedAccount: account)
169        }
170
171        access(Owner) fun removeAllowedAccount(account: Address) {
172            pre {
173                self.creator != account: "Cannot remove set creator"
174                self.allowedAccounts.contains(account): "Not in allowed accounts"
175            }
176
177            var index = 0
178            for acc in self.allowedAccounts {
179                if (acc == account) {
180                    self.allowedAccounts.remove(at: index)
181                    break
182                }
183                index = index + 1
184            }
185
186            emit SetRemovedAllowedAccount(setID: self.setID, allowedAccount: account)
187        }
188    }
189
190    // Set is a resource type that contains the functions to add and remove
191    // Templates from a set and mint NFTs.
192    //
193    // It is stored in a private field in the contract so that
194    // the admin resource can call its methods.
195    //
196    // The admin can add Templates to a Set so that the set can mint NFTs
197    // that reference that template data.
198    // The NFTs that are minted by a Set will be listed as belonging to
199    // the Set that minted it, as well as the Template it references.
200    //
201    // Admin can also lock Templates from the Set, meaning that the lockd
202    // Template can no longer have NFTs minted from it.
203    //
204    // If the admin locks the Set, no more Templates can be added to it, but
205    // NFTs can still be minted.
206    //
207    // If lockAll() and lock() are called back-to-back,
208    // the Set is closed off forever and nothing more can be done with it.
209    access(all) resource Set {
210
211        // Unique ID for the set
212        access(all) let setID: UInt64
213
214        // Array of templates that are a part of this set.
215        // When a template is added to the set, its ID gets appended here.
216        // The ID does not get removed from this array when a templates is locked.
217        access(all) var templates: [UInt64]
218
219        // Map of template IDs that Indicates if a template in this Set can be minted.
220        // When a templates is added to a Set, it is mapped to false (not locked).
221        // When a templates is locked, this is set to true and cannot be changed.
222        access(all) var lockedTemplates: {UInt64: Bool}
223
224        // Indicates if the Set is currently locked.
225        // When a Set is created, it is unlocked
226        // and templates are allowed to be added to it.
227        // When a set is locked, templates cannot be added.
228        // A Set can never be changed from locked to unlocked,
229        // the decision to lock a Set it is final.
230        // If a Set is locked, templates cannot be added, but
231        // NFTs can still be minted from templates
232        // that exist in the Set.
233        access(all) var locked: Bool
234
235        // Mapping of Template IDs that indicates the number of NFTs
236        // that have been minted for specific Templates in this Set.
237        // When a NFT is minted, this value is stored in the NFT to
238        // show its place in the Set, eg. 13 of 60.
239        access(all) var numberMintedPerTemplate: {UInt64: UInt64}
240
241        init(name: String, description: String, website: String, imageURI: String, creator: Address, marketFee: UFix64)
242         {
243            self.setID = Gaia.nextSetID
244            self.templates = []
245            self.lockedTemplates = {}
246            self.locked = false
247            self.numberMintedPerTemplate = {}
248            // Create a new SetData for this Set and store it in contract storage
249            Gaia.setDatas[self.setID] = SetData(name: name, description: description, website: website, imageURI: imageURI, creator: creator, marketFee: marketFee)
250        }
251
252        // addTemplate adds a template to the set
253        //
254        // Parameters: templateID: The ID of the template that is being added
255        //
256        // Pre-Conditions:
257        // The template needs to be an existing template
258        // The Set needs to be not locked
259        // The template can't have already been added to the Set
260        //
261        access(Owner) fun addTemplate(templateID: UInt64) {
262            pre {
263                Gaia.templateDatas[templateID] != nil: "Cannot add the Template to Set: Template doesn't exist."
264                !self.locked: "Cannot add the template to the Set after the set has been locked."
265                self.numberMintedPerTemplate[templateID] == nil: "The template has already beed added to the set."
266            }
267
268            // Add the Play to the array of Plays
269            self.templates.append(templateID)
270
271            // Open the Play up for minting
272            self.lockedTemplates[templateID] = false
273
274            // Initialize the Moment count to zero
275            self.numberMintedPerTemplate[templateID] = 0
276
277            emit TemplateAddedToSet(setID: self.setID, templateID: templateID)
278        }
279
280        // addTemplates adds multiple templates to the Set
281        //
282        // Parameters: templateIDs: The IDs of the templates that are being added
283        //
284        access(Owner) fun addTemplates(templateIDs: [UInt64]){
285            for template in templateIDs {
286                self.addTemplate(templateID: template)
287            }
288        }
289
290        // retirePlay retires a Play from the Set so that it can't mint new Moments
291        //
292        // Parameters: playID: The ID of the Play that is being retired
293        //
294        // Pre-Conditions:
295        // The Play is part of the Set and not retired (available for minting).
296        //
297        access(Owner) fun lockTemplate(templateID: UInt64) {
298            pre {
299                self.lockedTemplates[templateID] != nil: "Cannot lock the template: Template doesn't exist in this set!"
300            }
301
302            if !self.lockedTemplates[templateID]! {
303                self.lockedTemplates[templateID] = true
304
305                emit TemplateLockedFromSet(setID: self.setID, templateID: templateID, numNFTs: self.numberMintedPerTemplate[templateID]!)
306            }
307        }
308
309        // lockAll lock all the templates in the Set
310        // Afterwards, none of the locked templates will be able to mint new NFTs
311        //
312        access(Owner) fun lockAll() {
313            for template in self.templates {
314                self.lockTemplate(templateID: template)
315            }
316        }
317
318        // lock() locks the Set so that no more Templates can be added to it
319        //
320        // Pre-Conditions:
321        // The Set should not be locked
322        access(Owner) fun lock() {
323            if !self.locked {
324                self.locked = true
325                emit SetLocked(setID: self.setID)
326            }
327        }
328
329        // mintNFT mints a new NFT and returns the newly minted NFT
330        //
331        // Parameters: templateID: The ID of the Template that the NFT references
332        //
333        // Pre-Conditions:
334        // The Template must exist in the Set and be allowed to mint new NFTs
335        //
336        // Returns: The NFT that was minted
337        //
338
339        access(Mint) fun mintNFT(templateID: UInt64): @NFT {
340            pre {
341                self.lockedTemplates[templateID] != nil: "Cannot mint the NFT: This template doesn't exist."
342                !self.lockedTemplates[templateID]!: "Cannot mint the NFT from this template: This template has been locked."
343            }
344
345            // Gets the number of NFTs that have been minted for this Template
346            // to use as this NFT's serial number
347            let numInTemplate = self.numberMintedPerTemplate[templateID]!
348
349            // Mint the new moment
350            let newNFT: @NFT <- create NFT(mintNumber: numInTemplate + 1,
351                                              templateID: templateID,
352                                              setID: self.setID)
353
354            // Increment the count of Moments minted for this Play
355            self.numberMintedPerTemplate[templateID] = numInTemplate + 1
356
357            return <-newNFT
358        }
359
360        // batchMintNFT mints an arbitrary quantity of NFTs
361        // and returns them as a Collection
362        //
363        // Parameters: templateID: the ID of the Template that the NFTs are minted for
364        //             quantity: The quantity of NFTs to be minted
365        //
366        // Returns: Collection object that contains all the NFTs that were minted
367        //
368
369        access(all) fun batchMintNFT(templateID: UInt64, quantity: UInt64): @Collection {
370            let newCollection <- create Collection()
371
372            var i: UInt64 = 0
373            while i < quantity {
374                newCollection.deposit(token: <-self.mintNFT(templateID: templateID))
375                i = i + 1
376            }
377
378            return <-newCollection
379        }
380    }
381
382    access(all) struct NFTData {
383
384        // The ID of the Set that the Moment comes from
385        access(all) let setID: UInt64
386
387        // The ID of the Play that the Moment references
388        access(all) let templateID: UInt64
389
390        // The place in the edition that this Moment was minted
391        // Otherwise know as the serial number
392        access(all) let mintNumber: UInt64
393
394        init(setID: UInt64, templateID: UInt64, mintNumber: UInt64) {
395            self.setID = setID
396            self.templateID = templateID
397            self.mintNumber = mintNumber
398        }
399    }
400
401    access(contract) fun buildExternalURL(): MetadataViews.ExternalURL {
402        let baseURI = "https://flowty.io/collection/".concat(Gaia.account.address.toString())
403        return MetadataViews.ExternalURL(baseURI)
404    }
405
406    // NFT
407    // A Flow Asset as an NFT
408    //
409    access(all) resource NFT: NonFungibleToken.NFT{
410        // The token's ID
411        access(all) let id: UInt64
412        // Struct of NFT metadata
413        access(all) let data: NFTData
414
415        /// createEmptyCollection creates an empty Collection
416        /// and returns it to the caller so that they can own NFTs
417        /// @{NonFungibleToken.Collection}
418        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
419            return <-Gaia.createEmptyCollection(nftType: Type<@Gaia.NFT>())
420        }
421
422        access(all) view fun getViews(): [Type] {
423            return [
424                Type<MetadataViews.NFTView>(),
425                Type<MetadataViews.Display>(),
426                Type<MetadataViews.ExternalURL>(),
427                Type<MetadataViews.NFTCollectionDisplay>(),
428                Type<MetadataViews.NFTCollectionData>(),
429                Type<MetadataViews.Traits>(),
430                Type<MetadataViews.Royalties>(),
431                Type<MetadataViews.Serial>()
432            ]
433        }
434
435        access(contract) fun parseIPFSURICID(uri: String): String {
436            return uri.slice(from: 7, upTo: 53)
437        }
438
439        access(contract) fun parseIPFSURIPath(uri: String): String? {
440            return uri.length > 55 ? uri.slice(from: 54, upTo: uri.length) : nil
441        }
442
443        access(contract) fun parseThumbnail(img: String): {MetadataViews.File}? {
444            var file: {MetadataViews.File}? = nil
445
446            if img.slice(from: 0, upTo: 7) == "ipfs://" {
447                file = MetadataViews.IPFSFile(cid: self.parseIPFSURICID(uri: img), path: self.parseIPFSURIPath(uri: img))
448            } else {
449                file = MetadataViews.HTTPFile(url: img)
450            }
451
452            return file
453        }
454
455        access(contract) fun parseTraits(metadata: {String: String}, setData: SetData): MetadataViews.Traits? {
456            var traits: [MetadataViews.Trait] = []
457            var bypass: [String] = ["id", "name", "title", "description", "img", "url", "uri", "video", "editions", "series", "series_description", "set", "setID"]
458
459            for key in metadata.keys {
460                if bypass.contains(key) {
461                    continue
462                }
463                traits.append(MetadataViews.Trait(name: key, value: metadata[key]!, displayType: nil, rarity: nil))
464            }
465
466            traits.append(MetadataViews.Trait(name: "setID", value: setData.setID.toString(), displayType: nil, rarity: nil))
467            traits.append(MetadataViews.Trait(name: "set", value: setData.name, displayType: nil, rarity: nil))
468
469            return MetadataViews.Traits(traits)
470        }
471
472        access(all) fun resolveView(_ view: Type): AnyStruct? {
473            var setData: SetData = Gaia.getSetInfo(setID: self.data.setID)!
474            var templateMetadata: {String: String} = Gaia.getTemplateMetaData(templateID: self.data.templateID)!
475            let url = "https://flowty.io/collection/".concat(Gaia.account.address.toString()).concat("/Gaia/").concat(self.id.toString())
476
477            switch view {
478                case Type<MetadataViews.NFTView>():
479                    let viewResolver = &self as &{ViewResolver.Resolver}
480                    return MetadataViews.NFTView(
481                        id: self.id,
482                        uuid: self.uuid,
483                        display: MetadataViews.getDisplay(viewResolver),
484                        externalURL: MetadataViews.getExternalURL(viewResolver),
485                        collectionData: MetadataViews.getNFTCollectionData(viewResolver),
486                        collectionDisplay: MetadataViews.getNFTCollectionDisplay(viewResolver),
487                        royalties: MetadataViews.getRoyalties(viewResolver),
488                        traits: MetadataViews.getTraits(viewResolver)
489                    )
490                case Type<MetadataViews.Display>():
491                    var name: String = setData.name == "Ballerz" ? "Baller #".concat(templateMetadata["id"]!) : templateMetadata["title"]!
492                    var description: String = templateMetadata["description"]!
493                    var thumbnail: {MetadataViews.File}? = self.parseThumbnail(img: templateMetadata["img"]!)
494                    return MetadataViews.Display(name: name, description: description, thumbnail: thumbnail!)
495                case Type<MetadataViews.ExternalURL>():
496                    return MetadataViews.ExternalURL(url)
497                case Type<MetadataViews.NFTCollectionData>():
498                    return Gaia.resolveContractView(resourceType: Type<@Gaia.NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
499                case Type<MetadataViews.NFTCollectionDisplay>():
500                    return Gaia.resolveContractView(resourceType: Type<@Gaia.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
501                case Type<MetadataViews.Traits>():
502                    var metadata = Gaia.getTemplateMetaData(templateID: self.data.templateID)
503                    return self.parseTraits(metadata: metadata!, setData: setData)
504                case Type<MetadataViews.Royalties>():
505                    let royalties: [MetadataViews.Royalty] = []
506                    let royaltyReceiverCap =
507                        getAccount(Gaia.royaltyAddress(setName: setData.name)).capabilities.get<&{FungibleToken.Receiver}>(/public/dapperUtilityCoinReceiver)
508                    if royaltyReceiverCap.check() == true {
509                        royalties.append(
510                            MetadataViews.Royalty(
511                                receiver: royaltyReceiverCap,
512                                cut:  0.05,
513                                description: "Creator royalty fee."
514                            )
515                        )
516                    }
517                    return MetadataViews.Royalties(royalties)
518                case Type<MetadataViews.Serial>():
519                    var metadata = Gaia.getTemplateMetaData(templateID: self.data.templateID)
520                    let serial: UInt64? = (metadata != nil && metadata!.containsKey("id")) ? UInt64.fromString(metadata!["id"]!) : nil
521                    return serial != nil ? MetadataViews.Serial(serial!) : nil
522            }
523            return nil
524        }
525
526        // initializer
527        //
528        init(mintNumber: UInt64, templateID: UInt64, setID: UInt64) {
529            // Increment the global Moment IDs
530            Gaia.totalSupply = Gaia.totalSupply + 1
531
532            self.id = Gaia.totalSupply
533
534            // Set the metadata struct
535            self.data = NFTData(setID: setID, templateID: templateID, mintNumber: mintNumber)
536
537            emit Minted(assetID: self.id, templateID: templateID, setID: self.data.setID, mintNumber: self.data.mintNumber)
538        }
539    }
540
541    // Admin is a special authorization resource that
542    // allows the owner to perform important functions to modify the
543    // various aspects of the Templates, Sets, and NFTs
544    //
545    access(all) resource Admin {
546
547        // createTemplate creates a new Template struct
548        // and stores it in the Templates dictionary in the TopShot smart contract
549        //
550        // Parameters: metadata: A dictionary mapping metadata titles to their data
551        //                       example: {"Name": "John Doe", "DoB": "4/14/1990"}
552        //
553        // Returns: the ID of the new Template object
554        //
555        access(Owner) fun createTemplate(metadata: {String: String}): UInt64 {
556            // Create the new Template
557            var newTemplate = Template(metadata: metadata)
558            let newID = newTemplate.templateID
559
560            // Store it in the contract storage
561            Gaia.templateDatas[newID] = newTemplate
562
563            return newID
564        }
565
566         access(Owner) fun createTemplates(templates: [{String: String}], setID: UInt64, authorizedAccount: auth(Owner) &Account){
567
568              var templateIDs: [UInt64] = []
569            for metadata in templates {
570                var ID = self.createTemplate(metadata: metadata)
571                templateIDs.append(ID)
572            }
573            self.borrowSet(setID: setID, authorizedAccount: authorizedAccount).addTemplates(templateIDs: templateIDs)
574        }
575
576        // createSet creates a new Set resource and stores it
577        // in the sets mapping in the contract
578        //
579        // Parameters: name: The name of the Set
580        //
581        access(Owner) fun createSet(name: String, description: String, website: String, imageURI: String, creator: Address, marketFee: UFix64) {
582            // Create the new Set
583            var newSet <- create Set(name: name, description: description, website: website, imageURI: imageURI, creator: creator, marketFee: marketFee)
584            // Store it in the sets mapping field
585            Gaia.sets[newSet.setID] <-! newSet
586        }
587
588        // borrowSet returns a reference to a set in the contract
589        // so that the admin can call methods on it
590        //
591        // Parameters: setID: The ID of the Set that you want to
592        // get a reference to
593        //
594        // Returns: A reference to the Set with all of the fields
595        // and methods exposed
596        //
597        access(Owner) fun borrowSet(setID: UInt64, authorizedAccount: auth(Owner) &Account): auth(Owner) &Set {
598            pre {
599                Gaia.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
600                Gaia.setDatas[setID]!.returnAllowedAccounts().contains(authorizedAccount.address): "Account not authorized"
601            }
602
603            // Get a reference to the Set and return it
604            // use `&` to indicate the reference to the object and type
605            return (&Gaia.sets[setID])!
606        }
607
608        // createNewAdmin creates a new Admin resource
609        //
610        access(Owner) fun createNewAdmin(): @Admin {
611            return <-create Admin()
612        }
613    }
614
615    // This is the interface that users can cast their Gaia Collection as
616    // to allow others to deposit Gaia into their Collection. It also allows for reading
617    // the details of Gaia in the Collection.
618    access(all) resource interface CollectionPublic: NonFungibleToken.Collection {
619        access(all) fun deposit(token: @{NonFungibleToken.NFT})
620        access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection})
621        access(all) view fun borrowGaiaNFT(id: UInt64): &Gaia.NFT? {
622            // If the result isn't nil, the id of the returned reference
623            // should be the same as the argument to the function
624            post {
625                (result == nil) || (result?.id == id):
626                    "Cannot borrow GaiaAsset reference: The ID of the returned reference is incorrect"
627            }
628        }
629    }
630
631    // Collection
632    // A collection of GaiaAsset NFTs owned by an account
633    //
634    access(all) resource Collection: NonFungibleToken.Collection, CollectionPublic{
635        // dictionary of NFT conforming tokens
636        // NFT is a resource type with an `UInt64` ID field
637        //
638        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
639
640                /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
641        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
642            let supportedTypes: {Type: Bool} = {}
643            supportedTypes[Type<@Gaia.NFT>()] = true
644            return supportedTypes
645        }
646
647        /// Returns whether or not the given type is accepted by the collection
648        /// A collection that can accept any type should just return true by default
649        access(all) view fun isSupportedNFTType(type: Type): Bool {
650           return type == Type<@Gaia.NFT>()
651        }
652        // withdraw
653        // Removes an NFT from the collection and moves it to the caller
654        
655        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
656            let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
657
658            emit Withdraw(id: token.id, from: self.owner?.address)
659
660            return <-token
661        }
662
663
664        // batchWithdraw withdraws multiple tokens and returns them as a Collection
665        //
666        // Parameters: ids: An array of IDs to withdraw
667        //
668        // Returns: @NonFungibleToken.Collection: A collection that contains
669        //                                        the withdrawn NFTs
670        //
671        access(NonFungibleToken.Withdraw) fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} {
672            // Create a new empty Collection
673            var batchCollection <- create Collection()
674
675            // Iterate through the ids and withdraw them from the Collection
676            for id in ids {
677                batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
678            }
679
680            // Return the withdrawn tokens
681            return <-batchCollection
682        }
683
684        // deposit
685        // Takes a NFT and adds it to the collections dictionary
686        // and adds the ID to the id array
687        //
688        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
689            let token <- token as! @Gaia.NFT
690
691            let id: UInt64 = token.id
692
693            // add the new token to the dictionary which removes the old one
694            let oldToken <- self.ownedNFTs[id] <- token
695
696            emit Deposit(id: id, to: self.owner?.address)
697
698            destroy oldToken
699        }
700
701        // batchDeposit takes a Collection object as an argument
702        // and deposits each contained NFT into this Collection
703        access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) {
704
705            // Get an array of the IDs to be deposited
706            let keys = tokens.getIDs()
707
708            // Iterate through the keys in the collection and deposit each one
709            for key in keys {
710                self.deposit(token: <-tokens.withdraw(withdrawID: key))
711            }
712
713            // Destroy the empty Collection
714            destroy tokens
715        }
716
717        // getIDs
718        // Returns an array of the IDs that are in the collection
719        //
720        access(all) view fun getIDs(): [UInt64] {
721            return self.ownedNFTs.keys
722        }
723
724        /// Gets the amount of NFTs stored in the collection
725        access(all) view fun getLength(): Int {
726            return self.ownedNFTs.length
727        }
728
729        // borrowNFT
730        // Gets a reference to an NFT in the collection
731        // so that the caller can read its metadata and call its methods
732        //
733        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
734            return &self.ownedNFTs[id]
735        }
736
737        // borrowGaiaNFT
738        // Gets a reference to an NFT in the collection as a GaiaAsset,
739        // exposing all of its fields (including the typeID).
740        // This is safe as there are no functions that can be called on the GaiaAsset.
741        //
742        access(all) view fun borrowGaiaNFT(id: UInt64): &Gaia.NFT? {
743            if self.ownedNFTs[id] != nil {
744                let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
745                return ref as! &Gaia.NFT
746            } else {
747                return nil
748            }
749        }
750
751        // borrowViewResolver
752        // Gets a reference to an NFT in the collection as a GaiaAsset,
753        // exposing all of its fields (including the typeID).
754        // This is safe as there are no functions that can be called on the GaiaAsset.
755        //
756        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver} {
757            let nft = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
758            return nft as! &Gaia.NFT
759        }
760
761        /// createEmptyCollection creates an empty Collection of the same type
762        /// and returns it to the caller
763        /// @return A an empty collection of the same type
764        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
765            return <-Gaia.createEmptyCollection(nftType: Type<@Gaia.NFT>())
766        }
767
768        // initializer
769        //
770        init () {
771            self.ownedNFTs <- {}
772        }
773    }
774
775    // createEmptyCollection
776    // public function that anyone can call to create a new empty collection
777    //
778    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
779        return <- create Collection()
780    }
781
782    access(contract) fun getCollectionSquareImage(): MetadataViews.Media {
783        return MetadataViews.Media(
784            file: MetadataViews.HTTPFile(url: "https://ballerz.com/images/onchain/logo-stack.png"),
785            mediaType: "image/jpeg"
786        )
787    }
788
789    access(contract) fun getCollectionBannerImage(): MetadataViews.Media {
790        return MetadataViews.Media(
791            file: MetadataViews.HTTPFile(url: "https://ballerz.com/images/onchain/logo.jpg"),
792            mediaType: "image/jpeg"
793        )
794    }
795
796
797
798    /// Function that returns all the Metadata Views implemented by a Non Fungible Token
799        ///
800        /// @return An array of Types defining the implemented views. This value will be used by
801        ///         developers to know which parameter to pass to the resolveView() method.
802        ///
803        access(all) view fun getContractViews(resourceType: Type?): [Type] {
804        return [
805            Type<MetadataViews.NFTCollectionData>(),
806            Type<MetadataViews.NFTCollectionDisplay>()
807        ]
808        }
809
810        /// Function that resolves a metadata view for this contract.
811        ///
812        /// @param view: The Type of the desired view.
813        /// @return A structure representing the requested view.
814        ///
815
816        access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
817            switch viewType {
818            case Type<MetadataViews.NFTCollectionData>():
819                return MetadataViews.NFTCollectionData(
820                    storagePath: Gaia.CollectionStoragePath,
821                    publicPath: Gaia.CollectionPublicPath,
822                    publicCollection: Type<&{Gaia.CollectionPublic}>(),
823                    publicLinkedType: Type<&{Gaia.CollectionPublic}>(),
824                    createEmptyCollectionFunction: (
825                        fun (): @{NonFungibleToken.Collection} {
826                            return <- Gaia.createEmptyCollection(nftType: Type<@Gaia.NFT>())
827                        }
828                    )
829                )
830            case Type<MetadataViews.NFTCollectionDisplay>():
831                return MetadataViews.NFTCollectionDisplay(
832                    name: "Ballerz",
833                    description: "A basketball-inspired generative NFT living on the Flow blockchain",
834                    externalURL: self.buildExternalURL(),
835                    squareImage: self.getCollectionSquareImage(),
836                    bannerImage: self.getCollectionBannerImage(),
837                    socials: {
838                        "twitter": MetadataViews.ExternalURL("https://twitter.com/@BALLERZ_NFT")
839                    }
840                )
841            }
842            return nil
843    
844        }
845
846    // getAllTemplates returns all the plays in topshot
847    //
848    // Returns: An array of all the plays that have been created
849    access(all) view fun getAllTemplates(): [Gaia.Template] {
850        return Gaia.templateDatas.values
851    }
852
853    // getTemplateMetaData returns all the metadata associated with a specific Template
854    //
855    // Parameters: templateID: The id of the Template that is being searched
856    //
857    // Returns: The metadata as a String to String mapping optional
858    access(all) view fun getTemplateMetaData(templateID: UInt64): {String: String}? {
859        return self.templateDatas[templateID]?.metadata
860    }
861
862    // getTemplateMetaDataByField returns the metadata associated with a
863    //                        specific field of the metadata
864    //                        Ex: field: "Name" will return something
865    //                        like "John Doe"
866    //
867    // Parameters: templateID: The id of the Template that is being searched
868    //             field: The field to search for
869    //
870    // Returns: The metadata field as a String Optional
871    access(all) view fun getTemplateMetaDataByField(templateID: UInt64, field: String): String? {
872        // Don't force a revert if the playID or field is invalid
873        if let template = Gaia.templateDatas[templateID] {
874            return template.metadata[field]
875        } else {
876            return nil
877        }
878    }
879
880    // getSetName returns the name that the specified Set
881    //            is associated with.
882    //
883    // Parameters: setID: The id of the Set that is being searched
884    //
885    // Returns: The name of the Set
886    access(all) view fun getSetName(setID: UInt64): String? {
887        // Don't force a revert if the setID is invalid
888        return Gaia.setDatas[setID]?.name
889    }
890
891    access(all) view fun getSetMarketFee(setID: UInt64): UFix64? {
892        // Don't force a revert if the setID is invalid
893        return Gaia.setDatas[setID]?.marketFee
894    }
895
896    access(all) view fun getSetImage(setID: UInt64): String? {
897        // Don't force a revert if the setID is invalid
898        return Gaia.setDatas[setID]?.imageURI
899    }
900
901    access(all) view fun getSetInfo(setID: UInt64): SetData? {
902        // Don't force a revert if the setID is invalid
903        return Gaia.setDatas[setID]
904    }
905
906    // getSetIDsByName returns the IDs that the specified Set name
907    //                 is associated with.
908    //
909    // Parameters: setName: The name of the Set that is being searched
910    //
911    // Returns: An array of the IDs of the Set if it exists, or nil if doesn't
912
913    access(all) fun getSetIDsByName(setName: String): [UInt64]? {
914        var setIDs: [UInt64] = []
915
916        // Iterate through all the setDatas and search for the name
917        for setData in Gaia.setDatas.values {
918            if setName == setData.name {
919                // If the name is found, return the ID
920                setIDs.append(setData.setID)
921            }
922        }
923
924        // If the name isn't found, return nil
925        // Don't force a revert if the setName is invalid
926        if setIDs.length == 0 {
927            return nil
928        } else {
929            return setIDs
930        }
931    }
932
933    // getTemplatesInSet returns the list of Template IDs that are in the Set
934    //
935    // Parameters: setID: The id of the Set that is being searched
936    //
937    // Returns: An array of Template IDs
938    access(all) view fun getTemplatesInSet(setID: UInt64): [UInt64]? {
939        // Don't force a revert if the setID is invalid
940        return Gaia.sets[setID]?.templates
941    }
942
943    // isSetTemplateLocked returns a boolean that indicates if a Set/Template combo
944    //                  is locked.
945    //                  If an template is locked, it still remains in the Set,
946    //                  but NFTs can no longer be minted from it.
947    //
948    // Parameters: setID: The id of the Set that is being searched
949    //             playID: The id of the Play that is being searched
950    //
951    // Returns: Boolean indicating if the template is locked or not
952
953    access(all) fun isSetTemplateLocked(setID: UInt64, templateID: UInt64): Bool? {
954        // Don't force a revert if the set or play ID is invalid
955        // Remove the set from the dictionary to get its field
956        if let setToRead <- Gaia.sets.remove(key: setID) {
957
958            // See if the Play is retired from this Set
959            let locked = setToRead.lockedTemplates[templateID]
960
961            // Put the Set back in the contract storage
962            Gaia.sets[setID] <-! setToRead
963
964            // Return the retired status
965            return locked
966        } else {
967
968            // If the Set wasn't found, return nil
969            return nil
970        }
971    }
972
973    // isSetLocked returns a boolean that indicates if a Set
974    //             is locked. If it's locked,
975    //             new Plays can no longer be added to it,
976    //             but NFTs can still be minted from Templates the set contains.
977    //
978    // Parameters: setID: The id of the Set that is being searched
979    //
980    // Returns: Boolean indicating if the Set is locked or not
981    access(all) view fun isSetLocked(setID: UInt64): Bool? {
982        // Don't force a revert if the setID is invalid
983        return Gaia.sets[setID]?.locked
984    }
985
986    // getTotalMinted return the number of NFTS that have been
987    //                        minted from a certain set and template.
988    //
989    // Parameters: setID: The id of the Set that is being searched
990    //             templateID: The id of the Template that is being searched
991    //
992    // Returns: The total number of NFTs
993    //          that have been minted from an set and template
994
995    access(all) fun getTotalMinted(setID: UInt64, templateID: UInt64): UInt64? {
996        // Don't force a revert if the Set or play ID is invalid
997        // Remove the Set from the dictionary to get its field
998        if let setToRead <- Gaia.sets.remove(key: setID) {
999
1000            // Read the numMintedPerPlay
1001            let amount = setToRead.numberMintedPerTemplate[templateID]
1002
1003            // Put the Set back into the Sets dictionary
1004            Gaia.sets[setID] <-! setToRead
1005
1006            return amount
1007        } else {
1008            // If the set wasn't found return nil
1009            return nil
1010        }
1011    }
1012
1013    // fetch
1014    // Get a reference to a GaiaAsset from an account's Collection, if available.
1015    // If an account does not have a Gaia.Collection, panic.
1016    // If it has a collection but does not contain the itemID, return nil.
1017    // If it has a collection and that collection contains the itemID, return a reference to that.
1018    //
1019
1020    access(all) fun fetch(_ from: Address, itemID: UInt64): &Gaia.NFT? {
1021        let collection = getAccount(from)
1022            .capabilities.get<&{NonFungibleToken.Collection}>(Gaia.CollectionPublicPath).borrow() ?? panic("Could not borrow the collection")
1023        let gaiaCollection = collection as! &{Gaia.CollectionPublic} 
1024        // We trust Gaia.Collection.borowGaiaAsset to get the correct itemID
1025        // (it checks it before returning it).
1026        return gaiaCollection.borrowGaiaNFT(id: itemID)
1027    }
1028
1029    // checkSetup
1030    // Get a reference to a GaiaAsset from an account's Collection, if available.
1031    // If an account does not have a Gaia.Collection, returns false.
1032    // If it has a collection, return true.
1033    //
1034    access(all) fun checkSetup(_ address: Address): Bool {
1035        let cap = getAccount(address)
1036        .capabilities.get<&{NonFungibleToken.Collection}>(Gaia.CollectionPublicPath)
1037        return cap.check()
1038    }
1039
1040    // initializer
1041    //
1042    init() {
1043        // Set our named paths
1044        //FIXME: REMOVE SUFFIX BEFORE RELEASE
1045        self.CollectionStoragePath = /storage/GaiaCollection001
1046        self.CollectionPublicPath = /public/GaiaCollection001
1047
1048        // Initialize contract fields
1049        self.templateDatas = {}
1050        self.setDatas = {}
1051        self.sets <- {}
1052        self.nextTemplateID = 1
1053        self.nextSetID = 1
1054        self.totalSupply = 0
1055
1056        // Put a new Collection in storage
1057        self.account.storage.save<@Collection>(<- create Collection(), to: self.CollectionStoragePath)
1058
1059        // create a public capability for the collection
1060        let cap = self.account.capabilities.storage.issue<&{Gaia.CollectionPublic}>(self.CollectionStoragePath)
1061        self.account.capabilities.publish(cap, at: self.CollectionPublicPath)
1062
1063        // Put the Minter in storage
1064        self.account.storage.save<@Admin>(<- create Admin(), to: /storage/GaiaAdmin)
1065    }
1066}