Smart Contract

NastyGirlz

A.66b60643244a7738.NastyGirlz

Deployed

16h ago
Feb 28, 2026, 02:29:07 AM UTC

Dependents

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