Smart Contract

Magnetiq

A.8c55fba7d7090fee.Magnetiq

Deployed

1h ago
Feb 26, 2026, 07:30:05 PM UTC

Dependents

0 imports
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 0x9a0766d93b6608b7
39import NonFungibleToken from 0x631e88ae7f1d7c20
40import MetadataViews from 0x631e88ae7f1d7c20
41import MagnetiqLocking from 0x8c55fba7d7090fee
42import ViewResolver   from 0x631e88ae7f1d7c20
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}