Implementing Series for NFTs
Implementing Series for NFTs
14 Oct 2022
Contributed by Flow Blockchain
This cadence code will help you being to understand how to implement series and sets into your NFT project.
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
// the below is a series structure that lays out how a series is to be created
//
// Variable size dictionary of SeriesData structs
access(self) var seriesData: {UInt32: SeriesData}
// Variable size dictionary of Series resources
access(self) var series: @{UInt32: Series}
....
pub struct SeriesData {
// Unique ID for the Series
pub let seriesId: UInt32
// Dictionary of metadata key value pairs
access(self) var metadata: {String: String}
init(
seriesId: UInt32,
metadata: {String: String}) {
self.seriesId = seriesId
self.metadata = metadata
emit SeriesCreated(seriesId: self.seriesId)
}
pub fun getMetadata(): {String: String} {
return self.metadata
}
}
....
//
//
// Resource that allows an admin to mint new NFTs
//
pub resource Series {
// Unique ID for the Series
pub let seriesId: UInt32
// Array of NFTSets that belong to this Series
access(self) var setIds: [UInt32]
// Series sealed state
pub var seriesSealedState: Bool;
// Set sealed state
access(self) var setSealedState: {UInt32: Bool};
// Current number of editions minted per Set
access(self) var numberEditionsMintedPerSet: {UInt32: UInt32}
init(
seriesId: UInt32,
metadata: {String: String}) {
self.seriesId = seriesId
self.seriesSealedState = false
self.numberEditionsMintedPerSet = {}
self.setIds = []
self.setSealedState = {}
SetAndSeries.seriesData[seriesId] = SeriesData(
seriesId: seriesId,
metadata: metadata
)
}
pub fun addNftSet(
setId: UInt32,
maxEditions: UInt32,
ipfsMetadataHashes: {UInt32: String},
metadata: {String: String}) {
pre {
self.setIds.contains(setId) == false: "The Set has already been added to the Series."
}
// Create the new Set struct
var newNFTSet = NFTSetData(
setId: setId,
seriesId: self.seriesId,
maxEditions: maxEditions,
ipfsMetadataHashes: ipfsMetadataHashes,
metadata: metadata
)
// Add the NFTSet to the array of Sets
self.setIds.append(setId)
// Initialize the NFT edition count to zero
self.numberEditionsMintedPerSet[setId] = 0
// Store it in the sets mapping field
SetAndSeries.setData[setId] = newNFTSet
emit SetCreated(seriesId: self.seriesId, setId: setId)
}
.....
// mintSetAndSeries
// Mints a new NFT with a new ID
// and deposits it in the recipients collection using their collection reference
//
pub fun mintSetAndSeriesNFT(
recipient: &{NonFungibleToken.CollectionPublic},
tokenId: UInt64,
setId: UInt32) {
pre {
self.numberEditionsMintedPerSet[setId] != nil: "The Set does not exist."
self.numberEditionsMintedPerSet[setId]! < SetAndSeries.getSetMaxEditions(setId: setId)!:
"Set has reached maximum NFT edition capacity."
}
// Gets the number of editions that have been minted so far in
// this set
let editionNum: UInt32 = self.numberEditionsMintedPerSet[setId]! + (1 as UInt32)
// deposit it in the recipient's account using their reference
recipient.deposit(token: <-create SetAndSeries.NFT(
tokenId: tokenId,
setId: setId,
editionNum: editionNum
))
// Increment the count of global NFTs
SetAndSeries.totalSupply = SetAndSeries.totalSupply + (1 as UInt64)
// Update the count of Editions minted in the set
self.numberEditionsMintedPerSet[setId] = editionNum
}
....
// Admin is a special authorization resource that
// allows the owner to perform important NFT
// functions
//
pub resource Admin {
pub fun addSeries(seriesId: UInt32, metadata: {String: String}) {
pre {
SetAndSeries.series[seriesId] == nil:
"Cannot add Series: The Series already exists"
}
// Create the new Series
var newSeries <- create Series(
seriesId: seriesId,
metadata: metadata
)
// Add the new Series resource to the Series dictionary in the contract
SetAndSeries.series[seriesId] <-! newSeries
}
pub fun borrowSeries(seriesId: UInt32): &Series {
pre {
SetAndSeries.series[seriesId] != nil:
"Cannot borrow Series: The Series does not exist"
}
// Get a reference to the Series and return it
return &SetAndSeries.series[seriesId] as &Series
}
pub fun createNewAdmin(): @Admin {
return <-create Admin()
}
}
When creating an NFT that implements Sets and Series, you need to define structures that lay out how the sets and series are to be laid out. In this case we will go through series first.
Here we create first two variable size dictionaries. One contains a dictionary that holds all of our SeriesData structs and the scond is a dictionary of all of our Series resources. This helps us manage and make sure we aren't duplicating Series.
Afterwards we layout the structure of how we want our SeriesData to look. In this case we have a unique ID and some metadata, but you can add whatever type of variables you would like, key to note is to have a unique ID so that you are managing series appropriately.
Once the struct is created, we then create our Series resource that lists all the functions we are able to do when we have a series resource in our account. In this instance we're able to create NFT sets which will then lead to us being able to mint NFTS into sets of a series.
In order to be able to mint NFTS we first have to create a series resource in our account. In this instance we have an admin resource that is store in the deployer of the smart contracts account.
Here we have a function that allows us to addSeries and when that happens we are then able to create sets and mint NFTS.
We also include the ability to borrow a series so that we can access the functions in the series and do what we need to there.
Transaction Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import SetAndSeries from 0x01
transaction {
let adminCheck: &SetAndSeries.Admin
prepare(acct: AuthAccount) {
self.adminCheck = acct.borrow<&SetAndSeries.Admin>(from: SetAndSeries.AdminStoragePath)
?? panic("could not borrow admin reference")
}
execute {
self.adminCheck.addSeries(seriesId: 1, metadata: {"Series": "1"})
log("series added")
}
}
Here we take the admin resource from the AuthAccount and make sure that it contains one.
Once we are positive it contains the admin resource in its account, we then call the addSeries function in order to add a Series resource to use later.
Up Next: Creating a Set in Series
54%