Smart Contract

Collector

A.7c8995e83c4b1843.Collector

Deployed

5h ago
Feb 28, 2026, 01:01:57 PM UTC

Dependents

0 imports
1// SPDX-License-Identifier: UNLICENSED
2
3import NonFungibleToken from 0x1d7e57aa55817448
4import MetadataViews from 0x1d7e57aa55817448
5
6pub contract Collector: NonFungibleToken {
7  // Emitted when the Collector contract is created
8  pub event ContractInitialized()
9
10  // Emitted when a new Set is created
11  pub event SetCreated(id: UInt64, name: String)
12  // Emitted when a Set is locked, meaning Set data cannot be updated
13  pub event SetLocked(id: UInt64, name: String)
14  // Emitted when a Set is unlocked, meaning Set data can be updated
15  pub event SetUnlocked(id: UInt64, name: String)
16  // Emitted when a Set is updated
17  pub event SetUpdated(id: UInt64, name: String)
18
19  // Emitted when a new Template is created
20  pub event TemplateCreated(id: UInt64, name: String)
21  // Emitted when a Template is locked, meaning Template data cannot be updated
22  pub event TemplateLocked(id: UInt64, name: String)
23  // Emitted when a Template is updated
24  pub event TemplateUpdated(id: UInt64, name: String)
25  // Emitted when a Template is added to a Set
26  pub event TemplateAddedToSet(id: UInt64, name: String, setID: UInt64, setName: String)
27
28  // Emitted when an NFT is minted
29  pub event Minted(id: UInt64, templateID: UInt64, setID: UInt64)
30  // Emitted when an NFT is withdrawn from a Collection
31  pub event Withdraw(id: UInt64, from: Address?)
32  // Emitted when an NFT is deposited into a Collection
33  pub event Deposit(id: UInt64, to: Address?)
34  
35  pub let CollectionStoragePath: StoragePath
36  pub let CollectionPublicPath: PublicPath
37  pub let AdminStoragePath: StoragePath
38  pub let AdminPrivatePath: PrivatePath
39
40  // The total number of Collector NFT that have been minted
41  pub var totalSupply: UInt64
42  pub var nextTemplateID: UInt64
43  pub var nextSetID: UInt64
44
45  // Variable size dictionary of Template structs
46  access(self) var Templates: {UInt64: Template}
47
48  // Variable size dictionary of SetData structs
49  access(self) var SetsData: {UInt64: SetData}
50  
51  // Variable size dictionary of Set resources
52  access(self) var sets: @{UInt64: Set}
53
54  // An Template is a Struct that holds data associated with a specific NFT
55  pub struct Template {
56    pub let id: UInt64
57
58    pub var name: String
59    pub var description: String
60    pub var image: String
61
62    pub var locked: Bool
63    pub var addedToSet: UInt64
64
65    access(self) var metadata: {String: AnyStruct}
66
67    init(id: UInt64, name: String, description: String, image: String, metadata: {String: AnyStruct}){
68      pre {
69        metadata.length != 0: "New template metadata cannot be empty"
70      }
71
72      self.id = id
73      self.name = name
74      self.description = description
75      self.image = image
76      self.metadata = metadata
77      self.locked = false
78      self.addedToSet = 0
79
80      Collector.nextTemplateID = Collector.nextTemplateID + 1
81
82      emit TemplateCreated(id: self.id, name: self.name)
83    }
84
85    pub fun updateName(newName: String) {
86      pre {
87        self.locked == false: "Cannot update name: template is locked"
88      }
89
90      self.name = newName
91      emit TemplateUpdated(id: self.id, name: self.name)
92    }
93
94    pub fun updateDescription(newDescription: String) {
95      pre {
96        self.locked == false: "Cannot update description: template is locked"
97      }
98
99      self.description = newDescription
100      emit TemplateUpdated(id: self.id, name: self.name)
101    }
102
103    pub fun updateImage(newImage: String) {
104      pre {
105        self.locked == false: "Cannot update image: template is locked"
106      }
107
108      self.image = newImage
109      emit TemplateUpdated(id: self.id, name: self.name)
110    }
111
112    pub fun updateMetadata(newMetadata: {String: AnyStruct}) {
113      pre {
114        self.locked == false: "Cannot update metadata: template is locked"
115        newMetadata.length != 0: "New template metadata cannot be empty"
116      }
117
118      self.metadata = newMetadata
119      emit TemplateUpdated(id: self.id, name: self.name)
120    }
121    
122    pub fun markAddedToSet(setID: UInt64) {
123      pre {
124        self.addedToSet == 0: "Template is already to a set"
125      }
126
127      self.addedToSet = setID
128
129      let setName = Collector.SetsData[setID]!.name
130      emit TemplateAddedToSet(id: self.id, name: self.name, setID: setID, setName: setName)
131    }
132
133    pub fun lock() {      
134      pre {
135        self.locked == false: "Template is already locked"
136      }
137
138      self.locked = true
139      emit TemplateLocked(id: self.id, name: self.name)
140    }
141
142    pub fun getMetadata(): {String: AnyStruct} {
143      return self.metadata
144    }
145  }
146
147  // An SetData is a Struct that holds data associated with a specific Set
148  pub struct SetData {
149    pub let id: UInt64
150
151    pub var name: String
152    pub var description: String
153    pub var image: String
154
155    access(self) var metadata: {String: AnyStruct}
156
157    pub var maxSize: UInt64?
158
159    init(id: UInt64, name: String, description: String, image: String, metadata: {String: AnyStruct}, maxSize: UInt64?) {
160      self.id = id
161      self.name = name
162      self.description = description
163      self.image = image
164      self.metadata = metadata
165      self.maxSize = maxSize
166    }
167
168    pub fun getMetadata(): {String: AnyStruct} {
169      return self.metadata
170    }
171  }
172
173  /* DEPRECATED - DO NOT USE */
174  pub struct CollectorMetadataView {
175    pub let id: UInt64
176
177    pub let name: String
178    pub let description: String
179    pub let image: String
180
181    pub let externalUrl: String?
182
183    pub let bgArchitecture: String?
184    pub let bgPanelling: String?
185    pub let bgArchitecturalSupport: String?
186    pub let bgLighting: String?
187    pub let wineStorageContainer: String?
188    pub let wineBottle: String?
189    pub let wineBottleClosure: String?
190    pub let wineBottleGlassware: String?
191
192    pub let setID: UInt64
193    pub let templateID: UInt64
194    pub let serialNumber: UInt64
195    pub let mintingDate: UFix64
196    
197    init(
198      id: UInt64,
199      name: String,
200      description: String,
201      image: String,
202      externalUrl: String?,
203      bgArchitecture: String?,
204      bgPanelling: String?,
205      bgArchitecturalSupport: String?,
206      bgLighting: String?,
207      wineStorageContainer: String?,
208      wineBottle: String?,
209      wineBottleClosure: String?,
210      wineBottleGlassware: String?,
211      setID: UInt64,
212      templateID: UInt64,
213      serialNumber: UInt64,
214      mintingDate: UFix64
215    ) {
216      self.id = id
217      self.name = name
218      self.description = description
219      self.image = image
220      self.externalUrl = externalUrl
221      self.bgArchitecture = bgArchitecture
222      self.bgPanelling = bgPanelling
223      self.bgArchitecturalSupport = bgArchitecturalSupport
224      self.bgLighting = bgLighting
225      self.wineStorageContainer = wineStorageContainer
226      self.wineBottle = wineBottle
227      self.wineBottleClosure = wineBottleClosure
228      self.wineBottleGlassware = wineBottleGlassware
229      self.setID = setID
230      self.templateID = templateID
231      self.serialNumber = serialNumber
232      self.mintingDate = mintingDate
233    }
234  }
235
236  // This is an implementation of a custom metadata view for Cuvée Collective
237  pub struct CollectorMetadataViewV2 {
238    pub let id: UInt64
239
240    pub let name: String
241    pub let description: String
242    pub let image: String
243
244    pub let externalUrl: String?
245
246    pub let architecture: String?
247    pub let panelling: String?
248    pub let visualElements: String?
249    pub let ambience: String?
250    pub let wineStorage: String?
251    pub let wineBox: String?
252    pub let bottle: String?
253
254    pub let setID: UInt64
255    pub let templateID: UInt64
256    pub let serialNumber: UInt64
257    pub let mintingDate: UFix64
258
259    init(
260      id: UInt64,
261      name: String,
262      description: String,
263      image: String,
264      externalUrl: String?,
265      architecture: String?,
266      panelling: String?,
267      visualElements: String?,
268      ambience: String?,
269      wineStorage: String?,
270      wineBox: String?,
271      bottle: String?,
272      setID: UInt64,
273      templateID: UInt64,
274      serialNumber: UInt64,
275      mintingDate: UFix64
276    ) {
277      self.id = id
278      self.name = name
279      self.description = description
280      self.image = image
281      self.externalUrl = externalUrl
282      self.architecture = architecture
283      self.panelling = panelling
284      self.visualElements = visualElements
285      self.ambience = ambience
286      self.wineStorage = wineStorage
287      self.wineBox = wineBox
288      self.bottle = bottle
289      self.setID = setID
290      self.templateID = templateID
291      self.serialNumber = serialNumber
292      self.mintingDate = mintingDate
293    }
294  }
295
296  // A resource that represents the Collector NFT
297  pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
298    pub let id: UInt64
299
300    pub let setID: UInt64
301    pub let templateID: UInt64
302    pub let serialNumber: UInt64
303    pub let mintingDate: UFix64
304
305    init(id: UInt64, templateID: UInt64, serialNumber: UInt64) {
306      pre {
307        Collector.getTemplate(id: templateID) != nil: "Template not found"
308      }
309
310      let setID = Collector.getTemplate(id: templateID)!.addedToSet
311
312      self.id = id
313      self.setID = setID
314      self.templateID = templateID
315      self.serialNumber = serialNumber
316      self.mintingDate = getCurrentBlock().timestamp
317    }
318
319    pub fun getViews(): [Type] {
320      return [
321        Type<MetadataViews.Display>(),
322        Type<MetadataViews.Serial>(),
323        Type<MetadataViews.Royalties>(),
324        Type<MetadataViews.ExternalURL>(),
325        Type<MetadataViews.Editions>(),
326        Type<MetadataViews.Traits>(),
327        Type<CollectorMetadataViewV2>(),
328        Type<MetadataViews.NFTCollectionData>(),
329        Type<MetadataViews.NFTCollectionDisplay>()
330      ]
331    }
332
333    pub fun resolveView(_ view: Type): AnyStruct? {
334      switch view {
335        case Type<MetadataViews.Display>():
336          return MetadataViews.Display(
337            name: self.getTemplate().name,
338            description: self.getTemplate().description,
339            thumbnail: MetadataViews.HTTPFile(
340              url: self.getTemplate().image
341            )
342          )
343        case Type<MetadataViews.Serial>():
344          return MetadataViews.Serial(
345            self.serialNumber
346          )
347        case Type<MetadataViews.Royalties>():
348          var royalties: [MetadataViews.Royalty] = []
349          return MetadataViews.Royalties(royalties)
350        case Type<MetadataViews.ExternalURL>():
351          let externalUrl = self.getMetadata()["external_url"] ?? ""
352          return MetadataViews.ExternalURL(
353            externalUrl as! String
354          )
355        case Type<MetadataViews.Editions>():
356          let setData = Collector.SetsData[self.setID]!
357          let editionInfo = MetadataViews.Edition(
358            name: setData.name,
359            number: self.serialNumber,
360            max: setData.maxSize
361          )
362          let editionList: [MetadataViews.Edition] = [editionInfo]
363          return MetadataViews.Editions(
364            editionList
365          )
366        case Type<MetadataViews.Traits>():
367          let excludedTraits = ["external_url", "bottle"]
368          let traitsView = MetadataViews.dictToTraits(dict: self.getMetadata(), excludedNames: excludedTraits)
369
370          // mintingDate is a unix timestamp, we should mark it with a displayType so platforms know how to show it
371          let mintingDateTrait = MetadataViews.Trait(name: "minting_date", value: self.mintingDate, displayType: "Date", rarity: nil)
372          traitsView.addTrait(mintingDateTrait)
373
374          return traitsView
375        case Type<CollectorMetadataViewV2>():
376          let externalUrl = self.getMetadata()["external_url"] ?? ""
377          let architecture = self.getMetadata()["architecture"] ?? ""
378          let panelling = self.getMetadata()["panelling"] ?? ""
379          let visualElements = self.getMetadata()["visual_elements"] ?? ""
380          let ambience = self.getMetadata()["ambience"] ?? ""
381          let wineStorage = self.getMetadata()["wine_storage"] ?? ""
382          let wineBox = self.getMetadata()["wine_box"] ?? ""
383          let bottle = self.getMetadata()["bottle"] ?? ""
384          return CollectorMetadataViewV2(
385            id: self.id,
386            name: self.getTemplate().name,
387            description: self.getTemplate().description,
388            image: self.getTemplate().image,
389            externalUrl: externalUrl as? String,
390            architecture: architecture as? String,
391            panelling: panelling as? String,
392            visualElements: visualElements as? String,
393            ambience: ambience as? String,
394            wineStorage: wineStorage as? String,
395            wineBox: wineBox as? String,
396            bottle: bottle as? String,
397            setID: self.setID,
398            templateID: self.templateID,
399            serialNumber: self.serialNumber,
400            mintingDate: self.mintingDate
401          )
402        case Type<MetadataViews.NFTCollectionData>():
403          return MetadataViews.NFTCollectionData(
404            storagePath: Collector.CollectionStoragePath,
405            publicPath: Collector.CollectionPublicPath,
406            providerPath: /private/CollectorCollection,
407            publicCollection: Type<&Collector.Collection{Collector.NFTCollectionPublic}>(),
408            publicLinkedType: Type<&Collector.Collection{Collector.NFTCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,MetadataViews.ResolverCollection}>(),
409            providerLinkedType: Type<&Collector.Collection{Collector.NFTCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Provider,MetadataViews.ResolverCollection}>(),
410            createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
411              return <- Collector.createEmptyCollection()
412            })
413          )
414        case Type<MetadataViews.NFTCollectionDisplay>():
415          let externalUrl = "https://www.cuveecollective.com/marketplace/collections/collector"
416
417          return MetadataViews.NFTCollectionDisplay(
418            name: "Cuvée Collective - Collector",
419            description: "Our genesis collection for Cuvée Collective, called The Collector. The Collector NFT gives the NFT holder access to all future NFT drops 24 hours before they are open to the public, in addition to core Cuvée Collective benefits to include members-only discord channels, community voting status, concierge services, Sommelier hotline, and airdrops, events, and giveaways.",
420            externalURL: MetadataViews.ExternalURL(
421              url: externalUrl
422            ),
423            squareImage: MetadataViews.Media(
424              file: MetadataViews.HTTPFile(
425                url: "https://assets.test-cuveecollective.com/nfts/collector/nft_collector_1.jpg"
426              ),
427              mediaType: "image/jpeg"
428            ),
429            bannerImage: MetadataViews.Media(
430              file: MetadataViews.HTTPFile(
431                url: "https://assets.cuveecollective.com/nfts/collector/nft_collector_1_banner.jpg"
432              ),
433              mediaType: "image/jpeg"
434            ),
435            socials: {
436              "twitter": MetadataViews.ExternalURL(
437                url: "https://twitter.com/cuveecollective"
438              ),
439              "instagram": MetadataViews.ExternalURL(
440                url: "https://twitter.com/cuveecollectivehq"
441              ),
442              "discord": MetadataViews.ExternalURL(
443                url: "https://cuveecollective.com/discord"
444              )
445            }
446          )
447      }
448
449      return nil
450    }
451
452    pub fun getSetData(): SetData {
453      return Collector.SetsData[self.setID]!
454    }
455
456    pub fun getTemplate(): Template {
457      return Collector.Templates[self.templateID]!
458    }
459
460    pub fun getMetadata(): {String: AnyStruct} {
461      return Collector.Templates[self.templateID]!.getMetadata()
462    }
463  }
464
465  pub resource interface NFTCollectionPublic {
466    pub fun deposit(token: @NonFungibleToken.NFT)
467    pub fun getIDs(): [UInt64]
468    pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
469    pub fun borrowCollector(id: UInt64): &Collector.NFT? {
470      post {
471        (result == nil) || (result?.id == id):
472          "Cannot borrow collector reference: the ID of the returned reference is incorrect"
473      }
474    }
475  }
476
477  pub resource Collection: NFTCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
478    pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
479
480    pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
481      let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("Missing NFT")
482      emit Withdraw(id: token.id, from: self.owner?.address)
483      return <-token
484    }
485
486    pub fun deposit(token: @NonFungibleToken.NFT) {
487      let token <- token as! @Collector.NFT
488      let id: UInt64 = token.id
489      let oldToken <- self.ownedNFTs[id] <- token
490      emit Deposit(id: id, to: self.owner?.address)
491      destroy oldToken
492    }
493
494    pub fun batchDeposit(collection: @Collection) {
495      let keys = collection.getIDs()
496      for key in keys {
497        self.deposit(token: <-collection.withdraw(withdrawID: key))
498      }
499      destroy collection
500    }
501
502    pub fun getIDs(): [UInt64] {
503      return self.ownedNFTs.keys
504    }
505
506    pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
507      let ref = &self.ownedNFTs[id] as &NonFungibleToken.NFT?
508      return ref!
509    }
510
511    pub fun borrowCollector(id: UInt64): &Collector.NFT? {
512      if self.ownedNFTs[id] != nil {
513        let ref = &self.ownedNFTs[id] as auth &NonFungibleToken.NFT?
514        return ref! as! &Collector.NFT
515      } else {
516        return nil
517      }
518    }
519
520    pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
521      let nft = &self.ownedNFTs[id] as auth &NonFungibleToken.NFT?
522      let exampleNFT = nft! as! &Collector.NFT
523      return exampleNFT as &AnyResource{MetadataViews.Resolver}
524    }
525
526    destroy() {
527      destroy self.ownedNFTs
528    }
529
530    init() {
531      self.ownedNFTs <- {}
532    }
533  }
534  
535  pub fun createEmptyCollection(): @NonFungibleToken.Collection {
536    return <- create Collection()
537  }
538
539  // A Set is special resource type that contains functions to mint Collector NFTs, 
540  // add Templates, update Templates and Set metadata, and lock Sets and Templates.
541  pub resource Set {
542    pub let id: UInt64
543
544    pub var locked: Bool
545    pub var isPublic: Bool
546    pub var nextSerialNumber: UInt64
547
548    access(self) var templateIDs: [UInt64]
549    access(self) var availableTemplateIDs: [UInt64]
550
551    init(name: String, description: String, image: String, metadata: {String: AnyStruct}, maxSize: UInt64?) {
552      pre {
553        metadata.length != 0: "Set metadata cannot be empty"
554      }
555
556      self.id = Collector.nextSetID
557
558      self.locked = false
559      self.isPublic = false
560      self.nextSerialNumber = 1
561
562      self.templateIDs = []
563      self.availableTemplateIDs = []
564
565      Collector.SetsData[self.id] = SetData(
566        id: self.id,
567        name: name,
568        description: description,
569        image: image,
570        metadata: metadata,
571        maxSize: maxSize
572      )
573
574      Collector.nextSetID = Collector.nextSetID + 1
575
576      emit SetCreated(id: self.id, name: name)
577    }
578
579    pub fun updateImage(newImage: String) {
580      pre {
581        self.locked == false: "Cannot update image: set is locked"
582      }
583
584      let oldData = Collector.SetsData[self.id]!
585
586      Collector.SetsData[self.id] = SetData(
587        id: self.id,
588        name: oldData.name,
589        description: oldData.description,
590        image: newImage,
591        metadata: oldData.getMetadata(),
592        maxSize: oldData.maxSize
593      )
594
595      emit SetUpdated(id: self.id, name: oldData.name)
596    }
597
598    pub fun updateMetadata(newMetadata: {String: AnyStruct}) {
599      pre {
600        self.locked == false: "Cannot update metadata: set is locked"
601        newMetadata.length != 0: "New set metadata cannot be empty"
602      }
603
604      let oldData = Collector.SetsData[self.id]!
605
606      Collector.SetsData[self.id] = SetData(
607        id: self.id,
608        name: oldData.name,
609        description: oldData.description,
610        image: oldData.image,
611        metadata: newMetadata,
612        maxSize: oldData.maxSize
613      )
614
615      emit SetUpdated(id: self.id, name: oldData.name)
616    }
617
618    pub fun makePublic() {
619      pre {
620        self.isPublic == false: "Set is already public"
621      }
622
623      self.isPublic = true
624    }
625
626    pub fun makePrivate() {
627      pre {
628        self.isPublic == true: "Set is already private"
629      }
630
631      self.isPublic = false
632    }
633
634    pub fun addTemplate(id: UInt64) {
635      pre {
636        Collector.Templates[id] != nil:
637          "Template doesn't exist"
638        !self.locked:
639          "Cannot add template: set is locked"
640        !self.templateIDs.contains(id):
641          "Cannot add template: template is already added to the set"
642        !(Collector.Templates[id]!.addedToSet != 0):
643          "Cannot add template: template is already added to another set"
644      }
645
646      if let maxSize = Collector.SetsData[self.id]!.maxSize {
647        if UInt64(self.templateIDs.length) >= maxSize {
648          panic("Set is full")
649        }
650      }
651
652      self.templateIDs.append(id)
653      self.availableTemplateIDs.append(id)
654
655      // This function will automatically emit TemplateAddedToSet event
656      Collector.Templates[id]!.markAddedToSet(setID: self.id)
657    }
658
659    pub fun addTemplates(templateIDs: [UInt64]) {
660      for templateID in templateIDs {
661        self.addTemplate(id: templateID)
662      }
663    }
664
665    pub fun lock() {
666      pre {
667        self.locked == false: "Set is already locked"
668      }
669
670      self.locked = true
671      emit SetLocked(id: self.id, name: Collector.SetsData[self.id]!.name)
672    }
673
674    pub fun unlock() {
675      pre {
676        self.locked == true: "Set is already unlocked"
677      }
678
679      self.locked = false
680      emit SetUnlocked(id: self.id, name: Collector.SetsData[self.id]!.name)
681    }
682
683    pub fun mintNFT(): @NFT {
684      let templateID = self.availableTemplateIDs[0]
685
686      let newNFT: @NFT <- create Collector.NFT(id: Collector.totalSupply + 1, templateID: templateID, serialNumber: self.nextSerialNumber)
687      
688      Collector.totalSupply = Collector.totalSupply + 1
689      self.nextSerialNumber = self.nextSerialNumber + 1
690      self.availableTemplateIDs.remove(at: 0)
691
692      emit Minted(id: newNFT.id, templateID: newNFT.templateID, setID: newNFT.setID)
693      return <- newNFT
694    }
695
696    pub fun updateTemplateName(id: UInt64, newName: String) {
697      pre {
698        Collector.Templates[id] != nil:
699          "Template doesn't exist"
700        self.templateIDs.contains(id):
701          "Cannot edit template: template is not part of this set"
702        !self.locked:
703          "Cannot edit template: set is locked"
704      }
705
706      // This function will automatically emit TemplateUpdated event
707      Collector.Templates[id]!.updateName(newName: newName)
708    }
709
710    pub fun updateTemplateDescription(id: UInt64, newDescription: String) {
711      pre {
712        Collector.Templates[id] != nil:
713          "Template doesn't exist"
714        self.templateIDs.contains(id):
715          "Cannot edit template: template is not part of this set"
716        !self.locked:
717          "Cannot edit template: set is locked"
718      }
719
720      // This function will automatically emit TemplateUpdated event
721      Collector.Templates[id]!.updateDescription(newDescription: newDescription)
722    }
723
724    pub fun updateTemplateImage(id: UInt64, newImage: String) {
725      pre {
726        Collector.Templates[id] != nil:
727          "Template doesn't exist"
728        self.templateIDs.contains(id):
729          "Cannot edit template: template is not part of this set"
730        !self.locked:
731          "Cannot edit template: set is locked"
732      }
733
734      // This function will automatically emit TemplateUpdated event
735      Collector.Templates[id]!.updateImage(newImage: newImage)
736    }
737
738    pub fun updateTemplateMetadata(id: UInt64, newMetadata: {String: AnyStruct}) {
739      pre {
740        Collector.Templates[id] != nil:
741          "Template doesn't exist"
742        self.templateIDs.contains(id):
743          "Cannot edit template: template is not part of this set"
744        !self.locked:
745          "Cannot edit template: set is locked"
746      }
747
748      // This function will automatically emit TemplateUpdated event
749      Collector.Templates[id]!.updateMetadata(newMetadata: newMetadata)
750    }
751
752    pub fun lockTemplate(id: UInt64) {
753      pre {
754        Collector.Templates[id] != nil:
755          "Template doesn't exist"
756        self.templateIDs.contains(id):
757          "Cannot lock template: template is not part of this set"
758        !self.locked:
759          "Cannot lock template: set is locked"
760      }
761
762      // This function will automatically emit TemplateLocked event
763      Collector.Templates[id]!.lock()
764    }
765
766    pub fun getMetadata(): {String: AnyStruct} {
767      return Collector.SetsData[self.id]!.getMetadata()
768    }
769
770    pub fun getAvailableTemplateIDs(): [UInt64] {
771      return self.availableTemplateIDs
772    }
773  }
774
775  pub resource Admin {
776    pub fun mintNFT(recipient: &{NonFungibleToken.CollectionPublic}, setID: UInt64) {
777      let set = self.borrowSet(id: setID)
778      if (set.getAvailableTemplateIDs().length == 0){
779        panic("Set is empty")
780      }
781      recipient.deposit(token: <- set.mintNFT())
782    }
783
784    pub fun createTemplate(name: String, description: String, image: String, metadata: {String: AnyStruct}): UInt64 {
785      let templateID = Collector.nextTemplateID
786
787      // This function will automatically emit TemplateCreated event
788      Collector.Templates[templateID] = Template(
789        id: templateID,
790        name: name,
791        description: description,
792        image: image,
793        metadata: metadata
794      )
795
796      return templateID
797    }
798
799    pub fun updateTemplateName(id: UInt64, newName: String) {
800      pre {
801        Collector.Templates.containsKey(id) != nil:
802          "Template doesn't exits"
803      }
804
805      // This function will automatically emit TemplateUpdated event
806      Collector.Templates[id]!.updateName(newName: newName)
807    }
808
809    pub fun updateTemplateDescription(id: UInt64, newDescription: String) {
810      pre {
811        Collector.Templates.containsKey(id) != nil:
812          "Template doesn't exits"
813      }
814
815      // This function will automatically emit TemplateUpdated event
816      Collector.Templates[id]!.updateDescription(newDescription: newDescription)
817    }
818
819    pub fun updateTemplateImage(id: UInt64, newImage: String) {
820      pre {
821        Collector.Templates.containsKey(id) != nil:
822          "Template doesn't exits"
823      }
824
825      // This function will automatically emit TemplateUpdated event
826      Collector.Templates[id]!.updateImage(newImage: newImage)
827    }
828
829    pub fun updateTemplateMetadata(id: UInt64, newMetadata: {String: String}) {
830      pre {
831        Collector.Templates.containsKey(id) != nil:
832          "Template doesn't exits"
833      }
834
835      // This function will automatically emit TemplateUpdated event
836      Collector.Templates[id]!.updateMetadata(newMetadata: newMetadata)
837    }
838
839    pub fun lockTemplate(id: UInt64) {
840      pre {
841        Collector.Templates.containsKey(id) != nil:
842          "Template doesn't exits"
843      }
844
845      // This function will automatically emit TemplateLocked event
846      Collector.Templates[id]!.lock()
847    }
848
849    pub fun createSet(name: String, description: String, image: String, metadata: {String: String}, maxSize: UInt64?) {
850      var newSet <- create Set(
851        name: name,
852        description: description,
853        image: image,
854        metadata: metadata,
855        maxSize: maxSize
856      )
857      Collector.sets[newSet.id] <-! newSet
858    }
859
860    pub fun borrowSet(id: UInt64): &Set {
861      pre {
862        Collector.sets[id] != nil: "Cannot borrow set: set doesn't exist"
863      }
864      
865      let ref = &Collector.sets[id] as &Set?
866      return ref!
867    }
868
869    pub fun updateSetImage(id: UInt64, newImage: String) {
870      let set = self.borrowSet(id: id)
871      set.updateImage(newImage: newImage)
872    }
873
874    pub fun updateSetMetadata(id: UInt64, newMetadata: {String: AnyStruct}) {
875      let set = self.borrowSet(id: id)
876      set.updateMetadata(newMetadata: newMetadata)
877    }
878  }
879
880  pub fun getTemplate(id: UInt64): Collector.Template? {
881    return self.Templates[id]
882  }
883
884  pub fun getTemplates(): {UInt64: Collector.Template} {
885    return self.Templates
886  }
887
888  pub fun getSetIDs(): [UInt64] {
889    return self.sets.keys
890  }
891
892  pub fun getSetData(id: UInt64): Collector.SetData? {
893    return Collector.SetsData[id]
894  }
895
896  pub fun getSetsData(): {UInt64: Collector.SetData} {
897    return self.SetsData
898  }
899
900  pub fun getSetSize(id: UInt64): UInt64 {
901    pre {
902      self.sets[id] != nil: "Cannot borrow set: set doesn't exist"
903    }
904
905    let set = &self.sets[id] as &Set?
906
907    return set!.nextSerialNumber - 1
908  }
909
910  pub fun getAvailableTemplateIDsInSet(id: UInt64): [UInt64] {
911    pre {
912      self.sets[id] != nil: "Cannot borrow set: set doesn't exist"
913    }
914
915    let set = &self.sets[id] as &Set?
916    return set!.getAvailableTemplateIDs()
917  }
918
919  init() {
920    self.CollectionStoragePath = /storage/CollectorCollection
921    self.CollectionPublicPath = /public/CollectorCollection
922    self.AdminStoragePath = /storage/CollectorAdmin
923    self.AdminPrivatePath = /private/CollectorAdminUpgrade
924
925    self.totalSupply = 0
926    self.nextTemplateID = 1
927    self.nextSetID = 1
928    self.sets <- {}
929
930    self.SetsData = {}
931    self.Templates = {}
932
933    let admin <- create Admin()
934    self.account.save(<-admin, to: self.AdminStoragePath)
935
936    self.account.link<&Collector.Admin>(
937      self.AdminPrivatePath,
938      target: self.AdminStoragePath
939    ) ?? panic("Could not get a capability to the admin")
940
941    emit ContractInitialized()
942  }
943}
944