Smart Contract
DriverzNFT
A.a039bd7d55a96c0c.DriverzNFT
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