Smart Contract
RTLStoreItem
A.9d1d0d0c82bf1c59.RTLStoreItem
1/*
2 Description:
3
4 authors: Joseph Djenandji, Matthew Balazsi, Jennifer McIntyre
5
6*/
7
8// import NonFungibleToken from "./NonFungibleToken.cdc"
9// import FungibleToken from "./FungibleToken.cdc"
10// import MetadataViews from "./MetadataViews.cdc"
11
12// for emulator
13// import NonFungibleToken from "0xNonFungibleToken"
14// import ViewResolver from "0xViewResolver"
15// import MetadataViews from "0xMetadataViews"
16
17// for tests
18// import NonFungibleToken from NonFungibleToken
19// import MetadataViews from MetadataViews
20
21// for testnet
22// import NonFungibleToken from 0x631e88ae7f1d7c20
23// import MetadataViews from 0x631e88ae7f1d7c20
24// import ViewResolver from 0x631e88ae7f1d7c20
25
26// for mainnet
27import NonFungibleToken from 0x1d7e57aa55817448
28import MetadataViews from 0x1d7e57aa55817448
29import ViewResolver from 0x1d7e57aa55817448
30
31
32access(all) contract RTLStoreItem: NonFungibleToken {
33
34 // -----------------------------------------------------------------------
35 // Contract Events
36 // -----------------------------------------------------------------------
37
38 // Emitted when the contract is created
39 access(all) event ContractInitialized()
40
41 // Emitted when a new Edition struct is created
42 access(all) event EditionCreated(id: UInt32, name: String, printingLimit: UInt32?)
43
44 // Emitted when Edition Metadata is updated
45 access(all) event EditionMetadaUpdated(editionID: UInt32)
46
47
48 // default royalties
49 access(all) event DefaultRoyaltiesUpdated(name: String, cut: UFix64)
50
51 // remove default royalty
52 access(all) event DefaultRoyaltyRemoved(name: String)
53
54 // royalties for edition
55 access(all) event RoyaltiesForEditionUpdated(editionID: UInt32, name: String, cut: UFix64)
56
57 // remove royalty for edition
58 access(all) event RoyaltiesForEditionRemoved(editionID: UInt32, name: String)
59
60
61 // RevertRoyaltiesForEditionToDefault when the admin clears the specific royalties for that edition, to revert back to default royalties
62 access(all) event RevertRoyaltiesForEditionToDefault(editionID: UInt32)
63
64
65
66 // Emitted when a new item was minted
67 access(all) event ItemMinted(itemID:UInt64, merchantID: UInt32, editionID: UInt32, editionNumber: UInt32)
68
69 // Item related events
70 //
71 // Emitted when an Item is withdrawn from a Collection
72 access(all) event Withdraw(id: UInt64, from: Address?)
73 // Emitted when an Item is deposited into a Collection
74 access(all) event Deposit(id: UInt64, to: Address?)
75 // Emitted when an Item is destroyed
76 access(all) event ItemDestroyed(id: UInt64)
77
78
79 // Named paths
80 //
81 access(all) let CollectionStoragePath: StoragePath
82 access(all) let CollectionPublicPath: PublicPath
83 access(all) let AdminStoragePath: StoragePath
84
85
86 // -----------------------------------------------------------------------
87 // RTLStoreItem contract-level fields.
88 // These contain actual values that are stored in the smart contract.
89 // -----------------------------------------------------------------------
90
91 // Variable size dictionary of Editions resources
92 access(self) var editions: @{UInt32: Edition}
93
94
95 // the default royalties
96 access(self) var defaultRoyalties: {String: MetadataViews.Royalty}
97
98 // If a specific NFT requires their own royalties,
99 // the default royalties can be overwritten in this dictionary.
100 access(all) var royaltiesForSpecificEdition: {UInt32: {String: MetadataViews.Royalty}}
101
102
103 // The ID that is used to create Admins.
104 // Every Admins should have a unique identifier.
105 access(all) var nextAdminID: UInt32
106
107
108
109 // The ID that is used to create Editions.
110 // Every time an Edition is created, nextEditionID is assigned
111 // to the edition and then is incremented by 1.
112 access(all) var nextEditionID: UInt32
113
114
115
116 // The total number of NFTs that have been created for this smart contract
117 // Because NFTs can be destroyed, it doesn't necessarily mean that this
118 // reflects the total number of NFTs in existence, just the number that
119 // have been minted to date. Also used as global nft IDs for minting.
120 access(all) var totalSupply: UInt64
121
122
123 // -----------------------------------------------------------------------
124 // RTLStoreItem contract-level Composite Type definitions
125 // -----------------------------------------------------------------------
126 // These are just *definitions* for Types that this contract
127 // and other accounts can use. These definitions do not contain
128 // actual stored values, but an instance (or object) of one of these Types
129 // can be created by this contract that contains stored values.
130 // -----------------------------------------------------------------------
131
132
133 // EditionData is a struct definition to have all of the same fields as the Edition resource.
134 // it can be used to publicly read Edition data
135
136 access(all) struct EditionData {
137 access(all) let editionID: UInt32
138 access(all) let merchantID: UInt32
139 access(all) let name: String
140 access(all) var items: [UInt64]
141 access(all) var metadata: {String: String}
142 access(all) var numberOfItemsMinted: UInt32
143 access(all) var printingLimit: UInt32?
144
145 init(editionID: UInt32) {
146
147 if RTLStoreItem.editions[editionID] == nil {
148 panic("the editionID was not found")
149 }
150 let editionToRead = (&RTLStoreItem.editions[editionID] as &Edition?)!
151
152 self.editionID = editionID
153 self.metadata = (RTLStoreItem.editions[editionID]?.metadata ?? {})
154 self.merchantID = editionToRead.merchantID
155 self.name = editionToRead.name
156 self.printingLimit = editionToRead.printingLimit
157 self.numberOfItemsMinted=editionToRead.numberOfItemsMinted
158 self.items=RTLStoreItem.editions[editionID]?.items ?? []
159 }
160 }
161 // Edition is a Ressource that holds metadata associated
162 // with a specific NFT
163 //
164 // NFTs will all reference an Edition as the owner of
165 // its metadata. The Editions are publicly accessible, so anyone can
166 // read the metadata associated with a specific EditionID
167 //
168 access(all) resource Edition {
169
170 // The unique ID for the Edition
171 access(all) let editionID: UInt32
172
173 // The ID of the merchant that owns the edition
174 access(all) let merchantID: UInt32
175
176 // Stores all the metadata about the edition as a string mapping
177 // This is not the long term way NFT metadata will be stored. It's a temporary
178 // construct while we figure out a better way to do metadata.
179 //
180 access(all) let metadata: {String: String}
181
182 // Array of items that are a part of this collection.
183 // When an item is added to the collection, its ID gets appended here.
184 access(contract) var items: [UInt64]
185
186 // The number of items minted in this collection.
187 // When an item is added to the collection, the numberOfItems is incremented by 1
188 // It will be used to identify the editionNumber of an item
189 // if the edition is open (printingLimit=nil), we can keep minting new items
190 // if the edition is limited (printingLimit!=nil), we can keep minting items until we reach printingLimit
191 access(all) var numberOfItemsMinted: UInt32
192
193
194 // the limit of items that can be minted. For open editions, this value should be set to nil.
195 access(all) var printingLimit: UInt32?
196
197 // the name of the edition
198 access(all) var name: String
199
200 init(merchantID: UInt32, metadata: {String: String}, name: String, printingLimit:UInt32?) {
201 pre {
202 metadata.length != 0: "Metadata cannot be empty"
203 name!=nil: "Name is undefined"
204 }
205 self.editionID = RTLStoreItem.nextEditionID
206 self.merchantID = merchantID
207 self.metadata = metadata
208 self.name = name
209 self.printingLimit = printingLimit
210 self.numberOfItemsMinted=0
211 self.items = []
212
213
214 // Increment the ID so that it isn't used again
215 RTLStoreItem.nextEditionID = RTLStoreItem.nextEditionID + (1 as UInt32)
216
217 emit EditionCreated(id: self.editionID, name: self.name, printingLimit: self.printingLimit)
218 }
219
220
221 // mintItem mints a new Item and returns the newly minted Item
222 //
223 // Pre-Conditions:
224 // If the edition is limited the number of items minted in the edition must be strictly less than the printing limit
225 //
226 // Returns: The NFT that was minted
227 //
228 access(all) fun mintItem(): @NFT {
229 pre {
230 (self.numberOfItemsMinted < (self.printingLimit ?? (4294967295 as UInt32) )): "We have reached the printing limit for this edition"
231 }
232
233 // Gets the number of Itms that have been minted for this Edition
234 // to use as this Item's edition number
235 let numMinted = self.numberOfItemsMinted + (1 as UInt32)
236
237 // Mint the new item
238 let newItem: @NFT <- create NFT(merchantID: self.merchantID, editionID: self.editionID, editionNumber: numMinted)
239
240
241 // Add the Item to the array of items
242 self.items.append(newItem.id)
243
244 // Increment the count of Items
245 self.numberOfItemsMinted = numMinted
246
247 return <-newItem
248 }
249
250 // batchMintItems mints an arbitrary quantity of Items
251 // and returns them as a Collection
252 // Be sure there are enough
253 //
254 // Parameters: quantity: The quantity of Items to be minted
255 //
256 // Returns: Collection object that contains all the Items that were minted
257 //
258 access(all) fun batchMintItems(quantity: UInt32): @Collection {
259
260 pre {
261 ((self.numberOfItemsMinted+quantity)<=(self.printingLimit ?? (4294967295 as UInt32))): "We have reached the printing limit for this edition"
262 }
263
264 let newCollection <- create Collection()
265
266 var i: UInt32 = 0
267 while i < quantity {
268 newCollection.deposit(token: <-self.mintItem())
269 i = i + (1 as UInt32)
270 }
271
272 return <-newCollection
273 }
274
275 // updateMetadata updates the metadata
276 //
277 // Parameters:
278 //
279 // updates: a dictionary of key - values that is requested to be appended
280 //
281 // suffix: If the metadata already contains an attribute with a given key, this value should still be kept
282 // for posteriority. Therefore, the old value to be replaced will be stored in a metadata entry with key = key+suffix.
283 // This can offer some reassurance to the NFT owner that the metadata will never disappear.
284 //
285 // Returns: the EditionID
286 //
287 access(all) fun updateMetadata(updates: {String:String}, suffix: String): UInt32 {
288
289
290
291 // prevalidation
292 // if metadata[key] exists and metadata[key+suffix] exists, we have a clash.
293 for key in updates.keys {
294
295 let newKey = key.concat(suffix)
296
297 if self.metadata[key] != nil && self.metadata[newKey]!=nil {
298 var errorMsg = "attributes "
299 errorMsg = errorMsg.concat(key).concat(" and ").concat(newKey).concat(" are already defined")
300 panic(errorMsg)
301 }
302
303
304 }
305
306 // execution
307 for key in updates.keys {
308
309 let newKey = key.concat(suffix)
310
311 if self.metadata[key] != nil {
312 self.metadata[newKey] = self.metadata[key]
313 }
314 self.metadata[key] = updates[key]
315
316 }
317
318
319 emit EditionMetadaUpdated(editionID: self.editionID)
320
321 // Return the EditionID and return it
322 return self.editionID
323 }
324
325 }
326
327 // The struct representing an NFT Item data
328 access(all) struct ItemData {
329
330
331 // The ID of the merchant
332 access(all) let merchantID: UInt32
333
334 // The ID of the edition that the NFT comes from
335 access(all) let editionID: UInt32
336
337 // The number of the NFT within the edition
338 access(all) let editionNumber: UInt32
339
340
341
342 init(merchantID: UInt32, editionID: UInt32, editionNumber: UInt32) {
343 self.merchantID = merchantID
344 self.editionID = editionID
345 self.editionNumber = editionNumber
346 }
347
348 }
349
350 // The resource that represents the Item NFTs
351 //
352 access(all) resource NFT: NonFungibleToken.NFT {
353
354 // Global unique item ID
355 access(all) let id: UInt64
356
357 // Struct of the metadata
358 access(all) let data: ItemData
359
360
361 init(merchantID: UInt32, editionID: UInt32, editionNumber: UInt32) {
362
363 pre{
364 editionID > (0 as UInt32): "editionID cannot be 0"
365 editionNumber > (0 as UInt32): "editionNumber cannot be 0"
366 }
367 // Increment the global Item IDs
368 RTLStoreItem.totalSupply = RTLStoreItem.totalSupply + (1 as UInt64)
369
370 self.id = RTLStoreItem.totalSupply
371
372 // Set the metadata struct
373 self.data = ItemData(merchantID: merchantID, editionID: editionID, editionNumber: editionNumber)
374
375
376 emit ItemMinted(itemID: self.id, merchantID: merchantID, editionID: editionID, editionNumber: editionNumber)
377 }
378
379 access(all) view fun getData(): ItemData {
380 return self.data
381 }
382
383 /// createEmptyCollection creates an empty Collection
384 /// and returns it to the caller so that they can own NFTs
385 /// @{NonFungibleToken.Collection}
386 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
387 return <-RTLStoreItem.createEmptyCollection(nftType: Type<@RTLStoreItem.NFT>())
388 }
389
390
391 access(all) view fun getViews(): [Type] {
392 return [
393 Type<MetadataViews.Display>(),
394 Type<MetadataViews.Royalties>(),
395 Type<MetadataViews.Editions>(),
396 Type<MetadataViews.ExternalURL>(),
397 Type<MetadataViews.NFTCollectionData>(),
398 Type<MetadataViews.NFTCollectionDisplay>(),
399 Type<MetadataViews.Serial>(),
400 Type<MetadataViews.Traits>()
401 ]
402 }
403
404
405 access(all) fun resolveView(_ view: Type): AnyStruct? {
406 switch view {
407 case Type<MetadataViews.Display>():
408 let edition = EditionData(editionID: self.data.editionID);
409 return MetadataViews.Display(
410 name: edition.name,
411 description: edition.metadata["description"] ?? "",
412 thumbnail: MetadataViews.HTTPFile(
413 url: edition.metadata["thumbnail"] ?? ""
414 )
415 )
416 case Type<MetadataViews.Editions>():
417 let edition = EditionData(editionID: self.data.editionID);
418 let maxNumber = edition.printingLimit ?? nil
419 var max: UInt64? = nil;
420 if maxNumber != nil {
421 max = UInt64(maxNumber!)
422 }
423
424
425 let editionInfo = MetadataViews.Edition(name: edition.name, number: UInt64(self.data.editionNumber), max:max)
426 let editionList: [MetadataViews.Edition] = [editionInfo]
427 return MetadataViews.Editions(
428 editionList
429 )
430
431 case Type<MetadataViews.ExternalURL>():
432 let url = "https://wallet.sisi-nft.de/";
433 return MetadataViews.ExternalURL(url)
434
435
436 case Type<MetadataViews.Royalties>():
437 let royaltiesDictionary = RTLStoreItem.royaltiesForSpecificEdition[self.data.editionID] ?? RTLStoreItem.defaultRoyalties
438 var royalties: [MetadataViews.Royalty] = []
439 for royaltyName in royaltiesDictionary.keys {
440 royalties.append(royaltiesDictionary[royaltyName]!)
441 }
442 return MetadataViews.Royalties(royalties)
443
444 case Type<MetadataViews.NFTCollectionDisplay>():
445 let mediaSquare = MetadataViews.Media(
446 file: MetadataViews.HTTPFile(
447 url: "https://dkuxa1i6sgo8h.cloudfront.net/sisi/Sisi-square-whitebackground-small.png"
448 ),
449 mediaType: "image/png"
450 )
451 let mediaBanner = MetadataViews.Media(
452 file: MetadataViews.HTTPFile(
453 url: "https://dkuxa1i6sgo8h.cloudfront.net/sisi/Sisi+banner.png"
454 ),
455 mediaType: "image/png"
456 )
457 let url = "https://wallet.sisi-nft.de/";
458 let description = "";
459
460
461 return MetadataViews.NFTCollectionDisplay(
462 name: "RTLStoreItem",
463 description: description,
464 externalURL: MetadataViews.ExternalURL(url),
465 squareImage: mediaSquare,
466 bannerImage: mediaBanner,
467 socials: {}
468 )
469
470 case Type<MetadataViews.NFTCollectionData>():
471 return MetadataViews.NFTCollectionData(
472 storagePath: RTLStoreItem.CollectionStoragePath,
473 publicPath: RTLStoreItem.CollectionPublicPath,
474 publicCollection: Type<&RTLStoreItem.Collection>(),
475 publicLinkedType: Type<&RTLStoreItem.Collection>(),
476 createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
477 return <-RTLStoreItem.createEmptyCollection(nftType: Type<@RTLStoreItem.NFT>())
478 })
479 )
480
481
482 case Type<MetadataViews.Traits>():
483 // exclude essential metadata to keep unique traits
484 let excludedTraits = ["name", "description", "externalUrl", "squareImage", "squareImageType", "bannerImage", "bannerImageType", "thumbnail", "thumbnailType"]
485
486 let edition = EditionData(editionID: self.data.editionID);
487 let metadata = edition.metadata;
488
489 metadata.insert(key: "editionID", edition.editionID.toString());
490 metadata.insert(key: "merchantID", edition.merchantID.toString());
491
492 let traitsView = MetadataViews.dictToTraits(dict: metadata, excludedNames: excludedTraits)
493
494 return traitsView
495
496
497 case Type<MetadataViews.Serial>():
498 return MetadataViews.Serial(
499 UInt64(self.data.editionNumber)
500 )
501
502
503 }
504
505 return nil
506 }
507
508
509
510
511 }
512
513 access(all) view fun getContractViews(resourceType: Type?): [Type] {
514 return []
515 }
516
517 access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
518
519 return nil
520 }
521
522 // Admin is a special authorization resource that
523 // allows the owner to perform important functions to modify the
524 // various aspects of the Editions and Items
525 //
526 access(all) resource Admin {
527
528 access(all) let id: UInt32
529 // createEdition creates a new Edition struct
530 // and stores it in the Editions dictionary in the contract
531 //
532
533 init(id: UInt32) {
534 self.id = id
535 }
536
537
538 // createEdition creates a new Edition resource and stores it
539 // in the editions mapping in the contract
540 //
541 // Parameters:
542 // merchantID: The ID of the merchant
543 // metadata: the associated data
544 // name: The name of the Edition
545 // printingLimit: We can only mint this quantity of NFTs. If printingLimit is nil there is no limit (theoretically UInt32.max)
546 //
547 access(all) fun createEdition(merchantID: UInt32, metadata: {String: String}, name: String, printingLimit: UInt32?) {
548 // Create the new Edition
549 var newEdition <- create Edition(merchantID:merchantID, metadata: metadata, name: name, printingLimit:printingLimit)
550 let newID = newEdition.editionID
551
552 // Store it in the contract storage
553 RTLStoreItem.editions[newID] <-! newEdition
554
555 }
556
557
558 // borrowEdition returns a reference to an edition in the RTLStoreItem
559 // contract so that the admin can call methods on it
560 //
561 // Parameters: editionID: The ID of the Edition that you want to
562 // get a reference to
563 //
564 // Returns: A reference to the Edition with all of the fields
565 // and methods exposed
566 //
567 access(all) fun borrowEdition(editionID: UInt32): &Edition {
568 pre {
569 RTLStoreItem.editions[editionID] != nil: "Cannot borrow Edition: it does not exist"
570 }
571
572 // Get a reference to the Edition and return it
573 return (&RTLStoreItem.editions[editionID] as &Edition?)!
574 }
575
576 // updateEditionMetadata returns a reference to an edition in the RTLStoreItem
577 // contract so that the admin can call methods on it
578 //
579 // Parameters:
580 // editionID: The ID of the Edition that you want to update
581 //
582 // updates: a dictionary of key - values that is requested to be appended
583 //
584 // suffix: If the metadata already contains an attribute with a given key, this value should still be kept
585 // for posteriority. Therefore, the old value to be replaced will be stored in a metadata entry with key = key+suffix.
586 // This can offer some reassurance to the NFT owner that the metadata will never disappear.
587 //
588 // Returns: the EditionID
589 //
590 access(all) fun updateEditionMetadata(editionID: UInt32, updates: {String:String}, suffix: String): UInt32 {
591 pre {
592 RTLStoreItem.editions[editionID] != nil: "Cannot borrow Edition: it does not exist"
593 }
594
595 let editionRef = &RTLStoreItem.editions[editionID] as &Edition?
596 editionRef!.updateMetadata(updates: updates, suffix: suffix)
597
598 // Return the EditionID and return it
599 return editionID
600 }
601
602
603 // set default royalties
604 access(all) fun setDefaultRoyaltyByName(name: String, royalty: MetadataViews.Royalty) {
605 RTLStoreItem.defaultRoyalties[name] = royalty;
606 // verify total
607 let totalCut = RTLStoreItem.getDefaultRoyaltyTotalRate()
608 assert(totalCut <= 1.0, message: "Sum of cutInfos multipliers should not be greater than 1.0")
609 emit DefaultRoyaltiesUpdated(name: name, cut: royalty.cut)
610 }
611
612 access(all) fun removeDefaultRoyaltyByName(name: String) {
613
614 if !RTLStoreItem.defaultRoyalties.containsKey(name) {
615
616 var errorMsg = "Default Royalty with name ["
617 errorMsg = errorMsg.concat(name).concat("] does not exist")
618 panic(errorMsg)
619 }
620
621 RTLStoreItem.defaultRoyalties.remove(key: name);
622 emit DefaultRoyaltyRemoved(name: name)
623 }
624
625 // set royalties for edition
626 access(all) fun setEditionRoyaltyByName( editionID: UInt32, name: String, royalty: MetadataViews.Royalty) {
627
628 if !RTLStoreItem.royaltiesForSpecificEdition.containsKey(editionID) {
629 RTLStoreItem.royaltiesForSpecificEdition.insert(key:editionID, {})
630 }
631 //let royaltiesForSpecificEdition = RTLStoreItem.royaltiesForSpecificEdition[editionID]!
632 //royaltiesForSpecificEdition.insert(key: name, royalty);
633 RTLStoreItem.royaltiesForSpecificEdition[editionID]!.insert(key: name, royalty);
634 let totalCut = RTLStoreItem.getEditionRoyaltyTotalRate(editionID:editionID)
635 assert(totalCut <= 1.0, message: "Sum of cutInfos multipliers should not be greater than 1.0")
636
637 emit RoyaltiesForEditionUpdated(editionID: editionID, name: name, cut: royalty.cut)
638 }
639
640 // remove royalty for edition
641 access(all) fun removeEditionRoyaltyByName( editionID: UInt32, name: String) {
642
643 if !RTLStoreItem.royaltiesForSpecificEdition.containsKey(editionID) {
644 var errorMsg = "Royalty specific to editionID"
645 errorMsg = errorMsg.concat(editionID.toString()).concat(" does not exist")
646 panic(errorMsg)
647 }
648 let royaltiesForSpecificEdition = RTLStoreItem.royaltiesForSpecificEdition[editionID]!
649 if !royaltiesForSpecificEdition.containsKey(name) {
650 var errorMsg = "Royalty specific to editionID"
651 errorMsg = errorMsg.concat(editionID.toString()).concat(" with the name[").concat(name).concat("] does not exist")
652 panic(errorMsg)
653 }
654 RTLStoreItem.royaltiesForSpecificEdition[editionID]!.remove(key: name);
655 emit RoyaltiesForEditionRemoved(editionID: editionID, name: name)
656 }
657
658 access(all) fun revertRoyaltiesForEditionToDefault(editionID: UInt32){
659 if !RTLStoreItem.royaltiesForSpecificEdition.containsKey(editionID){
660 var errorMsg = "Royalty for editionID "
661 errorMsg = errorMsg.concat(editionID.toString()).concat(" does not exist")
662 panic(errorMsg)
663 }
664
665
666 RTLStoreItem.royaltiesForSpecificEdition.remove(key: editionID)
667 emit RevertRoyaltiesForEditionToDefault(editionID: editionID)
668 }
669
670
671
672
673 // createNewAdmin creates a new Admin resource
674 //
675 access(all) fun createNewAdmin(): @Admin {
676
677
678 let newID = RTLStoreItem.nextAdminID
679 // Increment the ID so that it isn't used again
680 RTLStoreItem.nextAdminID = RTLStoreItem.nextAdminID + (1 as UInt32)
681
682 return <-create Admin(id: newID)
683 }
684
685 }
686
687
688
689 // This is the interface that users can cast their RTLStoreItem Collection as
690 // to allow others to deposit RTLStoreItems into their Collection. It also allows for reading
691 // the IDs of RTLStoreItems in the Collection.
692 access(all) resource interface RTLStoreItemCollectionPublic {
693
694
695 }
696
697
698
699 // Collection is a resource that every user who owns NFTs
700 // will store in their account to manage their NFTS
701 //
702 access(all) resource Collection: NonFungibleToken.Collection, RTLStoreItemCollectionPublic {
703 // Dictionary of RTLStoreItem conforming tokens
704 // NFT is a resource type with a UInt64 ID field
705 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
706 // access(all) var ownedNFTs: @{UInt64: RTLStoreItem.NFT}
707
708 init() {
709 self.ownedNFTs <- {}
710 }
711
712
713
714
715 // withdraw removes a RTLStoreItem from the Collection and moves it to the caller
716 //
717 // Parameters: withdrawID: The ID of the NFT
718 // that is to be removed from the Collection
719 //
720 // returns: @NonFungibleToken.NFT the token that was withdrawn
721 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
722
723 // Remove the nft from the Collection
724 let token <- self.ownedNFTs.remove(key: withdrawID)
725 ?? panic("Cannot withdraw: RTLStoreItem does not exist in the collection")
726
727 emit Withdraw(id: token.id, from: self.owner?.address)
728
729 // Return the withdrawn token
730 return <-token
731 }
732
733 // batchWithdraw withdraws multiple tokens and returns them as a Collection
734 //
735 // Parameters: ids: An array of IDs to withdraw
736 //
737 // Returns: @NonFungibleToken.Collection: A collection that contains
738 // the withdrawn RTLStoreItem items
739 //
740 access(NonFungibleToken.Withdraw) fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} {
741 // Create a new empty Collection
742 var batchCollection <- create Collection()
743 // Iterate through the ids and withdraw them from the Collection
744 for id in ids {
745
746 let token <-self.withdraw(withdrawID: id)
747
748 batchCollection.deposit(token: <-token)
749 }
750
751 // Return the withdrawn tokens
752 return <-batchCollection
753 }
754
755 /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
756 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
757 let supportedTypes: {Type: Bool} = {}
758 supportedTypes[Type<@RTLStoreItem.NFT>()] = true
759 return supportedTypes
760 }
761
762 /// Returns whether or not the given type is accepted by the collection
763 /// A collection that can accept any type should just return true by default
764 access(all) view fun isSupportedNFTType(type: Type): Bool {
765 return type == Type<@RTLStoreItem.NFT>()
766 }
767
768 /// Gets the amount of NFTs stored in the collection
769 access(all) view fun getLength(): Int {
770 return self.ownedNFTs.length
771 }
772
773
774 // deposit takes a RTLStoreItem and adds it to the Collections dictionary
775 //
776 // Paramters: token: the NFT to be deposited in the collection
777 //
778 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
779
780 // Cast the deposited token as a RTLStoreItem NFT to make sure
781 // it is the correct type
782 let token <- token as! @RTLStoreItem.NFT
783
784 // Get the token's ID
785 let id = token.id
786
787 // Add the new token to the dictionary
788 let oldToken <- self.ownedNFTs[id] <- token
789
790 // Only emit a deposit event if the Collection
791 // is in an account's storage
792 if self.owner?.address != nil {
793 emit Deposit(id: id, to: self.owner?.address)
794 }
795
796 // Destroy the empty old token that was "removed"
797 destroy oldToken
798 }
799
800 // batchDeposit takes a Collection object as an argument
801 // and deposits each contained NFT into this Collection
802 access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) {
803
804 // Get an array of the IDs to be deposited
805 let keys = tokens.getIDs()
806
807 // Iterate through the keys in the collection and deposit each one
808 for key in keys {
809 self.deposit(token: <-tokens.withdraw(withdrawID: key))
810 }
811
812 // Destroy the empty Collection
813 destroy tokens
814 }
815
816 // getIDs returns an array of the IDs that are in the Collection
817 access(all) view fun getIDs(): [UInt64] {
818 return self.ownedNFTs.keys
819 }
820
821 // borrowNFT Returns a borrowed reference to a RTLStoreItem in the Collection
822 // so that the caller can read its ID
823 //
824 // Parameters: id: The ID of the NFT to get the reference for
825 //
826 // Returns: A reference to the NFT
827 //
828 // Note: This only allows the caller to read the ID of the NFT,
829 // not any RTLStoreItem specific data. Please use borrowRTLStoreItem to
830 // read RTLStoreItem data.
831 //
832 access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
833 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
834 }
835
836
837
838 /// Borrow the view resolver for the specified NFT ID
839 access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
840 if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
841 return nft as &{ViewResolver.Resolver}
842 }
843 return nil
844 }
845
846 /// createEmptyCollection creates an empty Collection of the same type
847 /// and returns it to the caller
848 /// @return A an empty collection of the same type
849 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
850 return <-RTLStoreItem.createEmptyCollection(nftType: Type<@RTLStoreItem.NFT>())
851 }
852
853 }
854
855
856
857 // -----------------------------------------------------------------------
858 // RTLStoreItem contract-level function definitions
859 // -----------------------------------------------------------------------
860
861 // createEmptyCollection creates a new, empty Collection object so that
862 // a user can store it in their account storage.
863 // Once they have a Collection in their storage, they are able to receive
864 // RTLStoreItems in transactions.
865 //
866 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
867 return <- create RTLStoreItem.Collection()
868 }
869
870 access(all) fun createEmptyRTLStoreItemCollection(): @RTLStoreItem.Collection {
871 return <-create RTLStoreItem.Collection()
872 }
873
874 // getDefaultRoyalties returns the default royalties
875 access(all) fun getDefaultRoyalties(): {String: MetadataViews.Royalty} {
876 return self.defaultRoyalties
877 }
878
879 // getDefaultRoyalties returns the default royalties
880 access(all) fun getDefaultRoyaltyNames(): [String] {
881 return self.defaultRoyalties.keys
882 }
883
884 // getDefaultRoyalties returns the default royalties
885 access(all) fun getDefaultRoyaltyByName(name: String): MetadataViews.Royalty? {
886 return self.defaultRoyalties[name]
887 }
888
889 // getDefaultRoyalties returns the default royalties total rate
890 access(all) fun getDefaultRoyaltyTotalRate(): UFix64 {
891 var cut = 0.0
892 for name in self.defaultRoyalties.keys {
893 cut=cut+self.defaultRoyalties[name]!.cut
894 }
895
896 return cut;
897 }
898
899
900
901
902
903 // getRoyaltiesForEdition returns the royalties set for a specific edition, that overrides the default
904 access(all) fun getEditionRoyalties(editionID: UInt32): {String: MetadataViews.Royalty} {
905 return self.royaltiesForSpecificEdition[editionID] ?? self.defaultRoyalties
906 }
907
908 // getRoyaltiesForEdition returns the royalties set for a specific edition, that overrides the default
909 access(all) fun getEditionRoyaltyNames(editionID: UInt32): [String] {
910 let royalties = RTLStoreItem.getEditionRoyalties(editionID: editionID)
911 return royalties.keys
912 }
913
914 // getRoyaltiesForEdition returns the royalties set for a specific edition, that overrides the default
915 access(all) fun getEditionRoyaltyByName(editionID: UInt32, name: String): MetadataViews.Royalty {
916 let royaltiesForSpecificEdition = RTLStoreItem.getEditionRoyalties(editionID: editionID)
917 return royaltiesForSpecificEdition[name]!
918 }
919
920 // getDefaultRoyalties returns the default royalties total rate
921 access(all) fun getEditionRoyaltyTotalRate(editionID: UInt32): UFix64 {
922 let royalties = RTLStoreItem.getEditionRoyalties(editionID: editionID)
923 var cut = 0.0
924 for name in royalties.keys {
925 cut=cut+royalties[name]!.cut
926 }
927
928 return cut;
929 }
930
931
932 // -----------------------------------------------------------------------
933 // initialization function
934 // -----------------------------------------------------------------------
935 //
936 init() {
937 // Initialize contract fields
938 self.editions <- {}
939 self.nextEditionID = 1
940 self.totalSupply = 0
941 self.defaultRoyalties = {}
942 self.royaltiesForSpecificEdition = {}
943
944
945 self.CollectionStoragePath = /storage/RTLStoreItemCollection
946 self.CollectionPublicPath = /public/RTLStoreItemCollection
947 self.AdminStoragePath = /storage/RTLStoreItemItemAdmin
948
949 // Put a new Collection in storage
950 self.account.storage.save<@Collection>(<- create Collection(), to: self.CollectionStoragePath)
951
952 // Create a public capability for the Collection
953 // self.account.link<&{RTLStoreItemCollectionPublic}>(self.CollectionPublicPath, target: self.CollectionStoragePath)
954 let collectionCap = self.account.capabilities.storage.issue<&RTLStoreItem.Collection>(self.CollectionStoragePath)
955 self.account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
956
957 // Put the admin ressource in storage
958 self.account.storage.save<@Admin>(<- create Admin(id: 1), to: self.AdminStoragePath)
959 self.nextAdminID = 2
960
961 emit ContractInitialized()
962 }
963
964
965}
966
967