Smart Contract
Gaia
A.8b148183c28ff88f.Gaia
1import NonFungibleToken from 0x1d7e57aa55817448
2import FungibleToken from 0xf233dcee88fe0abe
3import MetadataViews from 0x1d7e57aa55817448
4import ViewResolver from 0x1d7e57aa55817448
5
6// Gaia
7// NFT an open NFT standard!
8//
9access(all) contract Gaia: ViewResolver, NonFungibleToken {
10
11 // entitlement for all previous Admin resources/functions
12 access(all) entitlement Owner
13
14 access(all) entitlement Mint
15
16 // Events
17 //
18 access(all) event ContractInitialized()
19 access(all) event Withdraw(id: UInt64, from: Address?)
20 access(all) event Deposit(id: UInt64, to: Address?)
21 access(all) event TemplateCreated(id: UInt64, metadata: {String:String})
22 access(all) event SetCreated(setID: UInt64, name: String, description: String, website: String, imageURI: String, creator: Address, marketFee: UFix64)
23 access(all) event SetAddedAllowedAccount(setID: UInt64, allowedAccount: Address)
24 access(all) event SetRemovedAllowedAccount(setID: UInt64, allowedAccount: Address)
25 access(all) event TemplateAddedToSet(setID: UInt64, templateID: UInt64)
26 access(all) event TemplateLockedFromSet(setID: UInt64, templateID: UInt64, numNFTs: UInt64)
27 access(all) event SetLocked(setID: UInt64)
28 access(all) event Minted(assetID: UInt64, templateID: UInt64, setID: UInt64, mintNumber: UInt64)
29
30 /// Storage and Public Paths
31 access(all) let CollectionStoragePath: StoragePath
32 access(all) let CollectionPublicPath: PublicPath
33
34 // Variable size dictionary of Play structs
35 access(self) var templateDatas: {UInt64: Template}
36
37 // Variable size dictionary of SetData structs
38 access(self) var setDatas: {UInt64: SetData}
39
40 // Variable size dictionary of Set resources
41 access(self) var sets: @{UInt64: Set}
42
43 // totalSupply
44 // The total number of Gaia that have been minted
45 //
46 access(all) var totalSupply: UInt64
47
48 // The ID that is used to create Templates.
49 // Every time a Template is created, templateID is assigned
50 // to the new Template's ID and then is incremented by 1.
51 access(all) var nextTemplateID: UInt64
52
53 // The ID that is used to create Sets. Every time a Set is created
54 // setID is assigned to the new set's ID and then is incremented by 1.
55 access(all) var nextSetID: UInt64
56
57 access(all) fun royaltyAddress(setName: String): Address {
58 return setName == "Ballerz" || setName == "Sneakerz" ? 0x01 : 0x9eef2e4511390ce4
59 }
60
61 // -----------------------------------------------------------------------
62 // Gaia contract-level Composite Type definitions
63 // -----------------------------------------------------------------------
64 // These are just *definitions* for Types that this contract
65 // and other accounts can use. These definitions do not contain
66 // actual stored values, but an instance (or object) of one of these Types
67 // can be created by this contract that contains stored values.
68 // -----------------------------------------------------------------------
69
70 // Template is a Struct that holds metadata associated
71 // with a specific NFT template
72 // NFTs will all reference a single template as the owner of
73 // its metadata. The templates are publicly accessible, so anyone can
74 // read the metadata associated with a specific template ID
75 //
76 access(all) struct Template {
77
78 // The unique ID for the template
79 access(all) let templateID: UInt64
80
81 // Stores all the metadata about the template as a string mapping
82 // This is not the long term way NFT metadata will be stored.
83 access(all) let metadata: {String: String}
84
85 init(metadata: {String: String}) {
86 pre {
87 metadata.length != 0: "New Template metadata cannot be empty"
88 }
89 self.templateID = Gaia.nextTemplateID
90 self.metadata = metadata
91
92 // Increment the ID so that it isn't used again
93 Gaia.nextTemplateID = Gaia.nextTemplateID + 1
94
95 emit TemplateCreated(id: self.templateID, metadata: metadata)
96 }
97 }
98
99 // A Set is a grouping of Templates that have occured in the real world
100 // that make up a related group of collectibles, like sets of Magic cards.
101 // A Template can exist in multiple different sets.
102 // SetData is a struct that is stored in a field of the contract.
103 // Anyone can query the constant information
104 // about a set by calling various getters located
105 // at the end of the contract. Only the admin has the ability
106 // to modify any data in the private Set resource.
107 //
108 access(all) struct SetData {
109
110 // Unique ID for the Set
111 access(all) let setID: UInt64
112
113 // Name of the Set
114 access(all) let name: String
115
116 // Brief description of the Set
117 access(all) let description: String
118
119 // Set cover image
120 access(all) let imageURI: String
121
122 // Set website url
123 access(all) let website: String
124
125 // Set creator account address
126 access(all) let creator: Address
127
128 // Accounts allowed to mint
129 access(self) let allowedAccounts: [Address]
130
131 access(all) view fun returnAllowedAccounts(): [Address] {
132 return self.allowedAccounts
133 }
134
135 // Set marketplace fee
136 access(all) let marketFee: UFix64
137
138 init(name: String, description: String, website: String, imageURI: String, creator: Address, marketFee: UFix64) {
139 pre {
140 name.length > 0: "New set name cannot be empty"
141 description.length > 0: "New set description cannot be empty"
142 imageURI.length > 0: "New set imageURI cannot be empty"
143 creator != nil: "Creator must not be nil"
144 marketFee >= 0.0 && marketFee <= 0.15: "Market fee must be a number between 0.00 and 0.15"
145 }
146
147 self.setID = Gaia.nextSetID
148 self.name = name
149 self.description = description
150 self.website = website
151 self.imageURI = imageURI
152 self.creator = creator
153 self.allowedAccounts = [creator, Gaia.account.address]
154 self.marketFee = marketFee
155
156 // Increment the setID so that it isn't used again
157 Gaia.nextSetID = Gaia.nextSetID + 1
158 emit SetCreated(setID: self.setID, name: name, description: description, website: website, imageURI: imageURI, creator: creator, marketFee: marketFee)
159 }
160
161 access(Owner) fun addAllowedAccount(account: Address) {
162 pre {
163 !self.allowedAccounts.contains(account): "Account already allowed"
164 }
165
166 self.allowedAccounts.append(account)
167
168 emit SetAddedAllowedAccount(setID: self.setID, allowedAccount: account)
169 }
170
171 access(Owner) fun removeAllowedAccount(account: Address) {
172 pre {
173 self.creator != account: "Cannot remove set creator"
174 self.allowedAccounts.contains(account): "Not in allowed accounts"
175 }
176
177 var index = 0
178 for acc in self.allowedAccounts {
179 if (acc == account) {
180 self.allowedAccounts.remove(at: index)
181 break
182 }
183 index = index + 1
184 }
185
186 emit SetRemovedAllowedAccount(setID: self.setID, allowedAccount: account)
187 }
188 }
189
190 // Set is a resource type that contains the functions to add and remove
191 // Templates from a set and mint NFTs.
192 //
193 // It is stored in a private field in the contract so that
194 // the admin resource can call its methods.
195 //
196 // The admin can add Templates to a Set so that the set can mint NFTs
197 // that reference that template data.
198 // The NFTs that are minted by a Set will be listed as belonging to
199 // the Set that minted it, as well as the Template it references.
200 //
201 // Admin can also lock Templates from the Set, meaning that the lockd
202 // Template can no longer have NFTs minted from it.
203 //
204 // If the admin locks the Set, no more Templates can be added to it, but
205 // NFTs can still be minted.
206 //
207 // If lockAll() and lock() are called back-to-back,
208 // the Set is closed off forever and nothing more can be done with it.
209 access(all) resource Set {
210
211 // Unique ID for the set
212 access(all) let setID: UInt64
213
214 // Array of templates that are a part of this set.
215 // When a template is added to the set, its ID gets appended here.
216 // The ID does not get removed from this array when a templates is locked.
217 access(all) var templates: [UInt64]
218
219 // Map of template IDs that Indicates if a template in this Set can be minted.
220 // When a templates is added to a Set, it is mapped to false (not locked).
221 // When a templates is locked, this is set to true and cannot be changed.
222 access(all) var lockedTemplates: {UInt64: Bool}
223
224 // Indicates if the Set is currently locked.
225 // When a Set is created, it is unlocked
226 // and templates are allowed to be added to it.
227 // When a set is locked, templates cannot be added.
228 // A Set can never be changed from locked to unlocked,
229 // the decision to lock a Set it is final.
230 // If a Set is locked, templates cannot be added, but
231 // NFTs can still be minted from templates
232 // that exist in the Set.
233 access(all) var locked: Bool
234
235 // Mapping of Template IDs that indicates the number of NFTs
236 // that have been minted for specific Templates in this Set.
237 // When a NFT is minted, this value is stored in the NFT to
238 // show its place in the Set, eg. 13 of 60.
239 access(all) var numberMintedPerTemplate: {UInt64: UInt64}
240
241 init(name: String, description: String, website: String, imageURI: String, creator: Address, marketFee: UFix64)
242 {
243 self.setID = Gaia.nextSetID
244 self.templates = []
245 self.lockedTemplates = {}
246 self.locked = false
247 self.numberMintedPerTemplate = {}
248 // Create a new SetData for this Set and store it in contract storage
249 Gaia.setDatas[self.setID] = SetData(name: name, description: description, website: website, imageURI: imageURI, creator: creator, marketFee: marketFee)
250 }
251
252 // addTemplate adds a template to the set
253 //
254 // Parameters: templateID: The ID of the template that is being added
255 //
256 // Pre-Conditions:
257 // The template needs to be an existing template
258 // The Set needs to be not locked
259 // The template can't have already been added to the Set
260 //
261 access(Owner) fun addTemplate(templateID: UInt64) {
262 pre {
263 Gaia.templateDatas[templateID] != nil: "Cannot add the Template to Set: Template doesn't exist."
264 !self.locked: "Cannot add the template to the Set after the set has been locked."
265 self.numberMintedPerTemplate[templateID] == nil: "The template has already beed added to the set."
266 }
267
268 // Add the Play to the array of Plays
269 self.templates.append(templateID)
270
271 // Open the Play up for minting
272 self.lockedTemplates[templateID] = false
273
274 // Initialize the Moment count to zero
275 self.numberMintedPerTemplate[templateID] = 0
276
277 emit TemplateAddedToSet(setID: self.setID, templateID: templateID)
278 }
279
280 // addTemplates adds multiple templates to the Set
281 //
282 // Parameters: templateIDs: The IDs of the templates that are being added
283 //
284 access(Owner) fun addTemplates(templateIDs: [UInt64]){
285 for template in templateIDs {
286 self.addTemplate(templateID: template)
287 }
288 }
289
290 // retirePlay retires a Play from the Set so that it can't mint new Moments
291 //
292 // Parameters: playID: The ID of the Play that is being retired
293 //
294 // Pre-Conditions:
295 // The Play is part of the Set and not retired (available for minting).
296 //
297 access(Owner) fun lockTemplate(templateID: UInt64) {
298 pre {
299 self.lockedTemplates[templateID] != nil: "Cannot lock the template: Template doesn't exist in this set!"
300 }
301
302 if !self.lockedTemplates[templateID]! {
303 self.lockedTemplates[templateID] = true
304
305 emit TemplateLockedFromSet(setID: self.setID, templateID: templateID, numNFTs: self.numberMintedPerTemplate[templateID]!)
306 }
307 }
308
309 // lockAll lock all the templates in the Set
310 // Afterwards, none of the locked templates will be able to mint new NFTs
311 //
312 access(Owner) fun lockAll() {
313 for template in self.templates {
314 self.lockTemplate(templateID: template)
315 }
316 }
317
318 // lock() locks the Set so that no more Templates can be added to it
319 //
320 // Pre-Conditions:
321 // The Set should not be locked
322 access(Owner) fun lock() {
323 if !self.locked {
324 self.locked = true
325 emit SetLocked(setID: self.setID)
326 }
327 }
328
329 // mintNFT mints a new NFT and returns the newly minted NFT
330 //
331 // Parameters: templateID: The ID of the Template that the NFT references
332 //
333 // Pre-Conditions:
334 // The Template must exist in the Set and be allowed to mint new NFTs
335 //
336 // Returns: The NFT that was minted
337 //
338
339 access(Mint) fun mintNFT(templateID: UInt64): @NFT {
340 pre {
341 self.lockedTemplates[templateID] != nil: "Cannot mint the NFT: This template doesn't exist."
342 !self.lockedTemplates[templateID]!: "Cannot mint the NFT from this template: This template has been locked."
343 }
344
345 // Gets the number of NFTs that have been minted for this Template
346 // to use as this NFT's serial number
347 let numInTemplate = self.numberMintedPerTemplate[templateID]!
348
349 // Mint the new moment
350 let newNFT: @NFT <- create NFT(mintNumber: numInTemplate + 1,
351 templateID: templateID,
352 setID: self.setID)
353
354 // Increment the count of Moments minted for this Play
355 self.numberMintedPerTemplate[templateID] = numInTemplate + 1
356
357 return <-newNFT
358 }
359
360 // batchMintNFT mints an arbitrary quantity of NFTs
361 // and returns them as a Collection
362 //
363 // Parameters: templateID: the ID of the Template that the NFTs are minted for
364 // quantity: The quantity of NFTs to be minted
365 //
366 // Returns: Collection object that contains all the NFTs that were minted
367 //
368
369 access(all) fun batchMintNFT(templateID: UInt64, quantity: UInt64): @Collection {
370 let newCollection <- create Collection()
371
372 var i: UInt64 = 0
373 while i < quantity {
374 newCollection.deposit(token: <-self.mintNFT(templateID: templateID))
375 i = i + 1
376 }
377
378 return <-newCollection
379 }
380 }
381
382 access(all) struct NFTData {
383
384 // The ID of the Set that the Moment comes from
385 access(all) let setID: UInt64
386
387 // The ID of the Play that the Moment references
388 access(all) let templateID: UInt64
389
390 // The place in the edition that this Moment was minted
391 // Otherwise know as the serial number
392 access(all) let mintNumber: UInt64
393
394 init(setID: UInt64, templateID: UInt64, mintNumber: UInt64) {
395 self.setID = setID
396 self.templateID = templateID
397 self.mintNumber = mintNumber
398 }
399 }
400
401 access(contract) fun buildExternalURL(): MetadataViews.ExternalURL {
402 let baseURI = "https://flowty.io/collection/".concat(Gaia.account.address.toString())
403 return MetadataViews.ExternalURL(baseURI)
404 }
405
406 // NFT
407 // A Flow Asset as an NFT
408 //
409 access(all) resource NFT: NonFungibleToken.NFT{
410 // The token's ID
411 access(all) let id: UInt64
412 // Struct of NFT metadata
413 access(all) let data: NFTData
414
415 /// createEmptyCollection creates an empty Collection
416 /// and returns it to the caller so that they can own NFTs
417 /// @{NonFungibleToken.Collection}
418 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
419 return <-Gaia.createEmptyCollection(nftType: Type<@Gaia.NFT>())
420 }
421
422 access(all) view fun getViews(): [Type] {
423 return [
424 Type<MetadataViews.NFTView>(),
425 Type<MetadataViews.Display>(),
426 Type<MetadataViews.ExternalURL>(),
427 Type<MetadataViews.NFTCollectionDisplay>(),
428 Type<MetadataViews.NFTCollectionData>(),
429 Type<MetadataViews.Traits>(),
430 Type<MetadataViews.Royalties>(),
431 Type<MetadataViews.Serial>()
432 ]
433 }
434
435 access(contract) fun parseIPFSURICID(uri: String): String {
436 return uri.slice(from: 7, upTo: 53)
437 }
438
439 access(contract) fun parseIPFSURIPath(uri: String): String? {
440 return uri.length > 55 ? uri.slice(from: 54, upTo: uri.length) : nil
441 }
442
443 access(contract) fun parseThumbnail(img: String): {MetadataViews.File}? {
444 var file: {MetadataViews.File}? = nil
445
446 if img.slice(from: 0, upTo: 7) == "ipfs://" {
447 file = MetadataViews.IPFSFile(cid: self.parseIPFSURICID(uri: img), path: self.parseIPFSURIPath(uri: img))
448 } else {
449 file = MetadataViews.HTTPFile(url: img)
450 }
451
452 return file
453 }
454
455 access(contract) fun parseTraits(metadata: {String: String}, setData: SetData): MetadataViews.Traits? {
456 var traits: [MetadataViews.Trait] = []
457 var bypass: [String] = ["id", "name", "title", "description", "img", "url", "uri", "video", "editions", "series", "series_description", "set", "setID"]
458
459 for key in metadata.keys {
460 if bypass.contains(key) {
461 continue
462 }
463 traits.append(MetadataViews.Trait(name: key, value: metadata[key]!, displayType: nil, rarity: nil))
464 }
465
466 traits.append(MetadataViews.Trait(name: "setID", value: setData.setID.toString(), displayType: nil, rarity: nil))
467 traits.append(MetadataViews.Trait(name: "set", value: setData.name, displayType: nil, rarity: nil))
468
469 return MetadataViews.Traits(traits)
470 }
471
472 access(all) fun resolveView(_ view: Type): AnyStruct? {
473 var setData: SetData = Gaia.getSetInfo(setID: self.data.setID)!
474 var templateMetadata: {String: String} = Gaia.getTemplateMetaData(templateID: self.data.templateID)!
475 let url = "https://flowty.io/collection/".concat(Gaia.account.address.toString()).concat("/Gaia/").concat(self.id.toString())
476
477 switch view {
478 case Type<MetadataViews.NFTView>():
479 let viewResolver = &self as &{ViewResolver.Resolver}
480 return MetadataViews.NFTView(
481 id: self.id,
482 uuid: self.uuid,
483 display: MetadataViews.getDisplay(viewResolver),
484 externalURL: MetadataViews.getExternalURL(viewResolver),
485 collectionData: MetadataViews.getNFTCollectionData(viewResolver),
486 collectionDisplay: MetadataViews.getNFTCollectionDisplay(viewResolver),
487 royalties: MetadataViews.getRoyalties(viewResolver),
488 traits: MetadataViews.getTraits(viewResolver)
489 )
490 case Type<MetadataViews.Display>():
491 var name: String = setData.name == "Ballerz" ? "Baller #".concat(templateMetadata["id"]!) : templateMetadata["title"]!
492 var description: String = templateMetadata["description"]!
493 var thumbnail: {MetadataViews.File}? = self.parseThumbnail(img: templateMetadata["img"]!)
494 return MetadataViews.Display(name: name, description: description, thumbnail: thumbnail!)
495 case Type<MetadataViews.ExternalURL>():
496 return MetadataViews.ExternalURL(url)
497 case Type<MetadataViews.NFTCollectionData>():
498 return Gaia.resolveContractView(resourceType: Type<@Gaia.NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
499 case Type<MetadataViews.NFTCollectionDisplay>():
500 return Gaia.resolveContractView(resourceType: Type<@Gaia.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
501 case Type<MetadataViews.Traits>():
502 var metadata = Gaia.getTemplateMetaData(templateID: self.data.templateID)
503 return self.parseTraits(metadata: metadata!, setData: setData)
504 case Type<MetadataViews.Royalties>():
505 let royalties: [MetadataViews.Royalty] = []
506 let royaltyReceiverCap =
507 getAccount(Gaia.royaltyAddress(setName: setData.name)).capabilities.get<&{FungibleToken.Receiver}>(/public/dapperUtilityCoinReceiver)
508 if royaltyReceiverCap.check() == true {
509 royalties.append(
510 MetadataViews.Royalty(
511 receiver: royaltyReceiverCap,
512 cut: 0.05,
513 description: "Creator royalty fee."
514 )
515 )
516 }
517 return MetadataViews.Royalties(royalties)
518 case Type<MetadataViews.Serial>():
519 var metadata = Gaia.getTemplateMetaData(templateID: self.data.templateID)
520 let serial: UInt64? = (metadata != nil && metadata!.containsKey("id")) ? UInt64.fromString(metadata!["id"]!) : nil
521 return serial != nil ? MetadataViews.Serial(serial!) : nil
522 }
523 return nil
524 }
525
526 // initializer
527 //
528 init(mintNumber: UInt64, templateID: UInt64, setID: UInt64) {
529 // Increment the global Moment IDs
530 Gaia.totalSupply = Gaia.totalSupply + 1
531
532 self.id = Gaia.totalSupply
533
534 // Set the metadata struct
535 self.data = NFTData(setID: setID, templateID: templateID, mintNumber: mintNumber)
536
537 emit Minted(assetID: self.id, templateID: templateID, setID: self.data.setID, mintNumber: self.data.mintNumber)
538 }
539 }
540
541 // Admin is a special authorization resource that
542 // allows the owner to perform important functions to modify the
543 // various aspects of the Templates, Sets, and NFTs
544 //
545 access(all) resource Admin {
546
547 // createTemplate creates a new Template struct
548 // and stores it in the Templates dictionary in the TopShot smart contract
549 //
550 // Parameters: metadata: A dictionary mapping metadata titles to their data
551 // example: {"Name": "John Doe", "DoB": "4/14/1990"}
552 //
553 // Returns: the ID of the new Template object
554 //
555 access(Owner) fun createTemplate(metadata: {String: String}): UInt64 {
556 // Create the new Template
557 var newTemplate = Template(metadata: metadata)
558 let newID = newTemplate.templateID
559
560 // Store it in the contract storage
561 Gaia.templateDatas[newID] = newTemplate
562
563 return newID
564 }
565
566 access(Owner) fun createTemplates(templates: [{String: String}], setID: UInt64, authorizedAccount: auth(Owner) &Account){
567
568 var templateIDs: [UInt64] = []
569 for metadata in templates {
570 var ID = self.createTemplate(metadata: metadata)
571 templateIDs.append(ID)
572 }
573 self.borrowSet(setID: setID, authorizedAccount: authorizedAccount).addTemplates(templateIDs: templateIDs)
574 }
575
576 // createSet creates a new Set resource and stores it
577 // in the sets mapping in the contract
578 //
579 // Parameters: name: The name of the Set
580 //
581 access(Owner) fun createSet(name: String, description: String, website: String, imageURI: String, creator: Address, marketFee: UFix64) {
582 // Create the new Set
583 var newSet <- create Set(name: name, description: description, website: website, imageURI: imageURI, creator: creator, marketFee: marketFee)
584 // Store it in the sets mapping field
585 Gaia.sets[newSet.setID] <-! newSet
586 }
587
588 // borrowSet returns a reference to a set in the contract
589 // so that the admin can call methods on it
590 //
591 // Parameters: setID: The ID of the Set that you want to
592 // get a reference to
593 //
594 // Returns: A reference to the Set with all of the fields
595 // and methods exposed
596 //
597 access(Owner) fun borrowSet(setID: UInt64, authorizedAccount: auth(Owner) &Account): auth(Owner) &Set {
598 pre {
599 Gaia.sets[setID] != nil: "Cannot borrow Set: The Set doesn't exist"
600 Gaia.setDatas[setID]!.returnAllowedAccounts().contains(authorizedAccount.address): "Account not authorized"
601 }
602
603 // Get a reference to the Set and return it
604 // use `&` to indicate the reference to the object and type
605 return (&Gaia.sets[setID])!
606 }
607
608 // createNewAdmin creates a new Admin resource
609 //
610 access(Owner) fun createNewAdmin(): @Admin {
611 return <-create Admin()
612 }
613 }
614
615 // This is the interface that users can cast their Gaia Collection as
616 // to allow others to deposit Gaia into their Collection. It also allows for reading
617 // the details of Gaia in the Collection.
618 access(all) resource interface CollectionPublic: NonFungibleToken.Collection {
619 access(all) fun deposit(token: @{NonFungibleToken.NFT})
620 access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection})
621 access(all) view fun borrowGaiaNFT(id: UInt64): &Gaia.NFT? {
622 // If the result isn't nil, the id of the returned reference
623 // should be the same as the argument to the function
624 post {
625 (result == nil) || (result?.id == id):
626 "Cannot borrow GaiaAsset reference: The ID of the returned reference is incorrect"
627 }
628 }
629 }
630
631 // Collection
632 // A collection of GaiaAsset NFTs owned by an account
633 //
634 access(all) resource Collection: NonFungibleToken.Collection, CollectionPublic{
635 // dictionary of NFT conforming tokens
636 // NFT is a resource type with an `UInt64` ID field
637 //
638 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
639
640 /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
641 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
642 let supportedTypes: {Type: Bool} = {}
643 supportedTypes[Type<@Gaia.NFT>()] = true
644 return supportedTypes
645 }
646
647 /// Returns whether or not the given type is accepted by the collection
648 /// A collection that can accept any type should just return true by default
649 access(all) view fun isSupportedNFTType(type: Type): Bool {
650 return type == Type<@Gaia.NFT>()
651 }
652 // withdraw
653 // Removes an NFT from the collection and moves it to the caller
654
655 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
656 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
657
658 emit Withdraw(id: token.id, from: self.owner?.address)
659
660 return <-token
661 }
662
663
664 // batchWithdraw withdraws multiple tokens and returns them as a Collection
665 //
666 // Parameters: ids: An array of IDs to withdraw
667 //
668 // Returns: @NonFungibleToken.Collection: A collection that contains
669 // the withdrawn NFTs
670 //
671 access(NonFungibleToken.Withdraw) fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} {
672 // Create a new empty Collection
673 var batchCollection <- create Collection()
674
675 // Iterate through the ids and withdraw them from the Collection
676 for id in ids {
677 batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
678 }
679
680 // Return the withdrawn tokens
681 return <-batchCollection
682 }
683
684 // deposit
685 // Takes a NFT and adds it to the collections dictionary
686 // and adds the ID to the id array
687 //
688 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
689 let token <- token as! @Gaia.NFT
690
691 let id: UInt64 = token.id
692
693 // add the new token to the dictionary which removes the old one
694 let oldToken <- self.ownedNFTs[id] <- token
695
696 emit Deposit(id: id, to: self.owner?.address)
697
698 destroy oldToken
699 }
700
701 // batchDeposit takes a Collection object as an argument
702 // and deposits each contained NFT into this Collection
703 access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) {
704
705 // Get an array of the IDs to be deposited
706 let keys = tokens.getIDs()
707
708 // Iterate through the keys in the collection and deposit each one
709 for key in keys {
710 self.deposit(token: <-tokens.withdraw(withdrawID: key))
711 }
712
713 // Destroy the empty Collection
714 destroy tokens
715 }
716
717 // getIDs
718 // Returns an array of the IDs that are in the collection
719 //
720 access(all) view fun getIDs(): [UInt64] {
721 return self.ownedNFTs.keys
722 }
723
724 /// Gets the amount of NFTs stored in the collection
725 access(all) view fun getLength(): Int {
726 return self.ownedNFTs.length
727 }
728
729 // borrowNFT
730 // Gets a reference to an NFT in the collection
731 // so that the caller can read its metadata and call its methods
732 //
733 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
734 return &self.ownedNFTs[id]
735 }
736
737 // borrowGaiaNFT
738 // Gets a reference to an NFT in the collection as a GaiaAsset,
739 // exposing all of its fields (including the typeID).
740 // This is safe as there are no functions that can be called on the GaiaAsset.
741 //
742 access(all) view fun borrowGaiaNFT(id: UInt64): &Gaia.NFT? {
743 if self.ownedNFTs[id] != nil {
744 let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
745 return ref as! &Gaia.NFT
746 } else {
747 return nil
748 }
749 }
750
751 // borrowViewResolver
752 // Gets a reference to an NFT in the collection as a GaiaAsset,
753 // exposing all of its fields (including the typeID).
754 // This is safe as there are no functions that can be called on the GaiaAsset.
755 //
756 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver} {
757 let nft = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
758 return nft as! &Gaia.NFT
759 }
760
761 /// createEmptyCollection creates an empty Collection of the same type
762 /// and returns it to the caller
763 /// @return A an empty collection of the same type
764 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
765 return <-Gaia.createEmptyCollection(nftType: Type<@Gaia.NFT>())
766 }
767
768 // initializer
769 //
770 init () {
771 self.ownedNFTs <- {}
772 }
773 }
774
775 // createEmptyCollection
776 // public function that anyone can call to create a new empty collection
777 //
778 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
779 return <- create Collection()
780 }
781
782 access(contract) fun getCollectionSquareImage(): MetadataViews.Media {
783 return MetadataViews.Media(
784 file: MetadataViews.HTTPFile(url: "https://ballerz.com/images/onchain/logo-stack.png"),
785 mediaType: "image/jpeg"
786 )
787 }
788
789 access(contract) fun getCollectionBannerImage(): MetadataViews.Media {
790 return MetadataViews.Media(
791 file: MetadataViews.HTTPFile(url: "https://ballerz.com/images/onchain/logo.jpg"),
792 mediaType: "image/jpeg"
793 )
794 }
795
796
797
798 /// Function that returns all the Metadata Views implemented by a Non Fungible Token
799 ///
800 /// @return An array of Types defining the implemented views. This value will be used by
801 /// developers to know which parameter to pass to the resolveView() method.
802 ///
803 access(all) view fun getContractViews(resourceType: Type?): [Type] {
804 return [
805 Type<MetadataViews.NFTCollectionData>(),
806 Type<MetadataViews.NFTCollectionDisplay>()
807 ]
808 }
809
810 /// Function that resolves a metadata view for this contract.
811 ///
812 /// @param view: The Type of the desired view.
813 /// @return A structure representing the requested view.
814 ///
815
816 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
817 switch viewType {
818 case Type<MetadataViews.NFTCollectionData>():
819 return MetadataViews.NFTCollectionData(
820 storagePath: Gaia.CollectionStoragePath,
821 publicPath: Gaia.CollectionPublicPath,
822 publicCollection: Type<&{Gaia.CollectionPublic}>(),
823 publicLinkedType: Type<&{Gaia.CollectionPublic}>(),
824 createEmptyCollectionFunction: (
825 fun (): @{NonFungibleToken.Collection} {
826 return <- Gaia.createEmptyCollection(nftType: Type<@Gaia.NFT>())
827 }
828 )
829 )
830 case Type<MetadataViews.NFTCollectionDisplay>():
831 return MetadataViews.NFTCollectionDisplay(
832 name: "Ballerz",
833 description: "A basketball-inspired generative NFT living on the Flow blockchain",
834 externalURL: self.buildExternalURL(),
835 squareImage: self.getCollectionSquareImage(),
836 bannerImage: self.getCollectionBannerImage(),
837 socials: {
838 "twitter": MetadataViews.ExternalURL("https://twitter.com/@BALLERZ_NFT")
839 }
840 )
841 }
842 return nil
843
844 }
845
846 // getAllTemplates returns all the plays in topshot
847 //
848 // Returns: An array of all the plays that have been created
849 access(all) view fun getAllTemplates(): [Gaia.Template] {
850 return Gaia.templateDatas.values
851 }
852
853 // getTemplateMetaData returns all the metadata associated with a specific Template
854 //
855 // Parameters: templateID: The id of the Template that is being searched
856 //
857 // Returns: The metadata as a String to String mapping optional
858 access(all) view fun getTemplateMetaData(templateID: UInt64): {String: String}? {
859 return self.templateDatas[templateID]?.metadata
860 }
861
862 // getTemplateMetaDataByField returns the metadata associated with a
863 // specific field of the metadata
864 // Ex: field: "Name" will return something
865 // like "John Doe"
866 //
867 // Parameters: templateID: The id of the Template that is being searched
868 // field: The field to search for
869 //
870 // Returns: The metadata field as a String Optional
871 access(all) view fun getTemplateMetaDataByField(templateID: UInt64, field: String): String? {
872 // Don't force a revert if the playID or field is invalid
873 if let template = Gaia.templateDatas[templateID] {
874 return template.metadata[field]
875 } else {
876 return nil
877 }
878 }
879
880 // getSetName returns the name that the specified Set
881 // is associated with.
882 //
883 // Parameters: setID: The id of the Set that is being searched
884 //
885 // Returns: The name of the Set
886 access(all) view fun getSetName(setID: UInt64): String? {
887 // Don't force a revert if the setID is invalid
888 return Gaia.setDatas[setID]?.name
889 }
890
891 access(all) view fun getSetMarketFee(setID: UInt64): UFix64? {
892 // Don't force a revert if the setID is invalid
893 return Gaia.setDatas[setID]?.marketFee
894 }
895
896 access(all) view fun getSetImage(setID: UInt64): String? {
897 // Don't force a revert if the setID is invalid
898 return Gaia.setDatas[setID]?.imageURI
899 }
900
901 access(all) view fun getSetInfo(setID: UInt64): SetData? {
902 // Don't force a revert if the setID is invalid
903 return Gaia.setDatas[setID]
904 }
905
906 // getSetIDsByName returns the IDs that the specified Set name
907 // is associated with.
908 //
909 // Parameters: setName: The name of the Set that is being searched
910 //
911 // Returns: An array of the IDs of the Set if it exists, or nil if doesn't
912
913 access(all) fun getSetIDsByName(setName: String): [UInt64]? {
914 var setIDs: [UInt64] = []
915
916 // Iterate through all the setDatas and search for the name
917 for setData in Gaia.setDatas.values {
918 if setName == setData.name {
919 // If the name is found, return the ID
920 setIDs.append(setData.setID)
921 }
922 }
923
924 // If the name isn't found, return nil
925 // Don't force a revert if the setName is invalid
926 if setIDs.length == 0 {
927 return nil
928 } else {
929 return setIDs
930 }
931 }
932
933 // getTemplatesInSet returns the list of Template IDs that are in the Set
934 //
935 // Parameters: setID: The id of the Set that is being searched
936 //
937 // Returns: An array of Template IDs
938 access(all) view fun getTemplatesInSet(setID: UInt64): [UInt64]? {
939 // Don't force a revert if the setID is invalid
940 return Gaia.sets[setID]?.templates
941 }
942
943 // isSetTemplateLocked returns a boolean that indicates if a Set/Template combo
944 // is locked.
945 // If an template is locked, it still remains in the Set,
946 // but NFTs can no longer be minted from it.
947 //
948 // Parameters: setID: The id of the Set that is being searched
949 // playID: The id of the Play that is being searched
950 //
951 // Returns: Boolean indicating if the template is locked or not
952
953 access(all) fun isSetTemplateLocked(setID: UInt64, templateID: UInt64): Bool? {
954 // Don't force a revert if the set or play ID is invalid
955 // Remove the set from the dictionary to get its field
956 if let setToRead <- Gaia.sets.remove(key: setID) {
957
958 // See if the Play is retired from this Set
959 let locked = setToRead.lockedTemplates[templateID]
960
961 // Put the Set back in the contract storage
962 Gaia.sets[setID] <-! setToRead
963
964 // Return the retired status
965 return locked
966 } else {
967
968 // If the Set wasn't found, return nil
969 return nil
970 }
971 }
972
973 // isSetLocked returns a boolean that indicates if a Set
974 // is locked. If it's locked,
975 // new Plays can no longer be added to it,
976 // but NFTs can still be minted from Templates the set contains.
977 //
978 // Parameters: setID: The id of the Set that is being searched
979 //
980 // Returns: Boolean indicating if the Set is locked or not
981 access(all) view fun isSetLocked(setID: UInt64): Bool? {
982 // Don't force a revert if the setID is invalid
983 return Gaia.sets[setID]?.locked
984 }
985
986 // getTotalMinted return the number of NFTS that have been
987 // minted from a certain set and template.
988 //
989 // Parameters: setID: The id of the Set that is being searched
990 // templateID: The id of the Template that is being searched
991 //
992 // Returns: The total number of NFTs
993 // that have been minted from an set and template
994
995 access(all) fun getTotalMinted(setID: UInt64, templateID: UInt64): UInt64? {
996 // Don't force a revert if the Set or play ID is invalid
997 // Remove the Set from the dictionary to get its field
998 if let setToRead <- Gaia.sets.remove(key: setID) {
999
1000 // Read the numMintedPerPlay
1001 let amount = setToRead.numberMintedPerTemplate[templateID]
1002
1003 // Put the Set back into the Sets dictionary
1004 Gaia.sets[setID] <-! setToRead
1005
1006 return amount
1007 } else {
1008 // If the set wasn't found return nil
1009 return nil
1010 }
1011 }
1012
1013 // fetch
1014 // Get a reference to a GaiaAsset from an account's Collection, if available.
1015 // If an account does not have a Gaia.Collection, panic.
1016 // If it has a collection but does not contain the itemID, return nil.
1017 // If it has a collection and that collection contains the itemID, return a reference to that.
1018 //
1019
1020 access(all) fun fetch(_ from: Address, itemID: UInt64): &Gaia.NFT? {
1021 let collection = getAccount(from)
1022 .capabilities.get<&{NonFungibleToken.Collection}>(Gaia.CollectionPublicPath).borrow() ?? panic("Could not borrow the collection")
1023 let gaiaCollection = collection as! &{Gaia.CollectionPublic}
1024 // We trust Gaia.Collection.borowGaiaAsset to get the correct itemID
1025 // (it checks it before returning it).
1026 return gaiaCollection.borrowGaiaNFT(id: itemID)
1027 }
1028
1029 // checkSetup
1030 // Get a reference to a GaiaAsset from an account's Collection, if available.
1031 // If an account does not have a Gaia.Collection, returns false.
1032 // If it has a collection, return true.
1033 //
1034 access(all) fun checkSetup(_ address: Address): Bool {
1035 let cap = getAccount(address)
1036 .capabilities.get<&{NonFungibleToken.Collection}>(Gaia.CollectionPublicPath)
1037 return cap.check()
1038 }
1039
1040 // initializer
1041 //
1042 init() {
1043 // Set our named paths
1044 //FIXME: REMOVE SUFFIX BEFORE RELEASE
1045 self.CollectionStoragePath = /storage/GaiaCollection001
1046 self.CollectionPublicPath = /public/GaiaCollection001
1047
1048 // Initialize contract fields
1049 self.templateDatas = {}
1050 self.setDatas = {}
1051 self.sets <- {}
1052 self.nextTemplateID = 1
1053 self.nextSetID = 1
1054 self.totalSupply = 0
1055
1056 // Put a new Collection in storage
1057 self.account.storage.save<@Collection>(<- create Collection(), to: self.CollectionStoragePath)
1058
1059 // create a public capability for the collection
1060 let cap = self.account.capabilities.storage.issue<&{Gaia.CollectionPublic}>(self.CollectionStoragePath)
1061 self.account.capabilities.publish(cap, at: self.CollectionPublicPath)
1062
1063 // Put the Minter in storage
1064 self.account.storage.save<@Admin>(<- create Admin(), to: /storage/GaiaAdmin)
1065 }
1066}