Smart Contract
Bl0x
A.7620acf6d7f2468a.Bl0x
1import NonFungibleToken from 0x1d7e57aa55817448
2import MetadataViews from 0x1d7e57aa55817448
3import ViewResolver from 0x1d7e57aa55817448
4
5access(all)
6contract Bl0x: NonFungibleToken{
7 access(all)
8 var totalSupply: UInt64
9
10 access(all)
11 event ContractInitialized()
12
13 access(all)
14 event Withdraw(id: UInt64, from: Address?)
15
16 access(all)
17 event Deposit(id: UInt64, to: Address?)
18
19 access(all)
20 event Minted(id: UInt64, address: Address)
21
22 access(all)
23 let CollectionStoragePath: StoragePath
24
25 access(all)
26 let CollectionPublicPath: PublicPath
27
28 access(self)
29 var rarities: [String]
30
31 access(account)
32 let royalties: [MetadataViews.Royalty]
33
34 /*
35 Mythical
36 Legendary
37 Epic
38 Rare
39 Uncommon
40 Common
41 */
42
43 access(all)
44 struct Metadata{
45 access(all)
46 let nftId: UInt64
47
48 access(all)
49 let name: String
50
51 access(all)
52 let serial: UInt64
53
54 access(all)
55 let rarity: String
56
57 access(all)
58 let thumbnail: String
59
60 access(all)
61 let image: String
62
63 access(all)
64 let traits: [{String: String}]
65
66 init(nftId: UInt64, name: String, rarity: String, thumbnail: String, image: String, serial: UInt64, traits: [{String: String}]){
67 self.nftId = nftId
68 self.name = name
69 self.rarity = rarity
70 self.thumbnail = thumbnail
71 self.image = image
72 self.serial = serial
73 self.traits = traits
74 }
75 }
76
77 //TODO: This can be removed before mainnet
78 access(all)
79 struct Data{
80 access(all)
81 let nftId: UInt64
82
83 access(all)
84 let name: String
85
86 access(all)
87 let serial: UInt64
88
89 access(all)
90 let rarity: String
91
92 access(all)
93 let thumbnail: String
94
95 access(all)
96 let image: String
97
98 access(all)
99 let traits:{ String: Trait}
100
101 init(nftId: UInt64, name: String, rarity: String, thumbnail: String, image: String, serial: UInt64, traits:{ String: Trait}){
102 self.nftId = nftId
103 self.name = name
104 self.rarity = rarity
105 self.thumbnail = thumbnail
106 self.image = image
107 self.serial = serial
108 self.traits = traits
109 }
110 }
111
112 access(all)
113 resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver{
114 access(all)
115 let id: UInt64
116
117 access(all)
118 let serial: UInt64
119
120 access(all)
121 var nounce: UInt64
122
123 access(all)
124 let rootHash: String
125
126 access(all)
127 let season: UInt64
128
129 access(all)
130 let traits:{ String: UInt64}
131
132 access(all)
133 let royalties: MetadataViews.Royalties
134
135 init(serial: UInt64, rootHash: String, season: UInt64, traits:{ String: UInt64}){
136 self.nounce = 0
137 self.serial = serial
138 self.id = self.uuid
139 self.rootHash = rootHash
140 self.season = season
141 self.traits = traits
142 self.royalties = MetadataViews.Royalties(Bl0x.royalties)
143 }
144
145 access(all)
146 view fun getViews(): [Type]{
147 return [Type<MetadataViews.Display>(), Type<MetadataViews.Medias>(), Type<MetadataViews.Royalties>(), Type<MetadataViews.ExternalURL>(), Type<Data>(), Type<Metadata>(), Type<MetadataViews.NFTCollectionData>(), Type<MetadataViews.NFTCollectionDisplay>(), Type<MetadataViews.Rarity>(), Type<MetadataViews.Traits>()]
148 }
149
150 access(all)
151 fun resolveView(_ view: Type): AnyStruct?{
152 let imageFile = MetadataViews.IPFSFile(cid: self.rootHash, path: "thumbnail/".concat(self.serial.toString()).concat(".webp"))
153 var fullExtension = ".png"
154 var fullMediaType = "image/png"
155 let traits = self.traits
156 if self.serial == 885{
157 traits.remove(key: "Module")
158 }
159 if self.serial == 855{
160 traits["Module"] = 244
161 }
162 if traits.containsKey("Module"){
163 fullExtension = ".gif"
164 fullMediaType = "image/gif"
165 }
166 let fullFile = MetadataViews.IPFSFile(cid: self.rootHash, path: "fullsize/".concat(self.serial.toString()).concat(fullExtension))
167 let fullMedia = MetadataViews.Media(file: fullFile, mediaType: fullMediaType)
168 let season = self.season
169 let name = "Bl0x Season".concat(season.toString()).concat(" #").concat(self.serial.toString())
170 let description = "Bl0x Season".concat(season.toString())
171 switch view{
172 case Type<MetadataViews.Display>():
173 return MetadataViews.Display(name: name, description: description, thumbnail: imageFile)
174 case Type<MetadataViews.ExternalURL>():
175 return MetadataViews.ExternalURL("https://find.xyz/".concat((self.owner!).address.toString()).concat("/collection/bl0x/").concat(self.id.toString()))
176 case Type<MetadataViews.Royalties>():
177 return MetadataViews.Royalties(Bl0x.royalties)
178 case Type<MetadataViews.Medias>():
179 return MetadataViews.Medias([fullMedia])
180 case Type<Data>():
181 return Data(nftId: self.id, name: name, rarity: self.getRarity(), thumbnail: imageFile.uri(), image: fullFile.uri(), serial: self.serial, traits: self.getAllTraitsMetadata())
182 case Type<Metadata>():
183 return Metadata(nftId: self.id, name: name, rarity: self.getRarity(), thumbnail: imageFile.uri(), image: fullFile.uri(), serial: self.serial, traits: self.getAllTraitsMetadataAsArray())
184 case Type<MetadataViews.NFTCollectionDisplay>():
185 let externalURL = MetadataViews.ExternalURL("https://find.xyz/mp/bl0x")
186 let squareImage = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://bl0x.xyz/assets/home/Bl0xlogo.webp"), mediaType: "image")
187 let bannerImage = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://pbs.twimg.com/profile_banners/1535883931777892352/1661105339/1500x500"), mediaType: "image")
188 return MetadataViews.NFTCollectionDisplay(name: "bl0x", description: "Minting a Bl0x triggers the catalyst moment of a big bang scenario. Generating a treasure that is designed to relate specifically to its holder.", externalURL: externalURL, squareImage: squareImage, bannerImage: bannerImage, socials:{ "discord": MetadataViews.ExternalURL("https://t.co/iY7AhEumR9"), "twitter": MetadataViews.ExternalURL("https://twitter.com/Bl0xNFT")})
189 case Type<MetadataViews.NFTCollectionData>():
190 return MetadataViews.NFTCollectionData(storagePath: Bl0x.CollectionStoragePath, publicPath: Bl0x.CollectionPublicPath, publicCollection: Type<&Collection>(), publicLinkedType: Type<&Collection>(), createEmptyCollectionFunction: fun (): @{NonFungibleToken.Collection}{
191 return <-Bl0x.createEmptyCollection(nftType: Type<@Bl0x.Collection>())
192 })
193 case Type<MetadataViews.Rarity>():
194 return MetadataViews.Rarity(score: nil, max: nil, description: self.getRarity())
195 case Type<MetadataViews.Traits>():
196 return self.getTraitsAsTraits()
197 }
198 return nil
199 }
200
201 access(account)
202 fun increaseNounce(){
203 self.nounce = self.nounce + 1
204 }
205
206 access(all)
207 fun getRarity(): String{
208 var traitRarity: [String] = []
209 for trait in self.getAllTraitsMetadata().values{
210 traitRarity.append(trait.metadata["rarity"]!)
211 }
212 var rarity = ""
213 for rarityLevel in Bl0x.rarities{
214 if traitRarity.contains(rarityLevel){
215 rarity = rarityLevel
216 break
217 }
218 }
219 return rarity
220 }
221
222 access(all)
223 fun getTraitsAsTraits(): MetadataViews.Traits{
224 let traits = self.getAllTraitsMetadata()
225 let mvt: [MetadataViews.Trait] = []
226 for trait in traits.keys{
227 let traitValue = traits[trait]!
228 mvt.append(MetadataViews.Trait(name: trait, value: traitValue.getName(), displayType: "String", rarity: MetadataViews.Rarity(score: nil, max: nil, description: traitValue.getRarity())))
229 }
230 return MetadataViews.Traits(mvt)
231 }
232
233 access(all)
234 fun getAllTraitsMetadataAsArray(): [{String: String}]{
235 let traits = self.traits
236 if self.serial == 885{
237 traits.remove(key: "Module")
238 }
239 if self.serial == 855{
240 traits["Module"] = 244
241 }
242 var traitMetadata: [{String: String}] = []
243 for trait in traits.keys{
244 let traitId = traits[trait]!
245 traitMetadata.append((Bl0x.traits[traitId]!).metadata)
246 }
247 return traitMetadata
248 }
249
250 access(all)
251 fun getAllTraitsMetadata():{ String: Trait}{
252 let traits = self.traits
253 if self.serial == 885{
254 traits.remove(key: "Module")
255 }
256 if self.serial == 855{
257 traits["Module"] = 244
258 }
259 var traitMetadata:{ String: Trait} ={}
260 for trait in traits.keys{
261 let traitId = traits[trait]!
262 traitMetadata[trait] = Bl0x.traits[traitId]!
263 }
264 return traitMetadata
265 }
266
267 access(all)
268 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
269 return <-create Collection()
270 }
271 }
272
273 access(all)
274 resource Collection: NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.Collection, NonFungibleToken.CollectionPublic, ViewResolver.ResolverCollection{
275 // dictionary of NFT conforming tokens
276 // NFT is a resource type with an `UInt64` ID field
277 access(all)
278 var ownedNFTs: @{UInt64:{ NonFungibleToken.NFT}}
279
280 init(){
281 self.ownedNFTs <-{}
282 }
283
284 // withdraw removes an NFT from the collection and moves it to the caller
285 access(NonFungibleToken.Withdraw)
286 fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT}{
287 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
288 emit Withdraw(id: token.id, from: self.owner?.address)
289 return <-token
290 }
291
292 // deposit takes a NFT and adds it to the collections dictionary
293 // and adds the ID to the id array
294 access(all)
295 fun deposit(token: @{NonFungibleToken.NFT}): Void{
296 let token <- token as! @NFT
297 let id: UInt64 = token.id
298 //TODO: add nounce and emit better event the first time it is moved.
299 token.increaseNounce()
300 // add the new token to the dictionary which removes the old one
301 let oldToken <- self.ownedNFTs[id] <- token
302 emit Deposit(id: id, to: self.owner?.address)
303 destroy oldToken
304 }
305
306 // getIDs returns an array of the IDs that are in the collection
307 access(all)
308 view fun getIDs(): [UInt64]{
309 return self.ownedNFTs.keys
310 }
311
312 // borrowNFT gets a reference to an NFT in the collection
313 // so that the caller can read its metadata and call its methods
314 access(all)
315 view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?{
316 return &self.ownedNFTs[id]
317 }
318
319 access(all)
320 view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?{
321 return &self.ownedNFTs[id]
322 }
323
324 access(all)
325 view fun getSupportedNFTTypes():{ Type: Bool}{
326 panic("implement me")
327 }
328
329 access(all)
330 view fun isSupportedNFTType(type: Type): Bool{
331 panic("implement me")
332 }
333
334 access(all)
335 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
336 return <-create Collection()
337 }
338 }
339
340 // public function that anyone can call to create a new empty collection
341 access(all)
342 fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection}{
343 return <-create Collection()
344 }
345
346 // mintNFT mints a new NFT with a new ID
347 // and deposit it in the recipients collection using their collection reference
348 //The distinction between sending in a reference and sending in a capability is that when you send in a reference it cannot be stored. So it can only be used in this method
349 //while a capability can be stored and used later. So in this case using a reference is the right choice, but it needs to be owned so that you can have a good event
350 access(account)
351 fun mintNFT(recipient: &{NonFungibleToken.Receiver}, serial: UInt64, rootHash: String, season: UInt64, traits:{ String: UInt64}){
352 pre{
353 recipient.owner != nil:
354 "Recipients NFT collection is not owned"
355 }
356 Bl0x.totalSupply = Bl0x.totalSupply + 1
357 // create a new NFT
358 var newNFT <- create NFT(serial: serial, rootHash: rootHash, season: season, traits: traits)
359
360 //Always emit events on state changes! always contain human readable and machine readable information
361 //TODO: discuss that fields we want in this event. Or do we prefer to use the richer deposit event, since this is really done in the backend
362 //emit Minted(id:newNFT.id, address:recipient.owner!.address)
363 // deposit it in the recipient's account using their reference
364 recipient.deposit(token: <-newNFT)
365 }
366
367 /// A trait contains information about a trait
368 access(all)
369 struct Trait{
370 access(all)
371 let id: UInt64
372
373 access(all)
374 let metadata:{ String: String}
375
376 init(id: UInt64, metadata:{ String: String}){
377 pre{
378 metadata.containsKey("rarity"):
379 "metadata must contain rarity"
380 metadata.containsKey("name"):
381 "metadata must contain name"
382 }
383 self.id = id
384 self.metadata = metadata
385 }
386
387 access(all)
388 fun getName(): String{
389 return self.metadata["name"]!
390 }
391
392 access(all)
393 fun getRarity(): String{
394 return self.metadata["rarity"]!
395 }
396 }
397
398 access(self)
399 let traits:{ UInt64: Trait}
400
401 access(account)
402 fun addTrait(_ trait: Trait){
403 self.traits[trait.id] = trait
404 }
405
406 access(all)
407 fun getTraits():{ UInt64: Trait}{
408 return self.traits
409 }
410
411 access(all)
412 fun getTrait(_ id: UInt64): Trait?{
413 return self.traits[id]
414 }
415
416 access(account)
417 fun addRoyaltycut(_ cutInfo: MetadataViews.Royalty){
418 var cutInfos = self.royalties
419 cutInfos.append(cutInfo)
420 // for validation only
421 let royalties = MetadataViews.Royalties(cutInfos)
422 self.royalties.append(cutInfo)
423 }
424
425 access(all) view fun getContractViews(resourceType: Type?): [Type] {
426 return [
427 Type<MetadataViews.NFTCollectionData>(),
428 Type<MetadataViews.NFTCollectionDisplay>()
429 ]
430 }
431
432 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
433 switch viewType {
434
435 case Type<MetadataViews.NFTCollectionDisplay>():
436 let externalURL = MetadataViews.ExternalURL("https://find.xyz/mp/bl0x")
437 let squareImage = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://bl0x.xyz/assets/home/Bl0xlogo.webp"), mediaType: "image")
438 let bannerImage = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://pbs.twimg.com/profile_banners/1535883931777892352/1661105339/1500x500"), mediaType: "image")
439 return MetadataViews.NFTCollectionDisplay(name: "bl0x", description: "Minting a Bl0x triggers the catalyst moment of a big bang scenario. Generating a treasure that is designed to relate specifically to its holder.", externalURL: externalURL, squareImage: squareImage, bannerImage: bannerImage, socials:{ "discord": MetadataViews.ExternalURL("https://t.co/iY7AhEumR9"), "twitter": MetadataViews.ExternalURL("https://twitter.com/Bl0xNFT")})
440 case Type<MetadataViews.NFTCollectionData>():
441 return MetadataViews.NFTCollectionData(storagePath: Bl0x.CollectionStoragePath, publicPath: Bl0x.CollectionPublicPath, publicCollection: Type<&Collection>(), publicLinkedType: Type<&Collection>(), createEmptyCollectionFunction: fun (): @{NonFungibleToken.Collection}{
442 return <-Bl0x.createEmptyCollection(nftType: Type<@Bl0x.Collection>())
443 })
444 }
445 return nil
446 }
447
448
449 init(){
450 //Rarity (Is there a need to update this?)
451 self.rarities = ["Mythic", "Legendary", "Epic", "Rare", "Uncommon", "Common"]
452 self.traits ={}
453 // Initialize the total supply
454 self.totalSupply = 0
455
456 // Set Royalty cuts in a transaction
457 self.royalties = []
458
459 // Set the named paths
460 self.CollectionStoragePath = /storage/bl0xNFTs
461 self.CollectionPublicPath = /public/bl0xNFTs
462 self.account.storage.save<@{NonFungibleToken.Collection}>(<-Bl0x.createEmptyCollection(nftType: Type<@Bl0x.Collection>()), to: Bl0x.CollectionStoragePath)
463 var capability_1 = self.account.capabilities.storage.issue<&Bl0x.Collection>(Bl0x.CollectionStoragePath)
464 self.account.capabilities.publish(capability_1, at: Bl0x.CollectionPublicPath)
465 emit ContractInitialized()
466 }
467}
468
469