Smart Contract

Analogs

A.427ceada271aa0b1.Analogs

Deployed

1d ago
Feb 27, 2026, 06:32:36 PM UTC

Dependents

0 imports
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3import MetadataViews from 0x1d7e57aa55817448
4
5pub contract Analogs: NonFungibleToken {
6
7  pub event ContractInitialized()
8  pub event AccountInitialized()
9  pub event SetCreated(setID: UInt64)
10  pub event NFTTemplateCreated(templateID: UInt64, metadata: {String: String})
11  pub event Withdraw(id: UInt64, from: Address?)
12  pub event Deposit(id: UInt64, to: Address?)
13  pub event Minted(id: UInt64, templateID: UInt64)
14  pub event TemplateAddedToSet(setID: UInt64, templateID: UInt64)
15  pub event TemplateLockedFromSet(setID: UInt64, templateID: UInt64)
16  pub event TemplateUpdated(template: AnalogsTemplate)
17  pub event SetLocked(setID: UInt64)
18  pub event SetUnlocked(setID: UInt64)
19  
20  pub let CollectionStoragePath: StoragePath
21  pub let CollectionPublicPath: PublicPath
22  pub let AdminStoragePath: StoragePath
23
24  pub var totalSupply: UInt64
25  pub var initialNFTID: UInt64
26  pub var nextNFTID: UInt64
27  pub var nextTemplateID: UInt64
28  pub var nextSetID: UInt64
29
30  access(self) var analogsTemplates: {UInt64: AnalogsTemplate}
31  access(self) var sets: @{UInt64: Set}
32
33  pub resource interface AnalogsCollectionPublic {
34    pub fun deposit(token: @NonFungibleToken.NFT)
35    pub fun getIDs(): [UInt64]
36    pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
37    pub fun borrowAnalog(id: UInt64): &Analogs.NFT? {
38      post {
39        (result == nil) || (result?.id == id):
40          "Cannot borrow Analogs reference: The ID of the returned reference is incorrect"
41      }
42    }
43  }
44
45  pub struct AnalogsTemplate {
46    pub let templateID: UInt64
47    pub var name: String
48    pub var description: String
49    pub var locked: Bool
50    pub var addedToSet: UInt64
51    access(self) var metadata: {String: String}
52
53    pub fun getMetadata(): {String: String} {
54      return self.metadata
55    }
56
57    pub fun lockTemplate() {      
58      self.locked = true
59    }
60
61    pub fun updateMetadata(newMetadata: {String: String}) {
62      pre {
63        newMetadata.length != 0: "New Template metadata cannot be empty"
64      }
65      self.metadata = newMetadata
66    }
67    
68    pub fun markAddedToSet(setID: UInt64) {
69      self.addedToSet = setID
70    }
71
72    init(templateID: UInt64, name: String, description: String, metadata: {String: String}){
73      pre {
74        metadata.length != 0: "New Template metadata cannot be empty"
75      }
76
77      self.templateID = templateID
78      self.name = name
79      self.description= description
80      self.metadata = metadata
81      self.locked = false
82      self.addedToSet = 0
83
84      emit NFTTemplateCreated(templateID: self.templateID, metadata: self.metadata)
85    }
86  }
87
88  pub struct Royalty {
89    pub let address: Address
90    pub let primaryCut: UFix64
91    pub let secondaryCut: UFix64
92    pub let description: String
93
94    init(address: Address, primaryCut: UFix64, secondaryCut: UFix64, description: String) {
95      pre {
96          primaryCut >= 0.0 && primaryCut <= 1.0 : "primaryCut value should be in valid range i.e [0,1]"
97          secondaryCut >= 0.0 && secondaryCut <= 1.0 : "secondaryCut value should be in valid range i.e [0,1]"
98      }
99      self.address = address
100      self.primaryCut = primaryCut
101      self.secondaryCut = secondaryCut
102      self.description = description
103    }
104  }
105
106  pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
107    pub let id: UInt64
108    pub let templateID: UInt64
109    pub var serialNumber: UInt64
110    
111    pub fun getViews(): [Type] {
112      return [
113        Type<MetadataViews.ExternalURL>(),
114        Type<MetadataViews.NFTCollectionData>(),
115        Type<MetadataViews.NFTCollectionDisplay>(),
116        Type<MetadataViews.Display>(),
117        Type<MetadataViews.Medias>(),
118        Type<MetadataViews.Royalties>()
119      ]
120    }
121
122     pub fun resolveView(_ view: Type): AnyStruct? {
123      let metadata = Analogs.analogsTemplates[self.templateID]!.getMetadata()
124      let thumbnailCID = metadata["thumbnailCID"] != nil ? metadata["thumbnailCID"]! : metadata["imageCID"]!
125      switch view {
126        case Type<MetadataViews.ExternalURL>():
127          return MetadataViews.ExternalURL("https://ipfs.io/ipfs/".concat(thumbnailCID))
128        case Type<MetadataViews.NFTCollectionData>():
129          return MetadataViews.NFTCollectionData(
130              storagePath: Analogs.CollectionStoragePath,
131              publicPath: Analogs.CollectionPublicPath,
132              providerPath: /private/AnalogsCollection,
133              publicCollection: Type<&Analogs.Collection{Analogs.AnalogsCollectionPublic}>(),
134              publicLinkedType: Type<&Analogs.Collection{Analogs.AnalogsCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,MetadataViews.ResolverCollection}>(),
135              providerLinkedType: Type<&Analogs.Collection{Analogs.AnalogsCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Provider,MetadataViews.ResolverCollection}>(),
136              createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
137                  return <-Analogs.createEmptyCollection()
138              })
139          )
140        case Type<MetadataViews.NFTCollectionDisplay>():
141          let media = MetadataViews.Media(
142            file: MetadataViews.HTTPFile(url: "https://ipfs.io/ipfs/bafkreidhuylwtdgug3vuamphju44r7eam5wlels4tejbkz4nvelnluktcm"),
143            mediaType: "image/jpeg"
144          )
145          return MetadataViews.NFTCollectionDisplay(
146            name: "Heavy Metal Analogs",
147            description: "",
148            externalURL: MetadataViews.ExternalURL("https://sturdy.exchange/"),
149            squareImage: media,
150            bannerImage: media,
151            socials: {}
152          )
153        case Type<MetadataViews.Display>():
154          return MetadataViews.Display(
155            name: Analogs.analogsTemplates[self.templateID]!.name,
156            description: Analogs.analogsTemplates[self.templateID]!.description,
157            thumbnail: MetadataViews.HTTPFile(
158              url: "https://ipfs.io/ipfs/".concat(thumbnailCID)
159            )
160          )
161        case Type<MetadataViews.Medias>():
162          let medias: [MetadataViews.Media] = [];
163          let videoCID = Analogs.analogsTemplates[self.templateID]!.getMetadata()["videoCID"]
164          let imageCID = thumbnailCID
165          if videoCID != nil {
166            medias.append(
167              MetadataViews.Media(
168                file: MetadataViews.HTTPFile(
169                  url: "https://ipfs.io/ipfs/".concat(videoCID!)
170                ),
171                mediaType: "video/mp4"
172              )
173            )
174          }
175          else if imageCID != nil {
176            medias.append(
177              MetadataViews.Media(
178                file: MetadataViews.HTTPFile(
179                  url: "https://ipfs.io/ipfs/".concat(imageCID)
180                ),
181                mediaType: "image/jpeg"
182            )
183          )
184          }
185          return MetadataViews.Medias(medias)
186        case Type<MetadataViews.Royalties>():
187          let setID = Analogs.analogsTemplates[self.templateID]!.addedToSet
188          let setRoyalties = Analogs.getSetRoyalties(setID: setID)
189          let royalties: [MetadataViews.Royalty] = []
190          for royalty in setRoyalties {
191            royalties.append(
192              MetadataViews.Royalty(
193                receiver: getAccount(royalty.address)
194                    .getCapability<&{FungibleToken.Receiver}>(/public/flowUtilityTokenReceiver),
195                cut: royalty.secondaryCut,
196                description: royalty.description
197              )
198            )
199          }
200          return MetadataViews.Royalties(royalties)
201      }
202      return nil
203    }
204
205    pub fun getNFTMetadata(): {String: String} {
206      return Analogs.analogsTemplates[self.templateID]!.getMetadata()
207    }
208
209    init(initID: UInt64, initTemplateID: UInt64, serialNumber: UInt64) {
210      self.id = initID
211      self.templateID = initTemplateID
212      self.serialNumber = serialNumber
213    }
214  }
215
216  pub resource Collection: AnalogsCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
217    pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
218
219    pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
220      let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
221      emit Withdraw(id: token.id, from: self.owner?.address)
222      return <-token
223    }
224
225    pub fun deposit(token: @NonFungibleToken.NFT) {
226      let token <- token as! @Analogs.NFT
227      let id: UInt64 = token.id
228      let oldToken <- self.ownedNFTs[id] <- token
229      emit Deposit(id: id, to: self.owner?.address)
230      destroy oldToken
231    }
232
233    pub fun batchDeposit(collection: @Collection) {
234      let keys = collection.getIDs()
235      for key in keys {
236        self.deposit(token: <-collection.withdraw(withdrawID: key))
237      }
238      destroy collection
239    }
240
241    pub fun getIDs(): [UInt64] {
242      return self.ownedNFTs.keys
243    }
244
245    pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
246      return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
247    }
248
249    pub fun borrowAnalog(id: UInt64): &Analogs.NFT? {
250      if self.ownedNFTs[id] != nil {
251        let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
252        return ref as! &Analogs.NFT
253      } else {
254        return nil
255      }
256    }
257
258    pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
259      let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
260      let exampleNFT = nft as! &Analogs.NFT
261      return exampleNFT as &AnyResource{MetadataViews.Resolver}
262    }
263
264    destroy() {
265      destroy self.ownedNFTs
266    }
267
268    init () {
269      self.ownedNFTs <- {}
270    }
271  }
272  
273  pub fun createEmptyCollection(): @NonFungibleToken.Collection {
274    emit AccountInitialized()
275    return <- create Collection()
276  }
277
278  pub resource Set {
279    pub let setID: UInt64
280    pub let name: String
281    access(self) var templateIDs: [UInt64]
282    access(self) var availableTemplateIDs: [UInt64]
283    access(self) var lockedTemplates: {UInt64: Bool}
284    access(self) var metadata: {String: String}
285    pub var locked: Bool
286    pub var nextSetSerialNumber: UInt64
287    pub var isPublic: Bool
288    pub var analogRoyaltyAddress: Address
289    pub var analogRoyaltySecondaryCut: UFix64
290    pub var artistRoyalties: [Royalty]
291
292
293    init(name: String, analogRoyaltyAddress: Address, analogRoyaltySecondaryCut: UFix64, imageCID: String) {
294      self.name = name
295      self.setID = Analogs.nextSetID
296      self.templateIDs = []
297      self.lockedTemplates = {}
298      self.locked = false
299      self.availableTemplateIDs = []
300      self.nextSetSerialNumber = 1
301      self.isPublic = false
302      self.analogRoyaltyAddress = analogRoyaltyAddress
303      self.analogRoyaltySecondaryCut = analogRoyaltySecondaryCut
304      self.artistRoyalties = []
305      self.metadata = { "imageCID": imageCID }
306      
307      Analogs.nextSetID = Analogs.nextSetID + 1
308      emit SetCreated(setID: self.setID)
309    }
310
311    pub fun getAvailableTemplateIDs(): [UInt64] {
312      return self.availableTemplateIDs
313    }
314
315    pub fun makeSetPublic() {
316      self.isPublic = true
317    }
318
319    pub fun makeSetPrivate() {
320      self.isPublic = false
321    }
322
323    pub fun updateAnalogRoyaltyAddress(analogRoyaltyAddress: Address) {
324      self.analogRoyaltyAddress = analogRoyaltyAddress
325    }
326
327    pub fun updateAnalogRoyaltySecondaryCut(analogRoyaltySecondaryCut: UFix64) {
328      self.analogRoyaltySecondaryCut = analogRoyaltySecondaryCut
329    }
330
331    pub fun addArtistRoyalty(royalty: Royalty) {
332      self.artistRoyalties.append(royalty)
333    }
334
335    pub fun addTemplate(templateID: UInt64, available: Bool) {
336      pre {
337        Analogs.analogsTemplates[templateID] != nil:
338          "Template doesn't exist"
339        !self.locked:
340          "Cannot add template - set is locked"
341        !self.templateIDs.contains(templateID):
342          "Cannot add template - template is already added to the set"
343        !(Analogs.analogsTemplates[templateID]!.addedToSet != 0):
344          "Cannot add template - template is already added to another set"
345      }
346
347      self.templateIDs.append(templateID)
348      if available {
349        self.availableTemplateIDs.append(templateID)
350      }
351      self.lockedTemplates[templateID] = !available
352      Analogs.analogsTemplates[templateID]!.markAddedToSet(setID: self.setID)
353
354      emit TemplateAddedToSet(setID: self.setID, templateID: templateID)
355    }
356
357    pub fun addTemplates(templateIDs: [UInt64], available: Bool) {
358      for template in templateIDs {
359        self.addTemplate(templateID: template, available: available)
360      }
361    }
362
363    pub fun lockTemplate(templateID: UInt64) {
364      pre {
365        self.lockedTemplates[templateID] != nil:
366          "Cannot lock the template: Template is locked already!"
367        !self.availableTemplateIDs.contains(templateID):
368          "Cannot lock a not yet minted template!"
369      }
370
371      if !self.lockedTemplates[templateID]! {
372        self.lockedTemplates[templateID] = true
373        emit TemplateLockedFromSet(setID: self.setID, templateID: templateID)
374      }
375    }
376
377    pub fun lockAllTemplates() {
378      for template in self.templateIDs {
379        self.lockTemplate(templateID: template)
380      }
381    }
382
383    pub fun lock() {
384      if !self.locked {
385          self.locked = true
386          emit SetLocked(setID: self.setID)
387      }
388    }
389
390    pub fun unlock() {
391      if self.locked {
392          self.locked = false
393          emit SetUnlocked(setID: self.setID)
394      }
395    }
396
397    pub fun mintNFT(): @NFT {
398      let templateID = self.availableTemplateIDs[0]
399      if (Analogs.analogsTemplates[templateID]!.locked) {
400        panic("template is locked")
401      }
402
403      let newNFT: @NFT <- create Analogs.NFT(initID: Analogs.nextNFTID, initTemplateID: templateID, serialNumber: self.nextSetSerialNumber)
404      
405      Analogs.totalSupply = Analogs.totalSupply + 1
406      Analogs.nextNFTID = Analogs.nextNFTID + 1
407      self.nextSetSerialNumber = self.nextSetSerialNumber + 1
408      self.availableTemplateIDs.remove(at: 0)
409
410      emit Minted(id: newNFT.id, templateID: newNFT.templateID)
411
412      return <-newNFT
413    }
414
415    pub fun mintNFTByTemplateID(templateID: UInt64): @NFT {
416      let newNFT: @NFT <- create Analogs.NFT(initID: templateID, initTemplateID: templateID, serialNumber: self.nextSetSerialNumber)
417      
418      Analogs.totalSupply = Analogs.totalSupply + 1
419      self.nextSetSerialNumber = self.nextSetSerialNumber + 1
420      self.lockTemplate(templateID: templateID)
421
422      emit Minted(id: newNFT.id, templateID: newNFT.templateID)
423
424      return <-newNFT
425    }
426
427    pub fun updateTemplateMetadata(templateID: UInt64, newMetadata: {String: String}):AnalogsTemplate {
428      pre {
429        Analogs.analogsTemplates[templateID] != nil:
430          "Template doesn't exist"
431        !self.locked:
432          "Cannot edit template - set is locked"
433      }
434
435      Analogs.analogsTemplates[templateID]!.updateMetadata(newMetadata: newMetadata)
436      emit TemplateUpdated(template: Analogs.analogsTemplates[templateID]!)
437      return Analogs.analogsTemplates[templateID]!
438    }
439
440    pub fun getImageCID(): String? {
441      return self.metadata["imageCID"]
442    }
443
444    pub fun updateImageCID(imageCID: String) {
445      self.metadata["imageCID"] = imageCID
446    }
447  }
448
449  pub fun getSetName(setID: UInt64): String {
450    pre {
451      Analogs.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
452    }
453      
454    let set = (&Analogs.sets[setID] as &Set?)!
455    return set.name
456  }
457
458  pub fun getSetImageCID(setID: UInt64): String? {
459    pre {
460      Analogs.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
461    }
462      
463    let set = (&Analogs.sets[setID] as &Set?)!
464    return set.getImageCID()
465  }
466
467  pub fun getSetRoyalties(setID: UInt64): [Royalty] {
468    pre {
469      Analogs.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
470    }
471      
472    let set = (&Analogs.sets[setID] as &Set?)!
473    var analogRoyaltyPrimaryCut: UFix64 = 1.00
474    for royalty in set.artistRoyalties {
475      analogRoyaltyPrimaryCut = analogRoyaltyPrimaryCut - royalty.primaryCut
476    }
477    let royalties = [
478      Royalty(
479        address: set.analogRoyaltyAddress,
480        primaryCut: analogRoyaltyPrimaryCut,
481        secondaryCut: set.analogRoyaltySecondaryCut,
482        description: "Sturdy Royalty"
483      )
484    ]
485    royalties.appendAll(set.artistRoyalties)
486    return royalties
487  }
488
489  pub resource Admin {
490
491    pub fun mintNFT(recipient: &{NonFungibleToken.CollectionPublic}, setID: UInt64) {
492      let set = self.borrowSet(setID: setID)
493      if (set.getAvailableTemplateIDs()!.length == 0){
494        panic("Set is empty")
495      }
496      if (set.locked) {
497        panic("Set is locked")
498      }
499      recipient.deposit(token: <- set.mintNFT())
500    }
501
502    pub fun createAndMintNFT(recipient: &{NonFungibleToken.CollectionPublic}, templateID: UInt64, setID: UInt64, name: String, description: String, metadata: {String: String}) {
503      if Analogs.analogsTemplates[Analogs.nextTemplateID] != nil {
504        panic("Template already exists")
505      }
506      Analogs.analogsTemplates[templateID] = AnalogsTemplate(
507        templateID: templateID,
508        name: name,
509        description: description,
510        metadata: metadata
511      )
512      let set = self.borrowSet(setID: setID)
513      set.addTemplate(templateID: templateID, available: false)
514      recipient.deposit(token: <- set.mintNFTByTemplateID(templateID: templateID))
515    }
516
517    pub fun createAnalogsTemplate(name: String, description: String, metadata: {String: String}) {
518      Analogs.analogsTemplates[Analogs.nextTemplateID] = AnalogsTemplate(
519        templateID: Analogs.nextTemplateID,
520        name: name,
521        description: description,
522        metadata: metadata
523      )
524      Analogs.nextTemplateID = Analogs.nextTemplateID + 1
525    }
526
527    pub fun createSet(name: String, analogRoyaltyAddress: Address, analogRoyaltySecondaryCut: UFix64, imageCID: String): UInt64 {
528      var newSet <- create Set(name: name, analogRoyaltyAddress: analogRoyaltyAddress, analogRoyaltySecondaryCut: analogRoyaltySecondaryCut, imageCID: imageCID)
529      let setID = newSet.setID
530      Analogs.sets[setID] <-! newSet
531      return setID
532    }
533
534    pub fun borrowSet(setID: UInt64): &Set {
535      pre {
536        Analogs.sets[setID] != nil:
537          "Cannot borrow Set: The Set doesn't exist"
538      }
539      
540      return (&Analogs.sets[setID] as &Set?)!
541    }
542
543    pub fun updateSetImageCID(setID: UInt64, imageCID: String) {
544      pre {
545        Analogs.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
546      }
547        
548      let set = (&Analogs.sets[setID] as &Set?)!
549      return set.updateImageCID(imageCID: imageCID)
550    }
551
552    pub fun updateAnalogsTemplate(templateID: UInt64, newMetadata: {String: String}) {
553      pre {
554        Analogs.analogsTemplates.containsKey(templateID) != nil:
555          "Template does not exists."
556      }
557      Analogs.analogsTemplates[templateID]!.updateMetadata(newMetadata: newMetadata)
558    }
559
560    pub fun setInitialNFTID(initialNFTID: UInt64) {
561      pre {
562        Analogs.initialNFTID == 0:
563          "initialNFTID is already initialized"
564      }
565      Analogs.initialNFTID = initialNFTID
566      Analogs.nextNFTID = initialNFTID
567      Analogs.nextTemplateID = initialNFTID
568    }
569
570  }
571
572  pub fun getAnalogsTemplateByID(templateID: UInt64): Analogs.AnalogsTemplate {
573    return Analogs.analogsTemplates[templateID]!
574  }
575
576  pub fun getAnalogsTemplates(): {UInt64: Analogs.AnalogsTemplate} {
577    return Analogs.analogsTemplates
578  }
579
580  pub fun getAvailableTemplateIDsInSet(setID: UInt64): [UInt64] {
581    pre {
582        Analogs.sets[setID] != nil:
583        "Cannot borrow Set: The Set doesn't exist"
584    }
585    let set = (&Analogs.sets[setID] as &Set?)!
586    return set.getAvailableTemplateIDs()
587  }
588
589  init() {
590    self.CollectionStoragePath = /storage/AnalogsCollection
591    self.CollectionPublicPath = /public/AnalogsCollection
592    self.AdminStoragePath = /storage/AnalogsAdmin
593
594    self.totalSupply = 0
595    self.nextSetID = 1
596    self.initialNFTID = 0
597    self.nextNFTID = 0
598    self.nextTemplateID = 0
599    self.sets <- {}
600
601    self.analogsTemplates = {}
602
603    let admin <- create Admin()
604    self.account.save(<-admin, to: self.AdminStoragePath)
605
606    emit ContractInitialized()
607  }
608}