Smart Contract

CAT_EnterTheEvolution

A.7752ea736384322f.CAT_EnterTheEvolution

Deployed

3h ago
Feb 28, 2026, 08:14:46 PM UTC

Dependents

0 imports
1import TheFabricantMetadataViewsV2 from 0x7752ea736384322f
2import MetadataViews from 0x1d7e57aa55817448
3import NonFungibleToken from 0x1d7e57aa55817448
4import TheFabricantNFTStandardV2 from 0x7752ea736384322f
5import RevealableV2 from 0x7752ea736384322f
6import CoCreatableV2 from 0x7752ea736384322f
7import TheFabricantAccessList from 0x7752ea736384322f
8import FungibleToken from 0xf233dcee88fe0abe
9import FlowToken from 0x1654653399040a61
10
11// CAT
12// 4 shapes
13// 10 materials
14// 16 solid colors
15// 10 texture sets
16
17pub contract CAT_EnterTheEvolution: NonFungibleToken, TheFabricantNFTStandardV2, RevealableV2 {
18
19    // -----------------------------------------------------------------------
20    // Paths
21    // -----------------------------------------------------------------------
22
23    pub let CAT_EnterTheEvolutionCollectionStoragePath: StoragePath
24    pub let CAT_EnterTheEvolutionCollectionPublicPath: PublicPath
25    pub let CAT_EnterTheEvolutionProviderPath: PrivatePath
26    pub let CAT_EnterTheEvolutionPublicMinterStoragePath: StoragePath 
27    pub let CAT_EnterTheEvolutionAdminStoragePath: StoragePath
28    pub let CAT_EnterTheEvolutionPublicMinterPublicPath: PublicPath
29
30    // -----------------------------------------------------------------------
31    // Contract Events
32    // -----------------------------------------------------------------------
33
34    // Event that emitted when the NFT contract is initialized
35    //
36    pub event ContractInitialized()
37
38    pub event ItemMintedAndTransferred(
39        uuid: UInt64,
40        id: UInt64,
41        name: String,
42        description: String,
43        collection: String,
44        editionNumber: UInt64,
45        originalRecipient: Address,
46        license: MetadataViews.License?,
47        nftMetadataId: UInt64
48    )
49
50    pub event DataAllocationCreated(
51        uuid: UInt64,
52        id: UInt64,
53        editionNumber: UInt64,
54        originalRecipient: Address,
55        nftMetadataId: UInt64,
56        dataAllocationString: String
57    )
58
59    pub event ItemRevealed(
60        uuid: UInt64,
61        id: UInt64,
62        name: String,
63        description: String,
64        collection: String,
65        editionNumber: UInt64,
66        originalRecipient: Address,
67        license: MetadataViews.License?,
68        nftMetadataId: UInt64,
69        externalURL: MetadataViews.ExternalURL,
70        coCreatable: Bool,
71        coCreator: Address,
72    )
73
74    pub event TraitRevealed(
75        nftUuid: UInt64, 
76        id: UInt64,
77        trait: String,
78    )
79
80    pub event IsTraitRevealableUpdated(
81        nftUuid: UInt64, 
82        id: UInt64,
83        trait: String,
84        isRevealable: Bool
85    )
86
87    pub event ItemDestroyed(
88        uuid: UInt64,
89        id: UInt64,
90        name: String,
91        description: String,
92        collection: String,
93    )
94
95    pub event PublicMinterCreated(
96        uuid: UInt64,
97        name: String,
98        description: String,
99        collection: String,
100        path: String,
101    )
102
103    pub event PublicMinterIsOpenAccessChanged(
104        uuid: UInt64,
105        name: String,
106        description: String,
107        collection: String,
108        path: String,
109        isOpenAccess: Bool,
110        isAccessListOnly: Bool,
111        isOpen: Bool
112    )
113
114    pub event PublicMinterIsAccessListOnly(
115        uuid: UInt64,
116        name: String,
117        description: String,
118        collection: String,
119        path: String,
120        isOpenAccess: Bool,
121        isAccessListOnly: Bool,
122        isOpen: Bool
123    )
124
125    pub event PublicMinterMintingIsOpen(
126        uuid: UInt64,
127        name: String,
128        description: String,
129        collection: String,
130        path: String,
131        isOpenAccess: Bool,
132        isAccessListOnly: Bool,
133        isOpen: Bool
134    )
135
136    pub event PublicMinterSetAccessListId(
137        uuid: UInt64,
138        name: String,
139        description: String,
140        collection: String,
141        path: String,
142        isOpenAccess: Bool,
143        isAccessListOnly: Bool,
144        isOpen: Bool,
145        accessListId: UInt64
146    )
147
148    pub event PublicMinterSetPaymentAmount(
149        uuid: UInt64,
150        name: String,
151        description: String,
152        collection: String,
153        path: String,
154        isOpenAccess: Bool,
155        isAccessListOnly: Bool,
156        isOpen: Bool,
157        paymentAmount: UFix64
158    )
159    
160
161    pub event PublicMinterSetMinterMintLimit(
162        uuid: UInt64,
163        name: String,
164        description: String,
165        collection: String,
166        path: String,
167        isOpenAccess: Bool,
168        isAccessListOnly: Bool,
169        isOpen: Bool,
170        minterMintLimit: UInt64?
171    )
172
173    pub event AdminResourceCreated(
174        uuid:UInt64,
175        adminAddress: Address
176    )
177
178    pub event AdminSetMaxSupply(
179        maxSupply: UInt64 
180    )
181
182    pub event AdminSetAddressMintLimit(
183        addressMintLimit: UInt64 
184    )
185
186    pub event AdminSetCollectionId(
187        collectionId: String 
188    )
189
190    pub event AdminSetBaseURI(baseURI: String)
191
192    // Event that is emitted when a token is withdrawn,
193    // indicating the owner of the collection that it was withdrawn from.
194    //
195    // If the collection is not in an account's storage, `from` will be `nil`.
196    //
197    pub event Withdraw(id: UInt64, from: Address?)
198
199    // Event that emitted when a token is deposited to a collection.
200    //
201    // It indicates the owner of the collection that it was deposited to.
202    //
203    pub event Deposit(id: UInt64, to: Address?)
204
205    // -----------------------------------------------------------------------
206    // Contract State
207    // -----------------------------------------------------------------------
208
209    // NOTE: This is updated anywhere ownership of the nft is changed - on minting and therefore on deposit
210    access(contract) var nftIdsToOwner: {UInt64: Address}
211    access(contract) var publicMinterPaths: {UInt64: String}
212
213    // NOTE: this is contract-level so all minters can access it.
214    // Keeps track of the number of times an address has minted
215    access(contract) var addressMintCount: {Address: UInt64}
216
217    access(contract) var nftMetadata: {UInt64: AnyStruct{RevealableV2.RevealableMetadata}}
218
219    // Keeps track of characteristic combinations, ensuring unique combos
220    access(contract) var dataAllocations: {String: UInt64}
221    access(contract) var idsToDataAllocations: {UInt64: String}
222
223    // The total number of tokens of this type in existence
224    // NOTE: All public minters use totalSupply to assign the next
225    // id and edition number. Each public minter has a minterMintLimit property
226    // that defines the max no. of mints a pM can do. 
227    pub var totalSupply: UInt64
228    // NOTE: The max number of NFTs in this collection that will ever be minted
229    // Init as nil if there is no max. 
230    pub var maxSupply: UInt64?
231    
232    // NOTE: Max mints per address
233    pub var addressMintLimit: UInt64?
234
235    //NOTE: uuid of collection added to NFT and used by BE
236    pub var collectionId: String?
237
238    // Change these dictionaries to represent the characteristics
239    // of the nft that the user can choose from
240    access(contract) var shoeShapes: {UInt64: {CoCreatableV2.Characteristic}}
241    access(contract) var materials: {UInt64: {CoCreatableV2.Characteristic}}
242    access(contract) var solidColors: {UInt64: {CoCreatableV2.Characteristic}}
243    access(contract) var textureSets: {UInt64: {CoCreatableV2.Characteristic}}
244
245    access(contract) var baseTokenURI: String?
246
247    // -----------------------------------------------------------------------
248    // RevealableV2 Metadata Struct
249    // -----------------------------------------------------------------------
250
251    pub struct RevealableMetadata: RevealableV2.RevealableMetadata {
252
253        //NOTE: totalSupply value of attached NFT, therefore edition number. 
254        pub let id: UInt64 
255
256        // NOTE: !IMPORTANT! nftUuid is the uuid of the associated nft.
257        // This RevealableMetadata struct should be stored in the nftMetadata dict under this
258        // value. This is because the uuid is used across contracts for identification purposes
259        pub let nftUuid: UInt64 // uuid of NFT
260        
261        // NOTE: Name of NFT. Will most likely be the last node in the collection value.
262        // eg XXories Original.
263        // Will be combined with the edition number on the application
264        // Doesn't include the edition number.
265        pub var name: String
266
267        pub var description: String //Display
268        // NOTE: Thumbnail, which is needed for the Display view, should be set using one of the
269        // media properties
270        //pub let thumbnail: String //Display
271
272        pub let collection: String // Name of collection eg The Fabricant > Season 3 > Wholeland > XXories Originals
273
274        // Stores the metadata that describes this particular creation,
275        // but is not part of a characteristic eg mainImage, video etc
276        pub var metadata: {String: AnyStruct}
277
278        // This is where the user-chosen characteristics live. This represents
279        // the data that in older contracts, would've been separate NFTs.        
280        pub var characteristics: {String: {CoCreatableV2.Characteristic}}
281
282        pub var rarity: UFix64?
283        pub var rarityDescription: String?
284
285        // NOTE: Media is not implemented in the struct because MetadataViews.Medias
286        // is not mutable, so can't be updated. In addition, each 
287        // NFT collection might have a different number of image/video properties.
288        // Instead, the NFT should implement a function that rolls up the props
289        // into a MetadataViews.Medias struct
290        //pub let media: MetadataViews.Medias //Media
291
292        pub let license: MetadataViews.License? //License
293
294        pub let externalURL: MetadataViews.ExternalURL //ExternalURL
295        
296        pub let coCreatable: Bool
297        pub let coCreator: Address
298
299        pub var isRevealed: Bool?
300
301        // id and editionNumber might not be the same in the nft...
302        pub let editionNumber: UInt64 //Edition
303        pub let maxEditionNumber: UInt64?
304
305        pub let royalties: MetadataViews.Royalties //Royalty
306        pub let royaltiesTFMarketplace: TheFabricantMetadataViewsV2.Royalties
307        
308        access(contract) var revealableTraits: {String: Bool}
309
310        pub fun getRevealableTraits(): {String: Bool} {
311            return self.revealableTraits
312        }
313
314        //NOTE: Customise
315        //NOTE: This should be updated for each campaign contract!
316        // Called by the Admin to reveal the traits for this NFT.
317        // Should contain a switch function that knows how to modify
318        // the properties of this struct. Should check that the trait
319        // being revealed is allowed to be modified.
320        access(contract) fun revealTraits(traits: [{RevealableV2.RevealableTrait}]) {
321            assert(CAT_EnterTheEvolution.baseTokenURI != nil, message: "The base URI must be set before reveal can take place")
322            var i = 0
323            while i < traits.length {
324                let revealableTrait = traits[i]
325                let traitName = revealableTrait.name
326                let traitValue = revealableTrait.value
327
328                switch(traitName) {
329                    case "mainImage":
330                        assert(self.checkRevealableTrait(traitName: traitName)!, message: "Unrevealable trait passed in - please ensure trait can be revealed: ".concat(traitName))
331                        self.updateMetadata(key: traitName, value: traitValue)
332                        log("Revealing mainImage")
333                    case "video":
334                        assert(self.checkRevealableTrait(traitName: traitName)!, message: "Unrevealable trait passed in - please ensure trait can be revealed: ".concat(traitName))
335                        self.updateMetadata(key: traitName, value: traitValue)
336                        log("Revealing video")
337                    default:
338                        panic("Unrevealable trait passed in - please ensure trait can be revealed: ".concat(traitName))
339                }
340                i = i + 1
341            }
342            //NOTE: Customise
343            // Some collections may allow users to partially reveal their items. In this case, 
344            // it may not be appropriate to set isRevealed to true yet.
345            self.isRevealed = true
346        }
347
348        access(self) fun constructAssetPath(): String {
349            
350            // Get CharcteristicIds
351            let shoeShapeId = (self.characteristics["shoeShape"] as {CoCreatableV2.Characteristic}?)!.id
352            let materialId = (self.characteristics["material"] as {CoCreatableV2.Characteristic}?)!.id
353            let solidColorId = (self.characteristics["solidColor"] as! {CoCreatableV2.Characteristic}?)!.id
354            let textureSetId = (self.characteristics["textureSet"] as! {CoCreatableV2.Characteristic}?)!.id
355            return "/"
356                .concat(shoeShapeId.toString())
357                .concat("_").concat(materialId.toString())
358                .concat("_").concat(solidColorId.toString())
359                .concat("_").concat(textureSetId.toString())
360        }
361
362        access(contract) fun updateMetadata(key: String, value: AnyStruct) {
363            log("revealing key and value:")
364            log(key)
365            log(value)
366            self.metadata[key] = value
367        }
368
369        // Called by the nft owner to modify if a trait can be 
370        // revealed or not - used to revoke admin access
371        pub fun updateIsTraitRevealable(key: String, value: Bool) {
372            self.revealableTraits[key] = value
373        }
374
375        pub fun checkRevealableTrait(traitName: String): Bool? {
376            if let revealable = self.revealableTraits[traitName] {
377                return revealable
378            }
379            return nil
380        }
381
382        init(
383            id: UInt64,
384            nftUuid: UInt64,    
385            name: String,
386            description: String,
387            collection: String,
388            metadata: {String: AnyStruct},
389            characteristics: {String: {CoCreatableV2.Characteristic}},
390            license: MetadataViews.License?,
391            externalURL: MetadataViews.ExternalURL,
392            coCreatable: Bool,
393            coCreator: Address,
394            editionNumber: UInt64,
395            maxEditionNumber: UInt64?,
396            revealableTraits: {String: Bool},
397            royalties: MetadataViews.Royalties,
398            royaltiesTFMarketplace: TheFabricantMetadataViewsV2.Royalties
399
400        ) {
401
402            self.id = id
403            self.nftUuid = nftUuid
404            self.name = name
405            self.description = description
406            self.collection = collection
407            self.metadata = metadata
408            self.characteristics = characteristics
409            self.rarity = nil
410            self.rarityDescription = nil
411            self.license = license
412            self.externalURL = externalURL
413            self.coCreatable = coCreatable
414            self.coCreator = coCreator
415            //NOTE: Customise
416            // This should be nil if the nft can't be revealed!
417            self.isRevealed = true
418            self.editionNumber = editionNumber
419            self.maxEditionNumber = maxEditionNumber
420            self.revealableTraits = revealableTraits
421            self.royalties = royalties
422            self.royaltiesTFMarketplace = royaltiesTFMarketplace
423
424        }
425
426    }
427
428    // -----------------------------------------------------------------------
429    // Trait Struct
430    // -----------------------------------------------------------------------
431
432    // Used by txs to target traits/characteristics to be revealed
433    
434    pub struct Trait: RevealableV2.RevealableTrait {
435        pub let name: String
436        pub let value: AnyStruct
437
438        init(
439            name: String, 
440            value: AnyStruct
441            ) {
442            self.name = name
443            self.value = value
444        }
445    }
446
447    // -----------------------------------------------------------------------
448    // Characteristic Struct
449    // -----------------------------------------------------------------------
450
451    pub struct Characteristic: CoCreatableV2.Characteristic {
452        pub var id: UInt64
453
454        pub var version: UFix64
455        // This is the name that will be used for the trait, so 
456        // will be displayed on external MPs etc. Should be capitalised
457        // and spaced eg "Shoe Shape Name"
458        pub var traitName: String
459        // eg characteristicType = garmentData
460        pub var characteristicType: String
461        pub var characteristicDescription: String
462        pub var designerName: String?
463        pub var designerDescription: String?
464        pub var designerAddress: Address?
465        // Value is the name of the selected characteristic
466        // For example, for a garment, this might be "Adventurer Top"
467        pub var value: AnyStruct
468        pub var rarity: MetadataViews.Rarity?
469
470        pub var media: MetadataViews.Medias?
471
472        init(
473            id: UInt64,
474            traitName: String,
475            characteristicType: String,
476            characteristicDescription: String,
477            designerName: String?,
478            designerDescription: String?,
479            designerAddress: Address?,
480            value: AnyStruct,
481            rarity: MetadataViews.Rarity?,
482            media: MetadataViews.Medias?
483        ) {
484            self.id = id
485            //NOTE: Customise according to the 
486            // CoCreatableV2 contract version
487            self.version = 2.0
488            self.traitName = traitName
489            self.characteristicType = characteristicType
490            self.characteristicDescription = characteristicDescription
491            self.designerName = designerName
492            self.designerDescription = designerDescription
493            self.designerAddress = designerAddress
494            self.value = value
495            self.rarity = rarity
496            self.media = media
497        }
498        // NOTE: Customise
499        //Helper function that converts to traits for MetadataViews 
500        pub fun convertToTraits(): [MetadataViews.Trait] {
501            let traits: [MetadataViews.Trait] = []
502            let idTrait =  MetadataViews.Trait(
503                name: self.traitName.concat(" ID"),
504                value: self.id,
505                displayType: "Number",
506                rarity: nil
507            )
508            traits.append(idTrait)
509
510            if self.designerName != nil {
511                let designerNameTrait =  MetadataViews.Trait(
512                    name: self.traitName.concat(" Designer"),
513                    value: self.designerName,
514                    displayType: "String",
515                    rarity: nil
516                )
517                traits.append(designerNameTrait)
518            }
519
520            if self.designerDescription != nil {
521                let designerDescriptionTrait =  MetadataViews.Trait(
522                    name: self.traitName.concat(" Designer Description"),
523                    value: self.designerDescription,
524                    displayType: "String",
525                    rarity: nil
526                )
527                traits.append(designerDescriptionTrait)
528            }
529
530            let valueRarity = self.rarity != nil ? self.rarity : nil
531            // If the designer name is nil, then the trait wasn't designed and it must be a color.
532            // Therefore we give it a name of "XYZ Value"
533            let valueTrait =  MetadataViews.Trait(
534                name: self.designerName != nil ? self.traitName.concat(" Name"): self.traitName.concat(" Value"),
535                value: self.value,
536                displayType: "String",
537                rarity: valueRarity
538            )
539            traits.append(valueTrait)
540            return traits
541        }
542
543        //NOTE: Customise
544        access(contract) fun updateCharacteristic(key: String, value: AnyStruct) {
545            // Can be implemented if needed.
546            // Should be a switch statement like revealTrait() in Metadata struct
547        }
548
549        
550    }
551
552    // -----------------------------------------------------------------------
553    // NFT Resource
554    // -----------------------------------------------------------------------
555
556    // Restricted scope for borrowCAT_EnterTheEvolution() in Collection.
557    // Ensures that the returned NFT ref is read only.
558    pub resource interface PublicNFT {
559        pub fun getFullName(): String
560        pub fun getEditions(): MetadataViews.Editions
561        pub fun getMedias(): MetadataViews.Medias
562        pub fun getTraits(): MetadataViews.Traits?
563        pub fun getRarity(): MetadataViews.Rarity?
564        pub fun getExternalRoyalties(): MetadataViews.Royalties 
565        pub fun getTFRoyalties(): TheFabricantMetadataViewsV2.Royalties 
566        pub fun getMetadata(): {String: AnyStruct} 
567        pub fun getCharacteristics(): {String: {CoCreatableV2.Characteristic}}? 
568        pub fun getDisplay(): MetadataViews.Display 
569        pub fun getCollectionData(): MetadataViews.NFTCollectionData 
570        pub fun getCollectionDisplay(): MetadataViews.NFTCollectionDisplay
571        pub fun getNFTView(): MetadataViews.NFTView
572        pub fun getViews(): [Type]
573        pub fun resolveView(_ view: Type): AnyStruct?
574    }
575
576    pub resource NFT: TheFabricantNFTStandardV2.TFNFT, NonFungibleToken.INFT, MetadataViews.Resolver, PublicNFT {
577        pub let id: UInt64 
578        //pub let uuid: UInt64 //Display, Serial, <- uuid is set automatically so no need to add
579        
580        // NOTE: Ensure that the name for the nft is correct. This 
581        // will be shown to users. It should not include the edition number.
582
583        access(contract) let collectionId: String
584
585        access(contract) let editionNumber: UInt64 //Edition
586        access(contract) let maxEditionNumber: UInt64?
587
588        access(contract) let originalRecipient: Address
589
590        access(contract) let license: MetadataViews.License?
591
592        access(contract) let nftMetadataId: UInt64
593
594        pub fun getFullName(): String {
595            return CAT_EnterTheEvolution.nftMetadata[self.nftMetadataId]!.name!.concat(" #".concat(self.editionNumber.toString()))
596        }
597
598        // NOTE: This is important for Edition view
599        pub fun getEditionName(): String {
600            return CAT_EnterTheEvolution.nftMetadata[self.nftMetadataId]!.collection
601        }
602
603        pub fun getEditions(): MetadataViews.Editions {
604            // NOTE: In this case, id == edition number
605            let edition = MetadataViews.Edition(
606                name: CAT_EnterTheEvolution.nftMetadata[self.nftMetadataId]!.collection,
607                number: self.editionNumber,
608                max: CAT_EnterTheEvolution.maxSupply
609            )
610
611            return MetadataViews.Editions(
612                infoList: [edition]
613            )
614        }
615
616        //NOTE: Customise
617        //NOTE: This will be different for each campaign, determined by how
618        // many media files there are and their keys in metadata! Pay attention
619        // to where the media files are stored and therefore accessed
620        // NOTE: DOUBLE CHECK THE fileType IS CORRECT!!!
621        pub fun getMedias(): MetadataViews.Medias {
622            let nftMetadata = CAT_EnterTheEvolution.nftMetadata[self.id]!
623            let mainImage = nftMetadata.metadata["mainImage"]! as! String
624            // NOTE: This assumes that when the shoeShape characteristic is created
625            // in the update_shoe_shapes_char tx, the value property is created as a dictionary
626            let video = nftMetadata.metadata["video"]! as! String
627
628            let mainImageMedia = MetadataViews.Media(
629                file: MetadataViews.HTTPFile(url: mainImage), 
630                fileType: "image/png"
631            )
632            let videoMedia = MetadataViews.Media(
633                file: MetadataViews.HTTPFile(url: video), 
634                fileType: "video/mp4"
635            )
636            return MetadataViews.Medias(items: [
637                mainImageMedia,
638                videoMedia
639            ])
640        }
641
642        // NOTE: Customise
643        pub fun getImages(): {String: String} {
644            let nftMetadata = CAT_EnterTheEvolution.nftMetadata[self.id]!
645            let mainImage = nftMetadata.metadata["mainImage"]! as! String
646            return {
647                "mainImage": mainImage
648            }
649        }
650
651        // NOTE: Customise
652        pub fun getVideos(): {String: String} {
653            let nftMetadata = CAT_EnterTheEvolution.nftMetadata[self.id]!
654            let mainVideo = nftMetadata.metadata["video"]! as! String
655            return {
656                "mainVideo": mainVideo
657            }
658        }
659
660        // NOTE: Customise
661        // What are the traits that you want external marketplaces
662        // to display?
663        pub fun getTraits(): MetadataViews.Traits? {
664            let characteristics = CAT_EnterTheEvolution.nftMetadata[self.id]!.characteristics
665            log(characteristics)
666            let shoeShapeCharacteristic = characteristics["shoeShape"]!
667            let shoeTraits = shoeShapeCharacteristic.convertToTraits()
668            let materialCharacteristic = characteristics["material"]!
669            let materialTraits = materialCharacteristic.convertToTraits()
670            let solidColorCharacteristic = characteristics["solidColor"]!
671            let solidColorTraits = solidColorCharacteristic.convertToTraits()
672            let textureSetsCharacteristic = characteristics["textureSet"]!
673            let textureSetTraits = textureSetsCharacteristic.convertToTraits()
674            let concatenatedArrays = shoeTraits.concat(materialTraits).concat(solidColorTraits).concat(textureSetTraits)
675            return MetadataViews.Traits(concatenatedArrays)
676        }
677
678        pub fun getRarity(): MetadataViews.Rarity? {
679            return nil
680        }
681
682        pub fun getExternalRoyalties(): MetadataViews.Royalties {
683            let nftMetadata = CAT_EnterTheEvolution.nftMetadata[self.id]!
684            return nftMetadata.royalties
685        }
686
687        pub fun getTFRoyalties(): TheFabricantMetadataViewsV2.Royalties {
688            let nftMetadata = CAT_EnterTheEvolution.nftMetadata[self.id]!
689            return nftMetadata.royaltiesTFMarketplace
690        }
691
692        pub fun getMetadata(): {String: AnyStruct} {
693            return CAT_EnterTheEvolution.nftMetadata[self.id]!.metadata
694        }
695
696        //NOTE: This is not a CoCreatableV2 NFT, so no characteristics are present
697        pub fun getCharacteristics(): {String: {CoCreatableV2.Characteristic}}? {
698            return CAT_EnterTheEvolution.nftMetadata[self.id]!.characteristics
699        }
700
701        pub fun getRevealableTraits(): {String: Bool}? {
702            return CAT_EnterTheEvolution.nftMetadata[self.id]!.getRevealableTraits()
703        }
704
705
706        //NOTE: The first file in medias will be the thumbnail.
707        // Maybe put a file type check in here to ensure it is 
708        // an image?
709        pub fun getDisplay(): MetadataViews.Display {
710            return MetadataViews.Display(
711                name: self.getFullName(),
712                description: CAT_EnterTheEvolution.nftMetadata[self.nftMetadataId]!.description,
713                thumbnail: self.getMedias().items[0].file
714            )
715        }
716
717        pub fun getCollectionData(): MetadataViews.NFTCollectionData {
718            return MetadataViews.NFTCollectionData(
719                storagePath: CAT_EnterTheEvolution.CAT_EnterTheEvolutionCollectionStoragePath,
720                publicPath: CAT_EnterTheEvolution.CAT_EnterTheEvolutionCollectionPublicPath,
721                providerPath: CAT_EnterTheEvolution.CAT_EnterTheEvolutionProviderPath,
722                publicCollection: Type<&CAT_EnterTheEvolution.Collection{CAT_EnterTheEvolution.CAT_EnterTheEvolutionCollectionPublic, MetadataViews.ResolverCollection}>(),
723                publicLinkedType: Type<&CAT_EnterTheEvolution.Collection{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, CAT_EnterTheEvolution.CAT_EnterTheEvolutionCollectionPublic, MetadataViews.ResolverCollection}>(),
724                providerLinkedType: Type<&CAT_EnterTheEvolution.Collection{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection, CAT_EnterTheEvolutionCollectionPublic}>(),
725                createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
726                            return <-CAT_EnterTheEvolution.createEmptyCollection()
727                })
728            )
729        }
730
731        //NOTE: Customise
732        // NOTE: Update this function with the collection display image
733        // and TF socials
734        pub fun getCollectionDisplay(): MetadataViews.NFTCollectionDisplay {
735            let squareImage = MetadataViews.Media(
736                        file: MetadataViews.HTTPFile(
737                            url: "https://www.dropbox.com/s/8jgcbkus6rg4v6c/CAT-logo-768x461.png?dl=0"
738                        ),
739                        mediaType: "image/svg+xml"
740                    )
741            let bannerImage = MetadataViews.Media(
742                        file: MetadataViews.HTTPFile(
743                            url: "https://www.dropbox.com/s/do50ksxr5vpdvsh/Stage02_CloseStill4k_169-US.png?dl=0"
744                        ),
745                        mediaType: "image/svg+xml"
746                    )
747            return MetadataViews.NFTCollectionDisplay(
748                        name: self.getEditionName(),
749                        description: "Example Simple TFNFT",
750                        externalURL: CAT_EnterTheEvolution.nftMetadata[self.id]!.externalURL,
751                        squareImage: squareImage,
752                        bannerImage: bannerImage,
753                        socials: {
754                            "twitter": MetadataViews.ExternalURL("https://twitter.com/thefabricant"),
755                            "instagram":MetadataViews.ExternalURL( "https://www.instagram.com/the_fab_ric_ant/"),
756                            "facebook": MetadataViews.ExternalURL("https://www.facebook.com/thefabricantdesign/"),
757                            "artstation": MetadataViews.ExternalURL("https://www.artstation.com/thefabricant"),
758                            "behance": MetadataViews.ExternalURL("https://www.behance.net/thefabricant"),
759                            "linkedin": MetadataViews.ExternalURL("https://www.linkedin.com/company/the-fabricant"),
760                            "sketchfab": MetadataViews.ExternalURL("https://sketchfab.com/thefabricant"),
761                            "clolab": MetadataViews.ExternalURL("https://www.clo3d.com/en/clollab/thefabricant"),
762                            "tiktok": MetadataViews.ExternalURL("@digital_fashion"),
763                            "discord": MetadataViews.ExternalURL("https://discord.com/channels/692039738751713280/778601303013195836")
764                        }
765                    )
766        }
767
768        pub fun getNFTView(): MetadataViews.NFTView {
769            return MetadataViews.NFTView(
770                id: self.id,
771                uuid : self.uuid,
772                display : self.getDisplay(),
773                externalURL : CAT_EnterTheEvolution.nftMetadata[self.id]!.externalURL,
774                collectionData : self.getCollectionData(),
775                collectionDisplay : self.getCollectionDisplay(),
776                royalties : CAT_EnterTheEvolution.nftMetadata[self.id]!.royalties,
777                traits: self.getTraits()
778            )
779        }
780
781        pub fun getViews(): [Type] {
782            let viewArray: [Type] = [
783                Type<TheFabricantMetadataViewsV2.TFNFTIdentifierV1>(),
784                Type<TheFabricantMetadataViewsV2.TFNFTSimpleView>(),
785                Type<MetadataViews.NFTView>(),
786                Type<MetadataViews.Display>(),
787                Type<MetadataViews.Editions>(),
788                Type<MetadataViews.Serial>(),
789                Type<MetadataViews.Royalties>(),
790                Type<MetadataViews.Medias>(),
791                Type<MetadataViews.ExternalURL>(),
792                Type<MetadataViews.NFTCollectionData>(),
793                Type<MetadataViews.NFTCollectionDisplay>(),
794                Type<MetadataViews.Traits>()
795            ]
796
797            if self.license != nil {
798                viewArray.append(Type<MetadataViews.License>())
799            }
800            if self.getRarity() != nil {
801                viewArray.append(Type<MetadataViews.Rarity>())
802            }
803
804            return viewArray
805        }
806
807        pub fun resolveView(_ view: Type): AnyStruct? {
808            switch view {
809                case Type<TheFabricantMetadataViewsV2.TFNFTIdentifierV1>():
810                    return TheFabricantMetadataViewsV2.TFNFTIdentifierV1(
811                        uuid: self.uuid,
812                        id: self.id,
813                        name: self.getFullName(),
814                        collection: CAT_EnterTheEvolution.nftMetadata[self.nftMetadataId]!.collection,
815                        editions: self.getEditions(),
816                        address: self.owner!.address,
817                        originalRecipient: self.originalRecipient,
818                    )
819                case Type<TheFabricantMetadataViewsV2.TFNFTSimpleView>():
820                    return TheFabricantMetadataViewsV2.TFNFTSimpleView(
821                        uuid: self.uuid,
822                        id: self.id,
823                        name: self.getFullName(),
824                        description: CAT_EnterTheEvolution.nftMetadata[self.nftMetadataId]!.description,
825                        collection: CAT_EnterTheEvolution.nftMetadata[self.nftMetadataId]!.collection,
826                        collectionId: CAT_EnterTheEvolution.collectionId!,
827                        metadata: self.getMetadata(),
828                        media: self.getMedias(),
829                        images: self.getImages(),
830                        videos: self.getVideos(),
831                        externalURL: CAT_EnterTheEvolution.nftMetadata[self.id]!.externalURL,
832                        rarity: self.getRarity(),
833                        traits: self.getTraits(),
834                        characteristics: self.getCharacteristics(),
835                        coCreatable: CAT_EnterTheEvolution.nftMetadata[self.id]!.coCreatable,
836                        coCreators: CAT_EnterTheEvolution.nftMetadata[self.id]!.coCreator,
837                        isRevealed: CAT_EnterTheEvolution.nftMetadata[self.id]!.isRevealed,
838                        editions: self.getEditions(),
839                        originalRecipient: self.originalRecipient,
840                        royalties: CAT_EnterTheEvolution.nftMetadata[self.id]!.royalties,
841                        royaltiesTFMarketplace: CAT_EnterTheEvolution.nftMetadata[self.id]!.royaltiesTFMarketplace,
842                        revealableTraits: self.getRevealableTraits(),
843                        address: self.owner!.address
844                    )
845                case Type<MetadataViews.NFTView>():
846                    return self.getNFTView()
847                case Type<MetadataViews.Display>():
848                    return self.getDisplay()
849                case Type<MetadataViews.Editions>():
850                    return self.getEditions()
851                case Type<MetadataViews.Serial>():
852                    return self.id
853                case Type<MetadataViews.Royalties>():
854                    return CAT_EnterTheEvolution.nftMetadata[self.id]?.royalties
855                case Type<MetadataViews.Medias>():
856                    return self.getMedias()
857                case Type<MetadataViews.License>():
858                    return self.license
859                case Type<MetadataViews.ExternalURL>():
860                    return CAT_EnterTheEvolution.nftMetadata[self.id]?.externalURL
861                case Type<MetadataViews.NFTCollectionData>():
862                    return self.getCollectionData()
863                case Type<MetadataViews.NFTCollectionDisplay>():
864                    return self.getCollectionDisplay()
865                case Type<MetadataViews.Rarity>():
866                    return self.getRarity()
867                case Type<MetadataViews.Traits>():
868                    return self.getTraits()
869            }
870            return nil
871        }
872
873        pub fun updateIsTraitRevealable(key: String, value: Bool) {
874            let nftMetadata = CAT_EnterTheEvolution.nftMetadata[self.id]!
875            nftMetadata.updateIsTraitRevealable(key: key, value: value)
876            CAT_EnterTheEvolution.nftMetadata[self.id] = nftMetadata
877            emit IsTraitRevealableUpdated(
878                nftUuid: nftMetadata.nftUuid, 
879                id: nftMetadata.id,
880                trait: key,
881                isRevealable: value
882            )
883        }
884
885        init(
886            originalRecipient: Address,
887            license: MetadataViews.License?
888        ) {
889            assert(
890                CAT_EnterTheEvolution.collectionId != nil, 
891                message: "Ensure that Admin has set collectionId in the contract"
892            )
893            
894            CAT_EnterTheEvolution.totalSupply = CAT_EnterTheEvolution.totalSupply + 1
895            self.id = CAT_EnterTheEvolution.totalSupply
896            self.collectionId = CAT_EnterTheEvolution.collectionId!
897
898            // NOTE: Customise
899            // The edition number may need to be different to id
900            // for some campaigns
901            self.editionNumber = self.id
902            self.maxEditionNumber = CAT_EnterTheEvolution.maxSupply
903
904            self.originalRecipient = originalRecipient
905
906            self.license = license
907
908            self.nftMetadataId = self.id
909
910        }
911        destroy() {
912            emit ItemDestroyed(
913                uuid: self.uuid,
914                id: self.id,
915                name: CAT_EnterTheEvolution.nftMetadata[self.nftMetadataId]!.name,
916                description: CAT_EnterTheEvolution.nftMetadata[self.nftMetadataId]!.description,
917                collection: CAT_EnterTheEvolution.nftMetadata[self.nftMetadataId]!.collection
918            )
919        }
920    }
921
922    // -----------------------------------------------------------------------
923    // Collection Resource
924    // -----------------------------------------------------------------------
925
926    pub resource interface CAT_EnterTheEvolutionCollectionPublic {
927        pub fun borrowCAT_EnterTheEvolution(id: UInt64): &CAT_EnterTheEvolution.NFT{CAT_EnterTheEvolution.PublicNFT}?
928        pub fun deposit(token: @NonFungibleToken.NFT)
929        pub fun getIDs(): [UInt64]
930        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT        
931    }
932
933    pub resource Collection: NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, CAT_EnterTheEvolutionCollectionPublic, MetadataViews.ResolverCollection {
934
935        // Dictionary to hold the NFTs in the Collection
936        pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
937
938        pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
939            let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
940            let CAT_EnterTheEvolution = nft as! &CAT_EnterTheEvolution.NFT
941            return CAT_EnterTheEvolution as &AnyResource{MetadataViews.Resolver}
942        }
943
944        // withdraw removes an NFT from the collection and moves it to the caller
945        pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
946            // Remove the nft from the Collection
947            let token <- self.ownedNFTs.remove(key: withdrawID) 
948                ?? panic("Cannot withdraw: NFT does not exist in the collection")
949
950            emit Withdraw(id: token.id, from: self.owner?.address)
951            
952            // Return the withdrawn token
953            return <-token
954        }
955
956        // deposit takes an NFT and adds it to the collections dictionary
957        // and adds the ID to the id array
958        pub fun deposit(token: @NonFungibleToken.NFT) {
959            // By ensuring self.owner.address is not nil we keep the nftIdsToOwner dict 
960            // up to date.
961            pre {
962                self.owner?.address != nil:
963                    "The Collection resource must be stored in a users account"
964            }
965
966            // Cast the deposited token as  NFT to make sure
967            // it is the correct type
968            let token <- token as! @NFT
969            
970            // Get the token's ID
971            let id = token.id
972
973            // Add the new token to the dictionary
974            let oldToken <- self.ownedNFTs[id] <- token
975            
976            CAT_EnterTheEvolution.nftIdsToOwner[id] = self.owner!.address
977            emit Deposit(id: id, to: self.owner?.address)
978
979            // Destroy the empty old token that was "removed"
980            destroy oldToken
981        }
982
983        // getIDs returns an array of the IDs that are in the collection
984        pub fun getIDs(): [UInt64] {
985            return self.ownedNFTs.keys
986        }
987
988        // Returns a borrowed reference to an NFT in the collection
989        // so that the caller can read data and call methods from it
990        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
991            return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
992        }
993
994        pub fun borrowCAT_EnterTheEvolution(id: UInt64): &CAT_EnterTheEvolution.NFT? {
995            if self.ownedNFTs[id] != nil {
996                // Create an authorized reference to allow downcasting
997                let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
998                return ref as! &CAT_EnterTheEvolution.NFT
999            }
1000
1001            return nil
1002        }
1003
1004        // If a transaction destroys the Collection object,
1005        // All the NFTs contained within are also destroyed!
1006        //
1007        destroy() {
1008            destroy self.ownedNFTs
1009        }
1010
1011        init(
1012        ) {
1013            self.ownedNFTs <- {}
1014        }
1015    }
1016
1017    // -----------------------------------------------------------------------
1018    // Admin Resource
1019    // -----------------------------------------------------------------------
1020
1021    pub resource Admin {
1022
1023        pub fun setBaseURI(baseURI: String) {
1024            CAT_EnterTheEvolution.baseTokenURI = baseURI
1025
1026            emit AdminSetBaseURI (
1027                baseURI: baseURI
1028            )
1029        }
1030
1031        // The max supply determines the maximum number of NFTs that can be minted from this contract
1032        pub fun setMaxSupply(maxSupply: UInt64) {
1033            CAT_EnterTheEvolution.maxSupply = maxSupply
1034
1035            emit AdminSetMaxSupply(
1036                maxSupply: maxSupply 
1037            )
1038        }
1039
1040        pub fun setAddressMintLimit(addressMintLimit: UInt64) {
1041            CAT_EnterTheEvolution.addressMintLimit = addressMintLimit
1042
1043            emit AdminSetAddressMintLimit(
1044                addressMintLimit: addressMintLimit 
1045            )
1046        }
1047
1048        pub fun setCollectionId(collectionId: String) {
1049            CAT_EnterTheEvolution.collectionId = collectionId
1050
1051            emit AdminSetCollectionId(
1052                collectionId: collectionId 
1053            )
1054        }
1055
1056        // NOTE: It is in the public minter that you would create the restrictions
1057        // for minting. 
1058        pub fun createPublicMinter(
1059            name: String,
1060            description: String,
1061            collection: String,
1062            license: MetadataViews.License?,
1063            externalURL: MetadataViews.ExternalURL,
1064            coCreatable: Bool,
1065            revealableTraits: {String: Bool},
1066            minterMintLimit: UInt64?,
1067            royalties: MetadataViews.Royalties,
1068            royaltiesTFMarketplace: TheFabricantMetadataViewsV2.Royalties,
1069            paymentAmount: UFix64,
1070            paymentType: Type,
1071            paymentSplit: MetadataViews.Royalties?,
1072            typeRestrictions: [Type],
1073            accessListId: UInt64,
1074        ) {
1075
1076            pre {
1077                CAT_EnterTheEvolution.baseTokenURI != nil: "Please set a baseURI before creating a minter"
1078                CAT_EnterTheEvolution.materials.length != 0: "Please add materials to the contract"
1079                CAT_EnterTheEvolution.shoeShapes.length != 0: "Please add shoeShapes to the contract"
1080                CAT_EnterTheEvolution.textureSets.length != 0: "Please add textureSets to the contract"
1081                CAT_EnterTheEvolution.solidColors.length != 0: "Please add solidColors to the contract"
1082            }
1083
1084            let publicMinter: @CAT_EnterTheEvolution.PublicMinter <- create PublicMinter(
1085                name: name,
1086                description: description,
1087                collection: collection,
1088                license: license,
1089                externalURL: externalURL,
1090                coCreatable: coCreatable,
1091                revealableTraits: revealableTraits,
1092                minterMintLimit: minterMintLimit,
1093                royalties: royalties,
1094                royaltiesTFMarketplace: royaltiesTFMarketplace,
1095                paymentAmount: paymentAmount,
1096                paymentType: paymentType,
1097                paymentSplit: paymentSplit,
1098                typeRestrictions: typeRestrictions,
1099                accessListId: accessListId,
1100            )
1101
1102            // Save path: name_collection_uuid
1103            // Link the Public Minter to a Public Path of the admin account
1104            let publicMinterStoragePath = StoragePath(identifier: publicMinter.path)
1105            let publicMinterPublicPath = PublicPath(identifier: publicMinter.path)
1106            CAT_EnterTheEvolution.account.save(<- publicMinter, to: publicMinterStoragePath!)
1107            CAT_EnterTheEvolution.account.link<&PublicMinter{Minter}>(publicMinterPublicPath!, target: publicMinterStoragePath!)
1108        }
1109
1110        pub fun revealTraits(nftMetadataId: UInt64, traits: [{RevealableV2.RevealableTrait}]) {
1111            let nftMetadata = CAT_EnterTheEvolution.nftMetadata[nftMetadataId]! as!  CAT_EnterTheEvolution.RevealableMetadata
1112            
1113            nftMetadata.revealTraits(traits: traits)
1114            CAT_EnterTheEvolution.nftMetadata[nftMetadataId] = nftMetadata
1115
1116            // Event should be emitted in resource, not struct
1117            var i = 1
1118            while i < traits.length {
1119                let traitName = traits[i].name
1120                let traitValue = traits[i].value
1121                emit TraitRevealed(
1122                    nftUuid: nftMetadata.nftUuid, 
1123                    id: nftMetadata.id,
1124                    trait: traitName,
1125                )
1126                i = i + 1
1127            }
1128            emit ItemRevealed(
1129                uuid: nftMetadata.nftUuid,
1130                id: nftMetadata.id,
1131                name: nftMetadata.name,
1132                description: nftMetadata.description,
1133                collection: nftMetadata.collection,
1134                editionNumber: nftMetadata.editionNumber,
1135                originalRecipient: nftMetadata.coCreator,   
1136                license: nftMetadata.license,
1137                nftMetadataId: nftMetadata.id,
1138                externalURL: nftMetadata.externalURL,
1139                coCreatable: nftMetadata.coCreatable,
1140                coCreator: nftMetadata.coCreator,                 
1141            )
1142        }
1143
1144        // NOTE: Customise
1145        pub fun updateShoeShapes(shapes: {UInt64: {CoCreatableV2.Characteristic}}) {
1146            var i: UInt64 = 0
1147            let keys = shapes.keys
1148            let values = shapes.values
1149            while i < UInt64(shapes.length) {
1150                CAT_EnterTheEvolution.shoeShapes[keys[i]] = values[i]
1151                i = i + 1
1152            }
1153        }
1154
1155        // NOTE: Customise
1156        pub fun emptyShoeShapes() {
1157            CAT_EnterTheEvolution.shoeShapes = {}
1158        }
1159
1160        // NOTE: Customise
1161        pub fun updateMaterials(materials: {UInt64: {CoCreatableV2.Characteristic}}) {
1162            var i: UInt64 = 0
1163            let keys = materials.keys
1164            let values = materials.values
1165            while i < UInt64(materials.length) {
1166                CAT_EnterTheEvolution.materials[keys[i]] = values[i]
1167                i = i + 1
1168            }
1169        }
1170
1171        // NOTE: Customise
1172        pub fun emptyMaterials() {
1173            CAT_EnterTheEvolution.materials = {}
1174        }
1175
1176        // NOTE: Customise
1177        pub fun updateSolidColors(solidColors: {UInt64: {CoCreatableV2.Characteristic}}) {
1178            var i: UInt64 = 0
1179            let keys = solidColors.keys
1180            let values = solidColors.values
1181            while i < UInt64(solidColors.length) {
1182                CAT_EnterTheEvolution.solidColors[keys[i]] = values[i]
1183                i = i + 1
1184            }
1185        }
1186
1187        // NOTE: Customise
1188        pub fun emptySolidColors() {
1189            CAT_EnterTheEvolution.solidColors = {}
1190        }
1191
1192        // NOTE: Customise
1193        pub fun updateTextureSets(textureSets: {UInt64: {CoCreatableV2.Characteristic}}) {
1194            var i: UInt64 = 0
1195            let keys = textureSets.keys
1196            let values = textureSets.values
1197            while i < UInt64(textureSets.length) {
1198                CAT_EnterTheEvolution.textureSets[keys[i]] = values[i]
1199                i = i + 1
1200            }
1201        }
1202
1203        // NOTE: Customise
1204        pub fun emptyTextureSets() {
1205            CAT_EnterTheEvolution.textureSets = {}
1206        }
1207
1208        init(adminAddress: Address) {
1209
1210            emit AdminResourceCreated(
1211                uuid: self.uuid,
1212                adminAddress: adminAddress
1213            )
1214        }
1215    }
1216
1217    // -----------------------------------------------------------------------
1218    // PublicMinter Resource
1219    // -----------------------------------------------------------------------
1220
1221    // NOTE: The public minter is exposed via a capability to allow the public
1222    // to mint the NFT so long as they meet the criteria.
1223    // It is in the public minter that the various mint functions would be exposed
1224    // such as paid mint etc.
1225    // Every contract has to manage its own minting via the PublicMinter.
1226
1227    //NOTE: Customise
1228    // Update the mint functions
1229    pub resource interface Minter {
1230        pub fun mintUsingAccessList(
1231            receiver: &{NonFungibleToken.CollectionPublic},
1232            shoeShapeId: UInt64, 
1233            materialId: UInt64, 
1234            solidColorId: UInt64, 
1235            textureSetId: UInt64
1236        )
1237        pub fun getPublicMinterDetails(): {String: AnyStruct}
1238    }
1239
1240    pub resource PublicMinter: TheFabricantNFTStandardV2.TFNFTPublicMinter, Minter {
1241
1242        pub var path: String
1243
1244        pub var isOpen: Bool
1245        pub var isAccessListOnly: Bool
1246        pub var isOpenAccess: Bool
1247
1248        // NOTE: Remove these as required and update the NFT props and 
1249        // resolveView to reflect this, so that views that this nft
1250        // does not display are not provided
1251
1252        // Name of nft, not campaign. This will be combined with the edition number
1253        pub let name: String
1254        pub let description: String
1255        pub let collection: String
1256        pub let license: MetadataViews.License?
1257        pub let externalURL: MetadataViews.ExternalURL
1258        pub let coCreatable: Bool
1259        pub let revealableTraits: {String: Bool}
1260        // NOTE: The max number of mints this pM can do (eg multiple NFTs, a different minter for each one. Each NFT has a max number of mints allowed).
1261        pub var minterMintLimit: UInt64?
1262        pub var numberOfMints: UInt64
1263        pub let royalties: MetadataViews.Royalties
1264        pub let royaltiesTFMarketplace: TheFabricantMetadataViewsV2.Royalties
1265        pub var paymentAmount: UFix64
1266        pub let paymentType: Type
1267        // paymentSplit: How much each address gets paid on minting of NFT
1268        pub let paymentSplit: MetadataViews.Royalties?
1269        pub var typeRestrictions: [Type]?   
1270        pub var accessListId: UInt64     
1271
1272        pub fun changeIsOpenAccess(isOpenAccess: Bool) {
1273            self.isOpenAccess = isOpenAccess
1274
1275            emit PublicMinterIsOpenAccessChanged(
1276                uuid: self.uuid,
1277                name: self.name,
1278                description: self.description,
1279                collection: self.collection,
1280                path: self.path,
1281                isOpenAccess: self.isOpenAccess,
1282                isAccessListOnly: self.isAccessListOnly,
1283                isOpen: self.isOpen
1284            )
1285        }
1286
1287        pub fun changeIsAccessListOnly(isAccessListOnly: Bool) {
1288            self.isAccessListOnly = isAccessListOnly
1289
1290            emit PublicMinterIsAccessListOnly (
1291                uuid: self.uuid,
1292                name: self.name,
1293                description: self.description,
1294                collection: self.collection,
1295                path: self.path,
1296                isOpenAccess: self.isOpenAccess,
1297                isAccessListOnly: self.isAccessListOnly,
1298                isOpen: self.isOpen
1299             )
1300        }
1301
1302        pub fun changeMintingIsOpen(isOpen: Bool) {
1303            self.isOpen = isOpen
1304
1305            emit PublicMinterMintingIsOpen(
1306                uuid: self.uuid,
1307                name: self.name,
1308                description: self.description,
1309                collection: self.collection,
1310                path: self.path,
1311                isOpenAccess: self.isOpenAccess,
1312                isAccessListOnly: self.isAccessListOnly,
1313                isOpen: self.isOpen
1314            )
1315        }
1316
1317        pub fun setAccessListId(accessListId: UInt64) {
1318            self.accessListId = accessListId
1319
1320            emit PublicMinterSetAccessListId(
1321                uuid: self.uuid,
1322                name: self.name,
1323                description: self.description,
1324                collection: self.collection,
1325                path: self.path,
1326                isOpenAccess: self.isOpenAccess,
1327                isAccessListOnly: self.isAccessListOnly,
1328                isOpen: self.isOpen,
1329                accessListId: self.accessListId
1330            )
1331        }
1332
1333        pub fun setPaymentAmount(amount: UFix64) {
1334            self.paymentAmount = amount
1335
1336            emit PublicMinterSetPaymentAmount(
1337                uuid: self.uuid,
1338                name: self.name,
1339                description: self.description,
1340                collection: self.collection,
1341                path: self.path,
1342                isOpenAccess: self.isOpenAccess,
1343                isAccessListOnly: self.isAccessListOnly,
1344                isOpen: self.isOpen,
1345                paymentAmount: self.paymentAmount
1346            )
1347        }
1348
1349        pub fun setMinterMintLimit(minterMintLimit: UInt64) {
1350            self.minterMintLimit = minterMintLimit
1351
1352            emit PublicMinterSetMinterMintLimit(
1353                uuid: self.uuid,
1354                name: self.name,
1355                description: self.description,
1356                collection: self.collection,
1357                path: self.path,
1358                isOpenAccess: self.isOpenAccess,
1359                isAccessListOnly: self.isAccessListOnly,
1360                isOpen: self.isOpen,
1361                minterMintLimit: self.minterMintLimit
1362            )
1363        }
1364
1365        // The owner of the pM can access this via borrow in tx.
1366        pub fun updateTypeRestrictions(types: [Type]) {
1367            self.typeRestrictions = types
1368        }
1369
1370        //NOTE: Customise
1371        // mint not:
1372            // maxMint for this address has been hit
1373            // maxSupply has been hit √
1374            // minting isn't open (!isOpen) √
1375            // payment is insufficient √
1376            // maxSupply is hit √
1377            // minterMintLimit is hit √
1378        // mint if:
1379            // openAccess √
1380            // OR address on access list √
1381        // Output:
1382            // NFT √
1383            // nftMetadata √
1384            // Characteristics √
1385            // adds to dataAllocations dicts √
1386            // update mints per address √
1387        pub fun mintUsingAccessList(
1388            receiver: &{NonFungibleToken.CollectionPublic},
1389            shoeShapeId: UInt64, 
1390            materialId: UInt64, 
1391            solidColorId: UInt64, 
1392            textureSetId: UInt64
1393        ) {
1394            pre {
1395                self.isOpen : "Minting is not currently open!"
1396                !CAT_EnterTheEvolution.dataAllocations.containsKey(CAT_EnterTheEvolution.constructDataAllocationString(
1397                    shoeShapeId: shoeShapeId, 
1398                    materialId: materialId, 
1399                    solidColorId: solidColorId, 
1400                    textureSetId: textureSetId
1401                )): "Combination of characteristics already exists, please choose again" 
1402            }
1403            post {
1404                receiver.getIDs().length == before(receiver.getIDs().length) + 1
1405                    : "Minted NFT must be deposited into Collection"
1406            }
1407            // Total number of mints by this pM
1408            self.numberOfMints = self.numberOfMints + 1
1409
1410            // Ensure that minterMintLimit for this pM has not been hit
1411            if self.minterMintLimit != nil {
1412                assert(self.numberOfMints <= self.minterMintLimit!, message: "Maximum number of mints for this public minter has been hit")
1413            }
1414
1415            // Ensure that the maximum supply of nfts for this contract has not been hit
1416            if CAT_EnterTheEvolution.maxSupply != nil {
1417                assert((CAT_EnterTheEvolution.totalSupply + 1) <= CAT_EnterTheEvolution.maxSupply!, message: "Max supply for NFTs has been hit")
1418            }
1419
1420            // Ensure user hasn't minted more NFTs from this contract than allowed
1421            if CAT_EnterTheEvolution.addressMintLimit != nil {
1422                if CAT_EnterTheEvolution.addressMintCount[receiver.owner!.address] != nil {
1423                    assert(CAT_EnterTheEvolution.addressMintCount[receiver.owner!.address]! < CAT_EnterTheEvolution.addressMintLimit!,
1424                        message: "User has already minted the maximum allowance per address!")
1425                }
1426            }
1427
1428            // Ensure maxSupply hasn't been hit
1429            if CAT_EnterTheEvolution.maxSupply != nil {
1430                assert(
1431                    CAT_EnterTheEvolution.totalSupply <= CAT_EnterTheEvolution.maxSupply!, 
1432                    message: "Cannot mint any more NFTs, max supply has been reached!"
1433                )   
1434            }
1435            
1436           if !self.isOpenAccess {
1437                assert(TheFabricantAccessList.checkAccessForAddress(accessListDetailsId: self.accessListId, address: receiver.owner!.address), message: "User address is not on the access list and so cannot mint.")
1438            }
1439
1440            let nft <- create NFT(
1441                originalRecipient: receiver.owner!.address,
1442                license: self.license,
1443            )
1444
1445            //shoeShape.name is to be used for the NFT name
1446            let shoeShape = CAT_EnterTheEvolution.shoeShapes[shoeShapeId]
1447            let shoeShapeValue = shoeShape!.value as! {String: String}
1448            let shoeShapeName = (shoeShapeValue["name"] as String?)! 
1449            let shoeShapeDescription = shoeShape!.characteristicDescription
1450
1451            // Add CoCreator to external royalties
1452            let coCreatorReceiver = 
1453            getAccount(receiver.owner!.address).getCapability<&FlowToken.Vault{FungibleToken.Receiver}>
1454                (/public/flowTokenReceiver)
1455            let coCreatorExternalRoyalty = MetadataViews.Royalty(
1456                receiver: coCreatorReceiver,
1457                cut: 0.01,
1458                description: "CoCreator Royalty"
1459            )
1460
1461            let externalPublicMinterRoyalties = self.royalties.getRoyalties()
1462            externalPublicMinterRoyalties.append(coCreatorExternalRoyalty) 
1463            let externalRoyalties = MetadataViews.Royalties(cutInfos: externalPublicMinterRoyalties)
1464
1465            // Add CoCreator to internal royalties
1466            // Initial cut for CoCreator is 90%. They should also be the seller in the primary sale, unless they transferred the NFT.
1467            let coCreatorInternalRoyalty = TheFabricantMetadataViewsV2.Royalty(
1468                receiver: coCreatorReceiver,
1469                initialCut: 0.9,
1470                cut: 0.03,
1471                description: "CoCreator Royalty"
1472            )
1473            let internalPublicMinterRoyalties = self.royaltiesTFMarketplace.getRoyalties()
1474            internalPublicMinterRoyalties.append(coCreatorInternalRoyalty)
1475            let internalRoyalties = TheFabricantMetadataViewsV2.Royalties(cutInfos: internalPublicMinterRoyalties)
1476
1477            // Create Characteristics struct
1478            let characteristics = CAT_EnterTheEvolution.createCharacteristics(shoeShapeId: shoeShapeId, materialId: materialId, solidColorId: solidColorId, textureSetId: textureSetId)
1479
1480            //Update data allocation. This prevents people from minting the same combination
1481            CAT_EnterTheEvolution.updateDataAllocations(
1482                nftId: nft.id,
1483                shoeShapeId: shoeShapeId, 
1484                materialId: materialId, 
1485                solidColorId: solidColorId, 
1486                textureSetId: textureSetId
1487            )
1488
1489            CAT_EnterTheEvolution.createNftMetadata(
1490                id: nft.id, 
1491                nftUuid: nft.uuid, 
1492                name: shoeShapeName, 
1493                description: shoeShapeDescription, 
1494                collection: self.collection, 
1495                characteristics: characteristics, 
1496                license: nft.license, 
1497                externalURL: self.externalURL, 
1498                coCreatable: self.coCreatable, 
1499                coCreator: receiver.owner!.address, 
1500                editionNumber: nft.editionNumber, 
1501                maxEditionNumber: nft.maxEditionNumber, 
1502                revealableTraits: self.revealableTraits, 
1503                royalties: externalRoyalties, 
1504                royaltiesTFMarketplace: internalRoyalties
1505            )
1506
1507            //NOTE: Event is emitted here and not in nft init because
1508            // data is split between RevealableMetadata and nft,
1509            // so not all event data is accessible during nft init
1510            emit ItemMintedAndTransferred(
1511                uuid: nft.uuid,
1512                id: nft.id,
1513                name: CAT_EnterTheEvolution.nftMetadata[nft.nftMetadataId]!.name,
1514                description: CAT_EnterTheEvolution.nftMetadata[nft.nftMetadataId]!.description,
1515                collection: CAT_EnterTheEvolution.nftMetadata[nft.nftMetadataId]!.collection,
1516                editionNumber: nft.editionNumber,
1517                originalRecipient: nft.originalRecipient,   
1518                license: self.license,
1519                nftMetadataId: nft.nftMetadataId,
1520            )
1521
1522            emit DataAllocationCreated(
1523                uuid: nft.uuid,
1524                id: nft.id,
1525                editionNumber: nft.editionNumber,
1526                originalRecipient: nft.originalRecipient,
1527                nftMetadataId: nft.nftMetadataId,
1528                dataAllocationString: CAT_EnterTheEvolution.idsToDataAllocations[nft.nftMetadataId]!
1529            )
1530
1531            receiver.deposit(token: <- nft)
1532
1533            // Increment the number of mints that an address has
1534            if CAT_EnterTheEvolution.addressMintCount[receiver.owner!.address] != nil {
1535                CAT_EnterTheEvolution.addressMintCount[receiver.owner!.address] = CAT_EnterTheEvolution.addressMintCount[receiver.owner!.address]! + 1
1536            } else {
1537                CAT_EnterTheEvolution.addressMintCount[receiver.owner!.address] = 1
1538            }
1539        }
1540
1541        pub fun getPublicMinterDetails(): {String: AnyStruct} {
1542            let ret: {String: AnyStruct} = {}
1543
1544            ret["name"] = self.name
1545            ret["uuid"] = self.uuid
1546            ret["path"] = self.path
1547            ret["isOpen"] = self.isOpen
1548            ret["isAccessListOnly"] = self.isAccessListOnly
1549            ret["isOpenAccess"] = self.isOpenAccess
1550            ret["description"] = self.description
1551            ret["collection"] = self.collection
1552            ret["collectionId"] = CAT_EnterTheEvolution.collectionId
1553            ret["license"] = self.license
1554            ret["externalURL"] = self.externalURL
1555            ret["coCreatable"] = self.coCreatable
1556            ret["revealableTraits"] = self.revealableTraits
1557            ret["minterMintLimit"] = self.minterMintLimit
1558            ret["numberOfMints"] = self.numberOfMints
1559            ret["royalties"] = self.royalties
1560            ret["royaltiesTFMarketplace"] = self.royaltiesTFMarketplace
1561            ret["paymentAmount"] = self.paymentAmount
1562            ret["paymentType"] = self.paymentType
1563            ret["paymentSplit"] = self.paymentSplit
1564            ret["typeRestrictions"] = self.typeRestrictions
1565            ret["accessListId"] = self.accessListId
1566
1567            return ret
1568        }
1569
1570        init(
1571            name: String,
1572            description: String,
1573            collection: String,
1574            license: MetadataViews.License?,
1575            externalURL: MetadataViews.ExternalURL,
1576            coCreatable: Bool,
1577            revealableTraits: {String: Bool},
1578            minterMintLimit: UInt64?,
1579            royalties: MetadataViews.Royalties,
1580            royaltiesTFMarketplace: TheFabricantMetadataViewsV2.Royalties,
1581            paymentAmount: UFix64,
1582            paymentType: Type,
1583            paymentSplit: MetadataViews.Royalties?,
1584            typeRestrictions: [Type],
1585            accessListId: UInt64,
1586
1587        ) {
1588
1589            // Create and save path: name_collection_uuid
1590            let pathString = "TheFabricantNFTPublicMinter_CAT_EnterTheEvolution_".concat(self.uuid.toString())
1591            CAT_EnterTheEvolution.publicMinterPaths[self.uuid] = pathString
1592
1593            self.path = pathString
1594
1595            self.isOpen = false
1596            self.isAccessListOnly = true
1597            self.isOpenAccess = false
1598
1599            self.name = name
1600            self.description = description
1601            self.collection = collection
1602            self.license = license
1603            self.externalURL = externalURL
1604            self.coCreatable = coCreatable
1605            self.revealableTraits = revealableTraits
1606            self.minterMintLimit = minterMintLimit
1607            self.numberOfMints = 0
1608            self.royalties = royalties
1609            self.royaltiesTFMarketplace = royaltiesTFMarketplace
1610            self.paymentAmount = paymentAmount
1611            self.paymentType = paymentType
1612            self.paymentSplit = paymentSplit
1613            self.typeRestrictions = typeRestrictions
1614            self.accessListId = accessListId
1615
1616            emit PublicMinterCreated(
1617                uuid: self.uuid,
1618                name: name,
1619                description: description,
1620                collection: collection,
1621                path: self.path
1622            )
1623        }
1624}
1625
1626    // -----------------------------------------------------------------------
1627    // Private Utility Functions
1628    // -----------------------------------------------------------------------
1629
1630    //NOTE: Customise
1631    // This function generates the metadata for the minted nft.
1632    access(contract) fun createNftMetadata(
1633        id: UInt64,
1634        nftUuid: UInt64,    
1635        name: String,
1636        description: String,
1637        collection: String,
1638        characteristics: {String: {CoCreatableV2.Characteristic}},
1639        license: MetadataViews.License?,
1640        externalURL: MetadataViews.ExternalURL,
1641        coCreatable: Bool,
1642        coCreator: Address,
1643        editionNumber: UInt64,
1644        maxEditionNumber: UInt64?,
1645        revealableTraits: {String: Bool},
1646        royalties: MetadataViews.Royalties,
1647        royaltiesTFMarketplace: TheFabricantMetadataViewsV2.Royalties
1648    ) {
1649        pre {
1650            CAT_EnterTheEvolution.baseTokenURI != nil: "Ensure baseUri is set before minting!"
1651        }
1652
1653        //Instant reveal
1654        let dataAllocation = CAT_EnterTheEvolution.idsToDataAllocations[id]!
1655        let metadata = {
1656            "mainImage": CAT_EnterTheEvolution.baseTokenURI!
1657                            .concat("/")
1658                            .concat(dataAllocation)
1659                            .concat(".png"),
1660            "video": CAT_EnterTheEvolution.baseTokenURI!
1661                            .concat("/")
1662                            .concat(dataAllocation)
1663                            .concat(".mp4")
1664        }
1665
1666        let mD = RevealableMetadata(
1667            id: id,
1668            nftUuid: nftUuid,
1669            name: name,
1670            description: description,
1671            collection: collection,
1672            metadata: metadata,
1673            characteristics: characteristics,
1674            license: license,
1675            externalURL: externalURL,
1676            coCreatable: coCreatable,
1677            coCreator: coCreator,
1678            editionNumber: editionNumber,
1679            maxEditionNumber: maxEditionNumber,
1680            revealableTraits: revealableTraits,
1681            royalties: royalties,
1682            royaltiesTFMarketplace: royaltiesTFMarketplace,
1683        )
1684        CAT_EnterTheEvolution.nftMetadata[id] = mD
1685    }
1686
1687    //NOTE: Customise
1688        // This function generates the characteristics data for the minted nft.
1689        access(contract) fun createCharacteristics(
1690            shoeShapeId: UInt64, 
1691            materialId: UInt64, 
1692            solidColorId: UInt64, 
1693            textureSetId: UInt64
1694            ) : {String: {CoCreatableV2.Characteristic}} {
1695            let dict: {String: {CoCreatableV2.Characteristic}} = {}
1696
1697            let shoeShape = CAT_EnterTheEvolution.shoeShapes[shoeShapeId] ?? panic("A shoe shape with this id does not exist")
1698            dict["shoeShape"] = shoeShape
1699
1700            let material = CAT_EnterTheEvolution.materials[materialId] ?? panic("A material with this id does not exist")
1701            dict["material"] = material
1702
1703            let solidColor = CAT_EnterTheEvolution.solidColors[solidColorId]?? panic("A solid color with this id does not exist")
1704            dict["solidColor"] = solidColor
1705
1706            let textureSet = CAT_EnterTheEvolution.textureSets[textureSetId]?? panic("A texture set with this id does not exist")
1707            dict["textureSet"] = textureSet
1708            return dict
1709        }
1710
1711    access(self) fun nftsCanBeUsedForMint(
1712        receiver: &{NonFungibleToken.CollectionPublic},
1713        refs: [&AnyResource{NonFungibleToken.INFT}],
1714        typeRestrictions: [Type]
1715        ): Bool {
1716        assert(typeRestrictions.length != 0, message: "There are no typerestrictions for this promotion")
1717        var i = 0 
1718        while i < refs.length {
1719            if typeRestrictions.contains(refs[i].getType()) 
1720                && receiver.owner!.address == refs[i].owner!.address
1721                {
1722                return true
1723            }
1724            i = i + 1
1725        }
1726        return false
1727    }
1728
1729    // Used to create the string Ids for checking if combo is taken
1730    // shoeShape_material_solidColor_textureSet
1731    access(contract) fun constructDataAllocationString(
1732        shoeShapeId: UInt64, 
1733        materialId: UInt64, 
1734        solidColorId: UInt64,
1735        textureSetId: UInt64
1736        ): String {
1737        return "shoe".concat(shoeShapeId.toString())
1738            .concat("_mat")
1739            .concat(materialId.toString())
1740            .concat("_tex")
1741            .concat(textureSetId.toString())
1742            .concat("_col")
1743            .concat(solidColorId.toString())
1744            
1745    }
1746
1747    access(contract) fun updateDataAllocations(
1748        nftId: UInt64, 
1749        shoeShapeId: UInt64, 
1750        materialId: UInt64, 
1751        solidColorId: UInt64,
1752        textureSetId: UInt64
1753        ) {
1754        let allocationString = self.constructDataAllocationString(
1755            shoeShapeId: shoeShapeId,
1756            materialId: materialId,
1757            solidColorId: solidColorId,
1758            textureSetId: textureSetId,
1759        )
1760        self.dataAllocations[allocationString] = nftId
1761        self.idsToDataAllocations[nftId] = allocationString
1762    }
1763
1764    // -----------------------------------------------------------------------
1765    // Public Utility Functions
1766    // -----------------------------------------------------------------------
1767
1768    // createEmptyCollection creates an empty Collection
1769    // and returns it to the caller so that they can own NFTs
1770    pub fun createEmptyCollection(): @NonFungibleToken.Collection {
1771        return <-create Collection()
1772    }
1773
1774    pub fun getPublicMinterPaths(): {UInt64: String} {
1775        return CAT_EnterTheEvolution.publicMinterPaths
1776    }
1777
1778    pub fun getNftIdsToOwner(): {UInt64: Address} {
1779        return CAT_EnterTheEvolution.nftIdsToOwner
1780    }
1781
1782    pub fun getMaxSupply(): UInt64? {
1783        return CAT_EnterTheEvolution.maxSupply
1784    }
1785
1786    pub fun getTotalSupply(): UInt64 {
1787        return CAT_EnterTheEvolution.totalSupply
1788    }
1789
1790    pub fun getCollectionId(): String? {
1791        return CAT_EnterTheEvolution.collectionId
1792    }
1793
1794    pub fun getNftMetadatas(): {UInt64: AnyStruct{RevealableV2.RevealableMetadata}} {
1795        return self.nftMetadata
1796    }
1797
1798    pub fun getDataAllocations(): {String: UInt64} {
1799        return self.dataAllocations
1800    }
1801
1802    pub fun getAllIdsToDataAllocations(): {UInt64: String} {
1803        return self.idsToDataAllocations
1804    }
1805    pub fun getIdToDataAllocation(id: UInt64): String? {
1806        return self.idsToDataAllocations[id]
1807    }
1808
1809    pub fun getAllCharacteristics(): AnyStruct {
1810        let res = {
1811            "shoeShapes": CAT_EnterTheEvolution.shoeShapes,
1812            "materials": CAT_EnterTheEvolution.materials,
1813            "solidColors": CAT_EnterTheEvolution.solidColors,
1814            "textureSets": CAT_EnterTheEvolution.textureSets    
1815        }
1816        return res
1817    }
1818
1819    pub fun getBaseUri(): String? {
1820        return CAT_EnterTheEvolution.baseTokenURI
1821    }
1822
1823    // -----------------------------------------------------------------------
1824    // Contract Init
1825    // -----------------------------------------------------------------------
1826    
1827    init() {
1828        self.totalSupply = 0
1829        self.maxSupply = nil
1830        self.publicMinterPaths = {}
1831        self.collectionId = nil
1832
1833        self.nftIdsToOwner = {}
1834        self.addressMintCount = {}
1835
1836        self.nftMetadata = {}
1837
1838        self.dataAllocations = {}
1839        self.idsToDataAllocations = {}
1840
1841        self.addressMintLimit = nil
1842
1843        self.shoeShapes = {}
1844        self.materials = {}
1845        self.solidColors = {}
1846        self.textureSets = {}
1847
1848        self.baseTokenURI = nil
1849
1850        self.CAT_EnterTheEvolutionCollectionStoragePath = /storage/CAT_EnterTheEvolutionCollectionStoragePath
1851        self.CAT_EnterTheEvolutionCollectionPublicPath = /public/CAT_EnterTheEvolutionCollectionPublicPath
1852        self.CAT_EnterTheEvolutionProviderPath = /private/CAT_EnterTheEvolutionProviderPath
1853        self.CAT_EnterTheEvolutionAdminStoragePath = /storage/CAT_EnterTheEvolutionAdminStoragePath
1854        self.CAT_EnterTheEvolutionPublicMinterStoragePath = /storage/CAT_EnterTheEvolutionPublicMinterStoragePath
1855        self.CAT_EnterTheEvolutionPublicMinterPublicPath = /public/CAT_EnterTheEvolutionPublicMinterPublicPath
1856
1857        self.account.save(<- create Admin(adminAddress: self.account.address), to: self.CAT_EnterTheEvolutionAdminStoragePath)
1858
1859        emit ContractInitialized()
1860    }
1861}
1862