diff --git a/README.md b/README.md index 812267c..ee18376 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,8 @@ Inside `cadence` folder you will find: ## 👨‍💻 Start Developing After creating this project using the flow setup command you should then start the emulator by running: ``` -> flow emulator --contracts +> flow emulator ``` -_we use `--contracts` flag to include more already deployed contract we can then easily import in our project._ and then start the development command by running: ```shell diff --git a/cadence/contracts/standards/FungibleToken.cdc b/cadence/contracts/standards/FungibleToken.cdc new file mode 100644 index 0000000..48b092d --- /dev/null +++ b/cadence/contracts/standards/FungibleToken.cdc @@ -0,0 +1,237 @@ +/** + +# The Flow Fungible Token standard + +## `FungibleToken` contract interface + +The interface that all Fungible Token contracts would have to conform to. +If a users wants to deploy a new token contract, their contract +would need to implement the FungibleToken interface. + +Their contract would have to follow all the rules and naming +that the interface specifies. + +## `Vault` resource + +Each account that owns tokens would need to have an instance +of the Vault resource stored in their account storage. + +The Vault resource has methods that the owner and other users can call. + +## `Provider`, `Receiver`, and `Balance` resource interfaces + +These interfaces declare pre-conditions and post-conditions that restrict +the execution of the functions in the Vault. + +They are separate because it gives the user the ability to share +a reference to their Vault that only exposes the fields functions +in one or more of the interfaces. + +It also gives users the ability to make custom resources that implement +these interfaces to do various things with the tokens. +For example, a faucet can be implemented by conforming +to the Provider interface. + +By using resources and interfaces, users of Fungible Token contracts +can send and receive tokens peer-to-peer, without having to interact +with a central ledger smart contract. To send tokens to another user, +a user would simply withdraw the tokens from their Vault, then call +the deposit function on another user's Vault to complete the transfer. + +*/ + +/// The interface that Fungible Token contracts implement. +/// +pub contract interface FungibleToken { + + /// The total number of tokens in existence. + /// It is up to the implementer to ensure that the total supply + /// stays accurate and up to date + pub var totalSupply: UFix64 + + /// The event that is emitted when the contract is created + pub event TokensInitialized(initialSupply: UFix64) + + /// The event that is emitted when tokens are withdrawn from a Vault + pub event TokensWithdrawn(amount: UFix64, from: Address?) + + /// The event that is emitted when tokens are deposited into a Vault + pub event TokensDeposited(amount: UFix64, to: Address?) + + /// The interface that enforces the requirements for withdrawing + /// tokens from the implementing type. + /// + /// It does not enforce requirements on `balance` here, + /// because it leaves open the possibility of creating custom providers + /// that do not necessarily need their own balance. + /// + pub resource interface Provider { + + /// Subtracts tokens from the owner's Vault + /// and returns a Vault with the removed tokens. + /// + /// The function's access level is public, but this is not a problem + /// because only the owner storing the resource in their account + /// can initially call this function. + /// + /// The owner may grant other accounts access by creating a private + /// capability that allows specific other users to access + /// the provider resource through a reference. + /// + /// The owner may also grant all accounts access by creating a public + /// capability that allows all users to access the provider + /// resource through a reference. + /// + /// @param amount: The amount of tokens to be withdrawn from the vault + /// @return The Vault resource containing the withdrawn funds + /// + pub fun withdraw(amount: UFix64): @Vault { + post { + // `result` refers to the return value + result.balance == amount: + "Withdrawal amount must be the same as the balance of the withdrawn Vault" + } + } + } + + /// The interface that enforces the requirements for depositing + /// tokens into the implementing type. + /// + /// We do not include a condition that checks the balance because + /// we want to give users the ability to make custom receivers that + /// can do custom things with the tokens, like split them up and + /// send them to different places. + /// + pub resource interface Receiver { + + /// Takes a Vault and deposits it into the implementing resource type + /// + /// @param from: The Vault resource containing the funds that will be deposited + /// + pub fun deposit(from: @Vault) + + /// Below is referenced from the FLIP #69 https://github.com/onflow/flips/blob/main/flips/20230206-fungible-token-vault-type-discovery.md + /// + /// Returns the dictionary of Vault types that the the receiver is able to accept in its `deposit` method + /// this then it would return `{Type<@FlowToken.Vault>(): true}` and if any custom receiver + /// uses the default implementation then it would return empty dictionary as its parent + /// resource doesn't conform with the `FungibleToken.Vault` resource. + /// + /// Custom receiver implementations are expected to upgrade their contracts to add an implementation + /// that supports this method because it is very valuable for various applications to have. + /// + /// @return dictionary of supported deposit vault types by the implementing resource. + /// + pub fun getSupportedVaultTypes(): {Type: Bool} { + // Below check is implemented to make sure that run-time type would + // only get returned when the parent resource conforms with `FungibleToken.Vault`. + if self.getType().isSubtype(of: Type<@FungibleToken.Vault>()) { + return {self.getType(): true} + } else { + // Return an empty dictionary as the default value for resource who don't + // implement `FungibleToken.Vault`, such as `FungibleTokenSwitchboard`, `TokenForwarder` etc. + return {} + } + } + } + + /// The interface that contains the `balance` field of the Vault + /// and enforces that when new Vaults are created, the balance + /// is initialized correctly. + /// + pub resource interface Balance { + + /// The total balance of a vault + /// + pub var balance: UFix64 + + init(balance: UFix64) { + post { + self.balance == balance: + "Balance must be initialized to the initial balance" + } + } + + /// Function that returns all the Metadata Views implemented by a 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. + /// + pub fun getViews(): [Type] { + return [] + } + + /// Function that resolves a metadata view for this fungible token by type. + /// + /// @param view: The Type of the desired view. + /// @return A structure representing the requested view. + /// + pub fun resolveView(_ view: Type): AnyStruct? { + return nil + } + } + + /// The resource that contains the functions to send and receive tokens. + /// The declaration of a concrete type in a contract interface means that + /// every Fungible Token contract that implements the FungibleToken interface + /// must define a concrete `Vault` resource that conforms to the `Provider`, `Receiver`, + /// and `Balance` interfaces, and declares their required fields and functions + /// + pub resource Vault: Provider, Receiver, Balance { + + /// The total balance of the vault + pub var balance: UFix64 + + // The conforming type must declare an initializer + // that allows providing the initial balance of the Vault + // + init(balance: UFix64) + + /// Subtracts `amount` from the Vault's balance + /// and returns a new Vault with the subtracted balance + /// + /// @param amount: The amount of tokens to be withdrawn from the vault + /// @return The Vault resource containing the withdrawn funds + /// + pub fun withdraw(amount: UFix64): @Vault { + pre { + self.balance >= amount: + "Amount withdrawn must be less than or equal than the balance of the Vault" + } + post { + // use the special function `before` to get the value of the `balance` field + // at the beginning of the function execution + // + self.balance == before(self.balance) - amount: + "New Vault balance must be the difference of the previous balance and the withdrawn Vault" + } + } + + /// Takes a Vault and deposits it into the implementing resource type + /// + /// @param from: The Vault resource containing the funds that will be deposited + /// + pub fun deposit(from: @Vault) { + // Assert that the concrete type of the deposited vault is the same + // as the vault that is accepting the deposit + pre { + from.isInstance(self.getType()): + "Cannot deposit an incompatible token type" + } + post { + self.balance == before(self.balance) + before(from.balance): + "New Vault balance must be the sum of the previous balance and the deposited Vault" + } + } + } + + /// Allows any user to create a new Vault that has a zero balance + /// + /// @return The new Vault resource + /// + pub fun createEmptyVault(): @Vault { + post { + result.balance == 0.0: "The newly created Vault must have zero balance" + } + } +} diff --git a/cadence/contracts/standards/FungibleTokenMetadataViews.cdc b/cadence/contracts/standards/FungibleTokenMetadataViews.cdc new file mode 100644 index 0000000..d8dda1b --- /dev/null +++ b/cadence/contracts/standards/FungibleTokenMetadataViews.cdc @@ -0,0 +1,192 @@ +import FungibleToken from "FungibleToken" +import MetadataViews from "MetadataViews" + +/// This contract implements the metadata standard proposed +/// in FLIP-1087. +/// +/// Ref: https://github.com/onflow/flips/blob/main/application/20220811-fungible-tokens-metadata.md +/// +/// Structs and resources can implement one or more +/// metadata types, called views. Each view type represents +/// a different kind of metadata. +/// +pub contract FungibleTokenMetadataViews { + + /// FTView wraps FTDisplay and FTVaultData, and is used to give a complete + /// picture of a Fungible Token. Most Fungible Token contracts should + /// implement this view. + /// + pub struct FTView { + pub let ftDisplay: FTDisplay? + pub let ftVaultData: FTVaultData? + init( + ftDisplay: FTDisplay?, + ftVaultData: FTVaultData? + ) { + self.ftDisplay = ftDisplay + self.ftVaultData = ftVaultData + } + } + + /// Helper to get a FT view. + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A FTView struct + /// + pub fun getFTView(viewResolver: &{MetadataViews.Resolver}): FTView { + let maybeFTView = viewResolver.resolveView(Type()) + if let ftView = maybeFTView { + return ftView as! FTView + } + return FTView( + ftDisplay: self.getFTDisplay(viewResolver), + ftVaultData: self.getFTVaultData(viewResolver) + ) + } + + /// View to expose the information needed to showcase this FT. + /// This can be used by applications to give an overview and + /// graphics of the FT. + /// + pub struct FTDisplay { + /// The display name for this token. + /// + /// Example: "Flow" + /// + pub let name: String + + /// The abbreviated symbol for this token. + /// + /// Example: "FLOW" + pub let symbol: String + + /// A description the provides an overview of this token. + /// + /// Example: "The FLOW token is the native currency of the Flow network." + pub let description: String + + /// External link to a URL to view more information about the fungible token. + pub let externalURL: MetadataViews.ExternalURL + + /// One or more versions of the fungible token logo. + pub let logos: MetadataViews.Medias + + /// Social links to reach the fungible token's social homepages. + /// Possible keys may be "instagram", "twitter", "discord", etc. + pub let socials: {String: MetadataViews.ExternalURL} + + init( + name: String, + symbol: String, + description: String, + externalURL: MetadataViews.ExternalURL, + logos: MetadataViews.Medias, + socials: {String: MetadataViews.ExternalURL} + ) { + self.name = name + self.symbol = symbol + self.description = description + self.externalURL = externalURL + self.logos = logos + self.socials = socials + } + } + + /// Helper to get FTDisplay in a way that will return a typed optional. + /// + /// @param viewResolver: A reference to the resolver resource + /// @return An optional FTDisplay struct + /// + pub fun getFTDisplay(_ viewResolver: &{MetadataViews.Resolver}): FTDisplay? { + if let maybeDisplayView = viewResolver.resolveView(Type()) { + if let displayView = maybeDisplayView as? FTDisplay { + return displayView + } + } + return nil + } + + /// View to expose the information needed store and interact with a FT vault. + /// This can be used by applications to setup a FT vault with proper + /// storage and public capabilities. + /// + pub struct FTVaultData { + /// Path in storage where this FT vault is recommended to be stored. + pub let storagePath: StoragePath + + /// Public path which must be linked to expose the public receiver capability. + pub let receiverPath: PublicPath + + /// Public path which must be linked to expose the balance and resolver public capabilities. + pub let metadataPath: PublicPath + + /// Private path which should be linked to expose the provider capability to withdraw funds + /// from the vault. + pub let providerPath: PrivatePath + + /// Type that should be linked at the `receiverPath`. This is a restricted type requiring + /// the `FungibleToken.Receiver` interface. + pub let receiverLinkedType: Type + + /// Type that should be linked at the `receiverPath`. This is a restricted type requiring + /// the `FungibleToken.Balance` and `MetadataViews.Resolver` interfaces. + pub let metadataLinkedType: Type + + /// Type that should be linked at the aforementioned private path. This + /// is normally a restricted type with at a minimum the `FungibleToken.Provider` interface. + pub let providerLinkedType: Type + + /// Function that allows creation of an empty FT vault that is intended + /// to store the funds. + pub let createEmptyVault: ((): @FungibleToken.Vault) + + init( + storagePath: StoragePath, + receiverPath: PublicPath, + metadataPath: PublicPath, + providerPath: PrivatePath, + receiverLinkedType: Type, + metadataLinkedType: Type, + providerLinkedType: Type, + createEmptyVaultFunction: ((): @FungibleToken.Vault) + ) { + pre { + receiverLinkedType.isSubtype(of: Type<&{FungibleToken.Receiver}>()): "Receiver public type must include FungibleToken.Receiver." + metadataLinkedType.isSubtype(of: Type<&{FungibleToken.Balance, MetadataViews.Resolver}>()): "Metadata public type must include FungibleToken.Balance and MetadataViews.Resolver interfaces." + providerLinkedType.isSubtype(of: Type<&{FungibleToken.Provider}>()): "Provider type must include FungibleToken.Provider interface." + } + self.storagePath = storagePath + self.receiverPath = receiverPath + self.metadataPath = metadataPath + self.providerPath = providerPath + self.receiverLinkedType = receiverLinkedType + self.metadataLinkedType = metadataLinkedType + self.providerLinkedType = providerLinkedType + self.createEmptyVault = createEmptyVaultFunction + } + } + + /// Helper to get FTVaultData in a way that will return a typed Optional. + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional FTVaultData struct + /// + pub fun getFTVaultData(_ viewResolver: &{MetadataViews.Resolver}): FTVaultData? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? FTVaultData { + return v + } + } + return nil + } + + /// View to expose the total supply of the Vault's token + access(all) struct TotalSupply { + access(all) let supply: UFix64 + + init(totalSupply: UFix64) { + self.supply = totalSupply + } + } +} + \ No newline at end of file diff --git a/cadence/contracts/standards/MetadataViews.cdc b/cadence/contracts/standards/MetadataViews.cdc new file mode 100644 index 0000000..8857efb --- /dev/null +++ b/cadence/contracts/standards/MetadataViews.cdc @@ -0,0 +1,740 @@ +import FungibleToken from "FungibleToken" +import NonFungibleToken from "NonFungibleToken" + +/// This contract implements the metadata standard proposed +/// in FLIP-0636. +/// +/// Ref: https://github.com/onflow/flips/blob/main/application/20210916-nft-metadata.md +/// +/// Structs and resources can implement one or more +/// metadata types, called views. Each view type represents +/// a different kind of metadata, such as a creator biography +/// or a JPEG image file. +/// +pub contract MetadataViews { + + /// Provides access to a set of metadata views. A struct or + /// resource (e.g. an NFT) can implement this interface to provide access to + /// the views that it supports. + /// + pub resource interface Resolver { + pub fun getViews(): [Type] + pub fun resolveView(_ view: Type): AnyStruct? + } + + /// A group of view resolvers indexed by ID. + /// + pub resource interface ResolverCollection { + pub fun borrowViewResolver(id: UInt64): &{Resolver} + pub fun getIDs(): [UInt64] + } + + /// NFTView wraps all Core views along `id` and `uuid` fields, and is used + /// to give a complete picture of an NFT. Most NFTs should implement this + /// view. + /// + pub struct NFTView { + pub let id: UInt64 + pub let uuid: UInt64 + pub let display: Display? + pub let externalURL: ExternalURL? + pub let collectionData: NFTCollectionData? + pub let collectionDisplay: NFTCollectionDisplay? + pub let royalties: Royalties? + pub let traits: Traits? + + init( + id : UInt64, + uuid : UInt64, + display : Display?, + externalURL : ExternalURL?, + collectionData : NFTCollectionData?, + collectionDisplay : NFTCollectionDisplay?, + royalties : Royalties?, + traits: Traits? + ) { + self.id = id + self.uuid = uuid + self.display = display + self.externalURL = externalURL + self.collectionData = collectionData + self.collectionDisplay = collectionDisplay + self.royalties = royalties + self.traits = traits + } + } + + /// Helper to get an NFT view + /// + /// @param id: The NFT id + /// @param viewResolver: A reference to the resolver resource + /// @return A NFTView struct + /// + pub fun getNFTView(id: UInt64, viewResolver: &{Resolver}) : NFTView { + let nftView = viewResolver.resolveView(Type()) + if nftView != nil { + return nftView! as! NFTView + } + + return NFTView( + id : id, + uuid: viewResolver.uuid, + display: self.getDisplay(viewResolver), + externalURL : self.getExternalURL(viewResolver), + collectionData : self.getNFTCollectionData(viewResolver), + collectionDisplay : self.getNFTCollectionDisplay(viewResolver), + royalties : self.getRoyalties(viewResolver), + traits : self.getTraits(viewResolver) + ) + } + + /// Display is a basic view that includes the name, description and + /// thumbnail for an object. Most objects should implement this view. + /// + pub struct Display { + + /// The name of the object. + /// + /// This field will be displayed in lists and therefore should + /// be short an concise. + /// + pub let name: String + + /// A written description of the object. + /// + /// This field will be displayed in a detailed view of the object, + /// so can be more verbose (e.g. a paragraph instead of a single line). + /// + pub let description: String + + /// A small thumbnail representation of the object. + /// + /// This field should be a web-friendly file (i.e JPEG, PNG) + /// that can be displayed in lists, link previews, etc. + /// + pub let thumbnail: AnyStruct{File} + + init( + name: String, + description: String, + thumbnail: AnyStruct{File} + ) { + self.name = name + self.description = description + self.thumbnail = thumbnail + } + } + + /// Helper to get Display in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return An optional Display struct + /// + pub fun getDisplay(_ viewResolver: &{Resolver}) : Display? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Display { + return v + } + } + return nil + } + + /// Generic interface that represents a file stored on or off chain. Files + /// can be used to references images, videos and other media. + /// + pub struct interface File { + pub fun uri(): String + } + + /// View to expose a file that is accessible at an HTTP (or HTTPS) URL. + /// + pub struct HTTPFile: File { + pub let url: String + + init(url: String) { + self.url = url + } + + pub fun uri(): String { + return self.url + } + } + + /// View to expose a file stored on IPFS. + /// IPFS images are referenced by their content identifier (CID) + /// rather than a direct URI. A client application can use this CID + /// to find and load the image via an IPFS gateway. + /// + pub struct IPFSFile: File { + + /// CID is the content identifier for this IPFS file. + /// + /// Ref: https://docs.ipfs.io/concepts/content-addressing/ + /// + pub let cid: String + + /// Path is an optional path to the file resource in an IPFS directory. + /// + /// This field is only needed if the file is inside a directory. + /// + /// Ref: https://docs.ipfs.io/concepts/file-systems/ + /// + pub let path: String? + + init(cid: String, path: String?) { + self.cid = cid + self.path = path + } + + /// This function returns the IPFS native URL for this file. + /// Ref: https://docs.ipfs.io/how-to/address-ipfs-on-web/#native-urls + /// + /// @return The string containing the file uri + /// + pub fun uri(): String { + if let path = self.path { + return "ipfs://".concat(self.cid).concat("/").concat(path) + } + + return "ipfs://".concat(self.cid) + } + } + + /// Optional view for collections that issue multiple objects + /// with the same or similar metadata, for example an X of 100 set. This + /// information is useful for wallets and marketplaces. + /// An NFT might be part of multiple editions, which is why the edition + /// information is returned as an arbitrary sized array + /// + pub struct Edition { + + /// The name of the edition + /// For example, this could be Set, Play, Series, + /// or any other way a project could classify its editions + pub let name: String? + + /// The edition number of the object. + /// For an "24 of 100 (#24/100)" item, the number is 24. + pub let number: UInt64 + + /// The max edition number of this type of objects. + /// This field should only be provided for limited-editioned objects. + /// For an "24 of 100 (#24/100)" item, max is 100. + /// For an item with unlimited edition, max should be set to nil. + /// + pub let max: UInt64? + + init(name: String?, number: UInt64, max: UInt64?) { + if max != nil { + assert(number <= max!, message: "The number cannot be greater than the max number!") + } + self.name = name + self.number = number + self.max = max + } + } + + /// Wrapper view for multiple Edition views + /// + pub struct Editions { + + /// An arbitrary-sized list for any number of editions + /// that the NFT might be a part of + pub let infoList: [Edition] + + init(_ infoList: [Edition]) { + self.infoList = infoList + } + } + + /// Helper to get Editions in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return An optional Editions struct + /// + pub fun getEditions(_ viewResolver: &{Resolver}) : Editions? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Editions { + return v + } + } + return nil + } + + /// View representing a project-defined serial number for a specific NFT + /// Projects have different definitions for what a serial number should be + /// Some may use the NFTs regular ID and some may use a different + /// classification system. The serial number is expected to be unique among + /// other NFTs within that project + /// + pub struct Serial { + pub let number: UInt64 + + init(_ number: UInt64) { + self.number = number + } + } + + /// Helper to get Serial in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return An optional Serial struct + /// + pub fun getSerial(_ viewResolver: &{Resolver}) : Serial? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Serial { + return v + } + } + return nil + } + + /// View that defines the composable royalty standard that gives marketplaces a + /// unified interface to support NFT royalties. + /// + pub struct Royalty { + + /// Generic FungibleToken Receiver for the beneficiary of the royalty + /// Can get the concrete type of the receiver with receiver.getType() + /// Recommendation - Users should create a new link for a FlowToken + /// receiver for this using `getRoyaltyReceiverPublicPath()`, and not + /// use the default FlowToken receiver. This will allow users to update + /// the capability in the future to use a more generic capability + pub let receiver: Capability<&AnyResource{FungibleToken.Receiver}> + + /// Multiplier used to calculate the amount of sale value transferred to + /// royalty receiver. Note - It should be between 0.0 and 1.0 + /// Ex - If the sale value is x and multiplier is 0.56 then the royalty + /// value would be 0.56 * x. + /// Generally percentage get represented in terms of basis points + /// in solidity based smart contracts while cadence offers `UFix64` + /// that already supports the basis points use case because its + /// operations are entirely deterministic integer operations and support + /// up to 8 points of precision. + pub let cut: UFix64 + + /// Optional description: This can be the cause of paying the royalty, + /// the relationship between the `wallet` and the NFT, or anything else + /// that the owner might want to specify. + pub let description: String + + init(receiver: Capability<&AnyResource{FungibleToken.Receiver}>, cut: UFix64, description: String) { + pre { + cut >= 0.0 && cut <= 1.0 : "Cut value should be in valid range i.e [0,1]" + } + self.receiver = receiver + self.cut = cut + self.description = description + } + } + + /// Wrapper view for multiple Royalty views. + /// Marketplaces can query this `Royalties` struct from NFTs + /// and are expected to pay royalties based on these specifications. + /// + pub struct Royalties { + + /// Array that tracks the individual royalties + access(self) let cutInfos: [Royalty] + + pub init(_ cutInfos: [Royalty]) { + // Validate that sum of all cut multipliers should not be greater than 1.0 + var totalCut = 0.0 + for royalty in cutInfos { + totalCut = totalCut + royalty.cut + } + assert(totalCut <= 1.0, message: "Sum of cutInfos multipliers should not be greater than 1.0") + // Assign the cutInfos + self.cutInfos = cutInfos + } + + /// Return the cutInfos list + /// + /// @return An array containing all the royalties structs + /// + pub fun getRoyalties(): [Royalty] { + return self.cutInfos + } + } + + /// Helper to get Royalties in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional Royalties struct + /// + pub fun getRoyalties(_ viewResolver: &{Resolver}) : Royalties? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Royalties { + return v + } + } + return nil + } + + /// Get the path that should be used for receiving royalties + /// This is a path that will eventually be used for a generic switchboard receiver, + /// hence the name but will only be used for royalties for now. + /// + /// @return The PublicPath for the generic FT receiver + /// + pub fun getRoyaltyReceiverPublicPath(): PublicPath { + return /public/GenericFTReceiver + } + + /// View to represent, a file with an correspoiding mediaType. + /// + pub struct Media { + + /// File for the media + /// + pub let file: AnyStruct{File} + + /// media-type comes on the form of type/subtype as described here + /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types + /// + pub let mediaType: String + + init(file: AnyStruct{File}, mediaType: String) { + self.file=file + self.mediaType=mediaType + } + } + + /// Wrapper view for multiple media views + /// + pub struct Medias { + + /// An arbitrary-sized list for any number of Media items + pub let items: [Media] + + init(_ items: [Media]) { + self.items = items + } + } + + /// Helper to get Medias in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional Medias struct + /// + pub fun getMedias(_ viewResolver: &{Resolver}) : Medias? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Medias { + return v + } + } + return nil + } + + /// View to represent a license according to https://spdx.org/licenses/ + /// This view can be used if the content of an NFT is licensed. + /// + pub struct License { + pub let spdxIdentifier: String + + init(_ identifier: String) { + self.spdxIdentifier = identifier + } + } + + /// Helper to get License in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional License struct + /// + pub fun getLicense(_ viewResolver: &{Resolver}) : License? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? License { + return v + } + } + return nil + } + + /// View to expose a URL to this item on an external site. + /// This can be used by applications like .find and Blocto to direct users + /// to the original link for an NFT or a project page that describes the NFT collection. + /// eg https://www.my-nft-project.com/overview-of-nft-collection + /// + pub struct ExternalURL { + pub let url: String + + init(_ url: String) { + self.url=url + } + } + + /// Helper to get ExternalURL in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional ExternalURL struct + /// + pub fun getExternalURL(_ viewResolver: &{Resolver}) : ExternalURL? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? ExternalURL { + return v + } + } + return nil + } + + /// View to expose the information needed store and retrieve an NFT. + /// This can be used by applications to setup a NFT collection with proper + /// storage and public capabilities. + /// + pub struct NFTCollectionData { + /// Path in storage where this NFT is recommended to be stored. + pub let storagePath: StoragePath + + /// Public path which must be linked to expose public capabilities of this NFT + /// including standard NFT interfaces and metadataviews interfaces + pub let publicPath: PublicPath + + /// Private path which should be linked to expose the provider + /// capability to withdraw NFTs from the collection holding NFTs + pub let providerPath: PrivatePath + + /// Public collection type that is expected to provide sufficient read-only access to standard + /// functions (deposit + getIDs + borrowNFT) + /// This field is for backwards compatibility with collections that have not used the standard + /// NonFungibleToken.CollectionPublic interface when setting up collections. For new + /// collections, this may be set to be equal to the type specified in `publicLinkedType`. + pub let publicCollection: Type + + /// Type that should be linked at the aforementioned public path. This is normally a + /// restricted type with many interfaces. Notably the `NFT.CollectionPublic`, + /// `NFT.Receiver`, and `MetadataViews.ResolverCollection` interfaces are required. + pub let publicLinkedType: Type + + /// Type that should be linked at the aforementioned private path. This is normally + /// a restricted type with at a minimum the `NFT.Provider` interface + pub let providerLinkedType: Type + + /// Function that allows creation of an empty NFT collection that is intended to store + /// this NFT. + pub let createEmptyCollection: ((): @NonFungibleToken.Collection) + + init( + storagePath: StoragePath, + publicPath: PublicPath, + providerPath: PrivatePath, + publicCollection: Type, + publicLinkedType: Type, + providerLinkedType: Type, + createEmptyCollectionFunction: ((): @NonFungibleToken.Collection) + ) { + pre { + publicLinkedType.isSubtype(of: Type<&{NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, MetadataViews.ResolverCollection}>()): "Public type must include NonFungibleToken.CollectionPublic, NonFungibleToken.Receiver, and MetadataViews.ResolverCollection interfaces." + providerLinkedType.isSubtype(of: Type<&{NonFungibleToken.Provider, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection}>()): "Provider type must include NonFungibleToken.Provider, NonFungibleToken.CollectionPublic, and MetadataViews.ResolverCollection interface." + } + self.storagePath=storagePath + self.publicPath=publicPath + self.providerPath = providerPath + self.publicCollection=publicCollection + self.publicLinkedType=publicLinkedType + self.providerLinkedType = providerLinkedType + self.createEmptyCollection=createEmptyCollectionFunction + } + } + + /// Helper to get NFTCollectionData in a way that will return an typed Optional + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional NFTCollectionData struct + /// + pub fun getNFTCollectionData(_ viewResolver: &{Resolver}) : NFTCollectionData? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? NFTCollectionData { + return v + } + } + return nil + } + + /// View to expose the information needed to showcase this NFT's + /// collection. This can be used by applications to give an overview and + /// graphics of the NFT collection this NFT belongs to. + /// + pub struct NFTCollectionDisplay { + // Name that should be used when displaying this NFT collection. + pub let name: String + + // Description that should be used to give an overview of this collection. + pub let description: String + + // External link to a URL to view more information about this collection. + pub let externalURL: ExternalURL + + // Square-sized image to represent this collection. + pub let squareImage: Media + + // Banner-sized image for this collection, recommended to have a size near 1200x630. + pub let bannerImage: Media + + // Social links to reach this collection's social homepages. + // Possible keys may be "instagram", "twitter", "discord", etc. + pub let socials: {String: ExternalURL} + + init( + name: String, + description: String, + externalURL: ExternalURL, + squareImage: Media, + bannerImage: Media, + socials: {String: ExternalURL} + ) { + self.name = name + self.description = description + self.externalURL = externalURL + self.squareImage = squareImage + self.bannerImage = bannerImage + self.socials = socials + } + } + + /// Helper to get NFTCollectionDisplay in a way that will return a typed + /// Optional + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional NFTCollection struct + /// + pub fun getNFTCollectionDisplay(_ viewResolver: &{Resolver}) : NFTCollectionDisplay? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? NFTCollectionDisplay { + return v + } + } + return nil + } + + /// View to expose rarity information for a single rarity + /// Note that a rarity needs to have either score or description but it can + /// have both + /// + pub struct Rarity { + /// The score of the rarity as a number + pub let score: UFix64? + + /// The maximum value of score + pub let max: UFix64? + + /// The description of the rarity as a string. + /// + /// This could be Legendary, Epic, Rare, Uncommon, Common or any other string value + pub let description: String? + + init(score: UFix64?, max: UFix64?, description: String?) { + if score == nil && description == nil { + panic("A Rarity needs to set score, description or both") + } + + self.score = score + self.max = max + self.description = description + } + } + + /// Helper to get Rarity view in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional Rarity struct + /// + pub fun getRarity(_ viewResolver: &{Resolver}) : Rarity? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Rarity { + return v + } + } + return nil + } + + /// View to represent a single field of metadata on an NFT. + /// This is used to get traits of individual key/value pairs along with some + /// contextualized data about the trait + /// + pub struct Trait { + // The name of the trait. Like Background, Eyes, Hair, etc. + pub let name: String + + // The underlying value of the trait, the rest of the fields of a trait provide context to the value. + pub let value: AnyStruct + + // displayType is used to show some context about what this name and value represent + // for instance, you could set value to a unix timestamp, and specify displayType as "Date" to tell + // platforms to consume this trait as a date and not a number + pub let displayType: String? + + // Rarity can also be used directly on an attribute. + // + // This is optional because not all attributes need to contribute to the NFT's rarity. + pub let rarity: Rarity? + + init(name: String, value: AnyStruct, displayType: String?, rarity: Rarity?) { + self.name = name + self.value = value + self.displayType = displayType + self.rarity = rarity + } + } + + /// Wrapper view to return all the traits on an NFT. + /// This is used to return traits as individual key/value pairs along with + /// some contextualized data about each trait. + pub struct Traits { + pub let traits: [Trait] + + init(_ traits: [Trait]) { + self.traits = traits + } + + /// Adds a single Trait to the Traits view + /// + /// @param Trait: The trait struct to be added + /// + pub fun addTrait(_ t: Trait) { + self.traits.append(t) + } + } + + /// Helper to get Traits view in a typesafe way + /// + /// @param viewResolver: A reference to the resolver resource + /// @return A optional Traits struct + /// + pub fun getTraits(_ viewResolver: &{Resolver}) : Traits? { + if let view = viewResolver.resolveView(Type()) { + if let v = view as? Traits { + return v + } + } + return nil + } + + /// Helper function to easily convert a dictionary to traits. For NFT + /// collections that do not need either of the optional values of a Trait, + /// this method should suffice to give them an array of valid traits. + /// + /// @param dict: The dictionary to be converted to Traits + /// @param excludedNames: An optional String array specifying the `dict` + /// keys that are not wanted to become `Traits` + /// @return The generated Traits view + /// + pub fun dictToTraits(dict: {String: AnyStruct}, excludedNames: [String]?): Traits { + // Collection owners might not want all the fields in their metadata included. + // They might want to handle some specially, or they might just not want them included at all. + if excludedNames != nil { + for k in excludedNames! { + dict.remove(key: k) + } + } + + let traits: [Trait] = [] + for k in dict.keys { + let trait = Trait(name: k, value: dict[k]!, displayType: nil, rarity: nil) + traits.append(trait) + } + + return Traits(traits) + } + +} diff --git a/cadence/contracts/standards/NonFungibleToken.cdc b/cadence/contracts/standards/NonFungibleToken.cdc new file mode 100644 index 0000000..5ebd8fc --- /dev/null +++ b/cadence/contracts/standards/NonFungibleToken.cdc @@ -0,0 +1,202 @@ +/** + +## The Flow Non-Fungible Token standard + +## `NonFungibleToken` contract interface + +The interface that all Non-Fungible Token contracts could conform to. +If a user wants to deploy a new NFT contract, their contract would need +to implement the NonFungibleToken interface. + +Their contract would have to follow all the rules and naming +that the interface specifies. + +## `NFT` resource + +The core resource type that represents an NFT in the smart contract. + +## `Collection` Resource + +The resource that stores a user's NFT collection. +It includes a few functions to allow the owner to easily +move tokens in and out of the collection. + +## `Provider` and `Receiver` resource interfaces + +These interfaces declare functions with some pre and post conditions +that require the Collection to follow certain naming and behavior standards. + +They are separate because it gives the user the ability to share a reference +to their Collection that only exposes the fields and functions in one or more +of the interfaces. It also gives users the ability to make custom resources +that implement these interfaces to do various things with the tokens. + +By using resources and interfaces, users of NFT smart contracts can send +and receive tokens peer-to-peer, without having to interact with a central ledger +smart contract. + +To send an NFT to another user, a user would simply withdraw the NFT +from their Collection, then call the deposit function on another user's +Collection to complete the transfer. + +*/ + +/// The main NFT contract interface. Other NFT contracts will +/// import and implement this interface +/// +pub contract interface NonFungibleToken { + + /// The total number of tokens of this type in existence + pub var totalSupply: UInt64 + + /// Event that emitted when the NFT contract is initialized + /// + pub event ContractInitialized() + + /// Event that is emitted when a token is withdrawn, + /// indicating the owner of the collection that it was withdrawn from. + /// + /// If the collection is not in an account's storage, `from` will be `nil`. + /// + pub event Withdraw(id: UInt64, from: Address?) + + /// Event that emitted when a token is deposited to a collection. + /// + /// It indicates the owner of the collection that it was deposited to. + /// + pub event Deposit(id: UInt64, to: Address?) + + /// Interface that the NFTs have to conform to + /// The metadata views methods are included here temporarily + /// because enforcing the metadata interfaces in the standard + /// would break many contracts in an upgrade. Those breaking changes + /// are being saved for the stable cadence milestone + /// + pub resource interface INFT { + /// The unique ID that each NFT has + pub let id: UInt64 + + /// 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. + /// + pub fun getViews(): [Type] { + return [] + } + + /// Function that resolves a metadata view for this token. + /// + /// @param view: The Type of the desired view. + /// @return A structure representing the requested view. + /// + pub fun resolveView(_ view: Type): AnyStruct? { + return nil + } + } + + /// Requirement that all conforming NFT smart contracts have + /// to define a resource called NFT that conforms to INFT + /// + pub resource NFT: INFT { + pub let id: UInt64 + } + + /// Interface to mediate withdraws from the Collection + /// + pub resource interface Provider { + /// Removes an NFT from the resource implementing it and moves it to the caller + /// + /// @param withdrawID: The ID of the NFT that will be removed + /// @return The NFT resource removed from the implementing resource + /// + pub fun withdraw(withdrawID: UInt64): @NFT { + post { + result.id == withdrawID: "The ID of the withdrawn token must be the same as the requested ID" + } + } + } + + /// Interface to mediate deposits to the Collection + /// + pub resource interface Receiver { + + /// Adds an NFT to the resource implementing it + /// + /// @param token: The NFT resource that will be deposited + /// + pub fun deposit(token: @NFT) + } + + /// Interface that an account would commonly + /// publish for their collection + /// + pub resource interface CollectionPublic { + pub fun deposit(token: @NFT) + pub fun getIDs(): [UInt64] + pub fun borrowNFT(id: UInt64): &NFT + /// Safe way to borrow a reference to an NFT that does not panic + /// + /// @param id: The ID of the NFT that want to be borrowed + /// @return An optional reference to the desired NFT, will be nil if the passed id does not exist + /// + pub fun borrowNFTSafe(id: UInt64): &NFT? { + post { + result == nil || result!.id == id: "The returned reference's ID does not match the requested ID" + } + return nil + } + } + + /// Requirement for the concrete resource type + /// to be declared in the implementing contract + /// + pub resource Collection: Provider, Receiver, CollectionPublic { + + /// Dictionary to hold the NFTs in the Collection + pub var ownedNFTs: @{UInt64: NFT} + + /// Removes an NFT from the collection and moves it to the caller + /// + /// @param withdrawID: The ID of the NFT that will be withdrawn + /// @return The resource containing the desired NFT + /// + pub fun withdraw(withdrawID: UInt64): @NFT + + /// Takes a NFT and adds it to the collections dictionary + /// and adds the ID to the ID array + /// + /// @param token: An NFT resource + /// + pub fun deposit(token: @NFT) + + /// Returns an array of the IDs that are in the collection + /// + /// @return An array containing all the IDs on the collection + /// + pub fun getIDs(): [UInt64] + + /// Returns a borrowed reference to an NFT in the collection + /// so that the caller can read data and call methods from it + /// + /// @param id: The ID of the NFT that want to be borrowed + /// @return A reference to the NFT + /// + pub fun borrowNFT(id: UInt64): &NFT { + pre { + self.ownedNFTs[id] != nil: "NFT does not exist in the collection!" + } + } + } + + /// Creates an empty Collection and returns it to the caller so that they can own NFTs + /// + /// @return A new Collection resource + /// + pub fun createEmptyCollection(): @Collection { + post { + result.getIDs().length == 0: "The created collection must be empty!" + } + } +} + \ No newline at end of file diff --git a/flow.json b/flow.json index fbcd410..b6570e7 100644 --- a/flow.json +++ b/flow.json @@ -2,24 +2,45 @@ "contracts": { "ExampleNFT": "cadence/contracts/exampleNFT/ExampleNFT.cdc", "FungibleToken": { - "source": "", + "source": "cadence/contracts/standards/FungibleToken.cdc", "aliases": { - "emulator": "ee82856bf20e2aa6" + "emulator": "ee82856bf20e2aa6", + "mainnet": "f233dcee88fe0abe", + "testnet": "9a0766d93b6608b7" + } + }, + "FungibleTokenMetadataViews": { + "source": "cadence/contracts/standards/FungibleTokenMetadataViews.cdc", + "aliases": { + "emulator": "ee82856bf20e2aa6", + "mainnet": "f233dcee88fe0abe", + "testnet": "9a0766d93b6608b7" } }, "MetadataViews": { - "source": "", + "source": "cadence/contracts/standards/MetadataViews.cdc", "aliases": { - "emulator": "f8d6e0586b0a20c7" + "emulator": "f8d6e0586b0a20c7", + "mainnet": "1d7e57aa55817448", + "testnet": "631e88ae7f1d7c20" } }, "NonFungibleToken": { - "source": "", + "source": "cadence/contracts/standards/NonFungibleToken.cdc", "aliases": { - "emulator": "f8d6e0586b0a20c7" + "emulator": "f8d6e0586b0a20c7", + "mainnet": "1d7e57aa55817448", + "testnet": "631e88ae7f1d7c20" } }, - "ViewResolver": "cadence/contracts/standards/ViewResolver.cdc" + "ViewResolver": { + "source": "cadence/contracts/standards/ViewResolver.cdc", + "aliases": { + "emulator": "f8d6e0586b0a20c7", + "mainnet": "1d7e57aa55817448", + "testnet": "631e88ae7f1d7c20" + } + } }, "networks": { "emulator": "127.0.0.1:3569", @@ -29,20 +50,20 @@ }, "accounts": { "default": { - "address": "f669cb8d41ce0c74", - "key": "2e09fac1b3b8e128c67b2e57ba59ec8506ff8326053812135a954b4d3291096f" + "address": "01cf0e2f2f715450", + "key": "d2c3686da84d61c13627bdf2127866fe358165734f5470be792e6771901d2856" }, "emulator-account": { "address": "f8d6e0586b0a20c7", "key": "d2c3686da84d61c13627bdf2127866fe358165734f5470be792e6771901d2856" }, "exampleNFT": { - "address": "192440c99cb17282", - "key": "58cfc56dcbbfa8bfb7b9898586793b0710dfebe8a0358774044968abc9acc8c2" + "address": "f3fcd2c1a78f5eee", + "key": "d2c3686da84d61c13627bdf2127866fe358165734f5470be792e6771901d2856" }, "standards": { - "address": "fd43f9148d4b725d", - "key": "28946711cc802634ca4520c0f8b113e065c519dc02306a05ecb204a896ee80ed" + "address": "179b6b1cb6755e31", + "key": "d2c3686da84d61c13627bdf2127866fe358165734f5470be792e6771901d2856" } }, "deployments": { @@ -51,9 +72,7 @@ "exampleNFT": [ "ExampleNFT" ], - "standards": [ - "ViewResolver" - ] + "standards": [] } } } \ No newline at end of file