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