Smart Contract
Magnetiq
A.5257f1455ed366fe.Magnetiq
1/*
2 Description: Central Smart Contract for Magnetiq
3
4 This smart contract contains the core functionality for
5 Magnetiq, created by Hcode
6
7 The contract manages the data associated with all the magnets and brands
8 that are used as templates for the Tokens NFTs
9
10 When a new Magnet wants to be added to the records, an Admin creates
11 a new Magnet struct that is stored in the smart contract.
12
13 Then an Admin can create new Brands. Brands consist of a public struct that
14 contains public information about a brand, and a private resource used
15 to mint new tokens based off of magnets that have been linked to the Brand.
16
17 The admin resource has the power to do all of the important actions
18 in the smart contract. When admins want to call functions in a brand,
19 they call their borrowBrand function to get a reference
20 to a brand in the contract. Then, they can call functions on the brand using that reference.
21
22 When tokens are minted, they are initialized with a TokensData struct and
23 are returned by the minter.
24
25 The contract also defines a Collection resource. This is an object that
26 every Magnetiq NFT owner will store in their account
27 to manage their NFT collection.
28
29 The main Magnetiq account will also have its own Tokens collections
30 it can use to hold its own tokens that have not yet been sent to a user.
31
32 Note: All state changing functions will panic if an invalid argument is
33 provided or one of its pre-conditions or post conditions aren't met.
34 Functions that don't modify state will simply return 0 or nil
35 and those cases need to be handled by the caller.
36*/
37
38import FungibleToken from 0xf233dcee88fe0abe
39import NonFungibleToken from 0x1d7e57aa55817448
40import MetadataViews from 0x1d7e57aa55817448
41import MagnetiqLocking from 0x5257f1455ed366fe
42import ViewResolver from 0x1d7e57aa55817448
43
44access(all) contract Magnetiq: NonFungibleToken {
45 // -----------------------------------------------------------------------
46 // Magnetiq deployment variables
47 // -----------------------------------------------------------------------
48
49 // The network the contract is deployed on
50 access(all) fun Network() : String { return self.currentNetwork }
51
52 // The address to which royalties should be deposited
53 access(all) fun RoyaltyAddress() : Address { return self.royalityReceiver }
54
55
56 // -----------------------------------------------------------------------
57 // Magnetiq contract Events
58 // -----------------------------------------------------------------------
59
60 // Emitted when the Magnetiq contract is created
61 access(all) event ContractInitialized()
62
63 // Emitted when a new Magnet struct is created
64 access(all) event MagnetCreated(id: String, metadata: {String:String})
65
66
67 // Events for Brand-Related actions
68 //
69 // Emitted when a new Brand is created
70 access(all) event BrandCreated(brandID: String)
71 // Emitted when a new Magnet is added to a Brand
72 access(all) event MagnetAddedToBrand(brandID: String, magnetID: String)
73 // Emitted when a Magnet is retired from a Brand and cannot be used to mint
74 access(all) event MagnetRetiredFromBrand(brandID: String, magnetID: String, numTokens: UInt32)
75 // Emitted when a Brand is locked, meaning Magnets cannot be added
76 access(all) event BrandLocked(brandID: String)
77 // Emitted when a Tokens is minted from a Brand
78 access(all) event TokensMinted(tokenID: UInt64, tokenType:String , magnetiqID: String, brandID: String, serialNumber: UInt32)
79
80 // Events for Collection-related actions
81 //
82 // Emitted when a token is withdrawn from a Collection
83 access(all) event Withdraw(id: UInt64, from: Address?)
84 // Emitted when a token is deposited into a Collection
85 access(all) event Deposit(id: UInt64, to: Address?)
86
87 // Emitted when a Tokens is destroyed
88 access(all) event TokensDestroyed(id: UInt64)
89
90 // Emitted when a Memento is created
91 access(all) event MementoCreated(mementoID:String, magnetID:String)
92
93 // Emitted when a token mark claimed
94 access(all) event TokenClaimed(tokenID:UInt64)
95
96 // Emitted when magnet metadata updated
97 access(all) event MagnetMetadataUpdated(id:String, metadata:{String:String})
98
99 // Emitted when memento metadata updated
100 access(all) event MementoMetadataUpdated(id:String, metadata:{String:String})
101
102 // -----------------------------------------------------------------------
103 // Magnetiq contract-level fields.
104 // These contain actual values that are stored in the smart contract.
105 // -----------------------------------------------------------------------
106
107 // variable for network
108 access(all) var currentNetwork: String
109
110 // variable for roya;ity reciever address
111 access(all) var royalityReceiver: Address
112
113 // variable for royality percentage
114 access(all) var royalityPercentage: UFix64
115
116 // Series that this Brand belongs to.
117 // Series is a concept that indicates a group of Brands through time.
118 // Many Brands can exist at a time, but only one series.
119 access(all) var currentSeries: UInt32
120
121 // Variable size dictionary of Magnet structs
122 access(self) var magnetData: {String: Magnet}
123
124 // Variable size dictionary of Memento structs
125 access(self) var mementoData: {String:Memento}
126 access(self) var magnetiqTokenExtraInfo: {UInt64:{String:AnyStruct}}
127
128 // Variable size dictionary of BrandData structs
129 access(self) var brandData: {String: BrandData}
130
131 // Variable size dictionary of Brand resources
132 access(self) var brands: @{String: Brand}
133
134 // Dictionary of sellers allowed to sell non-sellable NFT
135 access(self) var allowedSellersForNonSellableNFT:[Address?]
136
137
138 // The total number of Magnetiq Tokens NFTs that have been created
139 // Because NFTs can be destroyed, it doesn't necessarily mean that this
140 // reflects the total number of NFTs in existence, just the number that
141 // have been minted to date. Also used as global token IDs for minting.
142 access(all) var totalSupply: UInt64
143
144
145 // -----------------------------------------------------------------------
146 // Magnetiq contract-level Composite Type definitions
147 // -----------------------------------------------------------------------
148 // These are just *definitions* for Types that this contract
149 // and other accounts can use. These definitions do not contain
150 // actual stored values, but an instance (or object) of one of these Types
151 // can be created by this contract that contains stored values.
152 // -----------------------------------------------------------------------
153
154 // Magnet is a Struct that holds metadata associated
155 // with a specific magnet
156 //
157 // Tokens NFTs will all reference a single magnet as the owner of
158 // its metadata. The magnets are publicly accessible, so anyone can
159 // read the metadata associated with a specific magnet ID
160 //
161 access(all) struct Magnet {
162
163 // The unique ID for the Magnet
164 access(all) let magnetID: String
165
166 // Array of memento ids that are a part of this Magnet.
167 // When a memento is added to the mangnet, its ID gets appended here.
168 access(contract) var mementos: [String]
169
170 // Stores all the metadata about the magnet as a string mapping
171 // This is not the long term way NFT metadata will be stored. It's a temporary
172 // construct while we figure out a better way to do metadata.
173 //
174 access(all) var metadata: {String: String}
175
176 // Mapping of memento IDs that indicates the number of tokens
177 // that have been minted for specific Memento in this Magnet.
178 access(contract) var numberMintedPerMemento: {String: UInt32}
179
180 init(metadata: {String: String}, magnetID:String) {
181 pre {
182 metadata.length != 0: "New Magnet metadata cannot be empty"
183 }
184 self.magnetID = magnetID
185 self.metadata = metadata
186 self.numberMintedPerMemento = {}
187 self.mementos = []
188 }
189
190 access(all) fun updateMementoList(mementoID: String) {
191 self.mementos.append(mementoID)
192 Magnetiq.magnetData[self.magnetID] = self
193 }
194
195 access(all) fun updateMementoCount(mementoID: String, count: UInt32) {
196 self.numberMintedPerMemento[mementoID] = count
197 Magnetiq.magnetData[self.magnetID] = self
198 }
199 access(all) fun updateMetadata(new_metadata:{String:String}){
200 self.metadata = new_metadata
201 }
202 }
203
204 access(all) struct Memento{
205 access(all) let mementoID:String
206 access(all) let magnetID: String
207 access(all) var metadata: {String:String}
208
209
210 init(mementoID:String, magnetID:String,metadata:{String:String}){
211 self.mementoID = mementoID
212 self.magnetID = magnetID
213 self.metadata = metadata
214 }
215
216 access(all) fun updateMementoMetadata(new_metadata:{String:String}){
217 self.metadata = new_metadata
218 }
219
220
221 }
222
223 // A Brand is a grouping of Magnets that have occured in the real world
224 // that make up a related group of collectibles, like brands of baseball
225 // or Magic cards. A Magnet can exist in multiple different brands.
226 //
227 // BrandData is a struct that is stored in a field of the contract.
228 // Anyone can query the constant informationbrands
229 // about a brand by calling various getters located
230 // at the end of the contract. Only the admin has the ability
231 // to modify any data in the private Brand resource.
232 //
233 access(all) struct BrandData {
234
235 // Unique ID for the Brand
236 access(all) let brandID: String
237
238 // Name of the Brand
239 access(all) let name: String
240
241
242 init(name: String, brandID:String) {
243 pre {
244 name.length > 0: "New Brand name cannot be empty"
245 }
246 self.brandID = brandID
247 self.name = name
248 }
249 }
250
251 // Brand is a resource type that contains the functions to add and remove
252 // Magnets from a brand and mint Tokens.
253 //
254 // It is stored in a private field in the contract so that
255 // the admin resource can call its methods.
256 //
257 // The admin can add Magnets to a Brand so that the brand can mint Tokens
258 // that reference that magnetdata.
259 // The Tokens that are minted by a Brand will be listed as belonging to
260 // the Brand that minted it, as well as the Magnet it references.
261 //
262 // Admin can also retire Magnets from the Brand, meaning that the retired
263 // Magnet can no longer have Tokens minted from it.
264 //
265 // If the admin locks the Brand, no more Magnets can be added to it, but
266 // Tokens can still be minted.
267 //
268 // If retireAll() and lock() are called back-to-back,
269 // the Brand is closed off forever and nothing more can be done with it.
270 access(all) resource Brand {
271
272 // Unique ID for the brand
273 access(all) let brandID: String
274
275 // Array of magnets that are a part of this brand.
276 // When a magnet is added to the brand, its ID gets appended here.
277 // The ID does not get removed from this array when a Magnet is retired.
278 access(contract) var magnets: [String]
279
280 // Map of Magnet IDs that Indicates if a Magnet in this Brand can be minted.
281 // When a Magnet is added to a Brand, it is mapped to false (not retired).
282 // When a Magnet is retired, this is brand to true and cannot be changed.
283 access(contract) var retired: {String: Bool}
284
285 // Indicates if the Brand is currently locked.
286 // When a Brand is created, it is unlocked
287 // and Magnets are allowed to be added to it.
288 // When a brand is locked, Magnets cannot be added.
289 // A Brand can never be changed from locked to unlocked,
290 // the decision to lock a Brand it is final.
291 // If a Brand is locked, Magnets cannot be added, but
292 // Tokens can still be minted from Magnets
293 // that exist in the Brand.
294 access(all) var locked: Bool
295
296 // Mapping of Magnet IDs that indicates the number of Tokens
297 // that have been minted for specific Magnets in this Brand.
298 // When a Tokens is minted, this value is stored in the Tokens to
299 // show its place in the Brand, eg. 13 of 60.
300 access(contract) var numberMintedPerMagnet: {String: UInt32}
301
302 init(name: String, brandID:String) {
303 self.brandID = brandID
304 self.magnets = []
305 self.retired = {}
306 self.locked = false
307 self.numberMintedPerMagnet = {}
308
309 // Create a new BrandData for this Brand and store it in contract storage
310 Magnetiq.brandData[self.brandID] = BrandData(name: name, brandID:brandID)
311 }
312
313 // addMagnet adds a magnet to the brand
314 //
315 // Parameters: magnetID: The ID of the Magnet that is being added
316 //
317 // Pre-Conditions:
318 // The Magnet needs to be an existing magnet
319 // The Brand needs to be not locked
320 // The Magnet can't have already been added to the Brand
321 //
322 access(all) fun addMagnet(magnetID: String) {
323 pre {
324 Magnetiq.magnetData[magnetID] != nil: "Cannot add the Magnet to Brand: Magnet doesn't exist."
325 !self.locked: "Cannot add the magnet to the Brand after the brand has been locked."
326 self.numberMintedPerMagnet[magnetID] == nil: "The magnet has already beed added to the brand."
327 }
328
329 // Add the Magnet to the array of Magnets
330 self.magnets.append(magnetID)
331
332 // Open the Magnet up for minting
333 self.retired[magnetID] = false
334
335 // Initialize the Tokens count to zero
336 self.numberMintedPerMagnet[magnetID] = 0
337
338 emit MagnetAddedToBrand(brandID: self.brandID, magnetID: magnetID)
339 }
340
341 // addMagnets adds multiple Magnets to the Brand
342 //
343 // Parameters: magnetIDs: The IDs of the Magnets that are being added
344 // as an array
345 //
346 access(all) fun addMagnets(magnetIDs: [String]) {
347 for magnet in magnetIDs {
348 self.addMagnet(magnetID: magnet)
349 }
350 }
351
352 // retireMagnet retires a Magnet from the Brand so that it can't mint new Tokens
353 //
354 // Parameters: magnetID: The ID of the Magnet that is being retired
355 //
356 // Pre-Conditions:
357 // The Magnet is part of the Brand and not retired (available for minting).
358 //
359 access(all) fun retireMagnet(magnetID: String) {
360 pre {
361 self.retired[magnetID] != nil: "Cannot retire the Magnet: Magnet doesn't exist in this brand!"
362 }
363
364 if !self.retired[magnetID]! {
365 self.retired[magnetID] = true
366
367 emit MagnetRetiredFromBrand(brandID: self.brandID, magnetID: magnetID, numTokens: self.numberMintedPerMagnet[magnetID]!)
368 }
369 }
370
371 // retireAll retires all the magnets in the Brand
372 // Afterwards, none of the retired Magnets will be able to mint new Tokens
373 //
374 access(all) fun retireAll() {
375 for magnet in self.magnets {
376 self.retireMagnet(magnetID: magnet)
377 }
378 }
379
380 // lock() locks the Brand so that no more Magnets can be added to it
381 //
382 // Pre-Conditions:
383 // The Brand should not be locked
384 access(all) fun lock() {
385 if !self.locked {
386 self.locked = true
387 emit BrandLocked(brandID: self.brandID)
388 }
389 }
390
391 // mintToken mints a new Tokens and returns the newly minted Tokens
392 //
393 // Parameters: magnetID: The ID of the Magnet that the Tokens references
394 //
395 // Pre-Conditions:
396 // The Magnet must exist in the Brand and be allowed to mint new Tokens
397 //
398 // Returns: The NFT that was minted
399 //
400 access(all) fun mintToken(magnetiqID: String, tokenType:String): @NFT {
401
402 var numInMagnetMemento: UInt32? = 0
403
404
405 if tokenType == "magnet" {
406 let magnet = Magnetiq.magnetData[magnetiqID]
407 if magnet == nil {
408 panic("Cannot mint the token: This magnet doesn't exist.")
409 }
410 if self.retired[magnetiqID]! {
411 panic("Cannot mint the token from this magnet: This magnet has been retired.")
412 }
413
414 numInMagnetMemento = self.numberMintedPerMagnet[magnetiqID]!
415 self.numberMintedPerMagnet[magnetiqID] = numInMagnetMemento! + UInt32(1)
416 }
417 else{
418 let memento = Magnetiq.mementoData[magnetiqID]
419 if memento == nil {
420 panic("Cannot mint the token: This memento doesn't exist.")
421 }
422 var magnet_id = memento?.magnetID!
423 let magnet = Magnetiq.magnetData[magnet_id]
424 if magnet != nil {
425 numInMagnetMemento = magnet?.numberMintedPerMemento![magnetiqID] ?? 0
426 magnet?.updateMementoCount(mementoID:magnetiqID, count:numInMagnetMemento! + UInt32(1))
427 }
428 }
429
430
431 // Gets the number of Tokens that have been minted for this Magnet
432 // to use as this Tokens's serial number
433
434 // Mint the new token
435 let newTokens: @NFT <- create NFT(serialNumber: numInMagnetMemento! + UInt32(1),
436 magnetiqID: magnetiqID,
437 brandID: self.brandID,
438 tokenType:tokenType
439 )
440
441
442
443 Magnetiq.magnetiqTokenExtraInfo[newTokens.id] = {}
444 return <-newTokens
445 }
446
447 // batchMintTokens mints an arbitrary quantity of Tokens
448 // and returns them as a Collection
449 //
450 // Parameters: magnetID: the ID of the Magnet that the Tokens are minted for
451 // quantity: The quantity of Tokens to be minted
452 //
453 // Returns: Collection object that contains all the Tokens that were minted
454 //
455 access(all) fun batchMintTokens(magnetiqID: String, quantity: UInt64, tokenType:String): @Collection {
456 let newCollection <- create Collection()
457
458 var i: UInt64 = 0
459 while i < quantity {
460 newCollection.deposit(token: <-self.mintToken(magnetiqID: magnetiqID,tokenType:tokenType))
461 i = i + UInt64(1)
462 }
463
464 return <-newCollection
465 }
466
467 access(all) fun getMagnets(): [String] {
468 return self.magnets
469 }
470
471 access(all) fun getRetired(): {String: Bool} {
472 return self.retired
473 }
474
475 access(all) fun getNumMintedPerMagnet(): {String: UInt32} {
476 return self.numberMintedPerMagnet
477 }
478 }
479
480 // Struct that contains all of the important data about a brand
481 // Can be easily queried by instantiating the `QueryBrandData` object
482 // with the desired brand ID
483 // let brandData = Magnetiq.QueryBrandData(brandID: 12)
484 //
485 access(all) struct QueryBrandData {
486 access(all) let brandID: String
487 access(all) let name: String
488 access(self) var magnets: [String]
489 access(self) var retired: {String: Bool}
490 access(all) var locked: Bool
491 access(self) var numberMintedPerMagnet: {String: UInt32}
492
493 init(brandID: String) {
494 pre {
495 Magnetiq.brands[brandID] != nil: "The brand with the provided ID does not exist"
496 }
497
498 let brand = (&Magnetiq.brands[brandID] as &Brand?)!
499 let brandData = Magnetiq.brandData[brandID]!
500
501 self.brandID = brandID
502 self.name = brandData.name
503 self.magnets = brand.getMagnets()
504 self.retired = brand.getRetired()
505 self.locked = brand.locked
506 self.numberMintedPerMagnet = brand.getNumMintedPerMagnet()
507 }
508
509 access(all) fun getMagnets(): [String] {
510 return self.magnets
511 }
512
513 access(all) fun getRetired(): {String: Bool} {
514 return self.retired
515 }
516
517 access(all) fun getNumberMintedPerMagnet(): {String: UInt32} {
518 return self.numberMintedPerMagnet
519 }
520 }
521
522 access(all) struct TokensData {
523
524 // The ID of the Brand that the Tokens comes from
525 access(all) let brandID: String
526
527 // The ID of the Magnet that the Tokens references
528 access(all) let magnetiqID: String
529
530 // The place in the edition that this Tokens was minted
531 // Otherwise know as the serial number
532 access(all) let serialNumber: UInt32
533 access(all) let tokenType: String
534
535
536 init(brandID: String, magnetiqID: String, serialNumber: UInt32, tokenType:String) {
537 self.brandID = brandID
538 self.magnetiqID = magnetiqID
539 self.serialNumber = serialNumber
540 self.tokenType = tokenType
541 }
542 }
543
544 // This is an implementation of a custom metadata view for Magnetiq.
545 // This view contains the magnet metadata.
546 // there will be
547 access(all) struct MagnetiqTokenMetadataView {
548
549 access(all) let name: String?
550 access(all) let tokenType: String?
551 access(all) let brandName: String?
552 access(all) let serialNumber: UInt32
553 access(all) let magnetID: String
554 access(all) let mementoID: String?
555 access(all) let brandID: String
556 access(all) let numTokensInEdition: UInt32?
557 access(all) let is_claimed: AnyStruct?
558 access(all) let is_sellable: AnyStruct?
559 access(all) let is_claimable: AnyStruct?
560 access(all) let is_visible: AnyStruct?
561
562
563 init(
564 name: String?,
565 tokenType: String?,
566 brandName: String?,
567 serialNumber: UInt32,
568 magnetID: String,
569 mementoID: String?,
570 brandID: String,
571 numTokensInEdition: UInt32?,
572 is_claimed: AnyStruct?,
573 is_sellable: AnyStruct?,
574 is_claimable: AnyStruct?,
575 is_visible: AnyStruct?
576 ) {
577 self.name = name
578 self.tokenType = tokenType
579 self.brandName = brandName
580 self.serialNumber = serialNumber
581 self.magnetID = magnetID
582 self.brandID = brandID
583 self.numTokensInEdition = numTokensInEdition
584 self.mementoID = mementoID
585 self.is_claimed= is_claimed
586 self.is_claimable = is_claimable
587 self.is_sellable = is_sellable
588 self.is_visible = is_visible
589 }
590 }
591
592 // This is an implementation of a custom metadata view for Magnetiq.
593 // This view contains the magnet metadata.
594 // there will be
595 access(all) struct MagnetTokenMetadataView {
596
597 access(all) let name: String?
598 access(all) let brandName: String?
599 access(all) let serialNumber: UInt32
600 access(all) let magnetID: String
601 access(all) let brandID: String
602 access(all) let numTokensInEdition: UInt32?
603 access(all) let is_sellable: AnyStruct?
604
605 init(
606 name: String?,
607 brandName: String?,
608 serialNumber: UInt32,
609 magnetID: String,
610 brandID: String,
611 numTokensInEdition: UInt32?,
612 is_sellable: AnyStruct?
613 ) {
614 self.name = name
615 self.brandName = brandName
616 self.serialNumber = serialNumber
617 self.magnetID = magnetID
618 self.brandID = brandID
619 self.numTokensInEdition = numTokensInEdition
620 self.is_sellable = is_sellable
621 }
622 }
623
624 // This is an implementation of a custom metadata view for Magnetiq.
625 // This view contains the magnet metadata.
626 // there will be
627 access(all) struct MementoTokenMetadataView {
628
629 access(all) let name: String?
630 access(all) let brandName: String?
631 access(all) let serialNumber: UInt32
632 access(all) let magnetID: String
633 access(all) let mementoID: String
634 access(all) let brandID: String
635 access(all) let numTokensInEdition: UInt32?
636 access(all) let is_claimed: AnyStruct?
637 access(all) let is_sellable: AnyStruct?
638 access(all) let is_claimable: AnyStruct?
639 access(all) let is_visible: AnyStruct?
640
641
642 init(
643 name: String?,
644 brandName: String?,
645 serialNumber: UInt32,
646 magnetID: String,
647 mementoID: String,
648 brandID: String,
649 numTokensInEdition: UInt32?,
650 is_claimed: AnyStruct?,
651 is_sellable: AnyStruct?,
652 is_claimable: AnyStruct?,
653 is_visible: AnyStruct?
654 ) {
655 self.name = name
656 self.brandName = brandName
657 self.serialNumber = serialNumber
658 self.magnetID = magnetID
659 self.mementoID = mementoID
660 self.brandID = brandID
661 self.numTokensInEdition = numTokensInEdition
662 self.is_claimed = is_claimed
663 self.is_sellable = is_sellable
664 self.is_claimable = is_claimable
665 self.is_visible = is_visible
666 }
667 }
668
669
670 // The resource that represents the Tokens NFTs
671 //
672 access(all) resource NFT: NonFungibleToken.NFT {
673
674 access(all) event ResourceDestroyed(id: UInt64 = self.id)
675
676 // Global unique token ID
677 access(all) let id: UInt64
678
679 // Struct of Tokens metadata
680 access(all) let data: TokensData
681
682 init(serialNumber: UInt32, magnetiqID: String, brandID: String, tokenType:String ) {
683 // Increment the global Tokens IDs
684 Magnetiq.totalSupply = Magnetiq.totalSupply + UInt64(1)
685
686 self.id = Magnetiq.totalSupply
687
688 // Brand the metadata struct
689 self.data = TokensData(brandID: brandID, magnetiqID: magnetiqID, serialNumber: serialNumber,tokenType:tokenType )
690
691 emit TokensMinted(tokenID: self.id, tokenType:tokenType, magnetiqID: magnetiqID, brandID: self.data.brandID, serialNumber: self.data.serialNumber)
692 }
693
694 // If the Tokens is destroyed, emit an event to indicate
695 // to outside ovbservers that it has been destroyed
696 // destroy() {
697 // emit TokensDestroyed(id: self.id)
698 // }
699
700 access(all) fun name(): String {
701 let tokType: String = self.data.tokenType
702 var fullName: String = ""
703 if tokType == "magnet"{
704 fullName = Magnetiq.getMagnetMetaDataByField(magnetiqID: self.data.magnetiqID, field: "name") ?? ""
705 }
706 else {
707 fullName = Magnetiq.getMementoMetaDataByField(magnetiqID: self.data.magnetiqID, field: "name") ?? ""
708 }
709 return fullName
710 .concat(" (")
711 .concat(tokType)
712 .concat(")")
713 }
714
715
716 access(self) fun buildDescString(): String {
717 let brandName: String = Magnetiq.getBrandName(brandID: self.data.brandID) ?? ""
718 let serialNumber: String = self.data.serialNumber.toString()
719 return "A "
720 .concat(self.data.tokenType)
721 .concat(" from brand")
722 .concat(brandName)
723 .concat(" with serial number ")
724 .concat(serialNumber)
725 }
726
727 access(all) fun description(): String {
728 var desc: String = ""
729 if self.data.tokenType == "magnet"{
730 desc = Magnetiq.getMagnetMetaDataByField(magnetiqID: self.data.magnetiqID, field: "description") ?? ""
731 }
732 else{
733 desc = Magnetiq.getMementoMetaDataByField(magnetiqID: self.data.magnetiqID, field: "description") ?? ""
734 }
735 return desc.length > 0 ? desc : self.buildDescString()
736 }
737
738 // All supported metadata views for the Token including the Core NFT Views
739 view access(all) fun getViews(): [Type] {
740 return [
741 Type<MetadataViews.Display>(),
742 Type<MagnetiqTokenMetadataView>(), // keep the common view, and dont add individual magnet/memento view
743 Type<MagnetTokenMetadataView>(),
744 Type<MementoTokenMetadataView>(),
745 Type<MetadataViews.Royalties>(),
746 Type<MetadataViews.Editions>(),
747 Type<MetadataViews.ExternalURL>(),
748 Type<MetadataViews.NFTCollectionData>(),
749 Type<MetadataViews.NFTCollectionDisplay>(),
750 Type<MetadataViews.Serial>(),
751 Type<MetadataViews.Traits>(),
752 Type<MetadataViews.Medias>()
753 ]
754 }
755
756
757
758 access(all) fun resolveView(_ view: Type): AnyStruct? {
759 switch view {
760 case Type<MetadataViews.Display>():
761 return MetadataViews.Display(
762 name: self.name(),
763 description: self.description(),
764 thumbnail: MetadataViews.HTTPFile(url: self.thumbnail())
765 )
766
767 // metadata view for magnet tokens
768 case Type<MagnetTokenMetadataView>():
769 return MagnetTokenMetadataView(
770 name: Magnetiq.getMagnetMetaDataByField(magnetiqID: self.data.magnetiqID, field: "name"),
771 brandName: Magnetiq.getBrandName(brandID: self.data.brandID),
772 serialNumber: self.data.serialNumber,
773 magnetID: self.data.magnetiqID,
774 brandID: self.data.brandID,
775 numTokensInEdition: Magnetiq.getNumTokensInEdition(brandID: self.data.brandID, magnetiqID: self.data.magnetiqID, tokenType:self.data.tokenType),
776 is_sellable: Magnetiq.getMagnetMetaDataByField(magnetiqID: self.data.magnetiqID, field: "is_sellable")
777 )
778
779 // metadata view for memento tokens
780 case Type<MementoTokenMetadataView>():
781 return MementoTokenMetadataView(
782 name: Magnetiq.getMementoMetaDataByField(magnetiqID: self.data.magnetiqID, field: "name"),
783 brandName: Magnetiq.getBrandName(brandID: self.data.brandID),
784 serialNumber: self.data.serialNumber,
785 magnetID: Magnetiq.mementoData[self.data.magnetiqID]?.magnetID!,
786 mementoID: self.data.magnetiqID,
787 brandID: self.data.brandID,
788 numTokensInEdition: Magnetiq.getNumTokensInEdition(brandID: self.data.brandID, magnetiqID: self.data.magnetiqID, tokenType:self.data.tokenType),
789 is_claimed: Magnetiq.magnetiqTokenExtraInfo[self.id]!["claimed"] ?? "",
790 is_sellable: Magnetiq.getMementoMetaDataByField(magnetiqID: self.data.magnetiqID, field: "is_sellable"),
791 is_claimable: Magnetiq.getMementoMetaDataByField(magnetiqID: self.data.magnetiqID, field: "is_claimable"),
792 is_visible: Magnetiq.getMementoMetaDataByField(magnetiqID: self.data.magnetiqID, field: "is_visible")
793 )
794
795 // generic metadata view for magnetiq token
796 case Type<MagnetiqTokenMetadataView>():
797 var magnetID:String = ""
798 var mementoID: String = ""
799 var is_claimed:AnyStruct? = nil
800 var is_sellable: AnyStruct? = nil
801 var is_claimable: AnyStruct? = nil
802 var is_visible: AnyStruct? = nil
803 if self.data.tokenType == "magnet" {
804 magnetID = self.data.magnetiqID
805 }
806 else {
807 mementoID = self.data.magnetiqID
808 magnetID = Magnetiq.mementoData[self.data.magnetiqID]?.magnetID!
809 is_sellable = Magnetiq.getMementoMetaDataByField(magnetiqID: self.data.magnetiqID, field: "is_sellable")
810 is_claimable = Magnetiq.getMementoMetaDataByField(magnetiqID: self.data.magnetiqID, field: "is_claimable")
811 is_visible = Magnetiq.getMementoMetaDataByField(magnetiqID: self.data.magnetiqID, field: "is_visible")
812 is_claimed= Magnetiq.magnetiqTokenExtraInfo[self.id]!["claimed"] ?? ""
813 }
814 return MagnetiqTokenMetadataView(
815 name: Magnetiq.getMementoMetaDataByField(magnetiqID: self.data.magnetiqID, field: "name"),
816 tokenType: self.data.tokenType,
817 brandName: Magnetiq.getBrandName(brandID: self.data.brandID),
818 serialNumber: self.data.serialNumber,
819 magnetID: magnetID,
820 mementoID: mementoID,
821 brandID: self.data.brandID,
822 numTokensInEdition: Magnetiq.getNumTokensInEdition(brandID: self.data.brandID, magnetiqID: self.data.magnetiqID, tokenType:self.data.tokenType),
823 is_claimed: is_claimed,
824 is_sellable: is_sellable,
825 is_claimable: is_claimable,
826 is_visible: is_visible
827 )
828
829 case Type<MetadataViews.Editions>():
830 let name = self.getEditionName()
831 let max = Magnetiq.getNumTokensInEdition(brandID: self.data.brandID, magnetiqID: self.data.magnetiqID,tokenType:self.data.tokenType) ?? 0
832 let editionInfo = MetadataViews.Edition(name: name, number: UInt64(self.data.serialNumber), max: max > 0 ? UInt64(max) : nil)
833 let editionList: [MetadataViews.Edition] = [editionInfo]
834 return MetadataViews.Editions(
835 editionList
836 )
837 case Type<MetadataViews.Serial>():
838 return MetadataViews.Serial(
839 UInt64(self.data.serialNumber)
840 )
841 case Type<MetadataViews.Royalties>():
842 let royaltyReceiver: Capability<&{FungibleToken.Receiver}> =
843 getAccount(Magnetiq.RoyaltyAddress()).capabilities.get<&{FungibleToken.Receiver}>(MetadataViews.getRoyaltyReceiverPublicPath())!
844 return MetadataViews.Royalties(
845 [
846 MetadataViews.Royalty(
847 receiver: royaltyReceiver,
848 cut: Magnetiq.royalityPercentage,
849 description: "Magnetiq marketplace royalty"
850 )
851 ]
852 )
853 case Type<MetadataViews.ExternalURL>():
854 return MetadataViews.ExternalURL(self.getTokensURL())
855 case Type<MetadataViews.NFTCollectionData>():
856 return MetadataViews.NFTCollectionData(
857 storagePath: /storage/MagnetiqTokensCollection,
858 publicPath: /public/MagnetiqTokensCollection,
859 publicCollection: Type<&Magnetiq.Collection>(),
860 publicLinkedType: Type<&Magnetiq.Collection>(),
861 createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
862 return <-Magnetiq.createEmptyCollection(nftType: Type<@Magnetiq.NFT>())
863 })
864 )
865 case Type<MetadataViews.NFTCollectionDisplay>():
866 let bannerImage = MetadataViews.Media(
867 file: MetadataViews.HTTPFile(
868 url: "https://magnetiq-static.s3.amazonaws.com/media/public/MAGNETIQ_banner.png"
869 ),
870 mediaType: "image/png"
871 )
872 let squareImage = MetadataViews.Media(
873 file: MetadataViews.HTTPFile(
874 url: "https://magnetiq-static.s3.amazonaws.com/media/public/MAGNETIQ_Square_Logo.png"
875 ),
876 mediaType: "image/png"
877 )
878 return MetadataViews.NFTCollectionDisplay(
879 name: "MAGNETIQ",
880 description: "MAGNETIQ is making managing brand community engagement easy and efficient with a plug and play, blockchain powered platform. MAGNETIQ NFTs represent your membership in brand communities.",
881 externalURL: MetadataViews.ExternalURL("https://www.magnetiq.xyz/"),
882 squareImage: squareImage,
883 bannerImage: bannerImage,
884 socials: {
885 "twitter": MetadataViews.ExternalURL("https://twitter.com/magnetiq_xyz"),
886 "discord": MetadataViews.ExternalURL("https://discord.com/invite/Magnetiq"),
887 "instagram": MetadataViews.ExternalURL("https://www.instagram.com/magnetiq_xyz")
888 }
889 )
890 case Type<MetadataViews.Traits>():
891 // sports radar team id
892 let excludedNames: [String] = ["TeamAtTokensID"]
893 // non magnet specific traits
894 let traitDictionary: {String: AnyStruct} = {
895 "BrandName": Magnetiq.getBrandName(brandID: self.data.brandID),
896 "SerialNumber": self.data.serialNumber
897 }
898 // add magnet specific data
899 let fullDictionary = self.mapMagnetData(dict: traitDictionary)
900 return MetadataViews.dictToTraits(dict: fullDictionary, excludedNames: excludedNames)
901 case Type<MetadataViews.Medias>():
902 return MetadataViews.Medias(
903 [
904 MetadataViews.Media(
905 file: MetadataViews.HTTPFile(
906 url: self.mediumimage()
907 ),
908 mediaType: "image/jpeg"
909 ),
910 MetadataViews.Media(
911 file: MetadataViews.HTTPFile(
912 url: self.video()
913 ),
914 mediaType: "video/mp4"
915 )
916 ]
917 )
918 }
919
920 return nil
921 }
922
923 // Functions used for computing MetadataViews
924
925 // mapMagnetData helps build our trait map from magnet metadata
926 // Returns: The trait map with all non-empty fields from magnet data added
927 access(all) fun mapMagnetData(dict: {String: AnyStruct}) : {String: AnyStruct} {
928 if self.data.tokenType == "magnet"{
929 let magnetMetadata = Magnetiq.getMagnetMetaData(magnetID: self.data.magnetiqID) ?? {}
930 for name in magnetMetadata.keys {
931 let value = magnetMetadata[name] ?? ""
932 if value != "" {
933 dict.insert(key: name, value)
934 }
935 }
936 return dict
937 }
938 return {}
939 }
940
941 // getTokensURL
942 // Returns: The computed external url of the token
943 view access(all) fun getTokensURL(): String {
944 return "https://backend.magnetiq.xyz/token/".concat(self.id.toString())
945 }
946 // getEditionName Tokens's edition name is a combination of the Tokens's brandName and magnetID
947 // `brandName: #magnetID`
948 access(all) fun getEditionName() : String {
949 if self.data.tokenType != "magnet"{
950 return ""
951 }
952 let brandName: String = Magnetiq.getBrandName(brandID: self.data.brandID) ?? ""
953 let editionName = brandName.concat(": #").concat(self.data.magnetiqID)
954 return editionName
955 }
956 access(all) fun assetPath(): String {
957 if self.data.tokenType == "magnet" {
958 let magnet = (&Magnetiq.magnetData[self.data.magnetiqID] as &Magnet?)!
959 if magnet==nil {
960 return "https://magnetiq-static.s3.amazonaws.com/media/public/default-magnet-Icon.png"
961 }
962
963 let image_url:String = magnet.metadata["image_url"]!
964 return image_url
965 }
966 else {
967 let memento = (&Magnetiq.mementoData[self.data.magnetiqID] as &Memento?)!
968 if memento==nil {
969 return "https://magnetiq-static.s3.amazonaws.com/media/public/default-magnet-Icon.png"
970 }
971
972 let image_url:String = memento.metadata["image_url"]!
973 return image_url
974 }
975
976 }
977
978 // returns a url to display an medium sized image
979 access(all) fun mediumimage(): String {
980 let url = self.assetPath().concat("?width=512")
981 return self.appendOptionalParams(url: url, firstDelim: "&")
982 }
983
984 // a url to display a thumbnail associated with the token
985 access(all) fun thumbnail(): String {
986 let url = self.assetPath().concat("?width=256")
987 return self.appendOptionalParams(url: url, firstDelim: "&")
988 }
989
990 // a url to display a video associated with the token
991 access(all) fun video(): String {
992 let url = self.assetPath().concat("/video")
993 return self.appendOptionalParams(url: url, firstDelim: "?")
994 }
995
996 // appends and optional network param needed to resolve the media
997 access(all) fun appendOptionalParams(url: String, firstDelim: String): String {
998 if (Magnetiq.Network() == "testnet") {
999 return url.concat(firstDelim).concat("env=testnet")
1000 }
1001 return url
1002 }
1003 // Create an empty Collection for Pinnacle NFTs and return it to the caller
1004 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
1005 return <- Magnetiq.createEmptyCollection(nftType: Type<@Magnetiq.NFT>())
1006 }
1007 }
1008
1009 // Admin is a special authorization resource that
1010 // allows the owner to perform important functions to modify the
1011 // various aspects of the Magnets, Brands, and Tokens
1012 //
1013 access(all) resource Admin {
1014
1015 // createMagnet creates a new Magnet struct
1016 // and stores it in the Magnets dictionary in the Magnetiq smart contract
1017 //
1018 // Parameters: metadata: A dictionary mapping metadata titles to their data
1019 // example: {"Magneter Name": "Kevin Durant", "Height": "7 feet"}
1020 // (because we all know Kevin Durant is not 6'9")
1021 //
1022 // Returns: the ID of the new Magnet object
1023 //
1024 access(all) fun createMagnet(metadata: {String: String}, magnetID:String): String {
1025 pre {
1026 Magnetiq.magnetData[magnetID] == nil: "Magnet already exists"
1027 }
1028
1029 // Create the new Magnet
1030 var newMagnet = Magnet(metadata: metadata, magnetID:magnetID)
1031 // Store it in the contract storage
1032 Magnetiq.magnetData[magnetID] = newMagnet
1033 // add refernce to magnetid in brand
1034 emit MagnetCreated(id: newMagnet.magnetID, metadata: metadata)
1035 return magnetID
1036 }
1037
1038 access(all) fun createAndAddMagnet(metadata: {String: String}, magnetID:String, brandID:String): String {
1039 pre {
1040 Magnetiq.brands[brandID] != nil : "The Brand doesn't exist"
1041 }
1042 let magnetID = self.createMagnet(metadata:metadata, magnetID:magnetID)
1043 let brand = (&Magnetiq.brands[brandID] as &Brand?)!
1044 brand.addMagnet(magnetID:magnetID)
1045 return magnetID
1046 }
1047
1048 access(all) fun updateMagnetMetadata(magnetID:String, new_metadata:{String:String}){
1049 pre {
1050 Magnetiq.magnetData[magnetID] != nil: "Magnet doesn't exist"
1051 }
1052 var magnet = Magnetiq.magnetData[magnetID]!
1053 magnet.updateMetadata(new_metadata: new_metadata)
1054 Magnetiq.magnetData[magnetID] = magnet
1055 emit MagnetMetadataUpdated(id:magnetID , metadata: new_metadata)
1056 }
1057
1058 access(all) fun updateMementoMetadata(mementoID:String, new_metadata:{String:String}){
1059 pre {
1060 Magnetiq.mementoData[mementoID] != nil: "Memento doesn't exist"
1061 }
1062 var memento = Magnetiq.mementoData[mementoID]!
1063 memento.updateMementoMetadata(new_metadata: new_metadata)
1064 Magnetiq.mementoData[mementoID] = memento
1065 emit MementoMetadataUpdated(id:mementoID , metadata: new_metadata)
1066 }
1067
1068 //createMemento creates a new momnto linked with a magnet and stores in mementoData struct
1069 access(all) fun createMemento(mementoID:String, magnetID:String,metadata:{String:String}): String
1070 {
1071 pre{
1072 Magnetiq.magnetData[magnetID]!=nil: "Cannot create Memento: Magnet Doesn't exist"
1073 }
1074 // Create the new Memento
1075 var newMemento = Memento(mementoID:mementoID, magnetID:magnetID, metadata:metadata)
1076 // Store it in the contract storage
1077 Magnetiq.mementoData[mementoID] = newMemento
1078 emit MementoCreated(mementoID:mementoID, magnetID:magnetID)
1079
1080 // update magent with this mementoid
1081 let magnet = Magnetiq.magnetData[magnetID]!
1082 magnet.updateMementoList(mementoID:mementoID)
1083
1084 return mementoID
1085 }
1086
1087
1088
1089 // createBrand creates a new Brand resource and stores it
1090 // in the brands mapping in the Magnetiq contract
1091 //
1092 // Parameters: name: The name of the Brand
1093 //
1094 // Returns: The ID of the created brand
1095 access(all) fun createBrand(name: String, brandID:String): String {
1096 pre {
1097 Magnetiq.brands[brandID] == nil: "Brand with this id already exists"
1098 }
1099
1100 // Create the new Brand
1101 var newBrand <- create Brand(name: name, brandID:brandID)
1102
1103 emit BrandCreated(brandID: brandID)
1104
1105 // Store it in the brands mapping field
1106 Magnetiq.brands[brandID] <-! newBrand
1107
1108 return brandID
1109 }
1110
1111 // borrowBrand returns a reference to a brand in the Magnetiq
1112 // contract so that the admin can call methods on it
1113 //
1114 // Parameters: brandID: The ID of the Brand that you want to
1115 // get a reference to
1116 //
1117 // Returns: A reference to the Brand with all of the fields
1118 // and methods exposed
1119 //
1120 access(all) fun borrowBrand(brandID: String): &Brand {
1121 pre {
1122 Magnetiq.brands[brandID] != nil: "Cannot borrow Brand: The Brand doesn't exist"
1123 }
1124
1125 // Get a reference to the Brand and return it
1126 // use `&` to indicate the reference to the object and type
1127 return (&Magnetiq.brands[brandID] as &Brand?)!
1128 }
1129
1130
1131 // createNewAdmin creates a new Admin resource
1132 //
1133 access(all) fun createNewAdmin(): @Admin {
1134 return <-create Admin()
1135 }
1136
1137 access(all) fun markTokenClaimed(tokenID:UInt64){
1138 pre{
1139 Magnetiq.magnetiqTokenExtraInfo[tokenID] !=nil : "Token does not exist"
1140 }
1141 Magnetiq.magnetiqTokenExtraInfo[tokenID]!.insert(key: "claimed",true)
1142 emit TokenClaimed(tokenID:tokenID)
1143 }
1144
1145 access(all) fun setRoyalityReceiverAndPercentage(royaltyReceiver:Address, royalityPercentage: UFix64){
1146 Magnetiq.royalityReceiver = royaltyReceiver
1147 Magnetiq.royalityPercentage = royalityPercentage
1148 }
1149
1150 access(all) fun setNetwork(networkName: String){
1151 Magnetiq.currentNetwork = networkName
1152 }
1153
1154 access(all) fun allowAddressToSellNonSellableNFT(addresses: [Address]){
1155 for addr in addresses{
1156 Magnetiq.allowedSellersForNonSellableNFT.append(addr)
1157 }
1158 }
1159
1160 // function to add custom field at magnetiqToken level
1161 access(all) fun addFieldsOnMagnetiqToken(tokenID:UInt64,fields:{String:AnyStruct}){
1162 if let tokenField = Magnetiq.magnetiqTokenExtraInfo[tokenID] {
1163 for name in fields.keys {
1164 let value = fields[name] ?? nil
1165 if value != nil {
1166 tokenField.insert(key: name, value)
1167 }
1168 }
1169 Magnetiq.magnetiqTokenExtraInfo[tokenID] = tokenField
1170 }
1171 else {
1172 panic("Magnetiq Token doesn't exist")
1173 }
1174 }
1175
1176 access(all) fun removeFieldsOnMagnetiqToken(tokenID:UInt64, fields:[String]){
1177 if let tokenField = Magnetiq.magnetiqTokenExtraInfo[tokenID] {
1178 for name in fields {
1179 tokenField.remove(key: name)
1180 }
1181 Magnetiq.magnetiqTokenExtraInfo[tokenID] = tokenField
1182 }
1183 else{
1184 panic("Magnetiq Token doesn't exist")
1185 }
1186 }
1187 access(all) fun resetAllowedSellersForNonSellableNFT(){
1188 Magnetiq.allowedSellersForNonSellableNFT=[Magnetiq.account.address]
1189 }
1190 }
1191 // This is the interface that users can cast their Tokens Collection as
1192 // to allow others to deposit Tokens into their Collection. It also allows for reading
1193 // the IDs of Tokens in the Collection.
1194 access(all) resource interface TokenCollectionPublic : NonFungibleToken.CollectionPublic {
1195 access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection})
1196 access(all) fun borrowToken(id: UInt64): &Magnetiq.NFT? {
1197 // If the result isn't nil, the id of the returned reference
1198 // should be the same as the argument to the function
1199 post {
1200 (result == nil) || (result?.id == id):
1201 "Cannot borrow Token reference: The ID of the returned reference is incorrect"
1202 }
1203 }
1204 }
1205
1206
1207 // Collection is a resource that every user who owns NFTs
1208 // will store in their account to manage their NFTS
1209 //
1210 access(all) resource Collection: TokenCollectionPublic, NonFungibleToken.Collection {
1211 // Dictionary of Token (Magnet/Memeto) conforming tokens
1212 // NFT is a resource type with a UInt64 ID field
1213 access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
1214
1215 init() {
1216 self.ownedNFTs <- {}
1217 }
1218
1219 // Return a list of NFT types that this receiver accepts
1220 access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
1221 let supportedTypes: {Type: Bool} = {}
1222 supportedTypes[Type<@Magnetiq.NFT>()] = true
1223 return supportedTypes
1224 }
1225
1226 // Return whether or not the given type is accepted by the collection
1227 // A collection that can accept any type should just return true by default
1228 access(all) view fun isSupportedNFTType(type: Type): Bool {
1229 if type == Type<@Magnetiq.NFT>() {
1230 return true
1231 }
1232 return false
1233 }
1234
1235 // Return the amount of NFTs stored in the collection
1236 access(all) view fun getLength(): Int {
1237 return self.ownedNFTs.length
1238 }
1239
1240 // Create an empty Collection for TopShot NFTs and return it to the caller
1241 access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
1242 return <- Magnetiq.createEmptyCollection(nftType: Type<@Magnetiq.NFT>())
1243 }
1244
1245 // withdraw removes an Token from the Collection and moves it to the caller
1246 //
1247 // Parameters: withdrawID: The ID of the NFT
1248 // that is to be removed from the Collection
1249 //
1250 // returns: @NonFungibleToken.NFT the token that was withdrawn
1251
1252 access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
1253
1254 // Borrow nft and check if locked
1255 let nft = self.borrowNFT(withdrawID)
1256 ?? panic("Cannot borrow: empty reference")
1257 if MagnetiqLocking.isLocked(nftRef: nft) {
1258 panic("Cannot withdraw: Token is locked")
1259 }
1260 if let magnetiq_nft = self.borrowToken(id: withdrawID){
1261 let token_data = magnetiq_nft.data
1262 let token_type=token_data.tokenType
1263 let magnetiq_id = token_data.magnetiqID
1264 var is_sellable: String? = ""
1265 if token_type == "magnet" {
1266 is_sellable = Magnetiq.getMagnetMetaDataByField(magnetiqID: magnetiq_id, field: "is_sellable")
1267 }
1268 else if token_type == "memento" {
1269 is_sellable = Magnetiq.getMementoMetaDataByField(magnetiqID: magnetiq_id, field: "is_sellable")
1270 }
1271
1272 if is_sellable?.toLower() == "false" && !Magnetiq.allowedSellersForNonSellableNFT.contains(self.owner?.address) {
1273 panic("Token is not sellable")
1274 }
1275 }
1276
1277 // Remove the nft from the Collection
1278 let token <- self.ownedNFTs.remove(key: withdrawID)
1279 ?? panic("Cannot withdraw: Token does not exist in the collection")
1280
1281 emit Withdraw(id: token.id, from: self.owner?.address)
1282
1283 // Return the withdrawn token
1284 return <-token
1285 }
1286
1287 // batchWithdraw withdraws multiple tokens and returns them as a Collection
1288 //
1289 // Parameters: ids: An array of IDs to withdraw
1290 //
1291 // Returns: @NonFungibleToken.Collection: A collection that contains
1292 // the withdrawn Token
1293 //
1294 access(NonFungibleToken.Withdraw) fun batchWithdraw(ids: [UInt64]): @{NonFungibleToken.Collection} {
1295 // Create a new empty Collection
1296 var batchCollection <- create Collection()
1297
1298 // Iterate through the ids and withdraw them from the Collection
1299 for id in ids {
1300 batchCollection.deposit(token: <-self.withdraw(withdrawID: id))
1301 }
1302
1303 // Return the withdrawn Token
1304 return <-batchCollection
1305 }
1306
1307 // deposit takes a Token and adds it to the Collections dictionary
1308 //
1309 // Paramters: token: the NFT to be deposited in the collection
1310 //
1311 access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
1312
1313 // Cast the deposited token as a Magnetiq NFT to make sure
1314 // it is the correct type
1315 let token <- token as! @Magnetiq.NFT
1316
1317 // Get the token's ID
1318 let id = token.id
1319
1320 // Add the new token to the dictionary
1321 let oldToken <- self.ownedNFTs[id] <- token
1322
1323 // Only emit a deposit event if the Collection
1324 // is in an account's storage
1325 if self.owner?.address != nil {
1326 emit Deposit(id: id, to: self.owner?.address)
1327 }
1328
1329 // Destroy the empty old token that was "removed"
1330 destroy oldToken
1331 }
1332
1333 // batchDeposit takes a Collection object as an argument
1334 // and deposits each contained NFT into this Collection
1335 access(all) fun batchDeposit(tokens: @{NonFungibleToken.Collection}) {
1336
1337 // Get an array of the IDs to be deposited
1338 let keys = tokens.getIDs()
1339
1340 // Iterate through the keys in the collection and deposit each one
1341 for key in keys {
1342 self.deposit(token: <-tokens.withdraw(withdrawID: key))
1343 }
1344
1345 // Destroy the empty Collection
1346 destroy tokens
1347 }
1348
1349 // lock takes a token id and a duration in seconds and locks
1350 // the token for that duration
1351 access(NonFungibleToken.Update) fun lock(id: UInt64, duration: UFix64) {
1352 // Remove the nft from the Collection
1353 let token <- self.ownedNFTs.remove(key: id)
1354 ?? panic("Cannot lock: Token does not exist in the collection")
1355
1356 // pass the token to the locking contract
1357 // store it again after it comes back
1358 let oldToken <- self.ownedNFTs[id] <- MagnetiqLocking.lockNFT(nft: <- token, duration: duration)
1359
1360 destroy oldToken
1361 }
1362
1363 // batchLock takes an array of token ids and a duration in seconds
1364 // it iterates through the ids and locks each for the specified duration
1365 access(NonFungibleToken.Update) fun batchLock(ids: [UInt64], duration: UFix64) {
1366 // Iterate through the ids and lock them
1367 for id in ids {
1368 self.lock(id: id, duration: duration)
1369 }
1370 }
1371
1372 // unlock takes a token id and attempts to unlock it
1373 // MagnetiqLocking.unlockNFT contains business logic around unlock eligibility
1374 access(NonFungibleToken.Update) fun unlock(id: UInt64) {
1375 // Remove the nft from the Collection
1376 let token <- self.ownedNFTs.remove(key: id)
1377 ?? panic("Cannot lock: Token does not exist in the collection")
1378
1379 // Pass the token to the MagnetiqLocking contract then get it back
1380 // Store it back to the ownedNFTs dictionary
1381 let oldToken <- self.ownedNFTs[id] <- MagnetiqLocking.unlockNFT(nft: <- token)
1382
1383 destroy oldToken
1384 }
1385
1386 // batchUnlock takes an array of token ids
1387 // it iterates through the ids and unlocks each if they are eligible
1388 access(NonFungibleToken.Update) fun batchUnlock(ids: [UInt64]) {
1389 // Iterate through the ids and unlocks them
1390 for id in ids {
1391 self.unlock(id: id)
1392 }
1393 }
1394
1395 // destroyMoments destroys moments in this collection
1396 // unlocks the moments if they are locked
1397 //
1398 // Parameters: ids: An array of NFT IDs
1399 // to be destroyed from the Collection
1400 access(NonFungibleToken.Update) fun destroyMoments(ids: [UInt64]) {
1401 let magnetiqLockingAdmin = Magnetiq.account.storage.borrow<&MagnetiqLocking.Admin>(from: MagnetiqLocking.AdminStoragePath())
1402 ?? panic("No MagnetiqLocking admin resource in storage")
1403
1404 for id in ids {
1405 // Remove the nft from the Collection
1406 let token <- self.ownedNFTs.remove(key: id)
1407 ?? panic("Cannot destroy: Moment does not exist in collection: ".concat(id.toString()))
1408
1409 // Emit a withdraw event here so that platforms do not have to understand TopShot-specific events to see ownership change
1410 // A withdraw without a corresponding deposit means the NFT in question has no owner address
1411 emit Withdraw(id: id, from: self.owner?.address)
1412
1413 // does nothing if the moment is not locked
1414 magnetiqLockingAdmin.unlockByID(id: id)
1415
1416 destroy token
1417 }
1418 }
1419
1420 // getIDs returns an array of the IDs that are in the Collection
1421 view access(all) fun getIDs(): [UInt64] {
1422 return self.ownedNFTs.keys
1423 }
1424
1425 // borrowNFT Returns a borrowed reference to a Token in the Collection
1426 // so that the caller can read its ID
1427 //
1428 // Parameters: id: The ID of the NFT to get the reference for
1429 //
1430 // Returns: A reference to the NFT
1431 //
1432 // Note: This only allows the caller to read the ID of the NFT,
1433 // not any Magnetiq specific data. Please use borrowToken to
1434 // read Token data.
1435 //
1436 view access(all) fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
1437 return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)!
1438 }
1439
1440 // borrowToken returns a borrowed reference to a Token
1441 // so that the caller can read data and call methods from it.
1442 // They can use this to read its brandID, magnetID, serialNumber,
1443 // or any of the brandData or Magnet data associated with it by
1444 // getting the brandID or magnetID and reading those fields from
1445 // the smart contract.
1446 //
1447 // Parameters: id: The ID of the NFT to get the reference for
1448 //
1449 // Returns: A reference to the NFT
1450 view access(all) fun borrowToken(id: UInt64): &Magnetiq.NFT? {
1451 if self.ownedNFTs[id] != nil {
1452 let ref = (&self.ownedNFTs[id] as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT}?)!
1453 return ref as! &Magnetiq.NFT
1454 } else {
1455 return nil
1456 }
1457 }
1458
1459 view access(all) fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver} {
1460 let nft = (&self.ownedNFTs[id] as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT}?)!
1461 let magnetiqNFT = nft as! &Magnetiq.NFT
1462 return magnetiqNFT as &{ViewResolver.Resolver}
1463 }
1464
1465 // If a transaction destroys the Collection object,
1466 // All the NFTs contained within are also destroyed!
1467 // Much like when Damian Lillard destroys the hopes and
1468 // dreams of the entire city of Houston.
1469 //
1470 // destroy() {
1471 // destroy self.ownedNFTs
1472 // }
1473 }
1474
1475 // -----------------------------------------------------------------------
1476 // Magnetiq contract-level function definitions
1477 // -----------------------------------------------------------------------
1478
1479 // createEmptyCollection creates a new, empty Collection object so that
1480 // a user can store it in their account storage.
1481 // Once they have a Collection in their storage, they are able to receive
1482 // Tokens in transactions.
1483 //
1484
1485 access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
1486 if nftType != Type<@Magnetiq.NFT>() {
1487 panic("NFT type is not supported")
1488 }
1489 return <-create Magnetiq.Collection()
1490 }
1491
1492 // getAllMagnets returns all the magnets in Magnetiq
1493 //
1494 // Returns: An array of all the magnets that have been created
1495 access(all) fun getAllMagnets(): [Magnetiq.Magnet] {
1496 return Magnetiq.magnetData.values
1497 }
1498
1499 // getAllMementos returns all the mementos in Magnetiq
1500 //
1501 // Returns: An array of all the mementos that have been created
1502 access(all) fun getAllMementos(): [Magnetiq.Memento] {
1503 return Magnetiq.mementoData.values
1504 }
1505
1506 // getMagnetMetaData returns all the metadata associated with a specific Magnet
1507 //
1508 // Parameters: magnetID: The id of the Magnet that is being searched
1509 //
1510 // Returns: The metadata as a String to String mapping optional
1511 access(all) fun getMagnetMetaData(magnetID: String): {String: String}? {
1512 return self.magnetData[magnetID]?.metadata
1513 }
1514
1515 // getMagnetMetaDataByField returns the metadata associated with a
1516 // specific field of the metadata
1517 // Ex: field: "Team" will return something
1518 // like "Memphis Grizzlies"
1519 //
1520 // Parameters: magnetID: The id of the Magnet that is being searched
1521 // field: The field to search for
1522 //
1523 // Returns: The metadata field as a String Optional
1524 access(all) fun getMagnetMetaDataByField(magnetiqID: String, field: String): String? {
1525 // Don't force a revert if the magnetID or field is invalid
1526 if let magnet = Magnetiq.magnetData[magnetiqID] {
1527 return magnet.metadata[field]
1528 } else {
1529 return nil
1530 }
1531 }
1532
1533 // getMementoMetaData returns all the metadata associated with a specific Memento
1534 //
1535 // Parameters: mementoID: The id of the Magnet that is being searched
1536 //
1537 // Returns: The metadata as a String to String mapping optional
1538 access(all) fun getMementoMetaData(magnetiqID: String): {String: String}? {
1539 return self.mementoData[magnetiqID]?.metadata
1540 }
1541
1542 access(all) fun getMementoMetaDataByField(magnetiqID: String, field: String): String? {
1543 // Don't force a revert if the magnetID or field is invalid
1544 if let memento = Magnetiq.mementoData[magnetiqID]{
1545 return memento.metadata[field]
1546 }
1547 return nil
1548 }
1549
1550 // getBrandData returns the data that the specified Brand
1551 // is associated with.
1552 //
1553 // Parameters: brandID: The id of the Brand that is being searched
1554 //
1555 // Returns: The QueryBrandData struct that has all the important information about the brand
1556 access(all) fun getBrandData(brandID: String): QueryBrandData? {
1557 if Magnetiq.brands[brandID] == nil {
1558 return nil
1559 } else {
1560 return QueryBrandData(brandID: brandID)
1561 }
1562 }
1563
1564 // getBrandName returns the name that the specified Brand
1565 // is associated with.
1566 //
1567 // Parameters: brandID: The id of the Brand that is being searched
1568 //
1569 // Returns: The name of the Brand
1570 access(all) fun getBrandName(brandID: String): String? {
1571 // Don't force a revert if the brandID is invalid
1572 return Magnetiq.brandData[brandID]?.name
1573 }
1574
1575 // getBrandIDsByName returns the IDs that the specified Brand name
1576 // is associated with.
1577 //
1578 // Parameters: brandName: The name of the Brand that is being searched
1579 //
1580 // Returns: An array of the IDs of the Brand if it exists, or nil if doesn't
1581 access(all) fun getBrandIDsByName(brandName: String): [String]? {
1582 var brandIDs: [String] = []
1583
1584 // Iterate through all the brandData and search for the name
1585 for brandData in Magnetiq.brandData.values {
1586 if brandName == brandData.name {
1587 // If the name is found, return the ID
1588 brandIDs.append(brandData.brandID)
1589 }
1590 }
1591
1592 // If the name isn't found, return nil
1593 // Don't force a revert if the brandName is invalid
1594 if brandIDs.length == 0 {
1595 return nil
1596 } else {
1597 return brandIDs
1598 }
1599 }
1600
1601 // getMagnetsInBrand returns the list of Magnet IDs that are in the Brand
1602 //
1603 // Parameters: brandID: The id of the Brand that is being searched
1604 //
1605 // Returns: An array of Magnet IDs
1606 access(all) fun getMagnetsInBrand(brandID: String): [String]? {
1607 // Don't force a revert if the brandID is invalid
1608 return Magnetiq.brands[brandID]?.magnets
1609 }
1610
1611 // getMementoInMagnet returns the list of Memento IDs that are in the Magnet
1612 //
1613 // Parameters: magnetID: The id of the Brand that is being searched
1614 //
1615 // Returns: An array of Memento IDs
1616 access(all) fun getMementoInMagnet(magnetID: String): [String]? {
1617 // Don't force a revert if the magnetID is invalid
1618 return Magnetiq.magnetData[magnetID]?.mementos
1619 }
1620
1621 // function to check if a memento is claimed or not
1622 access(all) fun isMementoTokenClaimed(tokenID:UInt64): AnyStruct? {
1623 if let is_claimed = Magnetiq.magnetiqTokenExtraInfo[tokenID]!["claimed"] {
1624 return is_claimed
1625 }
1626 return nil
1627 }
1628
1629 access(all) fun getMagnetiqTokenExtraData(tokenID:UInt64): {String:AnyStruct}?{
1630 return Magnetiq.magnetiqTokenExtraInfo[tokenID]
1631 }
1632
1633 // isBrandLocked returns a boolean that indicates if a Brand
1634 // is locked. If it's locked,
1635 // new Magnets can no longer be added to it,
1636 // but Tokens can still be minted from Magnets the brand contains.
1637 //
1638 // Parameters: brandID: The id of the Brand that is being searched
1639 //
1640 // Returns: Boolean indicating if the Brand is locked or not
1641 access(all) fun isBrandLocked(brandID: String): Bool? {
1642 // Don't force a revert if the brandID is invalid
1643 return Magnetiq.brands[brandID]?.locked
1644 }
1645
1646 // getNumTokensInEdition return the number of Tokens that have been
1647 // minted from a certain edition.
1648 //
1649 // Parameters: brandID: The id of the Brand that is being searched
1650 // magnetID: The id of the Magnet that is being searched
1651 //
1652 // Returns: The total number of Tokens
1653 // that have been minted from an edition
1654 access(all) fun getNumTokensInEdition(brandID: String, magnetiqID: String,tokenType:String): UInt32? {
1655 if tokenType == "magnet" {
1656 if let branddata = self.getBrandData(brandID: brandID) {
1657 // Read the numMintedPerMagnet
1658 let amount = branddata.getNumberMintedPerMagnet()[magnetiqID]
1659 return amount
1660 } else {
1661 // If the brand wasn't found return nil
1662 return nil
1663 }
1664 }
1665 // For memento type
1666 else {
1667 if let magnet = Magnetiq.magnetData[magnetiqID] {
1668 // Read the numMintedPerMemento
1669 let amount = magnet.numberMintedPerMemento[magnetiqID]
1670 return amount
1671 }
1672 else {
1673 return nil
1674 }
1675 }
1676
1677 }
1678
1679 // function which returns allowed sellers list
1680 access(all) fun getAllowedSellersForNonSellableNFT():[Address?]{
1681 return self.allowedSellersForNonSellableNFT
1682 }
1683
1684 //------------------------------------------------------------
1685 // Contract MetadataViews
1686 //------------------------------------------------------------
1687
1688 /// Return the metadata view types available for this contract
1689 ///
1690 view access(all) fun getContractViews(resourceType: Type?): [Type] {
1691 return [Type<MetadataViews.NFTCollectionData>(), Type<MetadataViews.NFTCollectionDisplay>()]
1692 }
1693
1694 /// Resolve this contract's metadata views
1695 ///
1696 view access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
1697 post {
1698 result == nil || result!.getType() == viewType: "The returned view must be of the given type or nil"
1699 }
1700 switch viewType {
1701 case Type<MetadataViews.NFTCollectionData>():
1702 return MetadataViews.NFTCollectionData(
1703 storagePath: /storage/MagnetiqTokensCollection,
1704 publicPath: /public/MagnetiqTokensCollection,
1705 publicCollection: Type<&Magnetiq.Collection>(),
1706 publicLinkedType: Type<&Magnetiq.Collection>(),
1707 createEmptyCollectionFunction: (fun (): @{NonFungibleToken.Collection} {
1708 return <-Magnetiq.createEmptyCollection(nftType: Type<@Magnetiq.NFT>())
1709 })
1710 )
1711 case Type<MetadataViews.NFTCollectionDisplay>():
1712 let bannerImage = MetadataViews.Media(
1713 file: MetadataViews.HTTPFile(
1714 url: "https://magnetiq-static.s3.amazonaws.com/media/public/MAGNETIQ_banner.png"
1715 ),
1716 mediaType: "image/png"
1717 )
1718 let squareImage = MetadataViews.Media(
1719 file: MetadataViews.HTTPFile(
1720 url: "https://magnetiq-static.s3.amazonaws.com/media/public/MAGNETIQ_Square_Logo.png"
1721 ),
1722 mediaType: "image/png"
1723 )
1724 return MetadataViews.NFTCollectionDisplay(
1725 name: "MAGNETIQ",
1726 description: "MAGNETIQ is making managing brand community engagement easy and efficient with a plug and play, blockchain powered platform. MAGNETIQ NFTs represent your membership in brand communities.",
1727 externalURL: MetadataViews.ExternalURL("https://www.magnetiq.xyz/"),
1728 squareImage: squareImage,
1729 bannerImage: bannerImage,
1730 socials: {
1731 "twitter": MetadataViews.ExternalURL("https://twitter.com/magnetiq_xyz"),
1732 "discord": MetadataViews.ExternalURL("https://discord.com/invite/Magnetiq"),
1733 "instagram": MetadataViews.ExternalURL("https://www.instagram.com/magnetiq_xyz")
1734 }
1735 )
1736 }
1737 return nil
1738 }
1739
1740 // -----------------------------------------------------------------------
1741 // Magnetiq initialization function
1742 // -----------------------------------------------------------------------
1743 //
1744 init() {
1745 // Initialize contract fields
1746 self.currentSeries = 0 // depricated and will be removed
1747 self.magnetData = {} // all magnets and their data with key as magnetID
1748 self.mementoData = {} // all mementos and their data with key as mementoID
1749 self.brandData = {} // all brands data(struct) with key as brandID
1750 self.brands <- {} // all brands(resources) dict with key as brandID
1751
1752 // all token ids as key with dictionary as value representing other info of NFT
1753 // e.g : claimed = true/false
1754 self.magnetiqTokenExtraInfo = {}
1755 self.totalSupply = 0 //total supply of all magnets/memento, used to assign new id to NFT
1756 self.currentNetwork = "testnet" // current network for contract
1757 self.royalityReceiver = 0x214cf7a682afadc6 // address to receive royality
1758 self.royalityPercentage = 0.05 // percentage of royality go to royalityReceiver
1759 self.allowedSellersForNonSellableNFT = [self.account.address] // allowing admin to sell non sellable NFT
1760
1761 // Put a new Collection in storage
1762 self.account.storage.save<@Collection>(<- create Collection(), to: /storage/MagnetiqTokensCollection)
1763
1764 // Create a public capability for the Collection
1765 let cap = self.account.capabilities.storage.issue<&Magnetiq.Collection>(/storage/MagnetiqTokensCollection)
1766 self.account.capabilities.publish(cap, at: /public/MagnetiqTokensCollection)
1767 // self.account.capabilities.storage.issue<&{TokenCollectionPublic}>(/public/MagnetiqTokensCollection, target: /storage/MagnetiqTokensCollection)
1768
1769 // Put the Minter in storage
1770 self.account.storage.save<@Admin>(<- create Admin(), to: /storage/MagnetiqAdmin)
1771
1772 emit ContractInitialized()
1773 }
1774}