Cadence Cookbook

Contribute

Create a TopShot Set

Create a TopShot Set

09 Oct 2022

Contributed by Flow Blockchain

Intermediate

Using the TopShot contract, this is how you would create a set so that you could add plays to them and mint moments from those plays.

Smart Contract Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 //TopShot Contract Code Above ... // Variable size dictionary of SetData structs access(self) var setDatas: {UInt32: SetData} // Variable size dictionary of Set resources access(self) var sets: @{UInt32: Set} // The ID that is used to create Sets. Every time a Set is created // setID is assigned to the new set's ID and then is incremented by 1. pub var nextSetID: UInt32 .... // A Set is a grouping of Plays that have occured in the real world // that make up a related group of collectibles, like sets of baseball // or Magic cards. A Play can exist in multiple different sets. // // SetData is a struct that is stored in a field of the contract. // Anyone can query the constant information // about a set by calling various getters located // at the end of the contract. Only the admin has the ability // to modify any data in the private Set resource. // pub struct SetData { // Unique ID for the Set pub let setID: UInt32 // Name of the Set // ex. "Times when the Toronto Raptors choked in the playoffs" pub let name: String // Series that this Set belongs to. // Series is a concept that indicates a group of Sets through time. // Many Sets can exist at a time, but only one series. pub let series: UInt32 init(name: String) { pre { name.length > 0: "New Set name cannot be empty" } self.setID = TopShot.nextSetID self.name = name self.series = TopShot.currentSeries } } .... pub resource Set { // Unique ID for the set pub let setID: UInt32 // Array of plays that are a part of this set. // When a play is added to the set, its ID gets appended here. // The ID does not get removed from this array when a Play is retired. access(contract) var plays: [UInt32] // Map of Play IDs that Indicates if a Play in this Set can be minted. // When a Play is added to a Set, it is mapped to false (not retired). // When a Play is retired, this is set to true and cannot be changed. access(contract) var retired: {UInt32: Bool} // Indicates if the Set is currently locked. // When a Set is created, it is unlocked // and Plays are allowed to be added to it. // When a set is locked, Plays cannot be added. // A Set can never be changed from locked to unlocked, // the decision to lock a Set it is final. // If a Set is locked, Plays cannot be added, but // Moments can still be minted from Plays // that exist in the Set. pub var locked: Bool // Mapping of Play IDs that indicates the number of Moments // that have been minted for specific Plays in this Set. // When a Moment is minted, this value is stored in the Moment to // show its place in the Set, eg. 13 of 60. access(contract) var numberMintedPerPlay: {UInt32: UInt32} init(name: String) { self.setID = TopShot.nextSetID self.plays = [] self.retired = {} self.locked = false self.numberMintedPerPlay = {} // Create a new SetData for this Set and store it in contract storage TopShot.setDatas[self.setID] = SetData(name: name) } // addPlay adds a play to the set // // Parameters: playID: The ID of the Play that is being added // // Pre-Conditions: // The Play needs to be an existing play // The Set needs to be not locked // The Play can't have already been added to the Set // pub fun addPlay(playID: UInt32) { pre { TopShot.playDatas[playID] != nil: "Cannot add the Play to Set: Play doesn't exist." !self.locked: "Cannot add the play to the Set after the set has been locked." self.numberMintedPerPlay[playID] == nil: "The play has already beed added to the set." } // Add the Play to the array of Plays self.plays.append(playID) // Open the Play up for minting self.retired[playID] = false // Initialize the Moment count to zero self.numberMintedPerPlay[playID] = 0 emit PlayAddedToSet(setID: self.setID, playID: playID) } // addPlays adds multiple Plays to the Set // // Parameters: playIDs: The IDs of the Plays that are being added // as an array // pub fun addPlays(playIDs: [UInt32]) { for play in playIDs { self.addPlay(playID: play) } } // retirePlay retires a Play from the Set so that it can't mint new Moments // // Parameters: playID: The ID of the Play that is being retired // // Pre-Conditions: // The Play is part of the Set and not retired (available for minting). // pub fun retirePlay(playID: UInt32) { pre { self.retired[playID] != nil: "Cannot retire the Play: Play doesn't exist in this set!" } if !self.retired[playID]! { self.retired[playID] = true emit PlayRetiredFromSet(setID: self.setID, playID: playID, numMoments: self.numberMintedPerPlay[playID]!) } } // retireAll retires all the plays in the Set // Afterwards, none of the retired Plays will be able to mint new Moments // pub fun retireAll() { for play in self.plays { self.retirePlay(playID: play) } } // lock() locks the Set so that no more Plays can be added to it // // Pre-Conditions: // The Set should not be locked pub fun lock() { if !self.locked { self.locked = true emit SetLocked(setID: self.setID) } } // mintMoment mints a new Moment and returns the newly minted Moment // // Parameters: playID: The ID of the Play that the Moment references // // Pre-Conditions: // The Play must exist in the Set and be allowed to mint new Moments // // Returns: The NFT that was minted // pub fun mintMoment(playID: UInt32): @NFT { pre { self.retired[playID] != nil: "Cannot mint the moment: This play doesn't exist." !self.retired[playID]!: "Cannot mint the moment from this play: This play has been retired." } // Gets the number of Moments that have been minted for this Play // to use as this Moment's serial number let numInPlay = self.numberMintedPerPlay[playID]! // Mint the new moment let newMoment: @NFT <- create NFT(serialNumber: numInPlay + UInt32(1), playID: playID, setID: self.setID) // Increment the count of Moments minted for this Play self.numberMintedPerPlay[playID] = numInPlay + UInt32(1) return <-newMoment } // batchMintMoment mints an arbitrary quantity of Moments // and returns them as a Collection // // Parameters: playID: the ID of the Play that the Moments are minted for // quantity: The quantity of Moments to be minted // // Returns: Collection object that contains all the Moments that were minted // pub fun batchMintMoment(playID: UInt32, quantity: UInt64): @Collection { let newCollection <- create Collection() var i: UInt64 = 0 while i < quantity { newCollection.deposit(token: <-self.mintMoment(playID: playID)) i = i + UInt64(1) } return <-newCollection } pub fun getPlays(): [UInt32] { return self.plays } pub fun getRetired(): {UInt32: Bool} { return self.retired } pub fun getNumMintedPerPlay(): {UInt32: UInt32} { return self.numberMintedPerPlay } } .... pub resource Admin { .... // createSet creates a new Set resource and stores it // in the sets mapping in the TopShot contract // // Parameters: name: The name of the Set // // Returns: The ID of the created set pub fun createSet(name: String): UInt32 { // Create the new Set var newSet <- create Set(name: name) // Increment the setID so that it isn't used again TopShot.nextSetID = TopShot.nextSetID + UInt32(1) let newID = newSet.setID emit SetCreated(setID: newSet.setID, series: TopShot.currentSeries) // Store it in the sets mapping field TopShot.sets[newID] <-! newSet return newID } .... } // More TopShot Code below

Simlarly to creating a Play, when you're creating a set you want to have dictionaries. The first dictionary would be one for SetData structures and the second dictionary would be for Set Resources. You'd also include a nextSetID that makes sure you don't overlap on sets.

Your SetData struct would contain information pertaining to the naming of the Set. That's the only parameter you would need to pass in the create a new struct. The SetData would take in nextSetID variable and the currentSeries variable to finishing creating the struct.

You would also need to define a resource called Set. When this resource is being initialized it will need to have an ID defined, an array that can store plays you have created, a boolean variable that checks what plays have been retired from being created in the current set, a lock variable that determines if you can add more plays to the set, and a dictionary that maps how many moments have been minted per play.

When you initialize a set, you also take in a name parameter that gets passed into the SetData struct so that it can be added to the contracts storage in the SetData dictionary. Once that is created you have a set resource that you can put minting functions and whole bunch of other things in to deal with creating NFTS and adding Plays.

To create a set, you would have a function in your admin resource that would allow you to do so. You would call the createSet function and pass in a name for the set. You'd create a Set by calling the create function on a resource and pass in the parameters. You'd then increment your setID so that it's not used again. Then you'd get the ID of the newly created Set resource, emit an event that you created a set and then add the new Set resource to be mapped in the Sets dictionary.

Transaction Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import TopShot from 0x01 transaction { let admin: &TopShot.Admin prepare(acct: AuthAccount) { self.admin = acct.borrow<&TopShot.Admin>(from: /storage/TopShotAdmin) ?? panic("Cant borrow admin resource") } execute{ self.admin.createSet(name: "Rookies") log("set created") } }

To create a set, you first need to get a reference to the admin resource from the AuthAccount.

Once you receive that reference you can then create a set that gets stored in the sets and setsData dictionary.


ProgressNFT Fundamentals

Up Next: Add a Play to TopShot Set

85%


Related Recipes

14 Oct 2022
Mint NFT
Beginner
14 Oct 2022
Collection for Holding NFTs
Beginner
14 Oct 2022
NFT with Metadata
Beginner