DeploySEALED
▓▓▓◇□╱^█&&*▪▒▒◇?■~□░○╳&▪█~░◆&░■▓◆**?█◆@◆◆╳□&▫▫╲●▒&%◆^□▫▪~%@╲▒$●▓
Transaction ID
Execution Fee
0.01398 FLOWTransaction Summary
DeployContract deployment
Contract deployment
Script Arguments
0nameString
Mneme
1codeString
// MADE BY: Noah_Overflow
// This contract is for Mneme, a proof of support platform
// on Flow.
// Mneme (Μνήμη) is one of the original three Muses in pre-Homeric Greek mythology.
// Before the more famous Nine Muses were standardized by Hesiod (daughters of Zeus and Mnemosyne)
// there were three older Muses:
// Melete – Muse of Practice/Contemplation
// Mneme – Muse of Memory
// Aoide – Muse of Song
// Mneme herself was seen as the preserver of knowledge and inspiration, responsible for the ability of poets, orators
// and artists to recall what came before and give it form. She represents the thread that links art to time and culture
// Literally, the memory of humanity encoded in creative work.
import NonFungibleToken from 0x1d7e57aa55817448
import FungibleToken from 0xf233dcee88fe0abe
import FlowToken from 0x1654653399040a61
import ViewResolver from 0x1d7e57aa55817448
import MetadataViews from 0x1d7e57aa55817448
import Pistis from 0x67c649092019e032
import RandomConsumer from 0x45caec600164c9e6
import Xorshift128plus from 0x45caec600164c9e6
import Burner from 0xf233dcee88fe0abe
import FlowTransactionScheduler from 0xe467b9dd11fa00df
// import "CrossVMMetadataViews"
// import "EVM"
access(all)
contract Mneme: NonFungibleToken {
// -----------------------------------------------------------------------
// Mneme contract-level fields.
// These contain actual values that are stored in the smart contract.
// -----------------------------------------------------------------------
// Dictionary to hold general collection information
access(self) let collectionInfo: {String: AnyStruct}
access(self) var artistOriginals: {Address: [Int64]}
access(self) var artistEditions: {Address: [Int64]}
access(self) var totalOriginals: UInt64
access(self) var totalEditions: UInt64
access(self) var totalCertificates: UInt64
access(all) let address: Address
/// The RandomConsumer.Consumer resource used to request & fulfill randomness
access(self) let consumer: @RandomConsumer.Consumer
// -----------------------------------------------------------------------
// Mneme account paths
// -----------------------------------------------------------------------
access(all) let CollectionStoragePath: StoragePath
access(all) let CollectionPublicPath: PublicPath
/// Path where the minter should be stored
/// The standard paths for the collection are stored in the collection resource type
access(all) let ArtDropStoragePath: StoragePath
access(all) let ArtDropPublicPath: PublicPath
access(all) let AdministratorStoragePath: StoragePath
access(all) let ArtistStoragePath: StoragePath
access(all) let HandlerStoragePath: StoragePath
// -----------------------------------------------------------------------
// Mneme Entitlements
// -----------------------------------------------------------------------
access(all) entitlement Admin
access(all) entitlement AddArtist
access(all) entitlement Editions
/// Event to show when an NFT is minted
access(all) event Minted(
type: String,
id: UInt64,
uuid: UInt64,
name: String,
description: String
)
access(all) event OriginalCreated(artistAddress: Address, originalId: UInt64)
access(all) event EditionCreated(artistAddress: Address, editionId: UInt64)
access(all) event MultipliersUpdated(artistAddress: Address, editionId: UInt64)
access(all) event RNGReceiptPopped(id: UInt64, multiplier: UFix64)
// -----------------------------------------------------------------------
// Mneme contract-level Composite Type definitions
// -----------------------------------------------------------------------
// These are just *definitions* for Types that this contract
// and other accounts can use. These definitions do not contain
// actual stored values, but an instance (or object) of one of these Types
// can be created by this contract that contains stored values.
// -----------------------------------------------------------------------
access(all) resource Original: Pistis.Pool {
access(all) let id: UInt64
access(all) var name: String
access(all) var description: String
access(all) var thumbnail: String
access(all) let artistAddress: Address
access(all) var editions: {UInt64: UInt64}
access(all) var totalMinted: Int64
access(all) var price: UFix64
access(all) var metadata: {String: String}
access(all) var extra: {String: AnyStruct}
// Pistis fields
access(all) var vaultsDict: @{Type: {FungibleToken.Vault}}
access(all) var vaultReceiverPath: {Type: PublicPath}
init (
id: UInt64,
name: String,
description: String,
thumbnail: String,
artistAddress: Address,
price: UFix64,
metadata: {String: String},
) {
self.id = id
self.name = name
self.description = description
self.thumbnail = thumbnail
self.artistAddress = artistAddress
self.editions = {}
self.totalMinted = 0
self.price = price
self.vaultsDict <- {}
self.vaultReceiverPath = {}
self.metadata = metadata
self.extra = {}
}
access(all) fun addEdition(editionId: UInt64) {
self.editions[editionId] = 0
}
access(all) fun increaseTotalMinted() {
self.totalMinted = self.totalMinted + 1
}
// helper funct to return metadata without ref
access(all) fun getMetadata(): {String: String} {
return self.metadata
}
}
// Edition resource represents the Art's metadata
// and its rewards rules
// Attachment
access(all) resource Edition {
access(all) let originalId: UInt64
access(all) let id: UInt64
access(all) var name: String
access(all) var price: UFix64
access(all) var type: String
access(all) var story: String
access(all) var dimensions: {String: String}
access(all) var reprintLimit: Int64
access(all) let artistAddress: Address
access(all) var totalMinted: Int64
access(all) var profitSplit: {Address: UFix64} // the address and the percentage of the profit
// ArtDrop, Original Painting, Charity, CopyrightsHolder/Reseller, communityPool. (Reseller Address is dynamic)
// the total of the percentages must be 100%
// distribute after claim/point of no return.
access(all) var multipliers: [UFix64]
// Map of multiplier to the id of the CertificateNFT
access(all) var usedMultipliers: {UFix64: UInt64}
access(all) let rngReceipts: @{UInt64: Receipt}
access(all) let revealReceipts: @{UInt64: FlowTransactionScheduler.ScheduledTransaction}
init(
originalId: UInt64,
id: UInt64,
name: String,
price: UFix64,
type: String,
story: String,
dimensions: {String: String},
reprintLimit: Int64,
artistAddress: Address,
multipliers: [UFix64],
profitSplit: {Address: UFix64}) {
self.originalId = originalId
self.name = name
self.price = price
self.id = id
self.type = type
self.story = story
self.dimensions = dimensions
self.reprintLimit = reprintLimit
self.artistAddress = artistAddress
self.multipliers = multipliers
self.usedMultipliers = {}
self.totalMinted = 0
self.rngReceipts <- {}
self.revealReceipts <- {}
self.profitSplit = profitSplit
// The number of multipliers should be equal to
// the reprint limit
if multipliers.length != Int(reprintLimit) {
panic("The number of multipliers should be equal to the reprint limit")
}
}
// helper funct to return dimension without ref
access(all) fun getDimensions(): {String: String} {
return self.dimensions
}
access(Editions) fun editEdition(
name: String?,
price: UFix64?,
type: String?,
story: String?,
dimensions: {String: String}?,
reprintLimit: Int64?) {
pre {
self.totalMinted == 0: "This edition has already been minted"
}
self.name = name ?? self.name
self.price = price ?? self.price
self.type = type ?? self.type
self.story = story ?? self.story
self.dimensions = dimensions ?? self.dimensions
self.reprintLimit = reprintLimit ?? self.reprintLimit
}
// update multipliers
access(Editions) fun updateMultipliers(multipliers: [UFix64]) {
// The number of multipliers should be equal to
// the reprint limit
if multipliers.length != Int(self.reprintLimit) {
panic("The number of multipliers should be equal to the reprint limit")
}
self.multipliers = multipliers
}
/// mintNFT mints a new NFT with a new ID
/// and returns it to the calling context
access(Editions)
fun mintCertificateNFT(thumbnail: String) {
// Get the original path
let originalStorageIdentifier = "\(Mneme.account.address)_ArtDrop_Original_\(self.artistAddress)_\(self.originalId)"
let originalStoragePath = StoragePath(identifier: originalStorageIdentifier)!
let originalRef = Mneme.account.storage.borrow< &Mneme.Original>(from: originalStoragePath)!
if originalRef == nil {
panic("Original not found")
}
// Check if the edition has a reprint limit
// If it does, check if the total minted count has reached the reprint limit
if self.reprintLimit != 0 {
if self.totalMinted >= self.reprintLimit {
panic("This edition has reached the reprint limit")
}
}
let metadata: {String: AnyStruct} = {}
let currentBlock = getCurrentBlock()
metadata["mintedBlock"] = currentBlock.height
metadata["mintedTime"] = currentBlock.timestamp
// this piece of metadata will be used to show embedding rarity into a trait
metadata["foo"] = "bar"
// [2x. 3x, 4x, 5x, 6x, 7x, 8x, 9x, 10x]
// increase the total minted count for this Edition
self.totalMinted = self.totalMinted + 1
// increase the total certificates count
Mneme.totalCertificates = Mneme.totalCertificates + 1
// request randomness
let request <- Mneme.consumer.requestRandomness()
let receipt <- create Receipt(id: Mneme.totalCertificates, request: <-request)
// Safe receipt linked to this Edition resource
self.rngReceipts[Mneme.totalCertificates] <-! receipt
// create a new NFT
var newNFT <- create CertificateNFT(
id: Mneme.totalCertificates,
serial: UInt64(self.totalMinted),
name: self.name,
description: self.story,
thumbnail: thumbnail,
metadata: metadata,
artistAddress: self.artistAddress
)
// Schedule a transaction to reveal the Receipt
let future = getCurrentBlock().timestamp + 5.0
let priority = FlowTransactionScheduler.Priority.Medium
let executionEffort: UInt64 = 1000
let estimate = FlowTransactionScheduler.estimate(
data: "",
timestamp: future,
priority: priority,
executionEffort: executionEffort
)
assert(
estimate.timestamp != nil || priority == FlowTransactionScheduler.Priority.Medium,
message: estimate.error ?? "estimation failed"
)
if Mneme.account.storage.borrow<&AnyResource>(from: Mneme.HandlerStoragePath) == nil {
let handler <- create Handler()
Mneme.account.storage.save(<-handler, to: Mneme.HandlerStoragePath)
}
// Withdraw FLOW fees from this contract's account vault
let vaultRef = Mneme.account.storage
.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
?? panic("missing FlowToken vault on contract account")
let fees <- vaultRef.withdraw(amount: estimate.flowFee ?? 0.0) as! @FlowToken.Vault
let handlerCap = Mneme.account.capabilities.storage
.issue<auth(FlowTransactionScheduler.Execute) &{FlowTransactionScheduler.TransactionHandler}>(Mneme.HandlerStoragePath)
let revealReceipt <- FlowTransactionScheduler.schedule(
handlerCap: handlerCap,
data: "",
timestamp: future,
priority: priority,
executionEffort: executionEffort,
fees: <-fees
)
// deposit fees to stake
self.revealReceipts[Mneme.totalCertificates] <-! revealReceipt
// Increase total minted count for the Original
originalRef.increaseTotalMinted()
// emit the Minted event
emit Minted(type: newNFT.getType().identifier,
id: newNFT.id,
uuid: newNFT.uuid,
name: newNFT.name,
description: newNFT.description
)
// send the new NFT to the artist's collection
let artistAccount = getAccount(self.artistAddress)
let artistCollection = artistAccount.capabilities.borrow<&{NonFungibleToken.Receiver}>(Mneme.CollectionPublicPath)!
artistCollection.deposit(token: <- newNFT)
}
// Pop the rng receipt
access(Editions) fun popRNGReceipt(id: UInt64) {
pre {
self.rngReceipts[id] != nil: "RNG receipt not found"
}
// get the receipt
let receipt <- self.rngReceipts.remove(key: id)!
// get a random number within the reprint limit
// of this edition
let randomNumber = Mneme._randomWithinRange(request: <- receipt.popRequest(), max: Int(self.reprintLimit))
// use this random number to get the multiplier from the multipliers array
let multiplier = self.multipliers[randomNumber]
// assign the multiplier to the id of the receipt/NFT
self.usedMultipliers[multiplier] = receipt.id
// return the used receipt
self.rngReceipts[id] <-! receipt
emit RNGReceiptPopped(id: id, multiplier: multiplier)
}
}
/// We choose the name NFT here, but this type can have any name now
/// because the interface does not require it to have a specific name any more
access(all) resource CertificateNFT: NonFungibleToken.NFT, Pistis.Pool {
access(all) let id: UInt64
access(all) let serial: UInt64
access(all) let artistAddress: Address
access(all) var vaultsDict: @{Type: {FungibleToken.Vault}}
access(all) var vaultReceiverPath: {Type: PublicPath}
access(all) let name: String
access(all) let description: String
access(all) let thumbnail: String
access(all) let royalties: MetadataViews.Royalty
access(all) let metadata: {String: AnyStruct}
init(
id: UInt64,
serial: UInt64,
name: String,
description: String,
thumbnail: String,
metadata: {String: AnyStruct},
artistAddress: Address
) {
self.id = id
self.serial = serial
self.artistAddress = artistAddress
self.name = name
self.description = description
self.thumbnail = thumbnail
self.royalties = MetadataViews.Royalty(
receiver: getAccount(Mneme.account.address).capabilities.get<&FlowToken.Vault>(/public/flowTokenReceiver),
cut: 0.5,
description: "The deployer gets 5% of every secondary sale."
)
self.metadata = metadata
self.vaultsDict <- {}
self.vaultReceiverPath = {}
}
view access(all)
fun getTraits(): {String: AnyStruct} {
let metadata: {String: AnyStruct} = {"name": self.name}
metadata["editionID"] = self.id
metadata["editionName"] = self.name
metadata["editionDescription"] = self.description
metadata["editionThumbnail"] = self.thumbnail
metadata["editionMetadata"] = self.metadata
metadata["editionArtistAddress"] = self.artistAddress
return metadata
}
/// createEmptyCollection creates an empty Collection
/// and returns it to the caller so that they can own NFTs
/// @{NonFungibleToken.Collection}
access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
return <-Mneme.createEmptyCollection(nftType: Type<@Mneme.CertificateNFT>())
}
access(all) view fun getViews(): [Type] {
return [
Type<MetadataViews.Display>(),
Type<MetadataViews.Royalties>(),
Type<MetadataViews.Editions>(),
Type<MetadataViews.ExternalURL>(),
Type<MetadataViews.NFTCollectionData>(),
Type<MetadataViews.NFTCollectionDisplay>(),
Type<MetadataViews.Serial>(),
Type<MetadataViews.Traits>(),
Type<MetadataViews.EVMBridgedMetadata>()
]
}
access(all) fun resolveView(_ view: Type): AnyStruct? {
switch view {
case Type<MetadataViews.Display>():
return MetadataViews.Display(
name: self.name,
description: self.description,
thumbnail: MetadataViews.HTTPFile(
url: self.thumbnail
)
)
case Type<MetadataViews.Editions>():
// There is no max number of NFTs that can be minted from this contract
// so the max edition field value is set to nil
let editionInfo = MetadataViews.Edition(name: "Example NFT Edition", number: self.id, max: nil)
let editionList: [MetadataViews.Edition] = [editionInfo]
return MetadataViews.Editions(
editionList
)
case Type<MetadataViews.Serial>():
return MetadataViews.Serial(
self.serial
)
case Type<MetadataViews.Royalties>():
return MetadataViews.Royalties(
[self.royalties]
)
case Type<MetadataViews.ExternalURL>():
return MetadataViews.ExternalURL("https://example-nft.onflow.org/".concat(self.id.toString()))
case Type<MetadataViews.NFTCollectionData>():
return Mneme.resolveContractView(resourceType: Type<@Mneme.CertificateNFT>(), viewType: Type<MetadataViews.NFTCollectionData>())
case Type<MetadataViews.NFTCollectionDisplay>():
return Mneme.resolveContractView(resourceType: Type<@Mneme.CertificateNFT>(), viewType: Type<MetadataViews.NFTCollectionDisplay>())
case Type<MetadataViews.Traits>():
// exclude mintedTime and foo to show other uses of Traits
let excludedTraits = self.getTraits()
return MetadataViews.dictToTraits(dict: self.getTraits(), excludedNames: [])
case Type<MetadataViews.EVMBridgedMetadata>():
// Implementing this view gives the project control over how the bridged NFT is represented as an
// ERC721 when bridged to EVM on Flow via the public infrastructure bridge.
// NOTE: If your NFT is a cross-VM NFT, meaning you control both your Cadence & EVM contracts and
// registered your custom association with the VM bridge, it's recommended you use the
// CrossVMMetadata.EVMBytesMetadata view to define and pass metadata as EVMBytes into your
// EVM contract at the time of bridging into EVM. For more information about cross-VM NFTs,
// see FLIP-318: https://github.com/onflow/flips/issues/318
// Get the contract-level name and symbol values
let contractLevel = Mneme.resolveContractView(
resourceType: nil,
viewType: Type<MetadataViews.EVMBridgedMetadata>()
) as! MetadataViews.EVMBridgedMetadata?
if let contractMetadata = contractLevel {
// Compose the token-level URI based on a base URI and the token ID, pointing to a JSON file. This
// would be a file you've uploaded and are hosting somewhere - in this case HTTP, but this could be
// IPFS, S3, a data URL containing the JSON directly, etc.
let baseURI = "https://example-nft.onflow.org/token-metadata/"
let uriValue = self.id.toString().concat(".json")
return MetadataViews.EVMBridgedMetadata(
name: contractMetadata.name,
symbol: contractMetadata.symbol,
uri: MetadataViews.URI(
baseURI: baseURI, // defining baseURI results in a concatenation of baseURI and value
value: self.id.toString().concat(".json")
)
)
} else {
return nil
}
/* case Type<CrossVMMetadataViews.EVMPointer>():
// This view is intended for NFT projects with corresponding NFT implementations in both Cadence and
// EVM. Resolving EVMPointer indicates the associated EVM implementation. Fully validating the
// cross-VM association would involve inspecting the associated EVM contract and ensuring that
// contract also points to the resolved Cadence type and contract address. For more information
// about cross-VM NFTs, see FLIP-318: https://github.com/onflow/flips/issues/318
return Mneme.resolveContractView(resourceType: self.getType(), viewType: view)
case Type<CrossVMMetadataViews.EVMBytesMetadata>():
// This view is intended for Cadence-native NFTs with corresponding ERC721 implementations. By
// resolving, you're able to pass arbitrary metadata into your EVM contract whenever an NFT is
// bridged which can be useful for Cadence NFTs with dynamic metadata values.
// See FLIP-318 for more information about cross-VM NFTs: https://github.com/onflow/flips/issues/318
// Here we encoded the EVMBridgedMetadata URI and encode the string as EVM bytes, but you could pass any
// Cadence values that can be abi encoded and decode them in your EVM contract as you wish. Within
// your EVM contract, you can abi decode the bytes and update metadata in your ERC721 contract as
// you see fit.
let bridgedMetadata = (self.resolveView(Type<MetadataViews.EVMBridgedMetadata>()) as! MetadataViews.EVMBridgedMetadata?)!
let uri = bridgedMetadata.uri.uri()
let encodedURI = EVM.encodeABI([uri])
let evmBytes = EVM.EVMBytes(value: encodedURI)
return CrossVMMetadataViews.EVMBytesMetadata(bytes: evmBytes) */
}
return nil
}
}
access(all) resource Collection: NonFungibleToken.Collection, Pistis.Loyalty {
/// dictionary of NFT conforming tokens
/// NFT is a resource type with an `UInt64` ID field
access(all) var ownedNFTs: @{UInt64: {NonFungibleToken.NFT}}
access(all) var loyaltyPoints: {Address: UFix64}
access(all) var supportedArtists: {Address: [Int64]}
init () {
self.ownedNFTs <- {}
self.loyaltyPoints = {}
self.loyaltyPoints[Mneme.account.address] = 0.0
self.supportedArtists = {}
}
// Function to calculate loyalty points
access(all) view fun calculateLoyaltyPoints(artistAddress: Address): Int {
// get the array of editions
let editions = self.supportedArtists[artistAddress]!
// loyalty points is the of editions multiplied by 10
let loyaltyPoints: Int = editions.length * 10
return loyaltyPoints
}
access(all) fun addVault(vaultType: Type, vault: @{FungibleToken.Vault}, id: UInt64, vaultReceiverPath: PublicPath) {
let nft <- self.ownedNFTs.remove(key: id) as! @Mneme.CertificateNFT
nft.addVault(vaultType: vaultType, vault: <- vault, vaultReceiverPath: vaultReceiverPath)
self.ownedNFTs[id] <-! nft
}
access(all) fun depositToVault(id: UInt64, vaultType: Type, vaultDeposit: @{FungibleToken.Vault}) {
let nft <- self.ownedNFTs.remove(key: id) as! @Mneme.CertificateNFT
nft.depositToVault(vaultType: vaultType, vaultDeposit: <- vaultDeposit)
self.ownedNFTs[id] <-! nft
}
// Withdraw from a vault
access(all) fun withdrawFromVault(id: UInt64, vaultType: Type) {
let nft <- self.ownedNFTs.remove(key: id) as! @Mneme.CertificateNFT
let newVault <- nft.withdrawFromVault(id: id, vaultType: vaultType)
let account = getAccount(self.owner!.address)
let vault <- newVault.remove(key: newVault.keys[0])!
let receiverRef = account.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!
receiverRef.deposit(from: <- vault.withdraw(amount: vault.balance))
destroy newVault
destroy vault
self.ownedNFTs[id] <-! nft
}
/// getSupportedNFTTypes returns a list of NFT types that this receiver accepts
access(all) view fun getSupportedNFTTypes(): {Type: Bool} {
let supportedTypes: {Type: Bool} = {}
supportedTypes[Type<@Mneme.CertificateNFT>()] = true
return supportedTypes
}
/// Returns whether or not the given type is accepted by the collection
/// A collection that can accept any type should just return true by default
access(all) view fun isSupportedNFTType(type: Type): Bool {
return type == Type<@Mneme.CertificateNFT>()
}
/// withdraw removes an NFT from the collection and moves it to the caller
access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @{NonFungibleToken.NFT} {
let token <- self.ownedNFTs.remove(key: withdrawID)
?? panic("Mneme.Collection.withdraw: Could not withdraw an NFT with ID "
.concat(withdrawID.toString())
.concat(". Check the submitted ID to make sure it is one that this collection owns."))
// Remove loyalty points from the collector
self.substractLoyalty(address: self.owner?.address!, loyaltyPoints: 10.0)
return <-token
}
/// deposit takes a NFT and adds it to the collections dictionary
/// and adds the ID to the id array
access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
let token <- token as! @Mneme.CertificateNFT
let id = token.id
let artistAddress = token.artistAddress
// add the new token to the dictionary which removes the old one
let oldToken <- self.ownedNFTs[token.id] <- token
if self.supportedArtists[artistAddress] == nil {
self.supportedArtists[artistAddress] = []
}
self.supportedArtists[artistAddress]!.append(Int64(id))
let loyaltyPoints = self.calculateLoyaltyPoints(artistAddress: artistAddress)
// Based on NFT's edition and other factors, add loyalty points to the collector
self.addLoyalty(address: self.owner?.address!, loyaltyPoints: UFix64(loyaltyPoints))
destroy oldToken
// This code is for testing purposes only
// Do not add to your contract unless you have a specific
// reason to want to emit the NFTUpdated event somewhere
// in your contract
let authTokenRef = (&self.ownedNFTs[id] as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT}?)!
//authTokenRef.updateTransferDate(date: getCurrentBlock().timestamp)
Mneme.emitNFTUpdated(authTokenRef)
}
/// getIDs returns an array of the IDs that are in the collection
access(all) view fun getIDs(): [UInt64] {
return self.ownedNFTs.keys
}
/// Gets the amount of NFTs stored in the collection
access(all) view fun getLength(): Int {
return self.ownedNFTs.length
}
access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
return &self.ownedNFTs[id]
}
/// Borrow the view resolver for the specified NFT ID
access(all) view fun borrowViewResolver(id: UInt64): &{ViewResolver.Resolver}? {
if let nft = &self.ownedNFTs[id] as &{NonFungibleToken.NFT}? {
return nft as &{ViewResolver.Resolver}
}
return nil
}
/// createEmptyCollection creates an empty Collection of the same type
/// and returns it to the caller
/// @return A an empty collection of the same type
access(all) fun createEmptyCollection(): @{NonFungibleToken.Collection} {
return <-Mneme.createEmptyCollection(nftType: Type<@Mneme.CertificateNFT>())
}
}
// -----------------------------------------------------------------------
// ArtDrop Scheduled Transaction Handler
// -----------------------------------------------------------------------
// Struct for the scheduled transaction data
access(all) struct revealData {
access(all) let id: UInt64
access(all) let artistAddress: Address
access(all) let editionId: UInt64
init(id: UInt64, artistAddress: Address, editionId: UInt64) {
self.id = id
self.artistAddress = artistAddress
self.editionId = editionId
}
}
/// Handler resource that implements the Scheduled Transaction interface
access(all) resource Handler: FlowTransactionScheduler.TransactionHandler {
access(FlowTransactionScheduler.Execute) fun executeTransaction(id: UInt64, data: AnyStruct?) {
// Extract reveal data from transaction data
let revealData = data as! revealData? ?? panic("revealData data is required")
// Get a ref to the Edition resource
// using the artistAddress and editionId
let storageIdentifier = "ArtDrop_Edition_\(revealData.artistAddress)_\(revealData.editionId)"
let storagePath = StoragePath(identifier: storageIdentifier)!
let editionRef = Mneme.account.storage.borrow<auth(Mneme.Editions) &Mneme.Edition>(from: storagePath)!
if editionRef == nil {
panic("Edition not found")
}
// get the rng receipts for the edition
// with this the revealData.id
let rngReceipt = editionRef.popRNGReceipt(id: revealData.id)
}
}
// -----------------------------------------------------------------------
// ArtDrop Receipt Resource
// -----------------------------------------------------------------------
/// The Receipt resource is used to store the associated randomness request. By listing the
/// RandomConsumer.RequestWrapper conformance, this resource inherits all the default implementations of the
/// interface. This is why the Receipt resource has access to the getRequestBlock() and popRequest() functions
/// without explicitly defining them.
///
access(all) resource Receipt : RandomConsumer.RequestWrapper {
/// The associated randomness request which contains the block height at which the request was made
// The setID of the intended pack
access(all) let id: UInt64
/// and whether the request has been fulfilled.
access(all) var request: @RandomConsumer.Request?
init(id: UInt64, request: @RandomConsumer.Request) {
self.id = id
self.request <- request
}
}
/// Returns a random number between 0 and 1 using the RandomConsumer.Consumer resource contained in the contract.
/// For the purposes of this contract, a simple modulo operation could have been used though this is not the case
/// for all ranges. Using the Consumer.fulfillRandomInRange function ensures that we can get a random number
/// within any range without a risk of bias.
///
access(self)
fun _randomWithinRange(request: @RandomConsumer.Request, max: Int): UInt64 {
return self.consumer.fulfillRandomInRange(request: <-request, min: 0, max: UInt64(max))
}
// -----------------------------------------------------------------------
// Mneme public functions
// -----------------------------------------------------------------------
// Get all the Artists and their Editions
access(all) view fun getAllArtists(): {Address: [Int64]} {
return self.artistEditions
}
// Struct for Original metadata
access(all) struct OriginalMetadata {
access(all) let name: String
access(all) let description: String
access(all) let thumbnail: String
access(all) let metadata: {String: String}
init(name: String, description: String, thumbnail: String, metadata: {String: String}) {
self.name = name
self.description = description
self.thumbnail = thumbnail
self.metadata = metadata
}
}
// Get all the Originals for an artist
access(all) fun getOriginalMetadata(artistAddress: Address, originalId: UInt64): OriginalMetadata? {
pre {
self.artistOriginals[artistAddress] != nil: "This artist does not exist"
}
let storageIdentifier = "\(Mneme.address)_ArtDrop_Original_\(artistAddress)_\(originalId)"
let publicPath = PublicPath(identifier: storageIdentifier)!
let originalRef = Mneme.account.capabilities.borrow<&Mneme.Original>(publicPath)!
let originalMetadata = originalRef.getMetadata()
let metadata = OriginalMetadata(name: originalRef.name, description: originalRef.description, thumbnail: originalRef.thumbnail, metadata: originalMetadata)
return metadata
}
// Struct for Edition metadata
access(all) struct EditionMetadata {
access(all) let originalId: UInt64
access(all) let id: UInt64
access(all) var name: String
access(all) var price: UFix64
access(all) var type: String
access(all) var story: String
access(all) var dimensions: {String: String}
access(all) var reprintLimit: Int64
access(all) let artistAddress: Address
access(all) var totalMinted: Int64
init(originalId: UInt64, id: UInt64, name: String, price: UFix64, type: String, story: String, dimensions: {String: String}, reprintLimit: Int64, artistAddress: Address, totalMinted: Int64) {
self.originalId = originalId
self.id = id
self.name = name
self.price = price
self.type = type
self.story = story
self.dimensions = dimensions
self.reprintLimit = reprintLimit
self.artistAddress = artistAddress
self.totalMinted = totalMinted
}
}
// Get an Edition's metadata
// parameters: artistAddress: Address, editionId: UInt64
access(all) fun getEditionMetadata(artistAddress: Address, editionId: UInt64): EditionMetadata? {
pre {
self.artistEditions[artistAddress] != nil: "This artist does not exist"
}
// Check if editionId exists in the array
let editionsArray = self.artistEditions[artistAddress]!
var editionExists = false
for edition in editionsArray {
if edition == Int64(editionId) {
editionExists = true
break
}
}
if !editionExists {
return nil
}
let storageIdentifier = "\(Mneme.account.address)_ArtDrop_Edition_\(artistAddress)_\(editionId)"
let storagePath = StoragePath(identifier: storageIdentifier)!
let editionRef = Mneme.account.storage.borrow<&Mneme.Edition>(from: storagePath)!
let dimensions = editionRef.getDimensions()
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)
return metadata
}
/// createEmptyCollection creates an empty Collection for the specified NFT type
/// and returns it to the caller so that they can own NFTs
access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
return <- create Collection()
}
/// Function that returns all the Metadata Views implemented by a Non Fungible Token
///
/// @return An array of Types defining the implemented views. This value will be used by
/// developers to know which parameter to pass to the resolveView() method.
///
access(all) view fun getContractViews(resourceType: Type?): [Type] {
return [
Type<MetadataViews.NFTCollectionData>(),
Type<MetadataViews.NFTCollectionDisplay>(),
Type<MetadataViews.EVMBridgedMetadata>()
]
}
/// Function that resolves a metadata view for this contract.
///
/// @param view: The Type of the desired view.
/// @return A structure representing the requested view.
///
access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
switch viewType {
case Type<MetadataViews.NFTCollectionData>():
let collectionData = MetadataViews.NFTCollectionData(
storagePath: self.CollectionStoragePath,
publicPath: self.CollectionPublicPath,
publicCollection: Type<&Mneme.Collection>(),
publicLinkedType: Type<&Mneme.Collection>(),
createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
return <-Mneme.createEmptyCollection(nftType: Type<@Mneme.CertificateNFT>())
})
)
return collectionData
case Type<MetadataViews.NFTCollectionDisplay>():
let media = MetadataViews.Media(
file: MetadataViews.HTTPFile(
url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg"
),
mediaType: "image/svg+xml"
)
return MetadataViews.NFTCollectionDisplay(
name: "The Example Collection",
description: "This collection is used as an example to help you develop your next Flow NFT.",
externalURL: MetadataViews.ExternalURL("https://example-nft.onflow.org"),
squareImage: media,
bannerImage: media,
socials: {
"twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain")
}
)
case Type<MetadataViews.EVMBridgedMetadata>():
// Implementing this view gives the project control over how the bridged NFT is represented as an ERC721
// when bridged to EVM on Flow via the public infrastructure bridge.
// Compose the contract-level URI. In this case, the contract metadata is located on some HTTP host,
// but it could be IPFS, S3, a data URL containing the JSON directly, etc.
return MetadataViews.EVMBridgedMetadata(
name: "Mneme",
symbol: "XMPL",
uri: MetadataViews.URI(
baseURI: nil, // setting baseURI as nil sets the given value as the uri field value
value: "https://example-nft.onflow.org/contract-metadata.json"
)
)
/* case Type<CrossVMMetadataViews.EVMPointer>():
// This view is intended for NFT projects with corresponding NFT implementations in both Cadence and
// EVM. Resolving EVMPointer indicates the associated EVM implementation. Fully validating the
// cross-VM association would involve inspecting the associated EVM contract and ensuring that contract
// also points to the resolved Cadence type and contract address. For more information about cross-VM
// NFTs, see FLIP-318: https://github.com/onflow/flips/issues/318
// Assigning a dummy EVM address and deserializing. Implementations would want to declare the actual
// EVM address corresponding to their corresponding ERC721. If using a proxy in your EVM contracts, this
// address should be your proxy's address.
let evmContractAddress = EVM.addressFromString(
"0x1234565789012345657890123456578901234565"
)
// Since this NFT is distributed in Cadence, it's declared as Cadence-native
let nativeVM = CrossVMMetadataViews.VM.Cadence
return CrossVMMetadataViews.EVMPointer(
cadenceType: Type<@Mneme.CertificateNFT>(),
cadenceContractAddress: self.account.address,
evmContractAddress: evmContractAddress,
nativeVM: nativeVM
) */
}
return nil
}
// Administrator resource
access(all) resource Administrator {
// Function to create a new Original resource
access(all) fun createOriginal(
name: String,
description: String,
thumbnail: String,
artistAddress: Address,
price: UFix64,
metadata: {String: String}
) {
// increase the total originals count
Mneme.totalOriginals = Mneme.totalOriginals + 1
// make this path dynamic based on contract's address
let storageIdentifier = "\(Mneme.account.address)_ArtDrop_Original_\(artistAddress)_\(Mneme.totalOriginals)"
let storagePath = StoragePath(identifier: storageIdentifier)!
let publicPath = PublicPath(identifier: storageIdentifier)!
// create a new original resource
let newOriginal <- create Original(id: Mneme.totalOriginals, name: name, description: description, thumbnail: thumbnail, artistAddress: artistAddress, price: price, metadata: metadata)
// save the new original to storage
Mneme.account.storage.save(<-newOriginal, to: storagePath)
// create a public capability for the original
let originalCap: Capability<&Mneme.Original> = Mneme.account.capabilities.storage.issue<&Mneme.Original>(storagePath)
Mneme.account.capabilities.publish(originalCap, at: publicPath)
// add the new original to the artist's originals
// add a new key to the artist's originals dictionary
if Mneme.artistOriginals[artistAddress] == nil {
Mneme.artistOriginals[artistAddress] = []
}
Mneme.artistOriginals[artistAddress]!.append(Int64(Mneme.totalOriginals))
// emit OriginalCreated event
emit OriginalCreated(artistAddress: artistAddress, originalId: Mneme.totalOriginals)
}
// Function to create a new Edition resource
access(all) fun createEdition(
originalId: UInt64,
name: String,
price: UFix64,
type: String,
story: String,
dimensions: {String: String},
reprintLimit: Int64,
artistAddress: Address,
multipliers: [UFix64],
profitSplit: {Address: UFix64}) {
if Mneme.artistEditions[artistAddress] == nil {
Mneme.artistEditions[artistAddress] = []
}
// increase the total editions count
Mneme.totalEditions = Mneme.totalEditions + 1
// make this path dynamic based on contract's address
let storageIdentifier = "\(Mneme.account.address)_ArtDrop_Edition_\(artistAddress)_\(Mneme.totalEditions)"
let storagePath = StoragePath(identifier: storageIdentifier)!
let publicPath = PublicPath(identifier: storageIdentifier)!
// create a new edition resource
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)
// add the new edition to the original's editions
// get the original path
let originalStorageIdentifier = "\(Mneme.account.address)_ArtDrop_Original_\(artistAddress)_\(originalId)"
let originalStoragePath = StoragePath(identifier: originalStorageIdentifier)!
let originalRef = Mneme.account.storage.borrow< &Mneme.Original>(from: originalStoragePath)!
if originalRef == nil {
panic("Original not found")
}
originalRef.addEdition(editionId: newEdition.id)
// save the new edition to storage
Mneme.account.storage.save(<- newEdition, to: storagePath)
// create a public capability for the edition
let editionCap: Capability<&Mneme.Edition> = Mneme.account.capabilities.storage.issue<&Mneme.Edition>(storagePath)
////////
// DO WE NEED THIS PUBLIC CAP? //
////////
// Mneme.account.capabilities.publish(editionCap, at: publicPath)
// add the new edition to the artist's editions
Mneme.artistEditions[artistAddress]!.append(Int64(Mneme.totalEditions))
// return <- newEdition
// publish an authorized capability to the
// stored edition resource to the artist
let artistCap: Capability<auth(Mneme.Editions) &Mneme.Edition> = Mneme.account.capabilities.storage.issue<auth(Editions) &Mneme.Edition>(storagePath)
Mneme.account.inbox.publish(artistCap, name: storageIdentifier, recipient: artistAddress)
// Emit an event to the artist
emit EditionCreated(artistAddress: artistAddress, editionId: Mneme.totalEditions)
}
// Function to update an edition's multipliers
access(all) fun updateMultipliers(
artistAddress: Address,
editionId: UInt64,
multipliers: [UFix64]) {
let storageIdentifier = "\(Mneme.account.address)_ArtDrop_Edition_\(artistAddress)_\(Mneme.totalEditions)"
let editionRef = Mneme.account.storage.borrow<auth(Editions) &Mneme.Edition>(from: StoragePath(identifier: storageIdentifier)!)!
editionRef.updateMultipliers(multipliers: multipliers)
emit MultipliersUpdated(artistAddress: artistAddress, editionId: editionId)
}
// Function to mint a new certificate NFT
access(all) fun mintCertificateNFT(
artistAddress: Address,
editionId: UInt64,
thumbnail: String
) {
let storageIdentifier = "\(Mneme.account.address)_ArtDrop_Edition_\(artistAddress)_\(editionId)"
let editionRef = Mneme.account.storage.borrow<auth(Editions) &Mneme.Edition>(from: StoragePath(identifier: storageIdentifier)!)!
if editionRef == nil {
panic("Edition not found")
}
editionRef.mintCertificateNFT(thumbnail: thumbnail)
// return <- newCertificateNFT
}
}
init() {
self.collectionInfo = {}
self.artistOriginals = {}
self.artistEditions = {}
self.totalOriginals = 0
self.totalCertificates = 0
self.totalEditions = 0
let identifier = "Mneme_\(self.account.address))"
// Create a RandomConsumer.Consumer resource
self.consumer <-RandomConsumer.createConsumer()
self.address = self.account.address
// Set the named paths
self.ArtDropStoragePath = StoragePath(identifier: identifier)!
self.ArtDropPublicPath = PublicPath(identifier: identifier)!
self.AdministratorStoragePath = StoragePath(identifier: "\(identifier)_Administrator")!
self.CollectionStoragePath = StoragePath(identifier: identifier)!
self.CollectionPublicPath = PublicPath(identifier: identifier)!
self.ArtistStoragePath = StoragePath(identifier: "\(identifier)_Artist")!
self.HandlerStoragePath = StoragePath(identifier: "\(identifier)_Handler")!
// Create a Collection resource and save it to storage
let collection <- create Collection()
self.account.storage.save(<-collection, to: self.CollectionStoragePath)
// create a public capability for the collection
let collectionCap = self.account.capabilities.storage.issue<&Mneme.Collection>(self.CollectionStoragePath)
self.account.capabilities.publish(collectionCap, at: self.CollectionPublicPath)
// Create an Administrator resource and save it to storage
let administrator <- create Administrator()
self.account.storage.save(<-administrator, to: self.AdministratorStoragePath)
// Create a Handler resource and save it to storage
let handler <- create Handler()
self.account.storage.save(<-handler, to: self.HandlerStoragePath)
}
}
Cadence Script
1transaction(name: String, code: String ) {
2 prepare(signer: auth(AddContract) &Account) {
3 signer.contracts.add(name: name, code: code.utf8 )
4 }
5 }