Smart Contract
Mneme
A.0e964e9e2b53ed06.Mneme
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}