Smart Contract
CAT_EnterTheEvolution
A.7752ea736384322f.CAT_EnterTheEvolution
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