Smart Contract
TheFabricantS2GarmentNFT
A.7752ea736384322f.TheFabricantS2GarmentNFT
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