Smart Contract

DriverzNFT

A.f887ece39166906e.DriverzNFT

Deployed

1d ago
Feb 26, 2026, 09:44:38 PM UTC

Dependents

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