Smart Contract

DriverzNFT

A.a039bd7d55a96c0c.DriverzNFT

Deployed

3d ago
Feb 25, 2026, 12:53:19 AM UTC

Dependents

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