Smart Contract
DarkCountry
A.c8c340cebd11f690.DarkCountry
1/*
2 Description: Central Smart Contract for DarkCountry NFTs
3
4 authors: Ivan Kravets evan@dapplica.io
5
6 This smart contract contains the core functionality for
7 DarkCountry NFTs.
8
9 The contract manages the data associated with all the ItemTemplate structures
10 that are used as templates for the DarkCountry NFTs
11
12 When a new NFTs wants to be added to the records and the type of the
13 NFT is not registered, a Minter creates
14 a new ItemTemplate struct that is stored in the smart contract.
15
16 E.g. Minter creates Item Template for Rare Land Pack, sets data associated with it.
17 Then they can mint new multiple NFTs of Common Rare Pack type by specifying
18 the appropriate item template ID.
19
20 Item Template is a public struct that
21 contains public information about the item template.
22 The private NFTMinter resource is used to mint new NFTs.
23
24 The NFT minter resource has the power to do configuration actions
25 in the smart contract. When Minter wants to call functions in an ItemTemplate,
26 they call their borrowSet function to get a reference
27 to an ItemTemplate structure in the contract.
28 Then, they can call functions on the ItemTemplate using that reference.
29
30 The contract also defines a Collection resource. This is an object that
31 every DarkCountry NFT owner will store in their account
32 to manage their NFT collection.
33
34 The main DarkCountry account and / or an account that holds NFT Minter resource,
35 will also have theirs own NFTs collections.
36 Those can be used to hold its own minted NFTs that have not yet been sent to a user.
37
38 Note: All state changing functions will panic if an invalid argument is
39 provided or one of its pre-conditions or post conditions aren't met.
40 Functions that don't modify state will simply return 0 or nil
41 and those cases need to be handled by the caller.
42*/
43
44
45import NonFungibleToken from 0x1d7e57aa55817448
46import DarkCountryStaking from 0xc8c340cebd11f690
47
48
49pub contract DarkCountry: NonFungibleToken {
50
51 // -----------------------------------------------------------------------
52 // Events
53 // -----------------------------------------------------------------------
54
55 // Emitted when the DarkCountry contract is created
56 pub event ContractInitialized()
57
58 // Events for Collection-related actions
59 //
60 // Emitted when a NFT is withdrawn from a Collection
61 pub event Withdraw(id: UInt64, from: Address?)
62 // Emitted when a NFT is deposited into a Collection
63 pub event Deposit(id: UInt64, to: Address?)
64
65 // Emitted when a NFT is minted
66 pub event Minted(id: UInt64, typeID: UInt64, serialNumber: UInt64)
67
68 // Emitted when a new ItemTemplate struct is created
69 pub event ItemTemplateCreated(id: UInt64, metadata: {String: String})
70
71 // Named Paths
72 //
73 pub let CollectionStoragePath: StoragePath
74 pub let CollectionPublicPath: PublicPath
75 pub let MinterStoragePath: StoragePath
76
77 // The total number of DarkCountry NFTs that have been minted
78 //
79 pub var totalSupply: UInt64
80
81 // The ID that is used to create ItemTemplate structs.
82 // Every time an ItemTemplate is created, nextItemTemplateID is assigned
83 // to the new ItemTemplate's ID and then is incremented by 1
84 pub var nextItemTemplateID: UInt64
85
86 // Variable size dictionary of number of minted items per
87 // a certain item template
88 //
89 // In other words it can be considered as totalSupply of
90 // NFTs of a certain type
91 access(account) var numberMintedPerItemTemplate: {UInt64: UInt64}
92
93 pub fun getNumberMintedPerItemTemplate(paramItemTemplateID: UInt64): UInt64? {
94
95 return self.numberMintedPerItemTemplate[paramItemTemplateID]
96 }
97 // Variable size dictionary of ItemTemplate structs
98 access(self) var itemTemplates: {UInt64: ItemTemplate}
99
100 // ItemTemplate is a Struct that holds metadata associated
101 // with a specific NFT type, like Common Land Packs all share
102 // the same data associated with them, e.g. name, series, description, rarity.
103 //
104 // DarkCountry NFTs will all reference a single ItemTemplate as the holder of
105 // its metadata. The ItemTemplate structs are publicly accessible, so anyone can
106 // read the metadata associated with a specific NFT's TypeID
107 //
108 pub struct ItemTemplate {
109
110 // The unique ID for the ItemTemplate
111 pub let itemTemplateID: UInt64
112
113 // Stores all the metadata about a specific NFT type, e.g. "name": "Common Land Pack"
114 // as a string mappings
115 //
116 pub let metadata: {String: String}
117
118 init(metadata: {String: String}) {
119 pre {
120 metadata.length != 0: "New ItemTemplate metadata cannot be empty"
121 }
122 self.itemTemplateID = DarkCountry.nextItemTemplateID
123 self.metadata = metadata
124
125 // Increment the item template ID so that it isn't used again
126 DarkCountry.nextItemTemplateID = DarkCountry.nextItemTemplateID + (1 as UInt64)
127
128 // Explicitly set the counter of minted items for newly created item template to 0
129 DarkCountry.numberMintedPerItemTemplate[self.itemTemplateID] = (0 as UInt64)
130
131 emit ItemTemplateCreated(id: self.itemTemplateID, metadata: metadata)
132 }
133 }
134
135
136 // getAllItemTemplates returns all the created item templates
137 //
138 // Returns: An array of all the item templates that have been created
139 //
140 pub fun getAllItemTemplates(): [DarkCountry.ItemTemplate] {
141 return DarkCountry.itemTemplates.values
142 }
143
144 // getItemTemplateMetaData returns all the metadata associated with a specific ItemTemplate
145 //
146 // Parameters: itemTemplateID: The id of the ItemTemplate that is being searched
147 //
148 // Returns: The metadata as a String to String mapping optional
149 //
150 pub fun getItemTemplateMetaData(itemTemplateID: UInt64): {String: String}? {
151 return self.itemTemplates[itemTemplateID]?.metadata
152 }
153
154 // getItemTemplateMetaDataByField returns the metadata associated with a
155 // specific field of the metadata
156 // Ex: field: "Rarity" will return something like "Super Rare"
157 //
158 // Parameters: itemTemplateID: The id of the ItemTemplate that is being searched
159 // field: The field to search for
160 //
161 // Returns: The metadata field as a String Optional
162 pub fun getItemTemplateMetaDataByField(itemTemplateID: UInt64, field: String): String? {
163 if let itemTemplate = DarkCountry.itemTemplates[itemTemplateID] {
164 return itemTemplate.metadata[field]
165 } else {
166 return nil
167 }
168 }
169
170 // NFT
171 // A DarkCountry item as a NFT
172 //
173 pub resource NFT: NonFungibleToken.INFT {
174 // The token's ID
175 // Increments once once any new NFT is minted
176 pub let id: UInt64
177
178 // The token's item template, e.g. 1 for "Common Land Pack"
179 pub let itemTemplateID: UInt64
180
181 // The token's serial number
182 // Specific for an ItemTemplate
183 pub let serialNumber: UInt64
184
185 // initializer
186 //
187 init(initID: UInt64, initItemTemplateID: UInt64, initSerialNumber: UInt64) {
188 self.id = initID
189 self.itemTemplateID = initItemTemplateID
190 self.serialNumber = initSerialNumber
191 }
192 }
193
194 // This is the interface that users can cast their DarkCountry Collection as
195 // to allow others to deposit DarkCountry into their Collection. It also allows for reading
196 // the details of DarkCountry in the Collection.
197 //
198 pub resource interface DarkCountryCollectionPublic {
199 pub fun deposit(token: @NonFungibleToken.NFT)
200 pub fun getIDs(): [UInt64]
201 pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
202 pub fun borrowDarkCountryNFT(id: UInt64): &DarkCountry.NFT? {
203 // If the result isn't nil, the id of the returned reference
204 // should be the same as the argument to the function
205 post {
206 (result == nil) || (result?.id == id):
207 "Cannot borrow DarkCountry reference: The ID of the returned reference is incorrect"
208 }
209 }
210 }
211
212 // Collection
213 // A collection of DarkCountry NFTs owned by an account
214 //
215 pub resource Collection: DarkCountryCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic {
216 // dictionary of NFT conforming tokens
217 // NFT is a resource type with an `UInt64` ID field
218 //
219 pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}
220
221 // withdraw
222 // Removes an NFT from the collection and moves it to the caller
223 //
224 pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
225 // make sure the NFT is not staked
226 if DarkCountryStaking.stakedItems.containsKey(self.owner?.address!) &&
227 DarkCountryStaking.stakedItems[self.owner?.address!]!.contains(withdrawID) {
228 panic("Cannot withdraw: the NFT is staked.")
229 }
230
231 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
232
233 emit Withdraw(id: token.id, from: self.owner?.address)
234
235 return <-token
236 }
237
238 // deposit
239 // Takes a NFT and adds it to the collections dictionary
240 // and adds the ID to the id array
241 //
242 pub fun deposit(token: @NonFungibleToken.NFT) {
243 let token <- token as! @DarkCountry.NFT
244
245 let id: UInt64 = token.id
246
247 // add the new token to the dictionary which removes the old one
248 let oldToken <- self.ownedNFTs[id] <- token
249
250 emit Deposit(id: id, to: self.owner?.address)
251
252 destroy oldToken
253 }
254
255 // getIDs
256 // Returns an array of the IDs that are in the collection
257 //
258 pub fun getIDs(): [UInt64] {
259 return self.ownedNFTs.keys
260 }
261
262 // borrowNFT
263 // Gets a reference to an NFT in the collection
264 // so that the caller can read its metadata and call its methods
265 //
266 pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
267 return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
268 }
269
270 // borrowDarkCountry
271 // Gets a reference to an NFT in the collection as a DarkCountry NFT,
272 // exposing all of its fields (including the typeID).
273 // This is safe as there are no functions that can be called on the DarkCountry.
274 //
275 pub fun borrowDarkCountryNFT(id: UInt64): &DarkCountry.NFT? {
276 if self.ownedNFTs[id] != nil {
277 let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
278 return ref as! &DarkCountry.NFT
279 } else {
280 return nil
281 }
282 }
283
284 // destructor
285 destroy() {
286 destroy self.ownedNFTs
287 }
288
289 // initializer
290 //
291 init () {
292 self.ownedNFTs <- {}
293 }
294 }
295
296 // createEmptyCollection
297 // public function that anyone can call to create a new empty collection
298 //
299 pub fun createEmptyCollection(): @NonFungibleToken.Collection {
300 return <- create Collection()
301 }
302
303 // NFT Minter
304 // Resource that admin would own to be
305 // able to:
306 // 1. Add new ItemTemplates that would define a new NFT type and its metadata
307 // 2. Mint new NFTs
308 //
309 pub resource NFTMinter {
310
311 // mintNFT
312 // Mints a new NFT with a new ID
313 // and deposit it in the recipients collection using their collection reference
314 //
315 pub fun mintNFT(recipient: &{NonFungibleToken.CollectionPublic}, itemTemplateID: UInt64) {
316 // make sure the aseetTypeID is a valid one
317 pre {
318 DarkCountry.itemTemplates[itemTemplateID] != nil: "Cannot mintNFT: itemTemplate doesn't exist."
319 }
320
321 // Gets the number of NFTs of the item template that have been minted
322 // to use as this NFT's serial number
323 let numOfItemTemplateNFTs = DarkCountry.numberMintedPerItemTemplate[itemTemplateID]!
324
325 emit Minted(id: DarkCountry.totalSupply, typeID: itemTemplateID, serialNumber: numOfItemTemplateNFTs)
326
327 // deposit it in the recipient's account using their reference
328 recipient.deposit(token: <-create DarkCountry.NFT(initID: DarkCountry.totalSupply, initItemTemplateID: itemTemplateID, initSerialNumber: numOfItemTemplateNFTs))
329
330 DarkCountry.totalSupply = DarkCountry.totalSupply + (1 as UInt64)
331
332 DarkCountry.numberMintedPerItemTemplate[itemTemplateID] = numOfItemTemplateNFTs + (1 as UInt64)
333 }
334
335 // createItemTemplate creates a new ItemTemplate struct
336 // and stores it in the itemTemplates dictionary in the DarkCountry smart contract
337 //
338 // Parameters: metadata: A dictionary mapping metadata titles to their data
339 // example: {"name": "Land Pack", "Rarity": "Super Rare"}
340 //
341 // Returns: the ID of the new itemTemplate object
342 //
343 pub fun createItemTemplate(metadata: {String: String}): UInt64 {
344 // Create the new ItemTemplate
345 var newItemTemplate = ItemTemplate(metadata: metadata)
346 let newID = newItemTemplate.itemTemplateID
347
348 // Store it in the contract storage
349 DarkCountry.itemTemplates[newID] = newItemTemplate
350
351 return newID
352 }
353
354 // createNewNFTMinter creates a new NFTMinter resource
355 //
356 pub fun createNewNFTMinter(): @NFTMinter {
357 return <- create NFTMinter()
358 }
359 }
360
361 // fetch
362 // Get a reference to a DarkCountry NFT from an account's Collection, if available.
363 // If an account does not have a DarkCountry.Collection, panic.
364 // If it has a collection but does not contain the itemID, return nil.
365 // If it has a collection and that collection contains the itemID, return a reference to that.
366 //
367 pub fun fetch(_ from: Address, itemID: UInt64): &DarkCountry.NFT? {
368 let collection = getAccount(from)
369 .getCapability(DarkCountry.CollectionPublicPath)
370 .borrow<&DarkCountry.Collection{DarkCountry.DarkCountryCollectionPublic}>()
371 ?? panic("Couldn't get collection")
372 // We trust DarkCountry.Collection.borowDarkCountryNFT to get the correct itemID
373 // (it checks it before returning it).
374 return collection.borrowDarkCountryNFT(id: itemID)
375 }
376
377 // initializer
378 //
379 init() {
380 // Set our named paths
381 self.CollectionStoragePath = /storage/DarkCountryCollection
382 self.CollectionPublicPath = /public/DarkCountryCollection
383 self.MinterStoragePath = /storage/DarkCountryMinter
384
385 // Initialize the total supply
386 self.totalSupply = 0
387 self.nextItemTemplateID = 1
388 self.numberMintedPerItemTemplate = {}
389 self.itemTemplates = {}
390
391 // Create a Minter resource and save it to storage
392 let minter <- create NFTMinter()
393 self.account.save(<-minter, to: self.MinterStoragePath)
394
395 emit ContractInitialized()
396 }
397}