Smart Contract

Patch

A.807c3d470888cc48.Patch

Deployed

1w ago
Feb 19, 2026, 10:24:09 AM UTC

Dependents

3 imports
1// SPDX-License-Identifier: UNLICENSED
2import NonFungibleToken from 0x1d7e57aa55817448
3import ViewResolver from 0x1d7e57aa55817448
4import MetadataViews from 0x1d7e57aa55817448
5
6access(all)
7contract Patch: NonFungibleToken{ 
8	access(all)
9	event ContractInitialized()
10	
11	access(all)
12	event NFTTemplateCreated(templateID: UInt64, template: Patch.PatchTemplate)
13	
14	access(all)
15	event Withdraw(id: UInt64, from: Address?)
16	
17	access(all)
18	event Deposit(id: UInt64, to: Address?)
19	
20	access(all)
21	event Mint(id: UInt64, templateID: UInt64, serialNumber: UInt64)
22	
23	access(all)
24	event Burn(id: UInt64)
25	
26	access(all)
27	event TemplateUpdated(template: PatchTemplate)
28	
29	access(all)
30	let CollectionStoragePath: StoragePath
31	
32	access(all)
33	let CollectionPublicPath: PublicPath
34	
35	access(all)
36	let AdminStoragePath: StoragePath
37	
38	access(all)
39	var totalSupply: UInt64
40	
41	access(all)
42	var nextTemplateID: UInt64
43	
44	access(self)
45	var PatchTemplates:{ UInt64: PatchTemplate}
46	
47	access(self)
48	var tokenMintedPerType:{ UInt64: UInt64}
49	
50	access(all)
51	resource interface PatchCollectionPublic{ 
52		access(all)
53		fun deposit(token: @{NonFungibleToken.NFT}): Void
54		
55		access(all)
56		view fun getIDs(): [UInt64]
57		
58		access(all)
59		view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
60		
61		access(all)
62		fun borrowPatch(id: UInt64): &Patch.NFT?{ 
63			post{ 
64				result == nil || result?.id == id:
65					"Cannot borrow Patch reference: The ID of the returned reference is incorrect"
66			}
67		}
68	}
69	
70	access(all)
71	struct PatchTemplate{ 
72		access(all)
73		let templateID: UInt64
74		
75		access(all)
76		var name: String
77		
78		access(all)
79		var description: String
80		
81		access(all)
82		var mintLimit: UInt64
83		
84		access(all)
85		var locked: Bool
86		
87		access(all)
88		var nextSerialNumber: UInt64
89		
90		access(self)
91		var metadata:{ String: String}
92		
93		access(all)
94		fun getMetadata():{ String: String}{ 
95			return self.metadata
96		}
97		
98		access(all)
99		fun incrementSerialNumber(){ 
100			self.nextSerialNumber = self.nextSerialNumber + 1
101		}
102		
103		access(all)
104		fun lockTemplate(){ 
105			self.locked = true
106		}
107		
108		access(all)
109		fun updateMetadata(newMetadata:{ String: String}){ 
110			pre{ 
111				newMetadata.length != 0:
112					"New Template metadata cannot be empty"
113			}
114			self.metadata = newMetadata
115		}
116		
117		init(templateID: UInt64, name: String, description: String, mintLimit: UInt64, metadata:{ String: String}){ 
118			pre{ 
119				metadata.length != 0:
120					"New Template metadata cannot be empty"
121			}
122			self.templateID = templateID
123			self.name = name
124			self.description = description
125			self.mintLimit = mintLimit
126			self.metadata = metadata
127			self.locked = false
128			self.nextSerialNumber = 1
129			Patch.nextTemplateID = Patch.nextTemplateID + 1
130			emit NFTTemplateCreated(templateID: self.templateID, template: self)
131		}
132	}
133	
134	access(all)
135	resource NFT: NonFungibleToken.NFT, ViewResolver.Resolver{ 
136		access(all)
137		let id: UInt64
138		
139		access(all)
140		let templateID: UInt64
141		
142		access(all)
143		let serialNumber: UInt64
144		
145		access(all)
146		view fun getViews(): [Type]{ 
147			return [Type<MetadataViews.Display>(), Type<MetadataViews.NFTCollectionData>(), Type<MetadataViews.NFTCollectionDisplay>(), Type<MetadataViews.ExternalURL>(), Type<MetadataViews.Royalties>()]
148		}
149		
150		access(all)
151		fun resolveView(_ view: Type): AnyStruct?{ 
152			switch view{ 
153				case Type<MetadataViews.Display>():
154					return MetadataViews.Display(name: self.getNFTTemplate().name, description: self.getNFTTemplate().description, thumbnail: MetadataViews.HTTPFile(url: self.getNFTTemplate().getMetadata()["uri"]!))
155				case Type<MetadataViews.ExternalURL>():
156					return MetadataViews.ExternalURL("https://flunks.io/")
157				case Type<MetadataViews.NFTCollectionData>():
158					return MetadataViews.NFTCollectionData(storagePath: Patch.CollectionStoragePath, publicPath: Patch.CollectionPublicPath, publicCollection: Type<&Patch.Collection>(), publicLinkedType: Type<&Patch.Collection>(), createEmptyCollectionFunction: fun (): @{NonFungibleToken.Collection}{ 
159							return <-Patch.createEmptyCollection(nftType: Type<@Patch.Collection>())
160						})
161				case Type<MetadataViews.NFTCollectionDisplay>():
162					let media = MetadataViews.Media(file: MetadataViews.HTTPFile(url: "https://storage.googleapis.com/flunks_public/website-assets/classroom.png"), mediaType: "image/png")
163					return MetadataViews.NFTCollectionDisplay(name: "Backpack Patch", description: "Backpack Patches #onFlow", externalURL: MetadataViews.ExternalURL("https://flunks.io/"), squareImage: media, bannerImage: media, socials:{ "twitter": MetadataViews.ExternalURL("https://twitter.com/flunks_nft")})
164				case Type<MetadataViews.Royalties>():
165					return MetadataViews.Royalties([])
166			}
167			return nil
168		}
169		
170		access(all)
171		fun getNFTTemplate(): PatchTemplate{ 
172			return Patch.PatchTemplates[self.templateID]!
173		}
174		
175		access(all)
176		fun getNFTMetadata():{ String: String}{ 
177			return (Patch.PatchTemplates[self.templateID]!).getMetadata()
178		}
179		
180		access(all)
181		fun createEmptyCollection(): @{NonFungibleToken.Collection}{ 
182			return <-create Collection()
183		}
184		
185		init(initID: UInt64, initTemplateID: UInt64, serialNumber: UInt64){ 
186			self.id = initID
187			self.templateID = initTemplateID
188			self.serialNumber = serialNumber
189		}
190	}
191	
192	access(all)
193	resource Collection: PatchCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.Collection, NonFungibleToken.CollectionPublic, ViewResolver.ResolverCollection{ 
194		access(all)
195		var ownedNFTs: @{UInt64:{ NonFungibleToken.NFT}}
196		
197		access(NonFungibleToken.Withdraw)
198		fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT}{ 
199			let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")
200			emit Withdraw(id: token.id, from: self.owner?.address)
201			return <-token
202		}
203		
204		access(all)
205		fun deposit(token: @{NonFungibleToken.NFT}): Void{ 
206			let token <- token as! @Patch.NFT
207			let id: UInt64 = token.id
208			let oldToken <- self.ownedNFTs[id] <- token
209			emit Deposit(id: id, to: self.owner?.address)
210			destroy oldToken
211		}
212		
213		access(self)
214		fun batchDeposit(collection: @Collection){ 
215			let keys = collection.getIDs()
216			for key in keys{ 
217				self.deposit(token: <-collection.withdraw(withdrawID: key))
218			}
219			destroy collection
220		}
221		
222		access(all)
223		view fun getIDs(): [UInt64]{ 
224			return self.ownedNFTs.keys
225		}
226		
227		access(all)
228		view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?{ 
229			return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
230		}
231		
232		access(all)
233		fun borrowPatch(id: UInt64): &Patch.NFT?{ 
234			if self.ownedNFTs[id] != nil{ 
235				let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
236				return ref as! &Patch.NFT
237			} else{ 
238				return nil
239			}
240		}
241		
242		access(all)
243		view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}?{ 
244			let nft = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
245			let exampleNFT = nft as! &Patch.NFT
246			return exampleNFT as &{ViewResolver.Resolver}
247		}
248		
249		access(all)
250		view fun getSupportedNFTTypes():{ Type: Bool}{ 
251			panic("implement me")
252		}
253		
254		access(all)
255		view fun isSupportedNFTType(type: Type): Bool{ 
256			panic("implement me")
257		}
258		
259		access(all)
260		fun createEmptyCollection(): @{NonFungibleToken.Collection}{ 
261			return <-create Collection()
262		}
263		
264		init(){ 
265			self.ownedNFTs <-{} 
266		}
267	}
268	
269	access(all)
270	fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection}{ 
271		return <-create Collection()
272	}
273	
274	access(all)
275	resource Admin{ 
276		access(self)
277		fun mintNFT(recipient: &{NonFungibleToken.CollectionPublic}, templateID: UInt64){ 
278			pre{ 
279				Patch.PatchTemplates[templateID] != nil:
280					"Template doesn't exist"
281				!(Patch.PatchTemplates[templateID]!).locked:
282					"Cannot mint Patch - template is locked"
283				(Patch.PatchTemplates[templateID]!).nextSerialNumber <= (Patch.PatchTemplates[templateID]!).mintLimit:
284					"Cannot mint Patch - mint limit reached"
285			}
286			
287			// TODO: mint Patch NFT
288			let nftTemplate = Patch.PatchTemplates[templateID]!
289			let newNFT: @NFT <- create Patch.NFT(initID: Patch.totalSupply, initTemplateID: templateID, serialNumber: nftTemplate.nextSerialNumber)
290			emit Mint(id: newNFT.id, templateID: nftTemplate.templateID, serialNumber: nftTemplate.nextSerialNumber)
291			Patch.totalSupply = Patch.totalSupply + 1
292			(Patch.PatchTemplates[templateID]!).incrementSerialNumber()
293			recipient.deposit(token: <-newNFT)
294		}
295		
296		access(self)
297		fun createPatchTemplate(name: String, description: String, mintLimit: UInt64, metadata:{ String: String}){ 
298			Patch.PatchTemplates[Patch.nextTemplateID] = PatchTemplate(templateID: Patch.nextTemplateID, name: name, description: description, mintLimit: mintLimit, metadata: metadata)
299		}
300		
301		access(self)
302		fun updatePatchTemplate(templateID: UInt64, newMetadata:{ String: String}){ 
303			pre{ 
304				Patch.PatchTemplates.containsKey(templateID) != nil:
305					"Template does not exits."
306			}
307			(Patch.PatchTemplates[templateID]!).updateMetadata(newMetadata: newMetadata)
308		}
309	}
310	
311	access(self)
312	fun getPatchTemplateByID(templateID: UInt64): Patch.PatchTemplate{ 
313		return Patch.PatchTemplates[templateID]!
314	}
315	
316	access(self)
317	fun getPatchTemplates():{ UInt64: Patch.PatchTemplate}{ 
318		return Patch.PatchTemplates
319	}
320
321	access(all) view fun getContractViews(resourceType: Type?): [Type] {
322		return [
323			Type<MetadataViews.NFTCollectionData>(),
324			Type<MetadataViews.NFTCollectionDisplay>(),
325			Type<MetadataViews.EVMBridgedMetadata>()
326		]
327	}
328
329	access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
330		return nil
331	}
332	
333	init(){ 
334		self.CollectionStoragePath = /storage/PatchCollection
335		self.CollectionPublicPath = /public/PatchCollection
336		self.AdminStoragePath = /storage/PatchAdmin
337		self.totalSupply = 0
338		self.nextTemplateID = 1
339		self.tokenMintedPerType ={} 
340		self.PatchTemplates ={} 
341		let admin <- create Admin()
342		self.account.storage.save(<-admin, to: self.AdminStoragePath)
343		emit ContractInitialized()
344	}
345}
346