Created
June 28, 2022 14:41
-
-
Save kharioki/1f1d282844600d0752c28555700ec2a2 to your computer and use it in GitHub Desktop.
A sample NFT on Flow network
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* | |
* This is an example implementation of a Flow Non-Fungible Token | |
* It is not part of the official standard but it assumed to be | |
* similar to how many NFTs would implement the core functionality. | |
* | |
* This contract does not implement any sophisticated classification | |
* system for its NFTs. It defines a simple NFT with minimal metadata. | |
* | |
*/ | |
import NonFungibleToken from "./NonFungibleToken.cdc" | |
import MetadataViews from "./MetadataViews.cdc" | |
pub contract ExampleNFT: NonFungibleToken { | |
pub var totalSupply: UInt64 | |
pub event ContractInitialized() | |
pub event Withdraw(id: UInt64, from: Address?) | |
pub event Deposit(id: UInt64, to: Address?) | |
pub let CollectionStoragePath: StoragePath | |
pub let CollectionPublicPath: PublicPath | |
pub let MinterStoragePath: StoragePath | |
pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver { | |
pub let id: UInt64 | |
pub let name: String | |
pub let description: String | |
pub let thumbnail: String | |
access(self) let royalties: [MetadataViews.Royalty] | |
access(self) let metadata: {String: AnyStruct} | |
init( | |
id: UInt64, | |
name: String, | |
description: String, | |
thumbnail: String, | |
royalties: [MetadataViews.Royalty], | |
metadata: {String: AnyStruct}, | |
) { | |
self.id = id | |
self.name = name | |
self.description = description | |
self.thumbnail = thumbnail | |
self.royalties = royalties | |
self.metadata = metadata | |
} | |
pub 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>() | |
] | |
} | |
pub 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.id | |
) | |
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 MetadataViews.NFTCollectionData( | |
storagePath: ExampleNFT.CollectionStoragePath, | |
publicPath: ExampleNFT.CollectionPublicPath, | |
providerPath: /private/exampleNFTCollection, | |
publicCollection: Type<&ExampleNFT.Collection{ExampleNFT.ExampleNFTCollectionPublic}>(), | |
publicLinkedType: Type<&ExampleNFT.Collection{ExampleNFT.ExampleNFTCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Receiver,MetadataViews.ResolverCollection}>(), | |
providerLinkedType: Type<&ExampleNFT.Collection{ExampleNFT.ExampleNFTCollectionPublic,NonFungibleToken.CollectionPublic,NonFungibleToken.Provider,MetadataViews.ResolverCollection}>(), | |
createEmptyCollectionFunction: (fun (): @NonFungibleToken.Collection { | |
return <-ExampleNFT.createEmptyCollection() | |
}) | |
) | |
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.Traits>(): | |
// exclude mintedTime and foo to show other uses of Traits | |
let excludedTraits = ["mintedTime", "foo"] | |
let traitsView = MetadataViews.dictToTraits(dict: self.metadata, excludedNames: excludedTraits) | |
// mintedTime is a unix timestamp, we should mark it with a displayType so platforms know how to show it. | |
let mintedTimeTrait = MetadataViews.Trait(name: "mintedTime", value: self.metadata["mintedTime"]!, displayType: "Date", rarity: nil) | |
traitsView.addTrait(mintedTimeTrait) | |
// foo is a trait with its own rarity | |
let fooTraitRarity = MetadataViews.Rarity(score: 10.0, max: 100.0, description: "Common") | |
let fooTrait = MetadataViews.Trait(name: "foo", value: self.metadata["foo"], displayType: nil, rarity: fooTraitRarity) | |
traitsView.addTrait(fooTrait) | |
return traitsView | |
} | |
return nil | |
} | |
} | |
pub resource interface ExampleNFTCollectionPublic { | |
pub fun deposit(token: @NonFungibleToken.NFT) | |
pub fun getIDs(): [UInt64] | |
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT | |
pub fun borrowExampleNFT(id: UInt64): &ExampleNFT.NFT? { | |
post { | |
(result == nil) || (result?.id == id): | |
"Cannot borrow ExampleNFT reference: the ID of the returned reference is incorrect" | |
} | |
} | |
} | |
pub resource Collection: ExampleNFTCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection { | |
// dictionary of NFT conforming tokens | |
// NFT is a resource type with an `UInt64` ID field | |
pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT} | |
init () { | |
self.ownedNFTs <- {} | |
} | |
// withdraw removes an NFT from the collection and moves it to the caller | |
pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT { | |
let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT") | |
emit Withdraw(id: token.id, from: self.owner?.address) | |
return <-token | |
} | |
// deposit takes a NFT and adds it to the collections dictionary | |
// and adds the ID to the id array | |
pub fun deposit(token: @NonFungibleToken.NFT) { | |
let token <- token as! @ExampleNFT.NFT | |
let id: UInt64 = token.id | |
// add the new token to the dictionary which removes the old one | |
let oldToken <- self.ownedNFTs[id] <- token | |
emit Deposit(id: id, to: self.owner?.address) | |
destroy oldToken | |
} | |
// getIDs returns an array of the IDs that are in the collection | |
pub fun getIDs(): [UInt64] { | |
return self.ownedNFTs.keys | |
} | |
// borrowNFT gets a reference to an NFT in the collection | |
// so that the caller can read its metadata and call its methods | |
pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT { | |
return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)! | |
} | |
pub fun borrowExampleNFT(id: UInt64): &ExampleNFT.NFT? { | |
if self.ownedNFTs[id] != nil { | |
// Create an authorized reference to allow downcasting | |
let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)! | |
return ref as! &ExampleNFT.NFT | |
} | |
return nil | |
} | |
pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} { | |
let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)! | |
let exampleNFT = nft as! &ExampleNFT.NFT | |
return exampleNFT as &AnyResource{MetadataViews.Resolver} | |
} | |
destroy() { | |
destroy self.ownedNFTs | |
} | |
} | |
// public function that anyone can call to create a new empty collection | |
pub fun createEmptyCollection(): @NonFungibleToken.Collection { | |
return <- create Collection() | |
} | |
// Resource that an admin or something similar would own to be | |
// able to mint new NFTs | |
// | |
pub resource NFTMinter { | |
// mintNFT mints a new NFT with a new ID | |
// and deposit it in the recipients collection using their collection reference | |
pub fun mintNFT( | |
recipient: &{NonFungibleToken.CollectionPublic}, | |
name: String, | |
description: String, | |
thumbnail: String, | |
royalties: [MetadataViews.Royalty] | |
) { | |
let metadata: {String: AnyStruct} = {} | |
let currentBlock = getCurrentBlock() | |
metadata["mintedBlock"] = currentBlock.height | |
metadata["mintedTime"] = currentBlock.timestamp | |
metadata["minter"] = recipient.owner!.address | |
// this piece of metadata will be used to show embedding rarity into a trait | |
metadata["foo"] = "bar" | |
// create a new NFT | |
var newNFT <- create NFT( | |
id: ExampleNFT.totalSupply, | |
name: name, | |
description: description, | |
thumbnail: thumbnail, | |
royalties: royalties, | |
metadata: metadata, | |
) | |
// deposit it in the recipient's account using their reference | |
recipient.deposit(token: <-newNFT) | |
ExampleNFT.totalSupply = ExampleNFT.totalSupply + UInt64(1) | |
} | |
} | |
init() { | |
// Initialize the total supply | |
self.totalSupply = 0 | |
// Set the named paths | |
self.CollectionStoragePath = /storage/exampleNFTCollection | |
self.CollectionPublicPath = /public/exampleNFTCollection | |
self.MinterStoragePath = /storage/exampleNFTMinter | |
// Create a Collection resource and save it to storage | |
let collection <- create Collection() | |
self.account.save(<-collection, to: self.CollectionStoragePath) | |
// create a public capability for the collection | |
self.account.link<&ExampleNFT.Collection{NonFungibleToken.CollectionPublic, ExampleNFT.ExampleNFTCollectionPublic, MetadataViews.ResolverCollection}>( | |
self.CollectionPublicPath, | |
target: self.CollectionStoragePath | |
) | |
// Create a Minter resource and save it to storage | |
let minter <- create NFTMinter() | |
self.account.save(<-minter, to: self.MinterStoragePath) | |
emit ContractInitialized() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment