Create a TopShot Set
Create a TopShot Set
09 Oct 2022
Contributed by Flow Blockchain
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.
Up Next: Add a Play to TopShot Set
85%