Smart Contract
DimensionX
A.e3ad6030cbaff1c2.DimensionX
1import FungibleToken from 0xf233dcee88fe0abe
2import NonFungibleToken from 0x1d7e57aa55817448
3import ViewResolver from 0x1d7e57aa55817448
4import MetadataViews from 0x1d7e57aa55817448
5
6access(all) contract DimensionX: NonFungibleToken {
7
8 access(all) var totalSupply: UInt64
9 access(all) var customSupply: UInt64
10 access(all) var genesisSupply: UInt64
11 access(all) var commonSupply: UInt64
12
13 access(all) var totalBurned: UInt64
14 access(all) var customBurned: UInt64
15 access(all) var genesisBurned: UInt64
16 access(all) var commonBurned: UInt64
17
18 access(all) var thulMintPrice: UFix64
19 access(all) var thulMintEnabled: Bool
20 access(all) var metadataUrl: String
21 access(all) var stakedNfts: {UInt64: Address} // map nftId -> ownerAddress
22
23 access(all) var crypthulhuAwake: UFix64
24 access(all) var crypthulhuSleepTime: UFix64
25 access(all) fun crypthulhuSleeps(): Bool {
26 return getCurrentBlock().timestamp - DimensionX.crypthulhuAwake > DimensionX.crypthulhuSleepTime
27 }
28
29 access(all) event ContractInitialized()
30 access(all) event Withdraw(id: UInt64, from: Address?)
31 access(all) event Deposit(id: UInt64, to: Address?)
32 access(all) event Mint(id: UInt64, type: UInt8)
33 access(all) event MintStore(id: UInt64, type: UInt8, setId: UInt32)
34 access(all) event Burn(id: UInt64, type: UInt8)
35 access(all) event Stake(id: UInt64, to: Address?)
36 access(all) event Unstake(id: UInt64, from: Address?)
37 access(all) event MinterCreated()
38
39 access(all) let CollectionStoragePath: StoragePath
40 access(all) let CollectionPublicPath: PublicPath
41 access(all) let AdminStoragePath: StoragePath
42 access(all) let MinterStoragePath: StoragePath
43
44 access(all) enum NFTType: UInt8 {
45 access(all) case custom
46 access(all) case genesis
47 access(all) case common
48 }
49
50 access(all) resource NFT: NonFungibleToken.NFT {
51
52 access(all) let id: UInt64
53 access(all) let type: NFTType
54
55 init(
56 id: UInt64,
57 type: NFTType,
58 ) {
59 self.id = id
60 self.type = type
61 }
62
63 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
64 return <-DimensionX.createEmptyCollection(nftType: Type<@DimensionX.NFT>())
65 }
66
67 access(all) view fun getViews(): [Type] {
68 return [
69 Type<MetadataViews.ExternalURL>(),
70 Type<MetadataViews.Display>(),
71 Type<MetadataViews.Royalties>(),
72 Type<MetadataViews.NFTCollectionData>(),
73 Type<MetadataViews.NFTCollectionDisplay>(),
74 Type<MetadataViews.Serial>(),
75 Type<MetadataViews.Traits>()
76 ]
77 }
78
79 access(all) fun resolveView(_ view: Type): AnyStruct? {
80 switch view {
81 case Type<MetadataViews.ExternalURL>():
82 return MetadataViews.ExternalURL(
83 DimensionX.metadataUrl.concat("heroes/").concat(self.id.toString())
84 )
85 case Type<MetadataViews.Display>():
86 return MetadataViews.Display(
87 name: ("DimensionX #").concat(self.id.toString()),
88 description: "A Superhero capable of doing battle in the DimensionX Game!",
89 thumbnail: MetadataViews.HTTPFile(
90 url: DimensionX.metadataUrl.concat("heroes/i/").concat(self.id.toString()).concat(".png")
91 )
92 )
93 case Type<MetadataViews.Royalties>():
94 let royalties : [MetadataViews.Royalty] = []
95 royalties.append(MetadataViews.Royalty(
96 receiver: DimensionX.account.capabilities.get<&{FungibleToken.Receiver}>(MetadataViews.getRoyaltyReceiverPublicPath()),
97 cut: 0.10,
98 description: "Crypthulhu royalties"))
99 return MetadataViews.Royalties(royalties)
100 case Type<MetadataViews.NFTCollectionData>():
101 return DimensionX.resolveContractView(resourceType: Type<@DimensionX.NFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
102 case Type<MetadataViews.NFTCollectionDisplay>():
103 return DimensionX.resolveContractView(resourceType: Type<@DimensionX.NFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
104 case Type<MetadataViews.Serial>():
105 return MetadataViews.Serial(
106 self.id
107 )
108 case Type<MetadataViews.Traits>():
109 return MetadataViews.Traits([
110 MetadataViews.Trait(
111 name: "Staked",
112 value: DimensionX.stakedNfts[self.uuid] != nil,
113 displayType: nil,
114 rarity: nil
115 )
116 ])
117 }
118
119 return nil
120 }
121 }
122
123 access(all) resource interface CollectionPublic {}
124
125 access(all) resource Collection: NonFungibleToken.Collection, CollectionPublic {
126 // dictionary of NFT conforming tokens
127 // NFT is a resource type with an `UInt64` ID field
128 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
129
130 init () {
131 self.ownedNFTs <- {}
132 }
133
134 access(NonFungibleToken.Withdraw) fun stake(id: UInt64) {
135 pre {
136 self.ownedNFTs.containsKey(id):
137 "Cannot stake: you can only stake tokens that you own"
138 !DimensionX.stakedNfts.containsKey(id):
139 "Cannot stake: the token is already staked"
140 }
141
142 let ownerAddress = self.owner?.address
143 DimensionX.stakedNfts[id] = ownerAddress
144
145 emit Stake(id: id, to:ownerAddress)
146 }
147
148 access(NonFungibleToken.Withdraw) fun unstake(id: UInt64) {
149 pre {
150 DimensionX.stakedNfts.containsKey(id):
151 "Cannot unstake: the token is not staked"
152 self.ownedNFTs.containsKey(id):
153 "Cannot unstake: you can only unstake tokens that you own"
154 }
155
156 if !DimensionX.crypthulhuSleeps() {
157 panic("Cannot unstake: you can only unstake through the game at this moment")
158 }
159
160 let ownerAddress = self.owner?.address
161 DimensionX.stakedNfts.remove(key: id)
162
163 emit Unstake(id: id, from: ownerAddress)
164 }
165
166 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
167 let supportedTypes: {Type: Bool} = {}
168 supportedTypes[Type<@DimensionX.NFT>()] = true
169 return supportedTypes
170 }
171
172 access(all) view fun isSupportedNFTType(type: Type): Bool {
173 return type == Type<@DimensionX.NFT>()
174 }
175
176 // withdraw removes an NFT from the collection and moves it to the caller
177 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
178 pre {
179 !DimensionX.stakedNfts.containsKey(withdrawID):
180 "Cannot withdraw: the token is staked"
181 }
182
183 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
184
185 emit Withdraw(id: token.id, from: self.owner?.address)
186
187 return <-token
188 }
189
190 // deposit takes a NFT and adds it to the collections dictionary
191 // and adds the ID to the id array
192 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
193 let token <- token as! @DimensionX.NFT
194
195 let id: UInt64 = token.id
196
197 // add the new token to the dictionary which removes the old one
198 let oldToken <- self.ownedNFTs[id] <- token
199
200 emit Deposit(id: id, to: self.owner?.address)
201
202 destroy oldToken
203 }
204
205 // getIDs returns an array of the IDs that are in the collection
206 access(all) view fun getIDs(): [UInt64] {
207 return self.ownedNFTs.keys
208 }
209
210 // borrowNFT gets a reference to an NFT in the collection
211 // so that the caller can read its metadata and call its methods
212 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
213 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
214 }
215
216 // access(all) fun borrowDimensionX(id: UInt64): &DimensionX.NFT? {
217 // if self.ownedNFTs[id] != nil {
218 // // Create an authorized reference to allow downcasting
219 // let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
220 // return ref as! &DimensionX.NFT
221 // }
222
223 // return nil
224 // }
225
226 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
227 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
228 return nft as &{ViewResolver.Resolver}
229 }
230 return nil
231 }
232
233 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
234 return <-DimensionX.createEmptyCollection(nftType: Type<@DimensionX.NFT>())
235 }
236 }
237
238 // public function that anyone can call to create a new empty collection
239 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
240 return <- create Collection()
241 }
242
243 access(all) view fun getContractViews(resourceType: Type?): [Type] {
244 return [
245 Type<MetadataViews.NFTCollectionData>(),
246 Type<MetadataViews.NFTCollectionDisplay>()
247 ]
248 }
249
250 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
251 switch viewType {
252 case Type<MetadataViews.NFTCollectionData>():
253 return MetadataViews.NFTCollectionData(
254 storagePath: DimensionX.CollectionStoragePath,
255 publicPath: DimensionX.CollectionPublicPath,
256 publicCollection: Type<&DimensionX.Collection>(),
257 publicLinkedType: Type<&DimensionX.Collection>(),
258 createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
259 return <-DimensionX.createEmptyCollection(nftType: Type<@DimensionX.NFT>())
260 })
261 )
262 case Type<MetadataViews.NFTCollectionDisplay>():
263 return MetadataViews.NFTCollectionDisplay(
264 name: "Dimension X",
265 description: "Dimension X is a Free-to-Play, Play-to-Earn strategic role playing game on the Flow blockchain set in the Dimension X comic book universe, where a pan-dimensional explosion created super powered humans, aliens and monsters with radical and terrifying superpowers!",
266 externalURL: MetadataViews.ExternalURL("https://dimensionxnft.com"),
267 squareImage: MetadataViews.Media(
268 file: MetadataViews.HTTPFile(url: DimensionX.metadataUrl.concat("heroes/collection_image.png")),
269 mediaType: "image/png"
270 ),
271 bannerImage: MetadataViews.Media(
272 file: MetadataViews.HTTPFile(url: DimensionX.metadataUrl.concat("heroes/collection_banner.png")),
273 mediaType: "image/png"
274 ),
275 socials: {
276 "discord": MetadataViews.ExternalURL("https://discord.gg/dimensionx"),
277 "twitter": MetadataViews.ExternalURL("https://twitter.com/DimensionX_NFT")
278 }
279 )
280 }
281 return nil
282 }
283
284 // Resource that an admin or something similar would own to be
285 // able to mint new NFTs
286 //
287 access(all) resource NFTMinter {
288 // range if possible
289 access(all) fun getNextCustomID(): UInt64 {
290 var nextId = DimensionX.customSupply + 1
291 return (nextId <= 1000) ? nextId : self.getNextCommonID()
292 }
293
294 // Determine the next available ID for genesis NFTs and use the reserved
295 // range if possible
296 access(all) fun getNextGenesisID(): UInt64 {
297 var nextId = UInt64(1000) + DimensionX.genesisSupply + UInt64(1)
298 return (nextId <= 11000) ? nextId : panic("Cannot mint more than 10000 genesis NFTs")
299 }
300
301 // Determine the next available ID for the rest of NFTs and take into
302 // account the custom NFTs that have been minted outside of the reserved
303 // range
304 access(all) fun getNextCommonID(): UInt64 {
305 var customIdOverflow = Int256(DimensionX.customSupply) - Int256(1000)
306 customIdOverflow = customIdOverflow > 0 ? customIdOverflow : 0
307 return 11000 + DimensionX.commonSupply + UInt64(customIdOverflow) + UInt64(1)
308 }
309
310 access(all) fun mintCustomNFT(
311 recipient: &DimensionX.Collection,
312 ) {
313 var nextId = self.getNextCustomID()
314
315 // Update supply counters
316 DimensionX.customSupply = DimensionX.customSupply + UInt64(1)
317 DimensionX.totalSupply = DimensionX.totalSupply + UInt64(1)
318
319 self.mint(
320 recipient: recipient,
321 id: nextId,
322 type: DimensionX.NFTType.custom
323 )
324 }
325
326 access(all) fun mintGenesisNFT(
327 recipient: &DimensionX.Collection,
328 ) {
329 // Determine the next available ID
330 var nextId = self.getNextGenesisID()
331
332 // Update supply counters
333 DimensionX.genesisSupply = DimensionX.genesisSupply + UInt64(1)
334 DimensionX.totalSupply = DimensionX.totalSupply + UInt64(1)
335
336 self.mint(
337 recipient: recipient,
338 id: nextId,
339 type: DimensionX.NFTType.genesis
340 )
341 }
342
343 access(all) fun mintNFT(
344 recipient: &DimensionX.Collection,
345 ) {
346 // Determine the next available ID
347 var nextId = self.getNextCommonID()
348
349 // Update supply counters
350 DimensionX.commonSupply = DimensionX.commonSupply + UInt64(1)
351 DimensionX.totalSupply = DimensionX.totalSupply + UInt64(1)
352
353 self.mint(
354 recipient: recipient,
355 id: nextId,
356 type: DimensionX.NFTType.common
357 )
358 }
359
360 access(all) fun mintStoreNFT(
361 recipient: &DimensionX.Collection,
362 setId: UInt32,
363 ) {
364 // Determine the next available ID
365 var nextId = self.getNextCommonID()
366
367 // Update supply counters
368 DimensionX.commonSupply = DimensionX.commonSupply + UInt64(1)
369 DimensionX.totalSupply = DimensionX.totalSupply + UInt64(1)
370
371 var newNFT <- create NFT(id: nextId, type: NFTType.common)
372 emit MintStore(id: nextId, type: UInt8(3), setId: setId)
373 recipient.deposit(token: <-newNFT)
374
375 let ownerAddress = recipient.owner?.address
376 DimensionX.stakedNfts[nextId] = ownerAddress
377 emit Stake(id: nextId, to:ownerAddress)
378 }
379
380 access(all) fun mintStakedNFT(
381 recipient: &DimensionX.Collection,
382 ) {
383 var nextId = self.getNextCommonID()
384 self.mintNFT(recipient: recipient)
385 let ownerAddress = recipient.owner?.address
386 DimensionX.stakedNfts[nextId] = ownerAddress
387 emit Stake(id: nextId, to:ownerAddress)
388 }
389
390 access(self) fun mint(
391 recipient: &DimensionX.Collection,
392 id: UInt64,
393 type: DimensionX.NFTType,
394 ) {
395 // create a new NFT
396 var newNFT <- create NFT(id: id, type: type)
397 switch newNFT.type {
398 case NFTType.custom:
399 emit Mint(id: id, type: 0)
400 case NFTType.genesis:
401 emit Mint(id: id, type: 1)
402 case NFTType.common:
403 emit Mint(id: id, type: 2)
404 }
405 // deposit it in the recipient's account using their reference
406 recipient.deposit(token: <-newNFT)
407 }
408 }
409
410 access(all) resource Admin {
411 access(all) fun unstake(id: UInt64) {
412 pre {
413 DimensionX.stakedNfts.containsKey(id):
414 "Cannot unstake: the token is not staked"
415 }
416
417 let ownerAddress = DimensionX.stakedNfts[id]
418 DimensionX.stakedNfts.remove(key: id)
419
420 emit Unstake(id: id, from: ownerAddress)
421 }
422
423 access(all) fun setMetadataUrl(url: String) {
424 DimensionX.metadataUrl = url
425 }
426
427 access(all) fun createNFTMinter(): @NFTMinter {
428 emit MinterCreated()
429 return <-create NFTMinter()
430 }
431
432 access(all) fun setCrypthulhuSleepTime(time: UFix64) {
433 DimensionX.crypthulhuSleepTime = time
434 self.crypthulhuAwake()
435 }
436
437 access(all) fun crypthulhuAwake() {
438 DimensionX.crypthulhuAwake = getCurrentBlock().timestamp
439 }
440 }
441
442 init() {
443 // Initialize supply counters
444 self.totalSupply = 0
445 self.customSupply = 0
446 self.genesisSupply = 0
447 self.commonSupply = 0
448
449 // Initialize burned counters
450 self.totalBurned = 0
451 self.customBurned = 0
452 self.genesisBurned = 0
453 self.commonBurned = 0
454
455 self.thulMintPrice = 120.0
456 self.thulMintEnabled = false
457 self.metadataUrl = "https://www.dimensionx.com/api/nfts/"
458 self.stakedNfts = {}
459
460 // Initialize Dead Man's Switch
461 self.crypthulhuAwake = getCurrentBlock().timestamp
462 self.crypthulhuSleepTime = UFix64(60 * 60 * 24 * 30)
463
464 // Set the named paths
465 self.CollectionStoragePath = /storage/dmxCollection
466 self.CollectionPublicPath = /public/dmxCollection
467 self.AdminStoragePath = /storage/dmxAdmin
468 self.MinterStoragePath = /storage/dmxMinter
469
470 // Create a Collection resource and save it to storage
471 let collection <- create Collection()
472 self.account.storage.save(<-collection, to: self.CollectionStoragePath)
473
474 // create a public capability for the collection
475 let collectionCap = self.account.capabilities.storage.issue<&DimensionX.Collection>(self.CollectionStoragePath)
476 self.account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
477
478 let admin <- create Admin()
479 let minter <- admin.createNFTMinter()
480 self.account.storage.save(<-admin, to: self.AdminStoragePath)
481 self.account.storage.save(<-minter, to: self.MinterStoragePath)
482
483 emit ContractInitialized()
484 }
485}
486