Smart Contract
Backpack
A.807c3d470888cc48.Backpack
1// SPDX-License-Identifier: UNLICENSED
2import FungibleToken from 0xf233dcee88fe0abe
3import ViewResolver from 0x1d7e57aa55817448
4import NonFungibleToken from 0x1d7e57aa55817448
5import MetadataViews from 0x1d7e57aa55817448
6import Patch from 0x807c3d470888cc48
7
8access(all)
9contract Backpack: NonFungibleToken{
10 access(all)
11 event ContractInitialized()
12
13 access(all)
14 event SetCreated(setID: UInt64)
15
16 access(all)
17 event NFTTemplateCreated(templateID: UInt64, metadata:{ String: String}, slots: UInt64)
18
19 access(all)
20 event Withdraw(id: UInt64, from: Address?)
21
22 access(all)
23 event Deposit(id: UInt64, to: Address?)
24
25 access(all)
26 event Mint(id: UInt64, templateID: UInt64)
27
28 access(all)
29 event Burn(id: UInt64)
30
31 access(all)
32 event TemplateAddedToSet(setID: UInt64, templateID: UInt64)
33
34 access(all)
35 event TemplateLockedFromSet(setID: UInt64, templateID: UInt64)
36
37 access(all)
38 event TemplateUpdated(template: BackpackTemplate)
39
40 access(all)
41 event SetLocked(setID: UInt64)
42
43 access(all)
44 event PatchAddedToBackpack(backpackId: UInt64, patchIds: [UInt64])
45
46 access(all)
47 event PatchRemovedFromBackpack(backpackId: UInt64, patchIds: [UInt64])
48
49 access(all)
50 let CollectionStoragePath: StoragePath
51
52 access(all)
53 let CollectionPublicPath: PublicPath
54
55 access(all)
56 let AdminStoragePath: StoragePath
57
58 access(all)
59 var totalSupply: UInt64
60
61 access(all)
62 var nextTemplateID: UInt64
63
64 access(all)
65 var nextSetID: UInt64
66
67 access(self)
68 var BackpackTemplates:{ UInt64: BackpackTemplate}
69
70 access(self)
71 var sets: @{UInt64: Set}
72
73 access(all)
74 resource interface BackpackCollectionPublic{
75 access(all)
76 fun deposit(token: @{NonFungibleToken.NFT}): Void
77
78 access(all)
79 view fun getIDs(): [UInt64]
80
81 access(all)
82 view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
83
84 access(all)
85 fun borrowBackpack(id: UInt64): &Backpack.NFT?{
86 post{
87 result == nil || result?.id == id:
88 "Cannot borrow Backpack reference: The ID of the returned reference is incorrect"
89 }
90 }
91 }
92
93 access(all)
94 struct BackpackTemplate{
95 access(all)
96 let templateID: UInt64
97
98 access(all)
99 var name: String
100
101 access(all)
102 var description: String
103
104 access(all)
105 var locked: Bool
106
107 access(all)
108 var addedToSet: UInt64
109
110 access(all)
111 var slots: UInt64
112
113 access(self)
114 var metadata:{ String: String}
115
116 access(all)
117 fun getMetadata():{ String: String}{
118 return self.metadata
119 }
120
121 access(all)
122 fun lockTemplate(){
123 self.locked = true
124 }
125
126 access(all)
127 fun updateMetadata(newMetadata:{ String: String}, newSlots: UInt64){
128 pre{
129 newMetadata.length != 0:
130 "New Template metadata cannot be empty"
131 newSlots <= 20:
132 "Slot cannot be more than 20"
133 }
134 self.metadata = newMetadata
135 self.slots = newSlots
136 }
137
138 access(all)
139 fun incrementSlot(){
140 pre{
141 self.slots + 1 <= 20:
142 "reached maximum slot capacity"
143 }
144 self.slots = self.slots + 1
145 }
146
147 access(all)
148 fun markAddedToSet(setID: UInt64){
149 self.addedToSet = setID
150 }
151
152 init(templateID: UInt64, name: String, description: String, metadata:{ String: String}, slots: UInt64){
153 pre{
154 metadata.length != 0:
155 "New Template metadata cannot be empty"
156 }
157 self.templateID = templateID
158 self.name = name
159 self.description = description
160 self.metadata = metadata
161 self.slots = slots
162 self.locked = false
163 self.addedToSet = 0
164 Backpack.nextTemplateID = Backpack.nextTemplateID + 1
165 emit NFTTemplateCreated(templateID: self.templateID, metadata: self.metadata, slots: slots)
166 }
167 }
168
169 access(all)
170 resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver{
171 access(all)
172 let id: UInt64
173
174 access(all)
175 let templateID: UInt64
176
177 access(all)
178 let serialNumber: UInt64
179
180 access(self)
181 let patches: @Patch.Collection
182
183 access(all)
184 view fun getViews(): [Type]{
185 return [Type<MetadataViews.Display>(), Type<MetadataViews.NFTCollectionData>(), Type<MetadataViews.NFTCollectionDisplay>(), Type<MetadataViews.ExternalURL>(), Type<MetadataViews.Edition>(), Type<MetadataViews.Royalties>(), Type<MetadataViews.Traits>()]
186 }
187
188 access(all)
189 fun resolveView(_ view: Type): AnyStruct?{
190 switch view{
191 case Type<MetadataViews.Display>():
192 return MetadataViews.Display(name: self.getNFTTemplate().name, description: self.getNFTTemplate().description, thumbnail: MetadataViews.HTTPFile(url: self.getNFTTemplate().getMetadata()["uri"]!))
193 case Type<MetadataViews.ExternalURL>():
194 return MetadataViews.ExternalURL("https://flunks.io/")
195 case Type<MetadataViews.NFTCollectionData>():
196 return MetadataViews.NFTCollectionData(storagePath: Backpack.CollectionStoragePath, publicPath: Backpack.CollectionPublicPath, publicCollection: Type<&Backpack.Collection>(), publicLinkedType: Type<&Backpack.Collection>(), createEmptyCollectionFunction: fun (): @{NonFungibleToken.Collection}{
197 return <-Backpack.createEmptyCollection(nftType: Type<@Backpack.Collection>())
198 })
199 case Type<MetadataViews.NFTCollectionDisplay>():
200 let media = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://storage.googleapis.com/flunks_public/website-assets/classroom.png"), mediaType: "image/png")
201 return MetadataViews.NFTCollectionDisplay(name: "Backpack", description: "Backpack #onFlow", externalURL: MetadataViews.ExternalURL("https://flunks.io/"), squareImage: media, bannerImage: media, socials:{ "twitter": MetadataViews.ExternalURL("https://twitter.com/flunks_nft")})
202 case Type<MetadataViews.Traits>():
203 let excludedTraits = ["mimetype", "uri", "pixelUri", "path", "cid"]
204 let metadata = self.getNFTTemplate().getMetadata()
205 metadata["slots"] = self.getSlots().toString()
206 let traitsView = MetadataViews.dictToTraits(dict: metadata, excludedNames: excludedTraits)
207 return traitsView
208 case Type<MetadataViews.Edition>():
209 return MetadataViews.Edition(name: "Backpack", number: self.templateID, max: 9999)
210 case Type<MetadataViews.Serial>():
211 return MetadataViews.Serial(self.templateID)
212 case Type<MetadataViews.Royalties>():
213 return MetadataViews.Royalties([])
214 }
215 return nil
216 }
217
218 access(all)
219 fun getNFTTemplate(): BackpackTemplate{
220 return Backpack.BackpackTemplates[self.templateID]!
221 }
222
223 access(all)
224 view fun getSlots(): UInt64{
225 return (Backpack.BackpackTemplates[self.templateID]!).slots
226 }
227
228 access(all)
229 fun getPatchIds(): [UInt64]{
230 return self.patches.getIDs()
231 }
232
233 access(all)
234 fun getNFTMetadata():{ String: String}{
235 return (Backpack.BackpackTemplates[self.templateID]!).getMetadata()
236 }
237
238 access(contract)
239 fun removePatches(patchTokenIDs: [UInt64]): @Patch.Collection{
240 let removedPatches <- Patch.createEmptyCollection(nftType: Type<@Patch.Collection>()) as! @Patch.Collection
241 for patchTokenId in patchTokenIDs{
242 removedPatches.deposit(token: <-self.patches.withdraw(withdrawID: patchTokenId))
243 }
244 let patchIDs = removedPatches.getIDs()
245 emit PatchRemovedFromBackpack(backpackId: self.id, patchIds: patchIDs)
246 return <-removedPatches
247 }
248
249 access(all)
250 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
251 return <-create Collection()
252 }
253
254 init(initID: UInt64, initTemplateID: UInt64, serialNumber: UInt64){
255 self.id = initID
256 self.templateID = initTemplateID
257 self.serialNumber = serialNumber
258 self.patches <- Patch.createEmptyCollection(nftType: Type<@Patch.Collection>()) as! @Patch.Collection
259 }
260 }
261
262 access(all)
263 resource Collection: BackpackCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.Collection, NonFungibleToken.CollectionPublic, ViewResolver.ResolverCollection{
264 access(all)
265 var ownedNFTs: @{UInt64:{ NonFungibleToken.NFT}}
266
267 access(NonFungibleToken.Withdraw)
268 fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT}{
269 let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
270 emit Withdraw(id: token.id, from: self.owner?.address)
271 return <-token
272 }
273
274 access(all)
275 fun deposit(token: @{NonFungibleToken.NFT}): Void{
276 let token <- token as! @Backpack.NFT
277 let id: UInt64 = token.id
278 let oldToken <- self.ownedNFTs[id] <- token
279 emit Deposit(id: id, to: self.owner?.address)
280 destroy oldToken
281 }
282
283 access(all)
284 fun batchDeposit(collection: @Collection){
285 let keys = collection.getIDs()
286 for key in keys{
287 self.deposit(token: <-collection.withdraw(withdrawID: key))
288 }
289 destroy collection
290 }
291
292 access(all)
293 view fun getIDs(): [UInt64]{
294 return self.ownedNFTs.keys
295 }
296
297 access(all)
298 view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?{
299 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
300 }
301
302 access(all)
303 fun borrowBackpack(id: UInt64): &Backpack.NFT?{
304 if self.ownedNFTs[id] != nil{
305 let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
306 return ref as! &Backpack.NFT
307 } else{
308 return nil
309 }
310 }
311
312 access(all)
313 view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?{
314 let nft = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
315 let exampleNFT = nft as! &Backpack.NFT
316 return exampleNFT as &{ViewResolver.Resolver}
317 }
318
319 access(all)
320 fun removePatches(tokenID: UInt64, patchTokenIDs: [UInt64]): @Patch.Collection{
321 pre{
322 self.ownedNFTs.keys.contains(tokenID):
323 "invalid tokenID - not in collection"
324 }
325 let backpackRef = self.borrowBackpack(id: tokenID)!
326 return <-backpackRef.removePatches(patchTokenIDs: patchTokenIDs)
327 }
328
329 access(all)
330 view fun getSupportedNFTTypes():{ Type: Bool}{
331 panic("implement me")
332 }
333
334 access(all)
335 view fun isSupportedNFTType(type: Type): Bool{
336 panic("implement me")
337 }
338
339 access(all)
340 fun createEmptyCollection(): @{NonFungibleToken.Collection}{
341 return <-create Collection()
342 }
343
344 init(){
345 self.ownedNFTs <-{}
346 }
347 }
348
349 access(all)
350 fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection}{
351 return <-create Collection()
352 }
353
354 access(all)
355 resource Set{
356 access(all)
357 let setID: UInt64
358
359 access(all)
360 let name: String
361
362 access(self)
363 var templateIDs: [UInt64]
364
365 access(self)
366 var availableTemplateIDs: [UInt64]
367
368 access(self)
369 var lockedTemplates:{ UInt64: Bool}
370
371 access(all)
372 var locked: Bool
373
374 access(all)
375 var nextSetSerialNumber: UInt64
376
377 access(all)
378 var isPublic: Bool
379
380 init(name: String){
381 self.name = name
382 self.setID = Backpack.nextSetID
383 self.templateIDs = []
384 self.lockedTemplates ={}
385 self.locked = false
386 self.availableTemplateIDs = []
387 self.nextSetSerialNumber = 1
388 self.isPublic = false
389 Backpack.nextSetID = Backpack.nextSetID + 1
390 emit SetCreated(setID: self.setID)
391 }
392
393 access(all)
394 fun getAvailableTemplateIDs(): [UInt64]{
395 return self.availableTemplateIDs
396 }
397
398 access(all)
399 fun makeSetPublic(){
400 self.isPublic = true
401 }
402
403 access(all)
404 fun makeSetPrivate(){
405 self.isPublic = false
406 }
407
408 access(all)
409 fun addTemplate(templateID: UInt64){
410 pre{
411 Backpack.BackpackTemplates[templateID] != nil:
412 "Template doesn't exist"
413 !self.locked:
414 "Cannot add template - set is locked"
415 !self.templateIDs.contains(templateID):
416 "Cannot add template - template is already added to the set"
417 !((Backpack.BackpackTemplates[templateID]!).addedToSet != 0):
418 "Cannot add template - template is already added to another set"
419 }
420 self.templateIDs.append(templateID)
421 self.availableTemplateIDs.append(templateID)
422 self.lockedTemplates[templateID] = false
423 (Backpack.BackpackTemplates[templateID]!).markAddedToSet(setID: self.setID)
424 emit TemplateAddedToSet(setID: self.setID, templateID: templateID)
425 }
426
427 access(all)
428 fun addTemplates(templateIDs: [UInt64]){
429 for template in templateIDs{
430 self.addTemplate(templateID: template)
431 }
432 }
433
434 access(all)
435 fun lockTemplate(templateID: UInt64){
436 pre{
437 self.lockedTemplates[templateID] != nil:
438 "Cannot lock the template: Template is locked already!"
439 !self.availableTemplateIDs.contains(templateID):
440 "Cannot lock a not yet minted template!"
441 }
442 if !self.lockedTemplates[templateID]!{
443 self.lockedTemplates[templateID] = true
444 emit TemplateLockedFromSet(setID: self.setID, templateID: templateID)
445 }
446 }
447
448 access(all)
449 fun lockAllTemplates(){
450 for template in self.templateIDs{
451 self.lockTemplate(templateID: template)
452 }
453 }
454
455 access(all)
456 fun lock(){
457 if !self.locked{
458 self.locked = true
459 emit SetLocked(setID: self.setID)
460 }
461 }
462
463 access(all)
464 fun mintNFT(): @NFT{
465 let templateID = self.availableTemplateIDs[0]
466 if (Backpack.BackpackTemplates[templateID]!).locked{
467 panic("template is locked")
468 }
469 let newNFT: @NFT <- create Backpack.NFT(initID: Backpack.totalSupply, initTemplateID: templateID, serialNumber: self.nextSetSerialNumber)
470 Backpack.totalSupply = Backpack.totalSupply + 1
471 self.nextSetSerialNumber = self.nextSetSerialNumber + 1
472 self.availableTemplateIDs.remove(at: 0)
473 emit Mint(id: newNFT.id, templateID: newNFT.getNFTTemplate().templateID)
474 return <-newNFT
475 }
476
477 access(all)
478 fun updateTemplateMetadata(templateID: UInt64, newMetadata:{ String: String}, newSlots: UInt64): BackpackTemplate{
479 pre{
480 Backpack.BackpackTemplates[templateID] != nil:
481 "Template doesn't exist"
482 !self.locked:
483 "Cannot edit template - set is locked"
484 }
485 (Backpack.BackpackTemplates[templateID]!).updateMetadata(newMetadata: newMetadata, newSlots: newSlots)
486 emit TemplateUpdated(template: Backpack.BackpackTemplates[templateID]!)
487 return Backpack.BackpackTemplates[templateID]!
488 }
489 }
490
491 access(all)
492 resource Admin{
493 access(all)
494 fun mintNFT(recipient: &{NonFungibleToken.CollectionPublic}, setID: UInt64){
495 let set = self.borrowSet(setID: setID)
496 if (set.getAvailableTemplateIDs()!).length == 0{
497 panic("set is empty")
498 }
499 if set.locked{
500 panic("set is locked")
501 }
502 recipient.deposit(token: <-set.mintNFT())
503 }
504
505 access(all)
506 fun borrowSet(setID: UInt64): &Set{
507 pre{
508 Backpack.sets[setID] != nil:
509 "Cannot borrow Set: The Set doesn't exist"
510 }
511 return (&Backpack.sets[setID] as &Set?)!
512 }
513
514 access(all)
515 fun updateBackpackTemplate(templateID: UInt64, newMetadata:{ String: String}, newSlots: UInt64){
516 pre{
517 Backpack.BackpackTemplates.containsKey(templateID) != nil:
518 "Template does not exits."
519 }
520 (Backpack.BackpackTemplates[templateID]!).updateMetadata(newMetadata: newMetadata, newSlots: newSlots)
521 }
522
523 access(all)
524 fun incrementBackpackSlot(templateID: UInt64){
525 pre{
526 Backpack.BackpackTemplates.containsKey(templateID) != nil:
527 "Template does not exits."
528 }
529 (Backpack.BackpackTemplates[templateID]!).incrementSlot()
530 emit TemplateUpdated(template: Backpack.BackpackTemplates[templateID]!)
531 }
532 }
533
534 access(all)
535 fun getBackpackTemplateByID(templateID: UInt64): Backpack.BackpackTemplate{
536 return Backpack.BackpackTemplates[templateID]!
537 }
538
539 access(all)
540 fun getBackpackTemplates():{ UInt64: Backpack.BackpackTemplate}{
541 return Backpack.BackpackTemplates
542 }
543
544 access(all)
545 fun getAvailableTemplateIDsInSet(setID: UInt64): [UInt64]{
546 pre{
547 Backpack.sets[setID] != nil:
548 "Cannot borrow Set: The Set doesn't exist"
549 }
550 let set = (&Backpack.sets[setID] as &Set?)!
551 return set.getAvailableTemplateIDs()
552 }
553
554 access(all) view fun getContractViews(resourceType: Type?): [Type] {
555 return [
556 Type<MetadataViews.NFTCollectionData>(),
557 Type<MetadataViews.NFTCollectionDisplay>(),
558 Type<MetadataViews.EVMBridgedMetadata>()
559 ]
560 }
561
562 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
563 return nil
564 }
565
566 init(){
567 self.CollectionStoragePath = /storage/BackpackCollection
568 self.CollectionPublicPath = /public/BackpackCollection
569 self.AdminStoragePath = /storage/BackpackAdmin
570 self.totalSupply = 0
571 self.nextTemplateID = 1
572 self.nextSetID = 1
573 self.sets <-{}
574 self.BackpackTemplates ={}
575 let admin <- create Admin()
576 self.account.storage.save(<-admin, to: self.AdminStoragePath)
577 emit ContractInitialized()
578 }
579}
580