Smart Contract

SNKRHUDNFT

A.80af1db15aa6535a.SNKRHUDNFT

Deployed

1d ago
Feb 27, 2026, 05:55:22 PM UTC

Dependents

0 imports
1// SPDX-License-Identifier: UNLICENSED
2
3import NonFungibleToken from 0x1d7e57aa55817448
4import MetadataViews from 0x1d7e57aa55817448
5import Crypto
6
7pub contract SNKRHUDNFT: NonFungibleToken {
8
9  // Events
10  //
11  // This contract is initialized
12  pub event ContractInitialized()
13
14  // NFT is minted
15  pub event NFTMinted(
16    nftID: UInt64,
17    setID: UInt64,
18    templateID: UInt64,
19    displayName: String,
20    displayDescription: String,
21    displayURI: String,
22    creator: Address,
23  )
24
25  // NFT is withdrawn from a collection
26  pub event Withdraw(id: UInt64, from: Address?)
27
28  // NFT is deposited from a collection
29  pub event Deposit(id: UInt64, to: Address?)
30
31  // NFT is destroyed
32  pub event NFTDestroyed(id: UInt64)
33
34  // NFT template metadata is revealed
35  pub event NFTRevealed(
36    nftID: UInt64,
37    setID: UInt64,
38    templateID: UInt64,
39    displayName: String,
40    displayDescription: String,
41    displayURI: String,
42    metadata: {String: String},
43  )
44
45  // Set has been created
46  pub event SetCreated(setID: UInt64, metadata: SetMetadata)
47
48  // Template has been added
49  pub event TemplateAdded(setID: UInt64, templateID: UInt64, displayName: String, displayDescription: String, displayURI: String)
50
51  // Set has been marked Locked
52  pub event SetLocked(setID: UInt64, numTemplates: UInt64)
53
54  // Paths
55  //
56  pub let CollectionStoragePath: StoragePath
57  pub let CollectionPublicPath: PublicPath
58  pub let CollectionPrivatePath: PrivatePath
59  pub let AdminStoragePath: StoragePath
60
61  // Total NFT supply
62  pub var totalSupply: UInt64
63
64  // Total number of sets
65  access(self) var totalSets: UInt64
66
67  // Dictionary mapping from set IDs to Set resources
68  access(self) var sets: @{UInt64: Set}
69
70  // Template metadata can have custom definitions but must have the
71  // following implemented in order to power all the features of the
72  // NFT contract.
73  pub struct interface TemplateMetadata {
74
75    // Hash representation of implementing structs.
76    pub fun hash(): [UInt8]
77
78    // Representative Display
79    pub fun display(): MetadataViews.Display
80
81    // Representative {string: string} serialization
82    pub fun repr(): {String: String}
83
84    // MetadataViews compliant
85    pub fun getViews(): [Type]
86    pub fun resolveView(_ view: Type): AnyStruct?
87  }
88
89  pub struct DynamicTemplateMetadata: TemplateMetadata {
90    access(self) let _display: MetadataViews.Display
91    access(self) let _metadata: {String: String}
92
93    pub fun hash(): [UInt8] {
94      return []
95    }
96
97    pub fun display(): MetadataViews.Display {
98      return self._display
99    }
100
101    pub fun getViews(): [Type] {
102      return [
103        Type<MetadataViews.Display>(),
104        Type<MetadataViews.Royalties>(),
105        Type<MetadataViews.ExternalURL>(),
106        Type<MetadataViews.NFTCollectionData>(),
107        Type<MetadataViews.NFTCollectionDisplay>(),
108        Type<MetadataViews.Serial>()
109      ]
110    }
111
112    pub fun resolveView(_ view: Type): AnyStruct? {
113      switch view {
114        case Type<MetadataViews.Display>():
115          let metadataDisplay = self.display()
116          let uri = metadataDisplay.thumbnail.uri()
117          let cid = uri.slice(from: 7, upTo: uri.length)
118          let httpURL = "https://snkrhud.mypinata.cloud/ipfs/".concat(cid);
119          return MetadataViews.Display(
120            name: metadataDisplay.name,
121            description: metadataDisplay.description,
122            thumbnail: MetadataViews.HTTPFile(httpURL)
123          )
124        case Type<MetadataViews.Royalties>():
125          return MetadataViews.Royalties([])
126        case Type<MetadataViews.ExternalURL>():
127          return MetadataViews.ExternalURL("https://www.snkrhud.com")
128        case Type<MetadataViews.NFTCollectionData>():
129          return MetadataViews.NFTCollectionData(
130              storagePath: SNKRHUDNFT.CollectionStoragePath,
131              publicPath: SNKRHUDNFT.CollectionPublicPath,
132              providerPath: /private/SNKRHUDNFTCollection,
133              publicCollection: Type<&SNKRHUDNFT.Collection{SNKRHUDNFT.CollectionPublic}>(),
134              publicLinkedType: Type<&SNKRHUDNFT.Collection{SNKRHUDNFT.CollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,MetadataViews.ResolverCollection}>(),
135              providerLinkedType: Type<&SNKRHUDNFT.Collection{SNKRHUDNFT.CollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Provider,MetadataViews.ResolverCollection}>(),
136              createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection {
137                  return <-SNKRHUDNFT.createEmptyCollection()
138              })
139          )
140        case Type<MetadataViews.NFTCollectionDisplay>():
141         let media = MetadataViews.Media(
142              file: MetadataViews.HTTPFile(
143                  url: "https://www.snkrhud.com/img/snkrhud-logo-white.fb9acd95.png"
144              ),
145              mediaType: "image/png"
146          )
147          return MetadataViews.NFTCollectionDisplay(
148                name: "SNKRHUD",
149                description: "SNKRHUD NFT",
150                externalURL: MetadataViews.ExternalURL("https://www.snkrhud.com"),
151                squareImage: media,
152                bannerImage: media,
153                socials: {
154                  "twitter": MetadataViews.ExternalURL("https://twitter.com/snkrhud"),
155                  "discord": MetadataViews.ExternalURL("https://discord.com/invite/snkrhud"),
156                  "instagram": MetadataViews.ExternalURL("https://www.instagram.com/snkrhud")
157                }
158            )
159
160      }
161      return nil
162    }
163
164    pub fun repr(): {String: String} {
165      return self.metadata()
166    }
167
168    pub fun metadata(): {String: String} {
169      return self._metadata
170    }
171
172    init(display: MetadataViews.Display, metadata: {String: String}) {
173      self._display = display
174      self._metadata = metadata
175    }
176  }
177
178  // NFT
179  //
180  // "Standard" NFTs that implement MetadataViews and point
181  // to a Template struct that give information about the NFTs metadata
182  pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
183
184    // id is unique among all SNKRHUDNFT NFTs on Flow, ordered sequentially from 0
185    pub let id: UInt64
186
187    // setID and templateID help us locate the specific template in the
188    // specific set which stores this NFTs metadata
189    pub let setID: UInt64
190    pub let templateID: UInt64
191
192    // The creator of the NFT
193    pub let creator: Address
194
195    // Fetch the metadata Template represented by this NFT
196    pub fun template(): {NFTTemplate} {
197      return SNKRHUDNFT.getTemplate(setID: self.setID, templateID: self.templateID)
198    }
199
200    // Proxy for MetadataViews.Resolver.getViews implemented by Template
201    pub fun getViews(): [Type] {
202      let template = self.template()
203      return template.getViews()
204    }
205
206    // Proxy for MetadataViews.Resolver.resolveView implemented by Template
207    pub fun resolveView(_ view: Type): AnyStruct? {
208      let template = self.template()
209      return template.resolveView(view)
210    }
211
212    // NFT needs to be told which Template it follows
213    init(setID: UInt64, templateID: UInt64, creator: Address) {
214      self.id = SNKRHUDNFT.totalSupply
215      SNKRHUDNFT.totalSupply = SNKRHUDNFT.totalSupply + 1
216      self.setID = setID
217      self.templateID = templateID
218      self.creator = creator
219      let defaultDisplay = self.template().defaultDisplay
220      emit NFTMinted(
221        nftID: self.id,
222        setID: self.setID,
223        templateID: self.templateID,
224        displayName: defaultDisplay.name,
225        displayDescription: defaultDisplay.description,
226        displayURI: defaultDisplay.thumbnail.uri(),
227        creator: self.creator
228      )
229    }
230
231    // Emit NFTDestroyed when destroyed
232    destroy() {
233      emit NFTDestroyed(
234        id: self.id,
235      )
236    }
237  }
238
239  // Collection
240  //
241  // Collections provide a way for collectors to store SNKRHUDNFT NFTs in their
242  // Flow account.
243
244  // Exposing this interface allows external parties to inspect a Flow
245  // account's SNKRHUDNFT Collection and deposit NFTs
246  pub resource interface CollectionPublic {
247    pub fun deposit(token: @NonFungibleToken.NFT)
248    pub fun getIDs(): [UInt64]
249    pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
250    pub fun borrowSNKRHUDNFT(id: UInt64): &NFT
251  }
252
253  pub resource Collection: CollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
254
255    // NFTs are indexed by its globally assigned id
256    pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
257
258    // Deposit a SNKRHUDNFT into the collection. Safe to assume id's are unique.
259    pub fun deposit(token: @NonFungibleToken.NFT) {
260      // Required to ensure this is a SNKRHUDNFT
261      let token <- token as! @SNKRHUDNFT.NFT
262      let id: UInt64 = token.id
263      let oldToken <- self.ownedNFTs[id] <- token
264      emit Deposit(id: id, to: self.owner?.address)
265      destroy oldToken
266    }
267
268    // Withdraw an NFT from the collection.
269    // Panics if NFT does not exist in the collection
270    pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
271      pre {
272        self.ownedNFTs.containsKey(withdrawID)
273          : "NFT does not exist in collection."
274      }
275      let token <- self.ownedNFTs.remove(key: withdrawID)!
276      emit Withdraw(id: token.id, from: self.owner?.address)
277      return <-token
278    }
279
280    // Return all the IDs from the collection.
281    pub fun getIDs(): [UInt64] {
282      return self.ownedNFTs.keys
283    }
284
285    // Borrow a reference to the specified NFT
286    // Panics if NFT does not exist in the collection
287    pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
288      pre {
289        self.ownedNFTs.containsKey(id)
290          : "NFT does not exist in collection."
291      }
292      return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
293    }
294
295    // Borrow a reference to the specified NFT as a SNKRHUDNFT.
296    // Panics if NFT does not exist in the collection
297    pub fun borrowSNKRHUDNFT(id: UInt64): &NFT {
298      pre {
299        self.ownedNFTs.containsKey(id)
300          : "NFT does not exist in collection."
301      }
302      let ref =(&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
303      return ref as! &NFT
304    }
305
306    // Return the MetadataViews.Resolver of the specified NFT
307    // Panics if NFT does not exist in the collection
308    pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver} {
309      pre {
310        self.ownedNFTs.containsKey(id)
311          : "NFT does not exist in collection."
312      }
313      let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
314      let typedNFT = nft as! &NFT
315      return typedNFT
316    }
317
318    init() {
319      self.ownedNFTs <- {}
320    }
321
322    // If the collection is destroyed, destroy the NFTs it holds, as well
323    destroy() {
324      destroy self.ownedNFTs
325    }
326  }
327
328  // Anyone can make and store collections
329  pub fun createEmptyCollection(): @Collection {
330    return <-create Collection()
331  }
332
333  pub resource Set {
334
335    // Globally assigned id based on number of created Sets.
336    pub let id: UInt64
337
338    pub var isLocked: Bool
339
340    // Metadata for the Set
341    pub var metadata: SetMetadata
342
343    // Templates configured to be minted from this Set
344    access(contract) var templates: [Template]
345
346    // Number of NFTs that have minted from this Set
347    pub var minted: UInt64
348
349    // Add a new Template to the Set, only if the Set is Open
350    pub fun addTemplate(template: Template) {
351      pre {
352        !self.isLocked : "Set is locked. It cannot be modified"
353      }
354      let templateID = self.templates.length
355      self.templates.append(template)
356
357      let display = template.defaultDisplay
358      emit TemplateAdded(setID: self.id, templateID: UInt64(templateID), displayName: display.name, displayDescription: display.description, displayURI: display.thumbnail.uri())
359    }
360
361    // Lock the Set if it is Open. This signals that this Set
362    // will mint NFTs based only on the Templates configured in this Set.
363    pub fun lock() {
364      pre {
365        !self.isLocked : "Only an Open set can be locked."
366        self.templates.length > 0
367          : "Set must be configured with at least one Template."
368      }
369      self.isLocked = true
370      emit SetLocked(setID: self.id, numTemplates: UInt64(self.templates.length))
371    }
372
373    // Mint numToMint NFTs with the supplied creator attribute. The NFT will
374    // be minted into the provided receiver
375    pub fun mint(
376      templateID: UInt64,
377      creator: Address
378    ): @NFT {
379      pre {
380        templateID < UInt64(self.templates.length)
381          : "templateID does not exist in Set."
382        self.templates[templateID].mintID == nil
383          : "Template has already been marked as minted."
384      }
385      let nft <-create NFT(
386          setID: self.id,
387          templateID: templateID,
388          creator: creator
389        )
390      self.templates[templateID].markMinted(nftID: nft.id)
391      self.minted = self.minted + 1
392      return <- nft
393    }
394
395    // Reveal a specified Template in a Set.
396    pub fun revealTemplate(
397      templateID: UInt64,
398      metadata: {TemplateMetadata},
399      salt: [UInt8]
400    ) {
401      pre {
402        templateID < UInt64(self.templates.length)
403          : "templateId does not exist in Set."
404        self.templates[templateID].mintID != nil
405          : "Template has not been marked as minted."
406      }
407      let template = &self.templates[templateID] as &Template
408      template.reveal(metadata: metadata, salt: salt)
409
410      let display = metadata.display()
411      emit NFTRevealed(
412        nftID: template.mintID!,
413        setID: self.id,
414        templateID: templateID,
415        displayName: display.name,
416        displayDescription: display.description,
417        displayURI: display.thumbnail.uri(),
418        metadata: metadata.repr(),
419      )
420    }
421
422    init(id: UInt64, metadata: SetMetadata) {
423      self.id = id
424      self.metadata = metadata
425
426      self.isLocked = false
427      self.templates = []
428
429      self.minted = 0
430      emit SetCreated(setID: id, metadata: metadata)
431    }
432  }
433
434  // Create and store a new Set. Return the id of the new Set.
435  access(contract) fun createSet(metadata: SetMetadata): UInt64 {
436    let setID = SNKRHUDNFT.totalSets
437
438    let newSet <- create Set(
439      id: setID,
440      metadata: metadata
441    )
442    SNKRHUDNFT.sets[setID] <-! newSet
443    SNKRHUDNFT.totalSets = SNKRHUDNFT.totalSets + 1
444    return setID
445  }
446
447  // Number of sets created by contract
448  pub fun setsCount(): UInt64 {
449    return SNKRHUDNFT.totalSets
450  }
451
452  // Metadata for the Set
453  pub struct SetMetadata {
454    pub var name: String
455    pub var description: String
456
457    // todo: rename
458    pub var externalID: String
459
460    init(name: String, description: String, externalID: String) {
461      self.name = name
462      self.description = description
463      self.externalID = externalID
464    }
465  }
466
467  // A summary report of a Set
468  pub struct SetReport {
469    pub let id: UInt64
470    pub let isLocked: Bool
471    pub let metadata: SetMetadata
472    pub let numTemplates: Int
473    pub let numMinted: UInt64
474    init(
475      id: UInt64,
476      isLocked: Bool,
477      metadata: SetMetadata,
478      numTemplates: Int,
479      numMinted: UInt64
480    ) {
481      self.id = id
482      self.isLocked = isLocked
483      self.metadata = metadata
484      self.numTemplates = numTemplates
485      self.numMinted = numMinted
486    }
487  }
488
489  // Generate a SetReport for informational purposes (to be used with scripts)
490  pub fun generateSetReport(setID: UInt64): SetReport {
491    let setRef = (&self.sets[setID] as &Set?)!
492    return SetReport(
493      id: setID,
494      isLocked: setRef.isLocked,
495      metadata: setRef.metadata,
496      numTemplates: setRef.templates.length,
497      numMinted: setRef.minted
498    )
499  }
500
501  // Template
502  //
503  // Templates are mechanisms for handling NFT metadata. These should ideally
504  // have a one to one mapping with NFTs, with the assumption that NFTs are
505  // designed to be unique. Template allows the creator to commit to an NFTs
506  // metadata without having to reveal the metadata itself. The constructor
507  // accepts a byte array checksum. After construction, anyone with access
508  // to this struct will be able to reveal the metadata, which must be any
509  // struct which implements TemplateMetadata and MetadataViews.Resolver such that
510  // SHA3_256(salt || metadata.hash()) == checksum.
511  //
512  // Templates can be seen as metadata managers for NFTs. As such, Templates
513  // also implement the MetadataResolver interface to conform with standards.
514
515  // Safe Template interface for anyone inspecting NFTs
516  pub struct interface NFTTemplate {
517    pub let defaultDisplay: MetadataViews.Display
518    pub var metadata: {TemplateMetadata}?
519    pub var mintID: UInt64?
520    pub fun checksum(): [UInt8]
521    pub fun salt(): [UInt8]?
522    pub fun revealed(): Bool
523    pub fun getViews(): [Type]
524    pub fun resolveView(_ view: Type): AnyStruct?
525  }
526
527  pub struct Template: NFTTemplate {
528
529    // checksum as described above
530    access(self) let _checksum: [UInt8]
531
532    // Default Display in case the Template has not yet been revealed
533    pub let defaultDisplay: MetadataViews.Display
534
535    // salt and metadata are optional so they can be revealed later, such that
536    // SHA3_256(salt || metadata.hash()) == checksum
537    access(self) var _salt: [UInt8]?
538    pub var metadata: {TemplateMetadata}?
539
540    // Convenience attribute to mark whether or not Template has minted NFT
541    pub var mintID: UInt64?
542
543    // Helper function to check if a proposed metadata and salt reveal would
544    // produce the configured checksum in a Template
545    pub fun validate(metadata: {TemplateMetadata}, salt: [UInt8]): Bool {
546      let hash = String.encodeHex(
547        HashAlgorithm.SHA3_256.hash(
548          salt.concat(metadata.hash())
549        )
550      )
551      let checksum = String.encodeHex(self.checksum())
552      return hash == checksum
553    }
554
555    // Reveal template metadata and salt. validate() is called as a precondition
556    // so collector can be assured metadata was not changed
557    pub fun reveal(metadata: AnyStruct{TemplateMetadata}, salt: [UInt8]) {
558      pre {
559        self.mintID != nil
560          : "Template has not yet been minted."
561        !self.revealed()
562          : "NFT Template has already been revealed"
563        self.validate(metadata: metadata, salt: salt)
564          : "salt || metadata.hash() does not hash to checksum"
565      }
566      self.metadata = metadata
567      self._salt = salt
568    }
569
570    pub fun checksum(): [UInt8] {
571      return self._checksum
572    }
573
574    pub fun salt(): [UInt8]? {
575      return self._salt
576    }
577
578    // Check to see if metadata has been revealed
579    pub fun revealed(): Bool {
580      return self.metadata != nil
581    }
582
583    // Mark the NFT as minted
584    pub fun markMinted(nftID: UInt64) {
585      self.mintID = nftID
586    }
587
588    // Implements MetadataResolver.getViews
589    pub fun getViews(): [Type] {
590      if (!self.revealed()) {
591        return [Type<MetadataViews.Display>()]
592      }
593      return self.metadata!.getViews()
594    }
595
596    // Implements MetadataResolver.resolveView
597    pub fun resolveView(_ view: Type): AnyStruct? {
598      if (!self.revealed()) {
599        if (view != Type<MetadataViews.Display>()) {
600          return nil
601        }
602        return self.defaultDisplay
603      }
604
605      if (view == Type<MetadataViews.Serial>() ) {
606        return MetadataViews.Serial( self.mintID! )
607      }
608      
609      return self.metadata!.resolveView(view)
610    }
611
612    init(checksum: [UInt8], defaultDisplay: MetadataViews.Display) {
613      self._checksum = checksum
614      self.defaultDisplay = defaultDisplay
615
616      self._salt = nil
617      self.metadata = nil
618      self.mintID = nil
619    }
620  }
621
622  // Public helper function to be able to inspect any Template
623  pub fun getTemplate(setID: UInt64, templateID: UInt64): {NFTTemplate} {
624    let setRef = (&self.sets[setID] as &Set?)!
625    return setRef.templates[templateID]
626  }
627
628  pub resource SetMinter {
629    pub let setID: UInt64
630
631    init(setID: UInt64) {
632      self.setID = setID
633    }
634
635    pub fun mint(templateID: UInt64, creator: Address): @NFT {
636      let set = (&SNKRHUDNFT.sets[self.setID] as &Set?)!
637      return <- set.mint(templateID: templateID, creator: creator)
638    }
639  }
640
641  // Admin
642  //
643  // The Admin is meant to be a singleton superuser of the contract. The Admin
644  // is responsible for creating Sets and SetManagers for managing the sets.
645  pub resource Admin {
646
647    // Create a set with the provided SetMetadata.
648    pub fun createSet(metadata: SetMetadata): UInt64 {
649      return SNKRHUDNFT.createSet(metadata: metadata)
650    }
651
652    pub fun borrowSet(setID: UInt64): &Set {
653      return (&SNKRHUDNFT.sets[setID] as &Set?)!
654    }
655
656    pub fun createSetMinter(setID: UInt64): @SetMinter {
657      return <- create SetMinter(setID: setID)
658    }
659  }
660
661  // Contract constructor
662  init() {
663
664    // Collection Paths
665    self.CollectionStoragePath = /storage/SNKRHUDNFTCollection
666    self.CollectionPublicPath = /public/SNKRHUDNFTCollection
667    self.CollectionPrivatePath = /private/SNKRHUDNFTCollection
668
669    // Admin Storage Path. Save the singleton Admin resource to contract
670    // storage.
671    self.AdminStoragePath = /storage/SNKRHUDNFTAdmin
672    self.account.save<@Admin>(<- create Admin(), to: self.AdminStoragePath)
673
674    // Initializations
675    self.totalSupply = 0
676    self.totalSets = 0
677    self.sets <- {}
678
679    emit ContractInitialized()
680  }
681}
682