Smart Contract

ZeedzINO

A.62b3063fbe672fc8.ZeedzINO

Deployed

1w ago
Feb 15, 2026, 02:19:36 PM UTC

Dependents

45 imports
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