Smart Contract

TheFabricantS2MaterialNFT

A.7752ea736384322f.TheFabricantS2MaterialNFT

Deployed

3h ago
Feb 28, 2026, 08:14:46 PM UTC

Dependents

0 imports
1/*
2	Description: TheFabricantS2MaterialNFT Contract
3   
4	TheFabricantS2MaterialNFT NFTs are minted by admins, and can be combined with 
5	TheFabricantS2GarmentlNFT NFTs to mint TheFabricantS2ItemNFT NFTs.
6*/
7
8import NonFungibleToken from 0x1d7e57aa55817448
9
10import FungibleToken from 0xf233dcee88fe0abe
11
12access(all)
13contract TheFabricantS2MaterialNFT: NonFungibleToken{ 
14	
15	// -----------------------------------------------------------------------
16	// TheFabricantS2MaterialNFT contract Events
17	// -----------------------------------------------------------------------
18	
19	// Emitted when the Material contract is created
20	access(all)
21	event ContractInitialized()
22	
23	// Emitted when a new MaterialData struct is created
24	access(all)
25	event MaterialDataCreated(materialDataID: UInt32, designerAddress: Address, metadata:{ String: String})
26	
27	// Emitted when a Material is minted
28	access(all)
29	event MaterialMinted(materialID: UInt64, materialDataID: UInt32, serialNumber: UInt32)
30	
31	access(all)
32	event MaterialDataIDRetired(materialDataID: UInt32)
33	
34	// Events for Collection-related actions
35	//
36	// Emitted when a Material is withdrawn from a Collection
37	access(all)
38	event Withdraw(id: UInt64, from: Address?)
39	
40	// Emitted when a Material is deposited into a Collection
41	access(all)
42	event Deposit(id: UInt64, to: Address?)
43	
44	// Emitted when a Material is destroyed
45	access(all)
46	event MaterialDestroyed(id: UInt64)
47	
48	// -----------------------------------------------------------------------
49	// contract-level fields.	  
50	// These contain actual values that are stored in the smart contract.
51	// -----------------------------------------------------------------------
52	// Contains standard storage and public paths of resources
53	access(all)
54	let CollectionStoragePath: StoragePath
55	
56	access(all)
57	let CollectionPublicPath: PublicPath
58	
59	access(all)
60	let AdminStoragePath: StoragePath
61	
62	// Variable size dictionary of Material structs
63	access(self)
64	var materialDatas:{ UInt32: MaterialData}
65	
66	// Dictionary with MaterialDataID as key and number of NFTs with MaterialDataID are minted
67	access(self)
68	var numberMintedPerMaterial:{ UInt32: UInt32}
69	
70	// Dictionary of materialDataID to  whether they are retired
71	access(self)
72	var isMaterialDataRetired:{ UInt32: Bool}
73	
74	// Dictionary of the nft with id and its current owner address
75	access(self)
76	var nftIDToOwner:{ UInt64: Address}
77	
78	// Keeps track of how many unique MaterialData's are created
79	access(all)
80	var nextMaterialDataID: UInt32
81	
82	access(all)
83	var totalSupply: UInt64
84	
85	// Royalty struct that each MaterialData will contain
86	access(all)
87	struct Royalty{ 
88		access(all)
89		let wallet: Capability<&{FungibleToken.Receiver}>
90		
91		access(all)
92		let initialCut: UFix64
93		
94		access(all)
95		let cut: UFix64
96		
97		/// @param wallet : The wallet to send royalty too
98		init(wallet: Capability<&{FungibleToken.Receiver}>, initialCut: UFix64, cut: UFix64){ 
99			self.wallet = wallet
100			self.initialCut = initialCut
101			self.cut = cut
102		}
103	}
104	
105	access(all)
106	struct MaterialData{ 
107		
108		// The unique ID for the Material Data
109		access(all)
110		let materialDataID: UInt32
111		
112		// The flow address of the designer
113		access(all)
114		let designerAddress: Address
115		
116		// Other metadata
117		access(self)
118		let metadata:{ String: String}
119		
120		// mapping of royalty name to royalty struct	
121		access(self)
122		let royalty:{ String: Royalty}
123		
124		init(designerAddress: Address, metadata:{ String: String}, royalty:{ String: Royalty}){ 
125			self.materialDataID = TheFabricantS2MaterialNFT.nextMaterialDataID
126			self.designerAddress = designerAddress
127			self.metadata = metadata
128			self.royalty = royalty
129			TheFabricantS2MaterialNFT.isMaterialDataRetired[self.materialDataID] = false
130			
131			// Increment the ID so that it isn't used again
132			TheFabricantS2MaterialNFT.nextMaterialDataID = TheFabricantS2MaterialNFT.nextMaterialDataID + 1 as UInt32
133			emit MaterialDataCreated(materialDataID: self.materialDataID, designerAddress: self.designerAddress, metadata: self.metadata)
134		}
135		
136		access(all)
137		fun updateMaterialMetadata(key: String, value: String){ 
138			self.metadata[key] = value
139		}
140		
141		access(all)
142		fun getMetadata():{ String: String}{ 
143			return self.metadata
144		}
145		
146		access(all)
147		fun getRoyalty():{ String: Royalty}{ 
148			return self.royalty
149		}
150	}
151	
152	access(all)
153	struct Material{ 
154		
155		// The ID of the MaterialData that the Material references
156		access(all)
157		let materialDataID: UInt32
158		
159		// The N'th NFT with 'MaterialDataID' minted
160		access(all)
161		let serialNumber: UInt32
162		
163		init(materialDataID: UInt32){ 
164			self.materialDataID = materialDataID
165			
166			// Increment the ID so that it isn't used again
167			TheFabricantS2MaterialNFT.numberMintedPerMaterial[materialDataID] = TheFabricantS2MaterialNFT.numberMintedPerMaterial[materialDataID]! + 1 as UInt32
168			self.serialNumber = TheFabricantS2MaterialNFT.numberMintedPerMaterial[materialDataID]!
169		}
170	}
171	
172	// The resource that represents the Material NFTs
173	//
174	access(all)
175	resource NFT: NonFungibleToken.NFT{ 
176		
177		// Global unique Material ID
178		access(all)
179		let id: UInt64
180		
181		// struct of Material
182		access(all)
183		let material: Material
184		
185		access(all)
186		fun createEmptyCollection(): @{NonFungibleToken.Collection}{ 
187			return <-create Collection()
188		}
189
190		access(all)
191		view fun getViews(): [Type]{ 
192			return []
193		}
194		
195		access(all)
196		fun resolveView(_ view: Type): AnyStruct?{ 
197			return nil
198		}
199		
200		init(serialNumber: UInt32, materialDataID: UInt32){ 
201			TheFabricantS2MaterialNFT.totalSupply = TheFabricantS2MaterialNFT.totalSupply + 1 as UInt64
202			self.id = TheFabricantS2MaterialNFT.totalSupply
203			self.material = Material(materialDataID: materialDataID)
204			
205			// Emitted when a Material is minted
206			emit MaterialMinted(materialID: self.id, materialDataID: materialDataID, serialNumber: serialNumber)
207		}
208	}
209	
210	// Admin is a special authorization resource that
211	// allows the owner to perform important functions to modify the 
212	// various aspects of the Material and NFTs
213	//
214	access(all)
215	resource Admin{ 
216		access(all)
217		fun createMaterialData(designerAddress: Address, metadata:{ String: String}, royalty:{ String: Royalty}): UInt32{ 
218			// Create the new MaterialData
219			var newMaterial = MaterialData(designerAddress: designerAddress, metadata: metadata, royalty: royalty)
220			let newID = newMaterial.materialDataID
221			
222			// Store it in the contract storage
223			TheFabricantS2MaterialNFT.materialDatas[newID] = newMaterial
224			TheFabricantS2MaterialNFT.numberMintedPerMaterial[newID] = 0 as UInt32
225			return newID
226		}
227		
228		access(all)
229		fun updateMaterialMetadata(id: UInt32, key: String, value: String){ 
230			assert(TheFabricantS2MaterialNFT.materialDatas[id] != nil, message: "garment data does not exist")
231			(TheFabricantS2MaterialNFT.materialDatas[id]!).updateMaterialMetadata(key: key, value: value)
232		}
233		
234		access(all)
235		fun removeMaterialData(id: UInt32){ 
236			TheFabricantS2MaterialNFT.materialDatas.remove(key: id)
237		}
238		
239		// createNewAdmin creates a new Admin resource
240		//
241		access(all)
242		fun createNewAdmin(): @Admin{ 
243			return <-create Admin()
244		}
245		
246		// Mint the new Material
247		access(all)
248		fun mintNFT(materialDataID: UInt32): @NFT{ 
249			let numInMaterial = TheFabricantS2MaterialNFT.numberMintedPerMaterial[materialDataID] ?? panic("no materialDataID found")
250			if TheFabricantS2MaterialNFT.isMaterialDataRetired[materialDataID]! == nil{ 
251				panic("Cannot mint Material. materialData not found")
252			}
253			if TheFabricantS2MaterialNFT.isMaterialDataRetired[materialDataID]!{ 
254				panic("Cannot mint material. materialDataID retired")
255			}
256			let newMaterial: @NFT <- create NFT(serialNumber: numInMaterial + 1, materialDataID: materialDataID)
257			return <-newMaterial
258		}
259		
260		access(all)
261		fun batchMintNFT(materialDataID: UInt32, quantity: UInt64): @Collection{ 
262			let newCollection <- create Collection()
263			var i: UInt64 = 0
264			while i < quantity{ 
265				newCollection.deposit(token: <-self.mintNFT(materialDataID: materialDataID))
266				i = i + 1 as UInt64
267			}
268			return <-newCollection
269		}
270		
271		// Retire materialData so that it cannot be used to mint anymore
272		access(all)
273		fun retireMaterialData(materialDataID: UInt32){ 
274			pre{ 
275				TheFabricantS2MaterialNFT.isMaterialDataRetired[materialDataID] != nil:
276					"Cannot retire Material: Material doesn't exist!"
277			}
278			if !TheFabricantS2MaterialNFT.isMaterialDataRetired[materialDataID]!{ 
279				TheFabricantS2MaterialNFT.isMaterialDataRetired[materialDataID] = true
280				emit MaterialDataIDRetired(materialDataID: materialDataID)
281			}
282		}
283	}
284	
285	// This is the interface users can cast their Material Collection as
286	// to allow others to deposit into their Collection. It also allows for reading
287	// the IDs of Material in the Collection.
288	access(all)
289	resource interface MaterialCollectionPublic{ 
290		access(all)
291		fun deposit(token: @{NonFungibleToken.NFT})
292		
293		access(all)
294		fun batchDeposit(tokens: @{NonFungibleToken.Collection})
295		
296		access(all)
297		fun getIDs(): [UInt64]
298		
299		access(all)
300		view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?
301		
302		access(all)
303		fun borrowMaterial(id: UInt64): &TheFabricantS2MaterialNFT.NFT?{ 
304			// If the result isn't nil, the id of the returned reference
305			// should be the same as the argument to the function
306			post{ 
307				result == nil || result?.id == id:
308					"Cannot borrow Material reference: The ID of the returned reference is incorrect"
309			}
310		}
311	}
312	
313	// Collection is a resource that every user who owns NFTs 
314	// will store in their account to manage their NFTS
315	//
316	access(all)
317	resource Collection: MaterialCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.Collection, NonFungibleToken.CollectionPublic{ 
318		// Dictionary of Material conforming tokens
319		// NFT is a resource type with a UInt64 ID field
320		access(all)
321		var ownedNFTs: @{UInt64:{ NonFungibleToken.NFT}}
322		
323		init(){ 
324			self.ownedNFTs <-{} 
325		}
326		
327		// withdraw removes an Material from the Collection and moves it to the caller
328		//
329		// Parameters: withdrawID: The ID of the NFT 
330		// that is to be removed from the Collection
331		//
332		// returns: @NonFungibleToken.NFT the token that was withdrawn
333		access(NonFungibleToken.Withdraw)
334		fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT}{ 
335			// Remove the nft from the Collection
336			let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("Cannot withdraw: Material does not exist in the collection")
337			emit Withdraw(id: token.id, from: self.owner?.address)
338			
339			// Return the withdrawn token
340			return <-token
341		}
342		
343		// batchWithdraw withdraws multiple tokens and returns them as a Collection
344		//
345		// Parameters: ids: An array of IDs to withdraw
346		//
347		// Returns: @NonFungibleToken.Collection: A collection that contains
348		//										the withdrawn Material
349		//
350		access(all)
351		fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection}{ 
352			// Create a new empty Collection
353			var batchCollection <- create Collection()
354			
355			// Iterate through the ids and withdraw them from the Collection
356			for id in ids{ 
357				batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
358			}
359			
360			// Return the withdrawn tokens
361			return <-batchCollection
362		}
363		
364		// deposit takes a Material and adds it to the Collections dictionary
365		//
366		// Parameters: token: the NFT to be deposited in the collection
367		//
368		access(all)
369		fun deposit(token: @{NonFungibleToken.NFT}){ 
370			// Cast the deposited token as NFT to make sure
371			// it is the correct type
372			let token <- token as! @TheFabricantS2MaterialNFT.NFT
373			
374			// Get the token's ID
375			let id = token.id
376			
377			// Add the new token to the dictionary
378			let oldToken <- self.ownedNFTs[id] <- token
379			
380			// Set the global mapping of nft id to new owner
381			TheFabricantS2MaterialNFT.nftIDToOwner[id] = self.owner?.address
382			
383			// Only emit a deposit event if the Collection 
384			// is in an account's storage
385			if self.owner?.address != nil{ 
386				emit Deposit(id: id, to: self.owner?.address)
387			}
388			
389			// Destroy the empty old token tMaterial was "removed"
390			destroy oldToken
391		}
392		
393		// batchDeposit takes a Collection object as an argument
394		// and deposits each contained NFT into this Collection
395		access(all)
396		fun batchDeposit(tokens: @{NonFungibleToken.Collection}){ 
397			// Get an array of the IDs to be deposited
398			let keys = tokens.getIDs()
399			
400			// Iterate through the keys in the collection and deposit each one
401			for key in keys{ 
402				self.deposit(token: <-tokens.withdraw(withdrawID: key))
403			}
404			
405			// Destroy the empty Collection
406			destroy tokens
407		}
408		
409		// getIDs returns an array of the IDs that are in the Collection
410		access(all)
411		view fun getIDs(): [UInt64]{ 
412			return self.ownedNFTs.keys
413		}
414		
415		// borrowNFT Returns a borrowed reference to a Material in the Collection
416		// so tMaterial the caller can read its ID
417		//
418		// Parameters: id: The ID of the NFT to get the reference for
419		//
420		// Returns: A reference to the NFT
421		//
422		// Note: This only allows the caller to read the ID of the NFT,
423		// not an specific data. Please use borrowMaterial to 
424		// read Material data.
425		//
426		access(all)
427		view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}?{ 
428			return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
429		}
430		
431		// Parameters: id: The ID of the NFT to get the reference for
432		//
433		// Returns: A reference to the NFT
434		access(all)
435		fun borrowMaterial(id: UInt64): &TheFabricantS2MaterialNFT.NFT?{ 
436			if self.ownedNFTs[id] != nil{ 
437				let ref = (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
438				return ref as! &TheFabricantS2MaterialNFT.NFT
439			} else{ 
440				return nil
441			}
442		}
443		
444		access(all)
445		view fun getSupportedNFTTypes():{ Type: Bool}{ 
446			panic("implement me")
447		}
448		
449		access(all)
450		view fun isSupportedNFTType(type: Type): Bool{ 
451			panic("implement me")
452		}
453		
454		access(all)
455		fun createEmptyCollection(): @{NonFungibleToken.Collection}{ 
456			return <-create Collection()
457		}
458	
459	// If a transaction destroys the Collection object,
460	// All the NFTs contained within are also destroyed!
461	//
462	}
463	
464	// -----------------------------------------------------------------------
465	// Material contract-level function definitions
466	// -----------------------------------------------------------------------
467	// createEmptyCollection creates a new, empty Collection object so that
468	// a user can store it in their account storage.
469	// Once they have a Collection in their storage, they are able to receive
470	// Material in transactions.
471	//
472	access(all)
473	fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection}{ 
474		return <-create TheFabricantS2MaterialNFT.Collection()
475	}
476	
477	// get dictionary of numberMintedPerMaterial
478	access(all)
479	fun getNumberMintedPerMaterial():{ UInt32: UInt32}{ 
480		return TheFabricantS2MaterialNFT.numberMintedPerMaterial
481	}
482	
483	// get how many Materials with materialDataID are minted 
484	access(all)
485	fun getMaterialNumberMinted(id: UInt32): UInt32{ 
486		let numberMinted = TheFabricantS2MaterialNFT.numberMintedPerMaterial[id] ?? panic("materialDataID not found")
487		return numberMinted
488	}
489	
490	// get the materialData of a specific id
491	access(all)
492	fun getMaterialData(id: UInt32): MaterialData{ 
493		let materialData = TheFabricantS2MaterialNFT.materialDatas[id] ?? panic("materialDataID not found")
494		return materialData
495	}
496	
497	// get all materialDatas created
498	access(all)
499	fun getMaterialDatas():{ UInt32: MaterialData}{ 
500		return TheFabricantS2MaterialNFT.materialDatas
501	}
502	
503	access(all)
504	fun getMaterialDatasRetired():{ UInt32: Bool}{ 
505		return TheFabricantS2MaterialNFT.isMaterialDataRetired
506	}
507	
508	access(all)
509	fun getMaterialDataRetired(materialDataID: UInt32): Bool{ 
510		let isMaterialDataRetired = TheFabricantS2MaterialNFT.isMaterialDataRetired[materialDataID] ?? panic("materialDataID not found")
511		return isMaterialDataRetired
512	}
513	
514	access(all)
515	fun getNftIdToOwner():{ UInt64: Address}{ 
516		return TheFabricantS2MaterialNFT.nftIDToOwner
517	}
518
519	access(all)
520	view fun getContractViews(resourceType: Type?): [Type]{ 
521		return []
522	}
523
524	access(all)
525	view fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct?{ 
526		return nil
527	}
528	
529	// -----------------------------------------------------------------------
530	// initialization function
531	// -----------------------------------------------------------------------
532	//
533	init(){ 
534		// Initialize contract fields
535		self.materialDatas ={} 
536		self.nftIDToOwner ={} 
537		self.numberMintedPerMaterial ={} 
538		self.nextMaterialDataID = 1
539		self.isMaterialDataRetired ={} 
540		self.totalSupply = 0
541		self.CollectionPublicPath = /public/S2MaterialCollection0028
542		self.CollectionStoragePath = /storage/S2MaterialCollection0028
543		self.AdminStoragePath = /storage/S2MaterialAdmin0028
544		
545		// Put a new Collection in storage
546		self.account.storage.save<@Collection>(<-create Collection(), to: self.CollectionStoragePath)
547		
548		// Create a public capability for the Collection
549		var capability_1 = self.account.capabilities.storage.issue<&{MaterialCollectionPublic}>(self.CollectionStoragePath)
550		self.account.capabilities.publish(capability_1, at: self.CollectionPublicPath)
551		
552		// Put the Minter in storage
553		self.account.storage.save<@Admin>(<-create Admin(), to: self.AdminStoragePath)
554		emit ContractInitialized()
555	}
556}
557