Smart Contract

Backpack

A.807c3d470888cc48.Backpack

Deployed

1w ago
Feb 16, 2026, 02:32:13 AM UTC

Dependents

3376 imports
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