Smart Contract

TheFabricantS2GarmentNFT

A.7752ea736384322f.TheFabricantS2GarmentNFT

Deployed

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

Dependents

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