Smart Contract

Mneme

A.0e964e9e2b53ed06.Mneme

Valid From

142,657,571

Deployed

2w ago
Feb 07, 2026, 04:07:44 AM UTC

Dependents

9 imports
1// MADE BY: Noah_Overflow
2
3// This contract is for Mneme, a proof of support platform
4// on Flow. 
5
6// Mneme (Μνήμη) is one of the original three Muses in pre-Homeric Greek mythology.
7// Before the more famous Nine Muses were standardized by Hesiod (daughters of Zeus and Mnemosyne)
8// there were three older Muses:
9
10// Melete – Muse of Practice/Contemplation
11// Mneme – Muse of Memory
12// Aoide – Muse of Song
13
14// Mneme herself was seen as the preserver of knowledge and inspiration, responsible for the ability of poets, orators 
15// and artists to recall what came before and give it form. She represents the thread that links art to time and culture 
16// Literally, the memory of humanity encoded in creative work.
17
18import NonFungibleToken from 0x1d7e57aa55817448
19import FungibleToken from 0xf233dcee88fe0abe
20import FlowToken from 0x1654653399040a61 
21import ViewResolver from 0x1d7e57aa55817448
22import MetadataViews from 0x1d7e57aa55817448
23import Pistis from 0x0e964e9e2b53ed06
24import RandomConsumer from 0x45caec600164c9e6
25import Xorshift128plus from 0x45caec600164c9e6
26import Burner from 0xf233dcee88fe0abe
27import FlowTransactionScheduler from 0xe467b9dd11fa00df
28import ArtDropToken from 0x0e964e9e2b53ed06
29// import "CrossVMMetadataViews"
30// import "EVM"
31
32access(all) 
33contract Mneme: NonFungibleToken {
34    // -----------------------------------------------------------------------
35    // Mneme contract-level fields.
36    // These contain actual values that are stored in the smart contract.
37    // -----------------------------------------------------------------------
38    // Dictionary to hold general collection information
39    access(self) let collectionInfo: {String: AnyStruct}  
40    access(self) var artistOriginals: {Address: [Int64]}
41    access(self) var artistEditions: {Address: [Int64]}
42    access(self) var totalOriginals: UInt64
43    access(self) var totalEditions: UInt64
44    access(self) var totalCertificates: UInt64
45    access(all) let address: Address
46    /// The RandomConsumer.Consumer resource used to request & fulfill randomness
47    access(self) let consumer: @RandomConsumer.Consumer
48    // -----------------------------------------------------------------------
49    // Mneme account paths
50    // -----------------------------------------------------------------------
51	access(all) let CollectionStoragePath: StoragePath
52	access(all) let CollectionPublicPath: PublicPath
53    /// Path where the minter should be stored
54    /// The standard paths for the collection are stored in the collection resource type
55    access(all) let ArtDropStoragePath: StoragePath
56    access(all) let ArtDropPublicPath: PublicPath
57    access(all) let AdministratorStoragePath: StoragePath
58    access(all) let ArtistStoragePath: StoragePath
59    access(all) let HandlerStoragePath: StoragePath
60    // -----------------------------------------------------------------------
61
62    // Mneme Entitlements
63    // ----------------------------------------------------------------------- 
64    access(all) entitlement Admin
65    access(all) entitlement AddArtist
66    access(all) entitlement Editions
67
68    /// Event to show when an NFT is minted
69    access(all) event Minted(
70        type: String,
71        id: UInt64,
72        uuid: UInt64,
73        name: String,
74        description: String
75    )
76    access(all) event OriginalCreated(artistAddress: Address, originalId: UInt64)
77    access(all) event EditionCreated(artistAddress: Address, editionId: UInt64)
78    access(all) event MultipliersUpdated(artistAddress: Address, editionId: UInt64)
79    access(all) event RNGReceiptPopped(id: UInt64, multiplier: UFix64)
80    // -----------------------------------------------------------------------
81    // Mneme contract-level Composite Type definitions
82    // -----------------------------------------------------------------------
83    // These are just *definitions* for Types that this contract
84    // and other accounts can use. These definitions do not contain
85    // actual stored values, but an instance (or object) of one of these Types
86    // can be created by this contract that contains stored values.
87    // -----------------------------------------------------------------------
88    access(all) resource Original: Pistis.Pool {
89        access(all) let id: UInt64
90        access(all) var name: String
91        access(all) var description: String
92        access(all) var thumbnail: String
93        access(all) let artistAddress: Address
94        access(all) var editions: {UInt64: UInt64}
95        access(all) var totalMinted: Int64
96        access(all) var price: UFix64
97        access(all) var metadata: {String: String}
98        access(all) var extra: {String: AnyStruct}
99        // Pistis fields
100        access(all) var vaultsDict: @{Type: {FungibleToken.Vault}}
101        access(all) var vaultReceiverPath: {Type: PublicPath}
102
103        init (
104            id: UInt64,
105            name: String,
106            description: String,
107            thumbnail: String,
108            artistAddress: Address,
109            price: UFix64,
110            metadata: {String: String},
111        ) {
112            self.id = id
113            self.name = name
114            self.description = description
115            self.thumbnail = thumbnail
116            self.artistAddress = artistAddress
117            self.editions = {}
118            self.totalMinted = 0
119            self.price = price
120            self.vaultsDict <- {}
121            self.vaultReceiverPath = {}
122            self.metadata = metadata
123            self.extra = {}
124        }
125
126        access(all) fun addEdition(editionId: UInt64) {
127            self.editions[editionId] = 0
128        } 
129        access(all) fun increaseTotalMinted() {
130            self.totalMinted = self.totalMinted + 1
131        }
132        // helper funct to return metadata without ref
133        access(all) fun getMetadata(): {String: String} {
134            return self.metadata
135        }
136    } 
137    // Edition resource represents the Art's metadata 
138    // and its rewards rules
139    // Attachment
140
141    access(all) resource Edition {
142        access(all) let originalId: UInt64
143        access(all) let id: UInt64
144        access(all) var name: String
145        access(all) var price: UFix64
146        access(all) var type: String
147        access(all) var story: String
148        access(all) var dimensions: {String: String}
149        access(all) var reprintLimit: Int64
150        access(all) let artistAddress: Address
151        access(all) var totalMinted: Int64
152        access(all) var profitSplit: {Address: UFix64} // the address and the percentage of the profit
153                                                       // ArtDrop, Original Painting, Charity, CopyrightsHolder/Reseller, communityPool. (Reseller Address is dynamic) 
154                                                       // the total of the percentages must be 100%
155                                                       // distribute after claim/point of no return.
156
157        access(all) var multipliers: [UFix64]
158        // Map of multiplier to the id of the CertificateNFT
159        access(all) var usedMultipliers: {UFix64: UInt64}
160        access(all) let rngReceipts: @{UInt64: Receipt}
161        access(all) let revealReceipts: @{UInt64: FlowTransactionScheduler.ScheduledTransaction}
162
163        init(
164            originalId: UInt64,
165            id: UInt64,
166            name: String,
167            price: UFix64,
168            type: String,
169            story: String,
170            dimensions: {String: String},
171            reprintLimit: Int64,
172            artistAddress: Address,
173            multipliers: [UFix64],
174            profitSplit: {Address: UFix64}) {
175
176            self.originalId = originalId
177            self.name = name
178            self.price = price
179            self.id = id
180            self.type = type
181            self.story = story
182            self.dimensions = dimensions
183            self.reprintLimit = reprintLimit
184            self.artistAddress = artistAddress
185            self.multipliers = multipliers
186            self.usedMultipliers = {}
187            self.totalMinted = 0
188            self.rngReceipts <- {}
189            self.revealReceipts <- {}
190            self.profitSplit = profitSplit
191
192            // The number of multipliers should be equal to
193            // the reprint limit
194            if multipliers.length != Int(reprintLimit) {
195                panic("The number of multipliers should be equal to the reprint limit")
196            }
197
198        }
199        // helper funct to return dimension without ref
200        access(all) fun getDimensions(): {String: String} {
201            return self.dimensions
202        }
203
204        access(Editions) fun editEdition(
205            name: String?,
206            price: UFix64?,
207            type: String?,
208            story: String?,
209            dimensions: {String: String}?,
210            reprintLimit: Int64?) {
211                pre {
212                    self.totalMinted == 0: "This edition has already been minted"
213                }
214            self.name = name ?? self.name
215            self.price = price ?? self.price
216            self.type = type ?? self.type
217            self.story = story ?? self.story
218            self.dimensions = dimensions ?? self.dimensions
219            self.reprintLimit = reprintLimit ?? self.reprintLimit
220        }
221        // update multipliers
222        access(Editions) fun updateMultipliers(multipliers: [UFix64]) {
223            // The number of multipliers should be equal to
224            // the reprint limit
225            if multipliers.length != Int(self.reprintLimit) {
226                panic("The number of multipliers should be equal to the reprint limit")
227            }
228            self.multipliers = multipliers
229        }
230        /// mintNFT mints a new NFT with a new ID
231        /// and returns it to the calling context
232        access(Editions) 
233        fun mintCertificateNFT(thumbnail: String) {
234            // Get the original path
235            let originalStorageIdentifier = "\(Mneme.account.address)_ArtDrop_Original_\(self.artistAddress)_\(self.originalId)"
236            let originalStoragePath = StoragePath(identifier: originalStorageIdentifier)!
237            let originalRef = Mneme.account.storage.borrow< &Mneme.Original>(from: originalStoragePath)!
238            if originalRef == nil {
239                panic("Original not found")
240            }
241            // Check if the edition has a reprint limit
242            // If it does, check if the total minted count has reached the reprint limit
243            if self.reprintLimit != 0 {
244                if self.totalMinted >= self.reprintLimit {
245                    panic("This edition has reached the reprint limit")
246                }
247            }            
248            let metadata: {String: AnyStruct} = {}
249            let currentBlock = getCurrentBlock()
250            metadata["mintedBlock"] = currentBlock.height
251            metadata["mintedTime"] = currentBlock.timestamp
252            // this piece of metadata will be used to show embedding rarity into a trait
253            metadata["DNA Chip"] = "7z5wp5re"
254            // [2x. 3x, 4x, 5x, 6x, 7x, 8x, 9x, 10x]
255
256            // increase the total minted count for this Edition
257            self.totalMinted = self.totalMinted + 1
258            // increase the total certificates count
259            Mneme.totalCertificates = Mneme.totalCertificates + 1
260            // request randomness
261            let request <- Mneme.consumer.requestRandomness()
262            let receipt <- create Receipt(id: Mneme.totalCertificates, request: <-request)
263            // Safe receipt linked to this Edition resource
264            self.rngReceipts[Mneme.totalCertificates] <-! receipt
265
266            // create a new NFT
267            var newNFT <- create CertificateNFT(
268                id: Mneme.totalCertificates,
269                serial: UInt64(self.totalMinted),
270                name: self.name,
271                description: self.story,
272                thumbnail: thumbnail,
273                metadata: metadata,
274                artistAddress: self.artistAddress
275            )
276            // Create a new reveal data    
277            let revealData = revealData(
278                id: Mneme.totalCertificates,
279                artistAddress: self.artistAddress,
280                editionId: self.id
281            )
282            // Schedule a transaction to reveal the Receipt
283            let future = getCurrentBlock().timestamp + 5.0
284            let priority = FlowTransactionScheduler.Priority.Medium
285            let executionEffort: UInt64 = 1000
286            let estimate = FlowTransactionScheduler.estimate(
287                data: revealData,
288                timestamp: future,
289                priority: priority,
290                executionEffort: executionEffort
291            )
292            assert(
293                estimate.timestamp != nil || priority == FlowTransactionScheduler.Priority.Medium,
294                message: estimate.error ?? "estimation failed"
295            )
296            if Mneme.account.storage.borrow<&AnyResource>(from: Mneme.HandlerStoragePath) == nil {
297                let handler <- create Handler()
298                Mneme.account.storage.save(<-handler, to: Mneme.HandlerStoragePath)
299            }
300            // Withdraw FLOW fees from this contract's account vault
301            let vaultRef = Mneme.account.storage
302                .borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
303                ?? panic("missing FlowToken vault on contract account")
304            let fees <- vaultRef.withdraw(amount: estimate.flowFee ?? 0.0) as! @FlowToken.Vault
305            let handlerCap = Mneme.account.capabilities.storage
306                .issue<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>(Mneme.HandlerStoragePath)
307
308
309
310            let revealReceipt <- FlowTransactionScheduler.schedule(
311                handlerCap: handlerCap,
312                data: revealData,
313                timestamp: future,
314                priority: priority,
315                executionEffort: executionEffort,
316                fees: <-fees
317            )
318            // deposit fees to stake
319       
320            self.revealReceipts[Mneme.totalCertificates] <-! revealReceipt
321            // Increase total minted count for the Original
322            originalRef.increaseTotalMinted()
323            // emit the Minted event
324            emit Minted(type: newNFT.getType().identifier,
325                        id: newNFT.id,
326                        uuid: newNFT.uuid,
327                        name: newNFT.name,
328                        description: newNFT.description
329                        )
330            // add a FlowToken vault to the new NFT
331            let newVault <- FlowToken.createEmptyVault(vaultType: Type<@FlowToken.Vault>())
332            newNFT.addVault(vaultType: Type<@FlowToken.Vault>(), vault: <- newVault, vaultReceiverPath: /public/flowTokenReceiver)
333            // add an ArtDropToken vault to the new NFT
334            let newArtDropVault <- ArtDropToken.createEmptyVault(vaultType: Type<@ArtDropToken.Vault>())
335            newNFT.addVault(vaultType: Type<@ArtDropToken.Vault>(), vault: <- newArtDropVault, vaultReceiverPath: /public/ArtDropTokenReceiver)
336            // send the new NFT to the artist's collection
337            let artistAccount = getAccount(self.artistAddress)
338            let artistCollection = artistAccount.capabilities.borrow<&{NonFungibleToken.Receiver}>(Mneme.CollectionPublicPath)!
339            artistCollection.deposit(token: <- newNFT)
340        }
341        // Pop the rng receipt
342        access(Editions) fun popRNGReceipt(id: UInt64) {
343            pre {
344                self.rngReceipts[id] != nil: "RNG receipt not found"
345            }
346            // get the receipt
347            let receipt <- self.rngReceipts.remove(key: id)!
348            // get a random number within the reprint limit
349            // of this edition
350            let randomNumber = Mneme._randomWithinRange(request: <- receipt.popRequest(), max: Int(self.reprintLimit))
351            // use this random number to get the multiplier from the multipliers array
352            let multiplier = self.multipliers[randomNumber]
353            // assign the multiplier to the id of the receipt/NFT
354            self.usedMultipliers[multiplier] = receipt.id
355            // return the used receipt
356            self.rngReceipts[id] <-! receipt
357
358            emit RNGReceiptPopped(id: id, multiplier: multiplier)
359        }
360    }
361    
362
363    /// We choose the name NFT here, but this type can have any name now
364    /// because the interface does not require it to have a specific name any more
365    access(all) resource CertificateNFT: NonFungibleToken.NFT, Pistis.Pool {
366        access(all) let id: UInt64
367        access(all) let serial: UInt64
368        access(all) let artistAddress: Address
369        access(all) var vaultsDict: @{Type: {FungibleToken.Vault}}
370        access(all) var vaultReceiverPath: {Type: PublicPath}
371        access(all) let name: String
372        access(all) let description: String
373        access(all) let thumbnail: String 
374        access(all) let royalties: MetadataViews.Royalty
375        access(all) let metadata: {String: AnyStruct}
376        
377        init(
378            id: UInt64,
379            serial: UInt64,
380            name: String,
381            description: String,
382            thumbnail: String,
383            metadata: {String: AnyStruct},
384            artistAddress: Address
385        ) {
386            self.id = id
387            self.serial = serial
388            self.artistAddress = artistAddress
389            self.name = name
390            self.description = description
391            self.thumbnail = thumbnail
392            self.royalties = MetadataViews.Royalty(
393                    receiver: getAccount(Mneme.account.address).capabilities.get<&FlowToken.Vault>(/public/flowTokenReceiver),
394                    cut: 0.5,
395                    description: "The deployer gets 5% of every secondary sale."
396                )
397            self.metadata = metadata
398            self.vaultsDict <- {}
399            self.vaultReceiverPath = {}
400        }
401
402        view access(all)
403        fun getTraits(): {String: AnyStruct} {
404            let metadata: {String: AnyStruct} = {"name": self.name}
405            metadata["editionID"] = self.id
406            metadata["editionName"] = self.name
407            metadata["editionDescription"] = self.description
408            metadata["editionThumbnail"] = self.thumbnail
409            metadata["editionMetadata"] = self.metadata
410            metadata["editionArtistAddress"] = self.artistAddress
411            return metadata
412        }
413        /// createEmptyCollection creates an empty Collection
414        /// and returns it to the caller so that they can own NFTs
415        /// @{NonFungibleToken.Collection}
416        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
417            return <-Mneme.createEmptyCollection(nftType: Type<@Mneme.CertificateNFT>())
418
419            
420        }
421
422        access(all) view fun getViews(): [Type] {
423            return [
424                Type<MetadataViews.Display>(),
425                Type<MetadataViews.Royalties>(),
426                Type<MetadataViews.Editions>(),
427                Type<MetadataViews.ExternalURL>(),
428                Type<MetadataViews.NFTCollectionData>(),
429                Type<MetadataViews.NFTCollectionDisplay>(),
430                Type<MetadataViews.Serial>(),
431                Type<MetadataViews.Traits>(),
432                Type<MetadataViews.EVMBridgedMetadata>()
433            ]
434        }
435
436        access(all) fun resolveView(_ view: Type): AnyStruct? {
437            switch view {
438                case Type<MetadataViews.Display>():
439                    return MetadataViews.Display(
440                        name: self.name,
441                        description: self.description,
442                        thumbnail: MetadataViews.HTTPFile(
443                            url: self.thumbnail
444                        )
445                    )
446                case Type<MetadataViews.Editions>():
447                    // There is no max number of NFTs that can be minted from this contract
448                    // so the max edition field value is set to nil
449                    let editionInfo = MetadataViews.Edition(name: "ArtDrop Certificate", number: self.id, max: nil)
450                    let editionList = [editionInfo]
451                    return MetadataViews.Editions(
452                        editionList
453                    )
454                case Type<MetadataViews.Serial>():
455                    return MetadataViews.Serial(
456                        self.serial
457                    )
458                case Type<MetadataViews.Royalties>():
459                    return MetadataViews.Royalties(
460                        [self.royalties]
461                    )
462                case Type<MetadataViews.ExternalURL>():
463                    return MetadataViews.ExternalURL("https://artdrop.me/\(self.id.toString())")
464                case Type<MetadataViews.NFTCollectionData>():
465                    return Mneme.resolveContractView(resourceType: Type<@Mneme.CertificateNFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
466                case Type<MetadataViews.NFTCollectionDisplay>():
467                    return Mneme.resolveContractView(resourceType: Type<@Mneme.CertificateNFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
468                case Type<MetadataViews.Traits>():
469                    // exclude mintedTime and foo to show other uses of Traits
470                    let excludedTraits = self.getTraits() 
471                    return MetadataViews.dictToTraits(dict: self.getTraits(), excludedNames: [])
472                case Type<MetadataViews.EVMBridgedMetadata>():
473                    // Implementing this view gives the project control over how the bridged NFT is represented as an
474                    // ERC721 when bridged to EVM on Flow via the public infrastructure bridge.
475                    // NOTE: If your NFT is a cross-VM NFT, meaning you control both your Cadence & EVM contracts and
476                    //      registered your custom association with the VM bridge, it's recommended you use the 
477                    //      CrossVMMetadata.EVMBytesMetadata view to define and pass metadata as EVMBytes into your
478                    //      EVM contract at the time of bridging into EVM. For more information about cross-VM NFTs,
479                    //      see FLIP-318: https://github.com/onflow/flips/issues/318
480
481                    // Get the contract-level name and symbol values
482                    let contractLevel = Mneme.resolveContractView(
483                            resourceType: nil,
484                            viewType: Type<MetadataViews.EVMBridgedMetadata>()
485                        ) as! MetadataViews.EVMBridgedMetadata?
486
487                    if let contractMetadata = contractLevel {
488                        // Compose the token-level URI based on a base URI and the token ID, pointing to a JSON file. This
489                        // would be a file you've uploaded and are hosting somewhere - in this case HTTP, but this could be
490                        // IPFS, S3, a data URL containing the JSON directly, etc.
491                        let baseURI = "https://artdrop-nft.onflow.org/token-metadata/"
492                        let uriValue = "\(self.id.toString()).json"
493
494                        return MetadataViews.EVMBridgedMetadata(
495                            name: contractMetadata.name,
496                            symbol: contractMetadata.symbol,
497                            uri: MetadataViews.URI(
498                                baseURI: baseURI, // defining baseURI results in a concatenation of baseURI and value
499                                value: "\(self.id.toString()).json"
500                            )
501                        )
502                    } else {
503                        return nil
504                    }
505/*                 case Type<CrossVMMetadataViews.EVMPointer>():
506                    // This view is intended for NFT projects with corresponding NFT implementations in both Cadence and
507                    // EVM. Resolving EVMPointer indicates the associated EVM implementation. Fully validating the
508                    // cross-VM association would involve inspecting the associated EVM contract and ensuring that
509                    // contract also points to the resolved Cadence type and contract address. For more information
510                    // about cross-VM NFTs, see FLIP-318: https://github.com/onflow/flips/issues/318
511
512                    return Mneme.resolveContractView(resourceType: self.getType(), viewType: view)
513                case Type<CrossVMMetadataViews.EVMBytesMetadata>():
514                    // This view is intended for Cadence-native NFTs with corresponding ERC721 implementations. By
515                    // resolving, you're able to pass arbitrary metadata into your EVM contract whenever an NFT is
516                    // bridged which can be useful for Cadence NFTs with dynamic metadata values.
517                    // See FLIP-318 for more information about cross-VM NFTs: https://github.com/onflow/flips/issues/318
518
519                    // Here we encoded the EVMBridgedMetadata URI and encode the string as EVM bytes, but you could pass any
520                    // Cadence values that can be abi encoded and decode them in your EVM contract as you wish. Within
521                    // your EVM contract, you can abi decode the bytes and update metadata in your ERC721 contract as
522                    // you see fit.
523                    let bridgedMetadata = (self.resolveView(Type<MetadataViews.EVMBridgedMetadata>()) as! MetadataViews.EVMBridgedMetadata?)!
524                    let uri = bridgedMetadata.uri.uri()
525                    let encodedURI = EVM.encodeABI([uri])
526                    let evmBytes = EVM.EVMBytes(value: encodedURI)
527                    return CrossVMMetadataViews.EVMBytesMetadata(bytes: evmBytes) */
528            }
529            return nil
530        }
531    }
532
533    access(all) resource Collection: NonFungibleToken.Collection, Pistis.Loyalty {
534        /// dictionary of NFT conforming tokens
535        /// NFT is a resource type with an `UInt64` ID field
536        access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
537        access(all) var loyaltyPoints: {Address: UFix64}
538        access(all) var supportedArtists: {Address: [Int64]}
539
540        init () {
541            self.ownedNFTs <- {}
542            self.loyaltyPoints = {}
543            self.loyaltyPoints[Mneme.account.address] = 0.0
544            self.supportedArtists = {}
545        }
546
547        // Function to calculate loyalty points
548        access(all) view fun calculateLoyaltyPoints(artistAddress: Address): Int {
549        // get the array of editions
550           let editions = self.supportedArtists[artistAddress]!
551           // loyalty points is the of editions multiplied by 10
552           let loyaltyPoints: Int = editions.length * 10
553
554
555           return loyaltyPoints
556        }
557
558        access(all) fun addVault(vaultType: Type, vault: @{FungibleToken.Vault}, id: UInt64, vaultReceiverPath: PublicPath) {
559            let nft <- self.ownedNFTs.remove(key: id) as! @Mneme.CertificateNFT
560            nft.addVault(vaultType: vaultType, vault: <- vault, vaultReceiverPath: vaultReceiverPath)
561            self.ownedNFTs[id] <-! nft 
562        } 
563
564        access(all) fun depositToVault(id: UInt64, vaultType: Type, vaultDeposit: @{FungibleToken.Vault}) {
565            let nft <- self.ownedNFTs.remove(key: id) as! @Mneme.CertificateNFT
566            nft.depositToVault(vaultType: vaultType, vaultDeposit: <- vaultDeposit)
567            self.ownedNFTs[id] <-! nft 
568        } 
569
570        // Withdraw from a vault
571        access(all) fun withdrawFromVault(id: UInt64, vaultType: Type) {
572            let nft <- self.ownedNFTs.remove(key: id) as! @Mneme.CertificateNFT
573            let newVault <- nft.withdrawFromVault(id: id, vaultType: vaultType)
574            let account = getAccount(self.owner!.address)
575            let vault <- newVault.remove(key: newVault.keys[0])!
576            let receiverRef = account.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!
577            receiverRef.deposit(from: <- vault.withdraw(amount: vault.balance))
578            destroy newVault
579            destroy vault
580            self.ownedNFTs[id] <-! nft 
581        }
582
583        /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
584        access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
585            let supportedTypes: {Type: Bool} = {}
586            supportedTypes[Type<@Mneme.CertificateNFT>()] = true
587            return supportedTypes
588        }
589
590        /// Returns whether or not the given type is accepted by the collection
591        /// A collection that can accept any type should just return true by default
592        access(all) view fun isSupportedNFTType(type: Type): Bool {
593            return type == Type<@Mneme.CertificateNFT>()
594        }
595
596        /// withdraw removes an NFT from the collection and moves it to the caller
597        access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
598            let token <- self.ownedNFTs.remove(key: withdrawID)
599                ?? panic("Mneme.Collection.withdraw: Could not withdraw an NFT with ID \(withdrawID.toString()). Check the submitted ID to make sure it is one that this collection owns.")
600
601            // Remove loyalty points from the collector
602            self.substractLoyalty(address: self.owner?.address!, loyaltyPoints: 10.0)
603            
604
605            return <-token
606        }
607
608        /// deposit takes a NFT and adds it to the collections dictionary
609        /// and adds the ID to the id array
610        access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
611            let token <- token as! @Mneme.CertificateNFT
612            let id = token.id
613            let artistAddress = token.artistAddress
614
615            // add the new token to the dictionary which removes the old one
616            let oldToken <- self.ownedNFTs[token.id] <- token
617            if self.supportedArtists[artistAddress] == nil {
618                self.supportedArtists[artistAddress] = []
619            }
620            self.supportedArtists[artistAddress]!.append(Int64(id))
621
622            let loyaltyPoints = self.calculateLoyaltyPoints(artistAddress: artistAddress)
623            // Based on NFT's edition and other factors, add loyalty points to the collector
624            self.addLoyalty(address: self.owner?.address!, loyaltyPoints: UFix64(loyaltyPoints))
625            destroy oldToken
626
627            // This code is for testing purposes only
628            // Do not add to your contract unless you have a specific
629            // reason to want to emit the NFTUpdated event somewhere
630            // in your contract
631            let authTokenRef = (&self.ownedNFTs[id] as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT}?)!
632            //authTokenRef.updateTransferDate(date: getCurrentBlock().timestamp)
633            Mneme.emitNFTUpdated(authTokenRef)
634        }
635
636        /// getIDs returns an array of the IDs that are in the collection
637        access(all) view fun getIDs(): [UInt64] {
638            return self.ownedNFTs.keys
639        }
640
641        /// Gets the amount of NFTs stored in the collection
642        access(all) view fun getLength(): Int {
643            return self.ownedNFTs.length
644        }
645
646        access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
647            return &self.ownedNFTs[id]
648        }
649
650        /// Borrow the view resolver for the specified NFT ID
651        access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
652            if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
653                return nft as &{ViewResolver.Resolver}
654            }
655            return nil
656        }
657
658        /// createEmptyCollection creates an empty Collection of the same type
659        /// and returns it to the caller
660        /// @return A an empty collection of the same type
661        access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
662            return <-Mneme.createEmptyCollection(nftType: Type<@Mneme.CertificateNFT>())
663        }
664    }
665    // -----------------------------------------------------------------------
666    // ArtDrop Scheduled Transaction Handler 
667    // -----------------------------------------------------------------------
668    // Struct for the scheduled transaction data
669    access(all) struct revealData {
670        access(all) let id: UInt64
671        access(all) let artistAddress: Address
672        access(all) let editionId: UInt64 
673
674        init(id: UInt64, artistAddress: Address, editionId: UInt64) {
675            self.id = id
676            self.artistAddress = artistAddress
677            self.editionId = editionId
678        }
679    }
680    /// Handler resource that implements the Scheduled Transaction interface
681    access(all) resource Handler: FlowTransactionScheduler.TransactionHandler {
682
683
684        access(FlowTransactionScheduler.Execute) fun executeTransaction(id: UInt64, data: AnyStruct?) {
685            // Extract reveal data from transaction data
686            let revealData = data as! revealData? ?? panic("revealData data is required")
687            // Get a ref to the Edition resource 
688            // using the artistAddress and editionId
689            let storageIdentifier = "ArtDrop_Edition_\(revealData.artistAddress)_\(revealData.editionId)"
690            let storagePath = StoragePath(identifier: storageIdentifier)!
691            let editionRef = Mneme.account.storage.borrow<auth(Mneme.Editions) &Mneme.Edition>(from: storagePath)!
692            if editionRef == nil {
693                panic("Edition not found")
694            }
695            // get the rng receipts for the edition
696            // with this the revealData.id
697            let rngReceipt = editionRef.popRNGReceipt(id: revealData.id)
698
699
700
701        }
702    }
703    // -----------------------------------------------------------------------
704    // ArtDrop Receipt Resource
705    // -----------------------------------------------------------------------
706    /// The Receipt resource is used to store the associated randomness request. By listing the
707    /// RandomConsumer.RequestWrapper conformance, this resource inherits all the default implementations of the
708    /// interface. This is why the Receipt resource has access to the getRequestBlock() and popRequest() functions
709    /// without explicitly defining them.
710    ///
711    access(all) resource Receipt : RandomConsumer.RequestWrapper {
712        /// The associated randomness request which contains the block height at which the request was made
713        // The setID of the intended pack
714        access(all) let id: UInt64
715
716        /// and whether the request has been fulfilled.
717        access(all) var request: @RandomConsumer.Request?
718
719        init(id: UInt64, request: @RandomConsumer.Request) {
720            self.id = id
721            self.request <- request
722        }
723    }
724    /// Returns a random number between 0 and 1 using the RandomConsumer.Consumer resource contained in the contract.
725    /// For the purposes of this contract, a simple modulo operation could have been used though this is not the case
726    /// for all ranges. Using the Consumer.fulfillRandomInRange function ensures that we can get a random number
727    /// within any range without a risk of bias.
728    ///
729    access(self) 
730    fun _randomWithinRange(request: @RandomConsumer.Request, max: Int): UInt64 {
731        return self.consumer.fulfillRandomInRange(request: <-request, min: 0, max: UInt64(max))
732    }
733    // -----------------------------------------------------------------------
734    // Mneme public functions
735    // -----------------------------------------------------------------------
736    // Get all the Artists and their Editions
737    access(all) view fun getAllArtists(): {Address: [Int64]} {
738        return self.artistEditions
739    }
740    // Struct for Original metadata
741    access(all) struct OriginalMetadata {
742        access(all) let name: String
743        access(all) let description: String
744        access(all) let thumbnail: String
745        access(all) let metadata: {String: String}
746        init(name: String, description: String, thumbnail: String, metadata: {String: String}) {
747            self.name = name
748            self.description = description
749            self.thumbnail = thumbnail
750            self.metadata = metadata
751        }   
752    }
753    // Get all the Originals for an artist
754    access(all)  fun getOriginalMetadata(artistAddress: Address, originalId: UInt64): OriginalMetadata? {
755        pre {
756            self.artistOriginals[artistAddress] != nil: "This artist does not exist"
757        }
758        let storageIdentifier = "\(Mneme.address)_ArtDrop_Original_\(artistAddress)_\(originalId)"
759        let publicPath = PublicPath(identifier: storageIdentifier)!
760
761        let originalRef = Mneme.account.capabilities.borrow<&Mneme.Original>(publicPath)!
762        let originalMetadata = originalRef.getMetadata()
763        let metadata = OriginalMetadata(name: originalRef.name, description: originalRef.description, thumbnail: originalRef.thumbnail, metadata: originalMetadata)
764        return metadata
765    }
766    // Struct for Edition metadata
767    access(all) struct EditionMetadata {
768        access(all) let originalId: UInt64
769        access(all) let id: UInt64
770        access(all) var name: String
771        access(all) var price: UFix64
772        access(all) var type: String
773        access(all) var story: String
774        access(all) var dimensions: {String: String}
775        access(all) var reprintLimit: Int64
776        access(all) let artistAddress: Address
777        access(all) var totalMinted: Int64
778        init(originalId: UInt64, id: UInt64, name: String, price: UFix64, type: String, story: String, dimensions: {String: String}, reprintLimit: Int64, artistAddress: Address, totalMinted: Int64) {
779            self.originalId = originalId
780            self.id = id
781            self.name = name
782            self.price = price
783            self.type = type
784            self.story = story
785            self.dimensions = dimensions
786            self.reprintLimit = reprintLimit
787            self.artistAddress = artistAddress
788            self.totalMinted = totalMinted
789        }
790    }
791    // Get an Edition's metadata
792    // parameters: artistAddress: Address, editionId: UInt64
793    access(all)  fun getEditionMetadata(artistAddress: Address, editionId: UInt64): EditionMetadata? {
794        pre {
795            self.artistEditions[artistAddress] != nil: "This artist does not exist"
796        }
797        // Check if editionId exists in the array
798        let editionsArray = self.artistEditions[artistAddress]!
799        var editionExists = false
800        for edition in editionsArray {
801            if edition == Int64(editionId) {
802                editionExists = true
803                break
804            }
805        }
806        if !editionExists {
807            return nil
808        }
809        let storageIdentifier = "\(Mneme.account.address)_ArtDrop_Edition_\(artistAddress)_\(editionId)"
810        let storagePath = StoragePath(identifier: storageIdentifier)!
811
812        let editionRef = Mneme.account.storage.borrow<&Mneme.Edition>(from: storagePath)!
813        let dimensions = editionRef.getDimensions()
814        let metadata = EditionMetadata(originalId: editionRef.originalId, id: editionRef.id, name: editionRef.name, price: editionRef.price, type: editionRef.type, story: editionRef.story, dimensions: dimensions, reprintLimit: editionRef.reprintLimit, artistAddress: editionRef.artistAddress, totalMinted: editionRef.totalMinted)
815        return metadata
816    }
817    /// createEmptyCollection creates an empty Collection for the specified NFT type
818    /// and returns it to the caller so that they can own NFTs
819    access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
820        return <- create Collection()
821    }
822
823    /// Function that returns all the Metadata Views implemented by a Non Fungible Token
824    ///
825    /// @return An array of Types defining the implemented views. This value will be used by
826    ///         developers to know which parameter to pass to the resolveView() method.
827    ///
828    access(all) view fun getContractViews(resourceType: Type?): [Type] {
829        return [
830            Type<MetadataViews.NFTCollectionData>(),
831            Type<MetadataViews.NFTCollectionDisplay>(),
832            Type<MetadataViews.EVMBridgedMetadata>()
833        ]
834    }
835
836    /// Function that resolves a metadata view for this contract.
837    ///
838    /// @param view: The Type of the desired view.
839    /// @return A structure representing the requested view.
840    ///
841    access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
842        switch viewType {
843            case Type<MetadataViews.NFTCollectionData>():
844                let collectionData = MetadataViews.NFTCollectionData(
845                    storagePath: self.CollectionStoragePath,
846                    publicPath: self.CollectionPublicPath,
847                    publicCollection: Type<&Mneme.Collection>(),
848                    publicLinkedType: Type<&Mneme.Collection>(),
849                    createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
850                        return <-Mneme.createEmptyCollection(nftType: Type<@Mneme.CertificateNFT>())
851                    })
852                )
853                return collectionData
854            case Type<MetadataViews.NFTCollectionDisplay>():
855                let media = MetadataViews.Media(
856                    file: MetadataViews.HTTPFile(
857                        url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg"
858                    ),
859                    mediaType: "image/svg+xml"
860                )
861                return MetadataViews.NFTCollectionDisplay(
862                    name: "The ArtDrop Certificate Collection",
863                    description: "This collection is used to store the ArtDrop Certificates.",
864                    externalURL: MetadataViews.ExternalURL("https://artdrop.me"),
865                    squareImage: media,
866                    bannerImage: media,
867                    socials: {
868                        "twitter": MetadataViews.ExternalURL("https://X.com/Artdrop_presents")
869                    }
870                )
871            case Type<MetadataViews.EVMBridgedMetadata>():
872                // Implementing this view gives the project control over how the bridged NFT is represented as an ERC721
873                // when bridged to EVM on Flow via the public infrastructure bridge.
874
875                // Compose the contract-level URI. In this case, the contract metadata is located on some HTTP host,
876                // but it could be IPFS, S3, a data URL containing the JSON directly, etc.
877                return MetadataViews.EVMBridgedMetadata(
878                    name: "Mneme",
879                    symbol: "XMPL",
880                    uri: MetadataViews.URI(
881                        baseURI: nil, // setting baseURI as nil sets the given value as the uri field value
882                        value: "https://artdrop-nft.onflow.org/contract-metadata.json"
883                    )
884                )
885/*             case Type<CrossVMMetadataViews.EVMPointer>():
886                // This view is intended for NFT projects with corresponding NFT implementations in both Cadence and
887                // EVM. Resolving EVMPointer indicates the associated EVM implementation. Fully validating the
888                // cross-VM association would involve inspecting the associated EVM contract and ensuring that contract
889                // also points to the resolved Cadence type and contract address. For more information about cross-VM
890                // NFTs, see FLIP-318: https://github.com/onflow/flips/issues/318
891
892                // Assigning a dummy EVM address and deserializing. Implementations would want to declare the actual
893                // EVM address corresponding to their corresponding ERC721. If using a proxy in your EVM contracts, this
894                // address should be your proxy's address.
895                let evmContractAddress = EVM.addressFromString(
896                        "0x1234565789012345657890123456578901234565"
897                    )
898                // Since this NFT is distributed in Cadence, it's declared as Cadence-native
899                let nativeVM = CrossVMMetadataViews.VM.Cadence
900                return CrossVMMetadataViews.EVMPointer(
901                    cadenceType: Type<@Mneme.CertificateNFT>(),
902                    cadenceContractAddress: self.account.address,
903                    evmContractAddress: evmContractAddress,
904                    nativeVM: nativeVM
905                ) */
906        }
907        return nil
908    }
909
910    // Administrator resource
911    access(all) resource Administrator {
912        // Function to create a new Original resource
913        access(all) fun createOriginal(
914            name: String,
915            description: String,
916            thumbnail: String,
917            artistAddress: Address,
918            price: UFix64,
919            metadata: {String: String}
920        ) {
921            // increase the total originals count
922            Mneme.totalOriginals = Mneme.totalOriginals + 1
923            // make this path dynamic based on contract's address
924            let storageIdentifier = "\(Mneme.account.address)_ArtDrop_Original_\(artistAddress)_\(Mneme.totalOriginals)"
925            let storagePath = StoragePath(identifier: storageIdentifier)!
926            let publicPath = PublicPath(identifier: storageIdentifier)!
927
928            // create a new original resource
929            let newOriginal <- create Original(id: Mneme.totalOriginals, name: name, description: description, thumbnail: thumbnail, artistAddress: artistAddress, price: price, metadata: metadata)
930            // save the new original to storage
931            Mneme.account.storage.save(<-newOriginal, to: storagePath)
932            // create a public capability for the original
933            let originalCap= Mneme.account.capabilities.storage.issue<&Mneme.Original>(storagePath)
934            Mneme.account.capabilities.publish(originalCap, at: publicPath)
935            // add the new original to the artist's originals
936            // add a new key to the artist's originals dictionary
937            if Mneme.artistOriginals[artistAddress] == nil {
938                Mneme.artistOriginals[artistAddress] = []
939            }
940            Mneme.artistOriginals[artistAddress]!.append(Int64(Mneme.totalOriginals))
941            // emit OriginalCreated event
942            emit OriginalCreated(artistAddress: artistAddress, originalId: Mneme.totalOriginals)
943        }
944        // Function to create a new Edition resource
945        access(all) fun createEdition(
946            originalId: UInt64,
947            name: String,
948            price: UFix64,
949            type: String,
950            story: String,
951            dimensions: {String: String},
952            reprintLimit: Int64,
953            artistAddress: Address,
954            multipliers: [UFix64],
955            profitSplit: {Address: UFix64}) {
956            if Mneme.artistEditions[artistAddress] == nil {
957                Mneme.artistEditions[artistAddress] = []
958            }
959            // increase the total editions count
960            Mneme.totalEditions = Mneme.totalEditions + 1
961            // make this path dynamic based on contract's address
962            let storageIdentifier = "\(Mneme.account.address)_ArtDrop_Edition_\(artistAddress)_\(Mneme.totalEditions)"
963            let storagePath = StoragePath(identifier: storageIdentifier)!
964            let publicPath = PublicPath(identifier: storageIdentifier)!
965
966            // create a new edition resource
967            let newEdition <- create Edition(originalId: originalId, id: Mneme.totalEditions, name: name, price: price, type: type, story: story, dimensions: dimensions, reprintLimit: reprintLimit, artistAddress: artistAddress, multipliers: multipliers, profitSplit: profitSplit)
968            // add the new edition to the original's editions
969            // get the original path
970            let originalStorageIdentifier = "\(Mneme.account.address)_ArtDrop_Original_\(artistAddress)_\(originalId)"
971            let originalStoragePath = StoragePath(identifier: originalStorageIdentifier)!
972            let originalRef = Mneme.account.storage.borrow< &Mneme.Original>(from: originalStoragePath)!
973            if originalRef == nil {
974                panic("Original not found")
975            }
976            originalRef.addEdition(editionId: newEdition.id)
977
978            // save the new edition to storage
979            Mneme.account.storage.save(<- newEdition, to: storagePath)
980            // create a public capability for the edition
981            let editionCap = Mneme.account.capabilities.storage.issue<&Mneme.Edition>(storagePath)
982            ////////
983            // DO WE NEED THIS PUBLIC CAP? //
984            ////////
985            // Mneme.account.capabilities.publish(editionCap, at: publicPath)
986            // add the new edition to the artist's editions
987            Mneme.artistEditions[artistAddress]!.append(Int64(Mneme.totalEditions))
988            // return <- newEdition
989
990            // publish an authorized capability to the 
991            // stored edition resource to the artist
992            let artistCap = Mneme.account.capabilities.storage.issue<auth(Editions)  &Mneme.Edition>(storagePath)
993            Mneme.account.inbox.publish(artistCap, name: storageIdentifier, recipient: artistAddress)
994
995            // Emit an event to the artist
996            emit EditionCreated(artistAddress: artistAddress, editionId: Mneme.totalEditions)
997
998        }
999        // Function to update an edition's multipliers
1000        access(all) fun updateMultipliers(
1001            artistAddress: Address,
1002            editionId: UInt64,
1003            multipliers: [UFix64]) {
1004            let storageIdentifier = "\(Mneme.account.address)_ArtDrop_Edition_\(artistAddress)_\(Mneme.totalEditions)"
1005            let editionRef = Mneme.account.storage.borrow<auth(Editions) &Mneme.Edition>(from: StoragePath(identifier: storageIdentifier)!)!
1006            editionRef.updateMultipliers(multipliers: multipliers)
1007            emit MultipliersUpdated(artistAddress: artistAddress, editionId: editionId)
1008        }
1009
1010        // Function to mint a new certificate NFT
1011        access(all) fun mintCertificateNFT(
1012            artistAddress: Address,
1013            editionId: UInt64,
1014            thumbnail: String
1015        ) { 
1016            let storageIdentifier = "\(Mneme.account.address)_ArtDrop_Edition_\(artistAddress)_\(editionId)"
1017            let editionRef = Mneme.account.storage.borrow<auth(Editions) &Mneme.Edition>(from: StoragePath(identifier: storageIdentifier)!)!
1018            if editionRef == nil {
1019                panic("Edition not found")
1020            }
1021            editionRef.mintCertificateNFT(thumbnail: thumbnail)
1022            // return <- newCertificateNFT  
1023        }
1024
1025    }
1026
1027
1028    init() {
1029        self.collectionInfo = {}
1030        self.artistOriginals = {}
1031        self.artistEditions = {}
1032        self.totalOriginals = 0
1033        self.totalCertificates = 0
1034        self.totalEditions = 0
1035        let identifier = "Mneme_\(self.account.address)"
1036        // Create a RandomConsumer.Consumer resource
1037        self.consumer <-RandomConsumer.createConsumer()
1038        self.address = self.account.address
1039        // Set the named paths
1040        self.ArtDropStoragePath = StoragePath(identifier: identifier)!
1041        self.ArtDropPublicPath = PublicPath(identifier: identifier)!
1042        self.AdministratorStoragePath = StoragePath(identifier: "\(identifier)_Administrator")!
1043        self.CollectionStoragePath = StoragePath(identifier: identifier)!
1044        self.CollectionPublicPath = PublicPath(identifier: identifier)!
1045        self.ArtistStoragePath = StoragePath(identifier: "\(identifier)_Artist")!
1046        self.HandlerStoragePath = StoragePath(identifier: "\(identifier)_Handler")!
1047
1048        // Create a Collection resource and save it to storage
1049        let collection <- create Collection()
1050        self.account.storage.save(<-collection, to: self.CollectionStoragePath)
1051        // create a public capability for the collection
1052        let collectionCap = self.account.capabilities.storage.issue<&Mneme.Collection>(self.CollectionStoragePath)
1053        self.account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
1054        // Create an Administrator resource and save it to storage
1055        let administrator <- create Administrator()
1056        self.account.storage.save(<-administrator, to: self.AdministratorStoragePath)
1057        // Create a Handler resource and save it to storage
1058        let handler <- create Handler()
1059        self.account.storage.save(<-handler, to: self.HandlerStoragePath)
1060    }
1061}