Smart Contract
Cryptoys
A.ca63ce22f0d6bdba.Cryptoys
1import ViewResolver from 0x1d7e57aa55817448
2import NonFungibleToken from 0x1d7e57aa55817448
3import MetadataViews from 0x1d7e57aa55817448
4import ICryptoys from 0xca63ce22f0d6bdba
5import CryptoysMetadataView from 0xca63ce22f0d6bdba
6
7// Cryptoys
8// The jam.
9//
10access(all)
11contract Cryptoys: NonFungibleToken, ICryptoys{
12 // Events
13 //
14 access(all)
15 event ContractInitialized()
16
17 access(all)
18 event Withdraw(id: UInt64, from: Address?)
19
20 access(all)
21 event Deposit(id: UInt64, to: Address?)
22
23 access(all)
24 event AddedToBucket(owner: Address, key: String, id: UInt64, uuid: UInt64)
25
26 access(all)
27 event WithdrawnFromBucket(owner: Address, key: String, id: UInt64, uuid: UInt64)
28
29 access(all)
30 event Minted(id: UInt64, uuid: UInt64, metadata:{ String: String}, royalties: [Royalty])
31
32 access(all)
33 event RoyaltyUpserted(name: String, royalty: Royalty)
34
35 access(all)
36 event DisplayUpdated(id: UInt64, image: String, video: String)
37
38 // Named Paths
39 //
40 access(all)
41 let CollectionStoragePath: StoragePath
42
43 access(all)
44 let CollectionPublicPath: PublicPath
45
46 access(all)
47 let AdminStoragePath: StoragePath
48
49 // totalSupply
50 // The total number of Cryptoys that have been minted
51 //
52 access(all)
53 var totalSupply: UInt64
54
55 access(all) view fun getContractViews(resourceType: Type?): [Type] {
56 return [ ]
57 }
58
59 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
60 return nil
61 }
62
63 // NFT
64 // A Cryptoy as an NFT
65 //
66 access(all)
67 resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver, ICryptoys.INFT{
68 // The token's ID
69 access(all)
70 let id: UInt64
71
72 access(account)
73 let metadata:{ String: String}
74
75 access(account)
76 let royalties: [String]
77
78 access(account)
79 let bucket: @{String:{ UInt64:{ ICryptoys.INFT}}}
80
81 init(id: UInt64, metadata:{ String: String}, royalties: [String]?){
82 pre{
83 metadata.length != 0:
84 "Cryptoy failed to initialize: metadata cannot be empty"
85 }
86 self.id = id
87 self.metadata = metadata
88 self.royalties = royalties ?? []
89 self.bucket <-{}
90 }
91
92 access(all)
93 fun getMetadata():{ String: String}{
94 return self.metadata
95 }
96
97 access(all)
98 fun getDisplay(): Display{
99 var image = ""
100 var video = ""
101 if Cryptoys.display[self.id] != nil{
102 image = (Cryptoys.display[self.id]!).image
103 video = (Cryptoys.display[self.id]!).video
104 }
105 if image == ""{
106 image = self.metadata["image"] ?? ""
107 }
108 if video == ""{
109 video = self.metadata["video"] ?? ""
110 }
111 return Cryptoys.Display(image: image, video: video)
112 }
113
114 access(all)
115 fun getRoyalties(): [Royalty]{
116 var nftRoyalties: [Royalty] = []
117 for royalty in self.royalties{
118 var nftRoyalty: Royalty? = Cryptoys.royalties[royalty]
119 if nftRoyalty != nil{
120 nftRoyalties.append(nftRoyalty!)
121 }
122 }
123 return nftRoyalties
124 }
125
126 access(account)
127 fun withdrawBucketItem(_ key: String, _ itemUuid: UInt64): @{ICryptoys.INFT} {
128 let resources = self.borrowBucketResourcesByKey(key) ?? panic("withdrawBucketItem() failed to find resources by key: ".concat(key))
129 let nft <- resources.remove(key: itemUuid) ?? panic("withdrawBucketItem() failed to find NFT in bucket with id: ".concat(itemUuid.toString()))
130 emit WithdrawnFromBucket(owner: (self.owner!).address, key: key, id: self.id, uuid: itemUuid)
131 return <-nft
132 }
133
134 access(all)
135 fun addToBucket(_ key: String, _ nft: @{ICryptoys.INFT}){
136 let nftUuid: UInt64 = nft.uuid
137
138 let resources = self.borrowBucketResourcesByKey(key)
139 if resources == nil {
140 let bucket = self.borrowBucket()
141 bucket[key] <-! {nftUuid: <- nft}
142 } else {
143 let exitingBucket = resources!
144 exitingBucket[nftUuid] <-! nft
145 }
146
147 emit AddedToBucket(owner: self.owner!.address, key: key, id: self.id, uuid: nftUuid)
148 }
149
150 access(all)
151 fun borrowBucketResourcesByKey(_ key: String): auth(Insert, Remove) &{UInt64: {ICryptoys.INFT}}? {
152 return &self.bucket[key]
153 }
154
155 access(all)
156 fun borrowBucket(): auth(Insert, Remove) &{String: {UInt64: {ICryptoys.INFT}}} {
157 return &self.bucket
158 }
159
160 access(all)
161 fun getBucketKeys(): [String]{
162 return *self.borrowBucket().keys
163 }
164
165 access(all)
166 fun getBucketResourceIdsByKey(_ key: String): [UInt64]{
167 let resources = self.borrowBucketResourcesByKey(key) ?? panic("getBucketResourceIdsByKey() failed to find resources by key: ".concat(key))
168 return *resources.keys
169 }
170
171 access(all)
172 fun borrowBucketItem(_ key: String, _ itemUuid: UInt64): &{ICryptoys.INFT} {
173 let resources = self.borrowBucketResourcesByKey(key) ?? panic("borrowBucketItem() failed to find resources by key: ".concat(key))
174 let bucketItem = resources[itemUuid] ?? panic("borrowBucketItem() failed to borrow resource with id: ".concat(itemUuid.toString()))
175 return bucketItem as! &Cryptoys.NFT
176 }
177
178 access(all)
179 view fun getViews(): [Type] {
180 return [Type<MetadataViews.Display>(), Type<CryptoysMetadataView.Cryptoy>()]
181 }
182
183 access(all)
184 fun resolveView(_ view: Type): AnyStruct?{
185 var display = self.getDisplay()
186 switch view{
187 case Type<MetadataViews.Display>():
188 return MetadataViews.Display(name: self.metadata["type"] ?? "", description: self.metadata["description"] ?? "", thumbnail: MetadataViews.HTTPFile(url: display.image))
189 case Type<CryptoysMetadataView.Cryptoy>():
190 return CryptoysMetadataView.Cryptoy(name: self.metadata["type"], description: self.metadata["description"], image: display.image, coreImage: self.metadata["coreImage"], video: display.video, platformId: self.metadata["platformId"], category: self.metadata["category"], type: self.metadata["type"], skin: self.metadata["skin"], tier: self.metadata["tier"], rarity: self.metadata["rarity"], edition: self.metadata["edition"], series: self.metadata["series"], legionId: self.metadata["legionId"], creator: self.metadata["creator"], packaging: self.metadata["packaging"], termsUrl: self.metadata["termsUrl"])
191 }
192 return nil
193 }
194
195 access(all)
196 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
197 return <-create Collection()
198 }
199 }
200
201 // Collection
202 // A collection of Cryptoy NFTs owned by an account
203 //
204 access(all)
205 resource Collection: ICryptoys.CryptoysCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.Collection, NonFungibleToken.CollectionPublic, ViewResolver.ResolverCollection{
206 // dictionary of NFT conforming tokens
207 // NFT is a resource type with an `UInt64` ID field
208 //
209 access(all)
210 var ownedNFTs: @{UInt64:{ NonFungibleToken.NFT}}
211
212 // withdraw
213 // Removes an NFT from the collection and moves it to the caller
214 //
215 access(NonFungibleToken.Withdraw)
216 fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT}{
217 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("withdraw() failed: missing NFT with id: ".concat(withdrawID.toString()))
218 emit Withdraw(id: token.id, from: self.owner?.address)
219 return <-token
220 }
221
222 // deposit
223 // Takes a NFT and adds it to the collections dictionary
224 // and adds the ID to the id array
225 //
226 access(all)
227 fun deposit(token: @{NonFungibleToken.NFT}){
228 let token <- token as! @Cryptoys.NFT
229 let id: UInt64 = token.id
230
231 // add the new token to the dictionary which removes the old one
232 let oldToken <- self.ownedNFTs[id] <- token
233 emit Deposit(id: id, to: self.owner?.address)
234 destroy oldToken
235 }
236
237 // getIDs
238 // Returns an array of the IDs that are in the collection
239 //
240 access(all)
241 view fun getIDs(): [UInt64]{
242 return self.ownedNFTs.keys
243 }
244
245 // borrowNFT
246 // Gets a reference to an NFT in the collection
247 // so that the caller can read its metadata and call its methods
248 //
249 access(all)
250 view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?{
251 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
252 }
253
254 access(all)
255 fun borrowCryptoy(id: UInt64): &Cryptoys.NFT{
256 if self.ownedNFTs[id] != nil{
257 let ref = (&self.ownedNFTs[id] as auth(Remove) &{NonFungibleToken.NFT}?)!
258 return ref as! &Cryptoys.NFT
259 } else{
260 return panic("borrowCryptoy() failed: cryptoy not found with id: ".concat(id.toString()))
261 }
262 }
263
264 access(all)
265 fun borrowBucketItem(_ id: UInt64, _ key: String, _ itemUuid: UInt64): &{ICryptoys.INFT}{
266 if self.ownedNFTs[id] == nil{
267 return panic("borrowBucketItem() failed: parent cryptoy not found with id: ".concat(id.toString()))
268 }
269 let ref = (&self.ownedNFTs[id] as auth(Remove) &{NonFungibleToken.NFT}?)!
270 let cryptoyRef = ref as! &Cryptoys.NFT
271 return cryptoyRef.borrowBucketItem(key, itemUuid)
272 }
273
274 access(all)
275 view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?{
276 let nft = (&self.ownedNFTs[id] as auth(Remove) &{NonFungibleToken.NFT}?)!
277 let cryptoyNFT = nft as! &Cryptoys.NFT
278 return cryptoyNFT
279 }
280
281 access(all)
282 fun getRoyalties(id: UInt64): [Royalty]{
283 let ref = (&self.ownedNFTs[id] as auth(Remove) &{NonFungibleToken.NFT}?)!
284 return (ref as! &NFT).getRoyalties()
285 }
286
287 access(NonFungibleToken.Withdraw)
288 fun withdrawBucketItem(parentId: UInt64, key: String, itemUuid: UInt64): @{ICryptoys.INFT}{
289 if self.ownedNFTs[parentId] == nil{
290 panic("withdrawBucketItem() failed: parent cryptoy not found with id: ".concat(parentId.toString()))
291 }
292 let nftRef = (&self.ownedNFTs[parentId] as auth(Remove) &{NonFungibleToken.NFT}?)!
293 let cryptoyRef = nftRef as! &Cryptoys.NFT
294 return <-cryptoyRef.withdrawBucketItem(key, itemUuid)
295 }
296
297 access(all)
298 view fun getSupportedNFTTypes():{ Type: Bool}{
299 panic("implement me")
300 }
301
302 access(all)
303 view fun isSupportedNFTType(type: Type): Bool{
304 panic("implement me")
305 }
306
307 access(all)
308 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
309 return <-create Collection()
310 }
311
312 // destructor
313 // initializer
314 //
315 init(){
316 self.ownedNFTs <-{}
317 }
318 }
319
320 access(all)
321 struct Royalty{
322 access(all)
323 let name: String
324
325 access(all)
326 let address: Address
327
328 access(all)
329 let fee: UFix64
330
331 init(name: String, address: Address, fee: UFix64){
332 self.name = name
333 self.address = address
334 self.fee = fee
335 }
336 }
337
338 access(all)
339 struct Display{
340 access(all)
341 let image: String
342
343 access(all)
344 let video: String
345
346 init(image: String, video: String){
347 self.image = image
348 self.video = video
349 }
350 }
351
352 // royalties
353 // royalties for each INFT
354 //
355 access(account)
356 let royalties:{ String: Royalty}
357
358 // display for each composed NFTs
359 //
360 access(account)
361 let display:{ UInt64: Display}
362
363 // createEmptyCollection
364 // public function that anyone can call to create a new empty collection
365 //
366 access(all)
367 fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection}{
368 return <-create Collection()
369 }
370
371 // Admin
372 // Resource that an admin or something similar would own to be
373 // able to mint new NFTs and update royalties
374 //
375 access(all)
376 resource Admin{
377
378 // mintNFT
379 // Mints a new NFT with a new ID
380 // and deposit it in the recipients collection using their collection reference
381 //
382 access(all)
383 fun mintNFT(recipient: Capability<&{NonFungibleToken.CollectionPublic}>, metadata:{ String: String}, royaltyNames: [String]?): UInt64{
384 var nftRoyalties: [Royalty] = []
385 if royaltyNames != nil{
386 for royalty in royaltyNames!{
387 var nftRoyalty = Cryptoys.royalties[royalty]
388 if nftRoyalty == nil{
389 panic("mintNFT() failed: royalty not found: ".concat(royalty))
390 }
391 nftRoyalties.append(nftRoyalty!)
392 }
393 }
394 let nftId = Cryptoys.totalSupply
395
396 // deposit it in the recipient's account using their reference
397 var nft <- create Cryptoys.NFT(id: nftId, metadata: metadata, royalties: royaltyNames)
398 emit Minted(id: nft.id, uuid: nft.uuid, metadata: nft.getMetadata(), royalties: nftRoyalties)
399 Cryptoys.totalSupply = Cryptoys.totalSupply + 1
400 (recipient.borrow()!).deposit(token: <-nft)
401 return nftId
402 }
403
404 access(all)
405 fun upsertRoyalty(royalty: Royalty){
406 Cryptoys.royalties[royalty.name] = royalty
407 emit RoyaltyUpserted(name: royalty.name, royalty: royalty)
408 }
409
410 access(all)
411 fun updateDisplay(cryptoy: &Cryptoys.NFT, display: Display?){
412 if display != nil{
413 Cryptoys.display.insert(key: cryptoy.id, display!)
414 } else{
415 Cryptoys.display.remove(key: cryptoy.id)
416 }
417 var display = cryptoy.getDisplay()
418 emit DisplayUpdated(id: cryptoy.id, image: display.image, video: display.video)
419 }
420
421 access(all)
422 fun getRoyalties():{ String: Royalty}{
423 return Cryptoys.royalties
424 }
425 }
426
427 // initializer
428 //
429 init(){
430 // Set our named paths
431 self.AdminStoragePath = /storage/cryptoysAdmin
432 self.CollectionStoragePath = /storage/cryptoysCollection
433 self.CollectionPublicPath = /public/cryptoysCollection
434
435 // Initialize the total supply
436 self.totalSupply = 0
437 self.royalties ={}
438 self.display ={}
439
440 // store an empty NFT Collection in account storage
441 self.account.storage.save(<-self.createEmptyCollection(nftType: Type<@Collection>()), to: self.CollectionStoragePath)
442
443 // publish a reference to the Collection in storage
444 var capability_1 = self.account.capabilities.storage.issue<&{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, ICryptoys.CryptoysCollectionPublic}>(self.CollectionStoragePath)
445 self.account.capabilities.publish(capability_1, at: self.CollectionPublicPath)
446
447 // Create a admin resource and save it to storage
448 let admin <- create Admin()
449 self.account.storage.save(<-admin, to: self.AdminStoragePath)
450 emit ContractInitialized()
451 }
452}
453