Smart Contract

DarkCountry

A.c8c340cebd11f690.DarkCountry

Deployed

2d ago
Feb 25, 2026, 01:47:50 PM UTC

Dependents

10 imports
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}