Smart Contract
ZeedzINO
A.62b3063fbe672fc8.ZeedzINO
1import NonFungibleToken from 0x1d7e57aa55817448
2import MetadataViews from 0x1d7e57aa55817448
3import ViewResolver from 0x1d7e57aa55817448
4/*
5 Description: Central Smart Contract for the first generation of Zeedle NFTs
6
7 Zeedles are cute little nature-inspired monsters that grow with the real world weather.
8 They are the main characters of Zeedz, the first play-for-purpose game where players
9 reduce global carbon emissions by growing Zeedles.
10
11 This smart contract encompasses the main functionality for the first generation
12 of Zeedle NFTs.
13
14 Oriented much on the standard NFT contract, each Zeedle NFT has a certain typeID,
15 which is the type of Zeedle - e.g. "Baby Aloe Vera" or "Ginger Biggy". A contract-level
16 dictionary takes account of the different quentities that have been minted per Zeedle type.
17
18 Different types also imply different rarities, and these are also hardcoded inside
19 the given Zeedle NFT in order to allow the direct querying of the Zeedle's rarity
20 in external applications and wallets.
21
22 Each batch-minting of Zeedles is resembled by an edition number, with the community pre-sale
23 being the first-ever edition (0). This way, each Zeedle can be traced back to the edition it
24 was created in, and the number of minted Zeedles of that type in the specific edition.
25
26 Many of the in-game purchases lead to real-world donations to NGOs focused on climate action.
27 The carbonOffset attribute of a Zeedle proves the impact the in-game purchases related to this Zeedle
28 have already made with regards to reducing greenhouse gases. This value is computed by taking the
29 current dollar-value of each purchase at the time of the purchase, and applying the dollar-to-CO2-offset
30 formular of the current climate action partner.
31*/
32access(all) contract ZeedzINO: NonFungibleToken {
33
34 // Events
35 access(all) event ContractInitialized()
36 access(all) event Withdraw(id: UInt64, from: Address?)
37 access(all) event Deposit(id: UInt64, to: Address?)
38 access(all) event Minted(id: UInt64, name: String, description: String, typeID: UInt32, serialNumber: String, edition: UInt32, rarity: String)
39 access(all) event Evolved(oldID: UInt64, newID: UInt64, name: String, description: String, typeID: UInt32, serialNumber: String, edition: UInt32, rarity: String, carbonOffset: UInt64)
40 access(all) event Burned(id: UInt64, from: Address?)
41 access(all) event Offset(id: UInt64, amount: UInt64)
42 access(all) event Redeemed(id: UInt64, message: String, from: Address?)
43
44 // Named Paths
45 access(all) let CollectionStoragePath: StoragePath
46 access(all) let CollectionPublicPath: PublicPath
47 access(all) let AdminStoragePath: StoragePath
48
49 access(all) var totalSupply: UInt64
50
51 access(contract) var numberMintedPerType: {UInt32: UInt64}
52
53 access(all) resource NFT: NonFungibleToken.NFT {
54 // The token's ID
55 access(all) let id: UInt64
56 // The memorable short name for the Zeedle, e.g. “Baby Aloe"
57 access(all) let name: String
58 // A short description of the Zeedle's type
59 access(all) let description: String
60 // Number id of the Zeedle type -> e.g "1 = Ginger Biggy, 2 = Baby Aloe, etc”
61 access(all) let typeID: UInt32
62 // A Zeedle's unique serial number from the Zeedle's edition
63 access(all) let serialNumber: String
64 // Number id of the Zeedle's edition -> e.g "1 = first edition, 2 = second edition, etc"
65 access(all) let edition: UInt32
66 // The total number of Zeedle's minted in this edition
67 access(all) let editionCap: UInt32
68 // The Zeedle's evolutionary stage
69 access(all) let evolutionStage: UInt32
70 // The Zeedle's rarity -> e.g "RARE, COMMON, LEGENDARY, etc"
71 access(all) let rarity: String
72 // URI to the image of the Zeedle
73 access(all) let imageURI: String
74 // The total amount this Zeedle has contributed to offsetting CO2 emissions
75 access(all) var carbonOffset: UInt64
76
77 init(initID: UInt64, initName: String, initDescription: String, initTypeID: UInt32, initSerialNumber: String, initEdition: UInt32, initEditionCap: UInt32, initEvolutionStage: UInt32, initRarity: String, initImageURI: String, initCarbonOffset: UInt64) {
78 self.id = initID
79 self.name = initName
80 self.description = initDescription
81 self.typeID = initTypeID
82 self.serialNumber = initSerialNumber
83 self.edition = initEdition
84 self.editionCap = initEditionCap
85 self.evolutionStage = initEvolutionStage
86 self.rarity = initRarity
87 self.imageURI = initImageURI
88 self.carbonOffset = initCarbonOffset
89 }
90
91 access(all) view fun getViews(): [Type] {
92 return [
93 Type<MetadataViews.Display>(),
94 Type<MetadataViews.ExternalURL>(),
95 Type<MetadataViews.NFTCollectionData>(),
96 Type<MetadataViews.NFTCollectionDisplay>(),
97 Type<MetadataViews.Royalties>(),
98 Type<MetadataViews.Traits>(),
99 Type<MetadataViews.Rarity>()
100 ]
101 }
102
103 access(all) view fun resolveView(_ view: Type): AnyStruct? {
104 switch view {
105 case Type<MetadataViews.Display>():
106 return MetadataViews.Display(
107 name: self.name,
108 description: self.description,
109 thumbnail: MetadataViews.IPFSFile(
110 cid: self.imageURI,
111 path: nil
112 )
113 )
114 case Type<MetadataViews.ExternalURL>():
115 return MetadataViews.ExternalURL("https://play.zeedz.io/")
116 case Type<MetadataViews.Royalties>():
117 return MetadataViews.Royalties([])
118 case Type<MetadataViews.Traits>():
119 let editionTrait = MetadataViews.Trait(
120 name: "edition number",
121 value: self.edition,
122 displayType: nil,
123 rarity: nil)
124 let editionCapTrait = MetadataViews.Trait(
125 name: "total minted in edition",
126 value: self.editionCap,
127 displayType: nil,
128 rarity: nil)
129 let serialNumberTrait = MetadataViews.Trait(
130 name: "serial number",
131 value: self.serialNumber,
132 displayType: nil,
133 rarity: nil)
134 let evolutionStageTrait = MetadataViews.Trait(
135 name: "evolution stage",
136 value: self.evolutionStage,
137 displayType: nil,
138 rarity: nil)
139 let carbonOffsetTrait = MetadataViews.Trait(
140 name: "carbon offset",
141 value: self.carbonOffset,
142 displayType: nil,
143 rarity: nil)
144 return MetadataViews.Traits([
145 editionTrait,
146 editionCapTrait,
147 serialNumberTrait,
148 evolutionStageTrait,
149 carbonOffsetTrait
150 ])
151 case Type<MetadataViews.Rarity>():
152 var rarityScore = 0.0
153 let maxRarityScore = 5.0
154 switch (self.rarity) {
155 case "COMMON":
156 rarityScore = 1.0
157 case "RARE":
158 rarityScore = 2.0
159 case "EPIC":
160 rarityScore = 3.0
161 case "LEGENDARY":
162 rarityScore = 4.0
163 case "CUSTOM":
164 rarityScore = 5.0
165 default:
166 rarityScore = 0.0
167 }
168 return MetadataViews.Rarity(score: rarityScore, max: maxRarityScore, description: self.rarity)
169 }
170 return ZeedzINO.resolveContractView(resourceType: Type<@ZeedzINO.NFT>(), viewType: view)
171 }
172
173 access(all) fun getMetadata(): {String: AnyStruct} {
174 return {"id": self.id, "name": self.name, "description": self.description, "typeID": self.typeID, "serialNumber": self.serialNumber, "edition": self.edition, "editionCap": self.editionCap, "evolutionStage": self.evolutionStage, "rarity": self.rarity, "imageURI": self.imageURI, "carbonOffset": self.carbonOffset}
175 }
176
177 access(contract) fun increaseOffset(amount: UInt64) {
178 self.carbonOffset = self.carbonOffset + amount
179 }
180
181 access(contract) fun changeOffset(offset: UInt64) {
182 self.carbonOffset = offset
183 }
184
185 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
186 return <- ZeedzINO.createEmptyCollection(nftType: Type<@ZeedzINO.NFT>())
187 }
188 }
189
190 //
191 // This is the interface that users can cast their Zeedz Collection as
192 // to allow others to deposit Zeedles into their Collection. It also allows for reading
193 // the details of Zeedles in the Collection.
194 //
195 access(all) resource interface ZeedzCollectionPublic {
196 access(all) view fun borrowZeedle(id: UInt64): &ZeedzINO.NFT?
197 }
198
199 //
200 // This is the interface that users can cast their Zeedz Collection as
201 // to allow themselves to call the burn function on their own collection.
202 //
203 access(all) resource interface ZeedzCollectionPrivate {
204 access(NonFungibleToken.Withdraw) fun burn(burnID: UInt64)
205 access(NonFungibleToken.Withdraw) fun redeem(redeemID: UInt64, message: String)
206 }
207
208 //
209 // A collection of Zeedz NFTs owned by an account.
210 //
211 access(all) resource Collection: ZeedzCollectionPublic, ZeedzCollectionPrivate, NonFungibleToken.Collection {
212
213 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
214
215 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
216 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("Not able to find specified NFT within the owner's collection")
217 emit Withdraw(id: token.id, from: self.owner?.address)
218 return <-token
219 }
220
221 access(NonFungibleToken.Withdraw) fun burn(burnID: UInt64){
222 let token <- self.ownedNFTs.remove(key: burnID) ?? panic("Not able to find specified NFT within the owner's collection")
223 let zeedle <- token as! @ZeedzINO.NFT
224 // reduce numberOfMinterPerType
225 ZeedzINO.numberMintedPerType[zeedle.typeID] = ZeedzINO.numberMintedPerType[zeedle.typeID]! - 1
226
227 destroy zeedle
228 emit Burned(id: burnID, from: self.owner?.address)
229 }
230
231 access(NonFungibleToken.Withdraw) fun redeem(redeemID: UInt64, message: String){
232 let token <- self.ownedNFTs.remove(key: redeemID) ?? panic("Not able to find specified NFT within the owner's collection")
233 let zeedle <- token as! @ZeedzINO.NFT
234 // reduce numberOfMinterPerType
235 ZeedzINO.numberMintedPerType[zeedle.typeID] = ZeedzINO.numberMintedPerType[zeedle.typeID]! - 1
236
237 destroy zeedle
238 emit Redeemed(id: redeemID, message: message, from: self.owner?.address)
239 }
240
241 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
242 let token <- token as! @ZeedzINO.NFT
243 let id: UInt64 = token.id
244 // add the new token to the dictionary which removes the old one
245 let oldToken <- self.ownedNFTs[id] <- token
246 emit Deposit(id: id, to: self.owner?.address)
247 destroy oldToken
248 }
249
250 //
251 // Returns an array of the IDs that are in the collection.
252 //
253 access(all) view fun getIDs(): [UInt64] {
254 return self.ownedNFTs.keys
255 }
256
257 //
258 // Gets a reference to an NFT in the collection
259 // so that the caller can read its metadata and call its methods.
260 //
261 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
262 return (&self.ownedNFTs[id])
263 }
264
265 //
266 // Gets a reference to an NFT in the collection as a Zeed,
267 // exposing all of its fields
268 // this is safe as there are no functions that can be called on the Zeed.
269 //
270 access(all) view fun borrowZeedle(id: UInt64): &ZeedzINO.NFT? {
271 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?) as! &ZeedzINO.NFT?
272 }
273
274 //
275 // Gets a reference to an NFT in the collection as a ViewResolver,
276 // exposing all of its fields
277 // this is safe as there are no functions that can be called on the ViewResolver.
278 //
279 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
280 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?) as! &NFT? as? &{ViewResolver.Resolver}
281 }
282
283 //
284 // Returns the number of NFTs in the collection.
285 //
286 access(all) view fun getLength(): Int {
287 return self.ownedNFTs.keys.length
288 }
289
290 //
291 // Creates a new empty collection.
292 //
293 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
294 return <- create Collection()
295 }
296
297 //
298 // Returns the supported NFT types for the collection
299 //
300 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
301 let supportedTypes: {Type: Bool} = {}
302 supportedTypes[Type<@ZeedzINO.NFT>()] = true
303 return supportedTypes
304 }
305
306 //
307 // Returns whether or not the given type is accepted by the collection
308 //
309 access(all) view fun isSupportedNFTType(type: Type): Bool {
310 if type == Type<@ZeedzINO.NFT>() {
311 return true
312 } else {
313 return false
314 }
315 }
316
317 init () {
318 self.ownedNFTs <- {}
319 }
320 }
321
322 //
323 // Public function that anyone can call to create a new empty collection.
324 //
325 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
326 return <- create Collection()
327 }
328
329 // Admin entitlement
330 access(all) entitlement Admin
331
332 //
333 // The Administrator resource that an Administrator or something similar
334 // would own to be able to mint & level-up NFT's.
335 //
336 access(all) resource Administrator {
337
338 //
339 // Mints a new NFT with a new ID
340 // and deposit it in the recipients collection using their collection reference.
341 //
342 access(Admin) fun mintNFT(recipient: &{NonFungibleToken.Collection}, name: String, description: String, typeID: UInt32, serialNumber: String, edition: UInt32, editionCap: UInt32, evolutionStage: UInt32, rarity: String, imageURI: String) {
343 // Create the NFT
344 recipient.deposit(
345 token: <-create ZeedzINO.NFT(
346 initID: ZeedzINO.totalSupply,
347 initName: name,
348 initDescription: description,
349 initTypeID: typeID,
350 initSerialNumber: serialNumber,
351 initEdition: edition,
352 initEditionCap: editionCap,
353 initEvolutionStage: evolutionStage,
354 initRarity: rarity,
355 initImageURI: imageURI,
356 initCarbonOffset: 0)
357 )
358
359 // Emit event
360 emit Minted(id: ZeedzINO.totalSupply, name: name, description: description, typeID: typeID, serialNumber: serialNumber, edition: edition, rarity: rarity)
361
362 // increase numberOfMinterPerType and totalSupply
363 ZeedzINO.totalSupply = ZeedzINO.totalSupply + 1
364 if ZeedzINO.numberMintedPerType[typeID] == nil {
365 ZeedzINO.numberMintedPerType[typeID] = 1
366 } else {
367 ZeedzINO.numberMintedPerType[typeID] = ZeedzINO.numberMintedPerType[typeID]! + 1
368 }
369 }
370
371 //
372 // Increase the Zeedle's total carbon offset by the given amount
373 //
374 access(Admin) fun increaseOffset(zeedleRef: &ZeedzINO.NFT, amount: UInt64) {
375 zeedleRef.increaseOffset(amount: amount)
376 emit Offset(id: zeedleRef.id, amount: zeedleRef.carbonOffset)
377 }
378
379 //
380 // Change the Zeedle's total carbon offset to the given amount
381 //
382 access(Admin) fun changeOffset(zeedleRef: &ZeedzINO.NFT, offset: UInt64) {
383 zeedleRef.changeOffset(offset: offset)
384 emit Offset(id: zeedleRef.id, amount: offset)
385 }
386
387 access(Admin) fun evolve(zeedle: @ZeedzINO.NFT?, nextEvolutionMetadata: {String: String}): @ZeedzINO.NFT {
388 let evolvedZeedle <-create ZeedzINO.NFT(
389 initID: ZeedzINO.totalSupply,
390 initName: nextEvolutionMetadata["name"]!,
391 initDescription: nextEvolutionMetadata["description"]!,
392 initTypeID: UInt32.fromString(nextEvolutionMetadata["typeID"]!)!,
393 initSerialNumber: zeedle?.serialNumber != nil ? zeedle?.serialNumber! : nextEvolutionMetadata["serialNumber"]!,
394 initEdition: zeedle?.edition != nil ? zeedle?.edition! : 99,
395 initEditionCap: zeedle?.editionCap != nil ? zeedle?.editionCap! : 0,
396 initEvolutionStage: UInt32.fromString(nextEvolutionMetadata["evolutionStage"]!)!,
397 initRarity: nextEvolutionMetadata["rarity"]!,
398 initImageURI:nextEvolutionMetadata["imageURI"]!,
399 initCarbonOffset: zeedle?.carbonOffset != nil ? zeedle?.carbonOffset! : 0)
400
401 // increase numberOfMinterPerType and totalSupply
402 ZeedzINO.totalSupply = ZeedzINO.totalSupply + 1
403 if ZeedzINO.numberMintedPerType[evolvedZeedle.typeID] == nil {
404 ZeedzINO.numberMintedPerType[evolvedZeedle.typeID] = 1
405 } else {
406 ZeedzINO.numberMintedPerType[evolvedZeedle.typeID] = ZeedzINO.numberMintedPerType[evolvedZeedle.typeID]! + 1
407 }
408
409 emit Evolved(oldID: zeedle?.id != nil ? zeedle?.id! : 0, newID: evolvedZeedle.id, name: evolvedZeedle.name, description: evolvedZeedle.description, typeID: evolvedZeedle.typeID, serialNumber: evolvedZeedle.serialNumber, edition: evolvedZeedle.edition, rarity: evolvedZeedle.rarity, carbonOffset: evolvedZeedle.carbonOffset)
410
411 destroy zeedle
412
413 return <- evolvedZeedle
414 }
415 }
416
417 //
418 // Get a reference to a Zeedle from an account's Collection, if available.
419 // If an account does not have a Zeedz.Collection, panic.
420 // If it has a collection but does not contain the zeedleId, return nil.
421 // If it has a collection and that collection contains the zeedleId, return a reference to that.
422 //
423 access(all) fun fetch(_ from: Address, zeedleID: UInt64): &ZeedzINO.NFT? {
424 let capability = getAccount(from).capabilities.get<&ZeedzINO.Collection>(ZeedzINO.CollectionPublicPath)!
425 if capability.check() {
426 let collection = capability.borrow()
427 return collection!.borrowZeedle(id: zeedleID)
428 } else {
429 return nil
430 }
431 }
432
433 //
434 // Returns the number of minted Zeedles for each Zeedle type.
435 //
436 access(all) fun getMintedPerType(): {UInt32: UInt64} {
437 return self.numberMintedPerType
438 }
439
440 access(all) view fun getContractViews(resourceType: Type?): [Type] {
441 return [
442 Type<MetadataViews.NFTCollectionData>(),
443 Type<MetadataViews.NFTCollectionDisplay>()
444 ]
445 }
446
447 access(all) view fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
448 switch viewType {
449 case Type<MetadataViews.NFTCollectionData>():
450 let collectionRef = self.account.storage.borrow<&ZeedzINO.Collection>(
451 from: ZeedzINO.CollectionStoragePath
452 ) ?? panic("Could not borrow a reference to the stored collection")
453 let collectionData = MetadataViews.NFTCollectionData(
454 storagePath: ZeedzINO.CollectionStoragePath,
455 publicPath: ZeedzINO.CollectionPublicPath,
456 publicCollection: Type<&ZeedzINO.Collection>(),
457 publicLinkedType: Type<&ZeedzINO.Collection>(),
458 createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
459 return <-ZeedzINO.createEmptyCollection(nftType: Type<@ZeedzINO.NFT>())
460 })
461 )
462 return collectionData
463 case Type<MetadataViews.NFTCollectionDisplay>():
464 let mediaSquare = MetadataViews.Media(
465 file: MetadataViews.HTTPFile(
466 url: "https://play.zeedz.io/logo-zeedz.svg"
467 ),
468 mediaType: "image/svg+xml"
469 )
470 let mediaBanner = MetadataViews.Media(
471 file: MetadataViews.HTTPFile(
472 url: "https://play.zeedz.io/background-zeedz.jpg"
473 ),
474 mediaType: "image/png"
475 )
476 let socials = {
477 "twitter": MetadataViews.ExternalURL("https://twitter.com/zeedz_official"),
478 "discord": MetadataViews.ExternalURL("http://discord.com/invite/zeedz"),
479 "linkedin": MetadataViews.ExternalURL("https://www.linkedin.com/company/zeedz"),
480 "medium": MetadataViews.ExternalURL("https://blog.zeedz.io/"),
481 "instagram": MetadataViews.ExternalURL("https://www.instagram.com/zeedz_official/"),
482 "youtube": MetadataViews.ExternalURL("https://www.youtube.com/c/zeedz_official")
483 }
484 return MetadataViews.NFTCollectionDisplay(
485 name: "Zeedz",
486 description: "Zeedz is the first play-for-purpose game where players reduce global carbon emissions by collecting plant-inspired creatures: Zeedles. They live as NFTs on an eco-friendly blockchain (Flow) and grow with the real-world weather.",
487 externalURL: MetadataViews.ExternalURL("https://play.zeedz.io"),
488 squareImage: mediaSquare,
489 bannerImage: mediaBanner,
490 socials: socials
491 )
492 }
493 return nil
494 }
495
496
497 init() {
498 self.CollectionStoragePath = /storage/ZeedzINOCollection
499 self.CollectionPublicPath = /public/ZeedzINOCollection
500 self.AdminStoragePath = /storage/ZeedzINOMinter
501
502 self.totalSupply = 0
503 self.numberMintedPerType = {}
504
505 // Create a Admin resource and save it to storage
506 self.account.storage.save(<- create Administrator(), to: self.AdminStoragePath)
507 let cap = self.account.capabilities.storage.issue<&Administrator>(self.AdminStoragePath)
508
509 emit ContractInitialized()
510 }
511}
512