From 4ab8d7aa098aeaa8d7cf988bc1c35723e55b15d3 Mon Sep 17 00:00:00 2001 From: Josh Hannan Date: Fri, 16 Dec 2022 16:11:27 -0600 Subject: [PATCH 1/5] first draft of nftv2 FLIP --- application/20221219-nft-v2.md | 348 +++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 application/20221219-nft-v2.md diff --git a/application/20221219-nft-v2.md b/application/20221219-nft-v2.md new file mode 100644 index 00000000..1833ff67 --- /dev/null +++ b/application/20221219-nft-v2.md @@ -0,0 +1,348 @@ +--- +status: draft +flip: NNN (do not set) +authors: Joshua Hannan (joshua.hannan@dapperlabs.com) +sponsor: Joshua Hannan (joshua.hannan@dapperlabs.com) +updated: 2022-12-19 +--- + +# Non-Fungible Token Standard V2 + +## Objective + +This FLIP proposes multiple updates to the Flow Non-Fungible Token Standard contracts, +primarily about encapsulating functionality within token resources instead of +with the contracts. There are other smaller quality of life changes to the token standard, +such as integration of metadata, improving error handling, +and including a transfer method and interface. +Some of these changes are dependent on other Cadence FLIPs being approved, +primarily [interface inheritance](https://github.com/onflow/flips/pull/40), +removal of nested type requirements, +and [allowing interfaces to emit events](https://github.com/onflow/cadence/issues/2069). + +Most of the changes proposed here would be breaking for all non-fungible token implementations +on the Flow blockchain, but should not be for third-party integrations such as +event listeners and apps that interface with the contracts. + +## Motivation + +The current non-fungible token standard for Flow +was designed in mid 2019, at a time when Cadence itself was still being designed. +The current standard, though functional, leaves much to be desired. + +The current token standard uses contract interfaces. +They are designed in a way which requires each concrete contract +to provide exactly one `NFT` type and one `Collection` type. +This means that any project that needs multiple tokens must deploy multiple contracts. +In the case of very simple tokens, this is a lot of complexity for very little value. + +Related to this problem, functionality and metadata associated with some tokens, +such as paths, events, and empty collection creation methods, +is only accessible directly through the contract itself, when it should also +be accessible directly through an instance of a token resource and/or interface. + +In the case of events, currently there is no way for the standard to ensure that +implementations are emitting standardized and correct events, which this upgrade will address. + +Additionally, the usage of nested type requirements creates some confusing +interactions, which are not useful and make learning about +and interacting with token smart contracts more difficult. + +## User Benefit + +With these upgrades, users will now be able to: +* Define multiple tokens in a single contract +* Query all the data about a token directly through the token resource and core interfaces +* Have standard events emitted correctly for important operations + +## Design Proposal + +[The original proposal](https://forum.onflow.org/t/streamlined-token-standards-proposal/3075/1) +is on the Flow Forum. +A [pull request with the suggested code changes](https://github.com/onflow/flow-nft/pull/126) +is in the flow fungible token github repository. + +The main code changes and their implications are described here. +The linked proposals provide more context. + +### Move Event Definitions and emissions to resource interfaces + +Instead of requiring events to be defined in the token implementations contracts, +they will only be defined in the non-fungible token standard smart contract and will +be emitted in post-conditions from the correct methods +in the resource interfaces defined in the standard. + +This feature is still being designed, so there is not a code sample yet. +See https://github.com/onflow/cadence/issues/2069 for potential examples + +### Add Type and Metadata parameters to events + +Standard events contain more information about the NFT that is being transferred, +such as the type of the NFT and important metadata about the NFT. + +Here is a simple example of what this could look like: + +```cadence +pub event Deposit(id: UInt64, to: Address?, type: Type, displayView: MetadataViews.Display?, serialView: MetadataViews.Serial?) +``` +Or a more involved example: (from [find](https://f.dnz.dev/4fd2a0ccdc3a6cdb2774872b4ac5b32c167e60ae5850ba2b34ef4a749433fe4e)): + +``` +from: 0x46625f59708ec2f8 +fromName: null +to: 0x1eef2d5df9d6dd7a +toName: null +uuid: "122520380" +nftInfo: + id: "122520380" + name: "Alphonso Davies - CH01P01 #308" + thumbnail: "ipfs://bafybeidfyk5boobk3n767gz3dc3sma2do6b43k7qsu3s6dlob4h7mmnb2y" + type: "A.46625f59708ec2f8.AeraPanel3.NFT" + rarity: "Common" + editionNumber: null + totalInEdition: null + scalars: + panel_id: "1.00000000" + player_number: "19.00000000" + edition_set_max: "455.00000000" + chapter_id: "1.00000000" + slot: "1.00000000" + chapter_index: "1.00000000" + edition_set_number: "307.00000000" + player_id: "23540953.00000000" + tags: + player_jersey_name: "Davies" + panel_description: "Alphonso Davies’ parents, Victoria and Debeah Davies, decide to escape the horrors of civil war by gathering all their possessions and leaving Liberia with their children in search of a better life." + player_position: "LB" + player_nationality: "Canada" + collectionName: "Aera" + collectionDescription: "Aera by OneFootball" +context: + tenant: "onefootball" + challenge: "test1" +remark: null +``` + +### Include Transferable Interface with transfer method + +For managing simple transfers, tokens can now implement the transferable interface +and the transfer method. + +```cadence + pub resource interface Transferable { + /// Function for a direct transfer instead of having to do a deposit and withdrawal + /// Returns Bool to indicate if the transfer succeeded or not + pub fun transfer(id: UInt64, receiver: Capability<&AnyResource{Receiver}>): Bool + } +``` + +### Add `getAcceptedTypes()` method to `Receiver` interface + +It is useful to be able to query a non-fungible token receiver to see what types of tokens +it can accept. + +```cadence + pub resource interface Receiver { + + /// deposit takes an NFT as an argument and adds it to the Collection + /// + pub fun deposit(token: @AnyResource{NFT}) + + /// getAcceptedTypes returns a list of NFT types that this receiver accepts + pub fun getAcceptedTypes(): {Type: Bool} + } +``` + +### Change the return type of `borrowNFT()` to an optional + +In the current standard, `borrowNFT()` is meant to panic if the NFT to borrow +is not found in the collection. This is clearly the wrong choice, because +it is a best practice to have getter functions return `nil` if something goes wrong +instead of panicing and reverting the transaction. + +```cadence +pub fun borrowNFT(id: UInt64): &AnyResource{NFT}? +``` + +### Remove the requirement for the `ownedNFTs` field in `Collection` + +The requirement to include an `ownedNFTs: {UInt64: @NFT}` field +in Collection implementations is restrictive because developers may want +to store tokens in a unique way that is different what what could be specified. +This proposal removes `ownedNFTs` and only requires a `getIDs()` method +for the collection to indicate which IDs it contains. + +### Move `createEmptyCollection()` to inside the Collection definition in addition to the contract + +It is useful to be able to create a new empty collection +directly from a `Collection` object instead of having to import +the contract and call the method from the contract. + +```cadence +pub resource interface Collection { + + /// createEmptyCollection creates an empty Collection + /// and returns it to the caller so that they can own NFTs + pub fun createEmptyCollection(): @{Collection} { + post { + result.getIDs().length == 0: "The created collection must be empty!" + } + } +} +``` + +### Add Metadata Views methods to the standard + +Metadata Views for non-fungible tokens should be easily accessible +from interfaces defined by the standard. +This propsal adds the metadata methods to the `NFT` interface and to the +`CollectionPublic` interface. + +```cadence + /// Interface that the NFTs must conform to + /// + pub resource interface NFT: MetadataViews.Resolver { + /// The unique ID that each NFT has + pub fun getID(): UInt64 + + pub fun getViews(): [Type] { + return [] + } + pub fun resolveView(_ view: Type): AnyStruct? { + return nil + } + } + + pub resource interface CollectionPublic: MetadataViews.ResolverCollection { + ... + pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver}? { + return nil + } + ... + } +``` + +In addition to these methods, the proposal also includes methods in the contract +to indicate which NFT types and Collection types the contract defines, +as well as return shared metadata about the project and types. + +```cadence + /// Return the types that the contract defines + pub fun getNFTTypes(): [Type] { + post { + result.length > 0: "Must indicate what non-fungible token types this contract defines" + } + } + + /// get a list of all the NFT collection types that the contract defines + /// could include a post-condition that verifies that each Type is an NFT collection type + pub fun getCollectionTypes(): [Type] + + /// tells what collection type should be used for the specified NFT type + /// return `nil` if no collection type exists for the specified NFT type + pub fun getCollectionTypeForNftType(nftType: Type): Type? + + /// resolve a type to its CollectionData so you know where to store it + /// Returns `nil` if no collection type exists for the specified NFT type + pub fun getCollectionData(nftType: Type): MetadataViews.NFTCollectionData? + + /// Returns the CollectionDisplay view for the NFT type that is specified + pub fun getCollectionDisplay(nftType: Type): MetadataViews.NFTCollectionDisplay? + +``` + +### Drawbacks + +The main drawback of this upgrade is the breaking changes. +It could cause downtime for some projects who aren't prepared to perform the upgrade +right after stable cadence is enabled, +but that applies to any breaking change in stable cadence. +The updates that developers will have to do are fairly straightforward +and will not require much work. + +Please share any other drawbacks that you may discover with these changes. + +### Alternatives Considered + +1. Keep the standard the same: + * If nested type requirements are removed, this may not be possible + * This would avoid the breaking changes, which would be nice in the short term, but would not be setting up cadence developers for success in the long term. + +### Performance Implications + +Most of the methods in the non-fungible token interface are expected to be O(1), +so there is no performance requirements to enforce with the methods. +That is mostly up to the developers who are implementing the tokens to ensure. + +The `getIDs()` method can sometimes return a list that is too large for +the transaction or script to handle. We'd like to have some sort of method +that returns a paginated list of IDs instead of the whole list. +Any proposals for that would be appreciated, +but it will likely require a Cadence language update. + +Adding metadata parameters to the standard events could cause event payloads +to be quite large if the metadata contained in the events is large. +We don't believe this will be a significant problem though. + +### Dependencies + +* Adds no new dependencies +* Dependent projects + * All non-fungible tokens on Flow and some projects that utilize them. + +### Engineering Impact + +* Build and test time will stay the same. they are relatively small changes. +* The Flow smart contract engineering team will maintain the code +* The code can be tested on its own once a compatible version of cadence is released. + +### Best Practices + +* Some of the changes illustrate Cadence best practices, such as encapsulating functionality +within resources, avoiding public fields, and giving developers flexibility to write composable code. + +### Tutorials and Examples + +* Coming soon once the proposal reaches a more final state + +### Compatibility + +* FCL, emulator, and other such tools should not be affected besides potentially +having to update standard transactions if they aren't compatible. + +### User Impact + +* The upgrade will go out at the same time as stable cadence if approved + +## Related Issues + +### Scoped Providers + +A critical piece of tooling for non-fungible tokens would be a struct that contains +a provider capability but restricts the capability to only be able to withdraw +specified IDs or number of tokens from the underlying collection. +Currently, providers have no limit, but all tokens should be able +to create scoped providers. + +This feature is out of the scope of this proposal, but should definitely be a standard +that lives alongside the main fungible token standard. +We hope to shepard a proposal for these soon. + +### Universal Collection + +Each NFT implementor also needs to implement their own Collection resource. +The Collection implementation of the Example NFT contract is 5x as long as the NFT implementation itself. Almost all collection code is copied, so it could be +very nice to have a universal collection that can hold any type of NFT. + +This is out of scope of this proposal and can be introduced later. +There are still many more considerations for the universal collection +that need to be discussed. + +## Prior Art + +In combination with the upgrades to the FT standard, we'd like for users to +be able to utilize more sophisticated functionality in their tokens, such as +what was enabled with an upgrade like ERC-1155 and other such upgrades in Ethereum. +We would greatly appreciate if any developers with ethereum experience could think +about these upgrades from the perspective of being able to create the same kinds +of projects that are possible with other token standards in other languages. \ No newline at end of file From 57ba54730d932b66beab95fffa058550ce99700f Mon Sep 17 00:00:00 2001 From: Josh Hannan Date: Fri, 26 Jan 2024 15:17:05 -0600 Subject: [PATCH 2/5] update to the final design --- application/20221219-nft-v2.md | 301 ++++++++++++++++++--------------- 1 file changed, 161 insertions(+), 140 deletions(-) diff --git a/application/20221219-nft-v2.md b/application/20221219-nft-v2.md index 1833ff67..f08fb403 100644 --- a/application/20221219-nft-v2.md +++ b/application/20221219-nft-v2.md @@ -3,26 +3,30 @@ status: draft flip: NNN (do not set) authors: Joshua Hannan (joshua.hannan@dapperlabs.com) sponsor: Joshua Hannan (joshua.hannan@dapperlabs.com) -updated: 2022-12-19 +updated: 2024-01-26 --- # Non-Fungible Token Standard V2 ## Objective -This FLIP proposes multiple updates to the Flow Non-Fungible Token Standard contracts, -primarily about encapsulating functionality within token resources instead of -with the contracts. There are other smaller quality of life changes to the token standard, -such as integration of metadata, improving error handling, -and including a transfer method and interface. -Some of these changes are dependent on other Cadence FLIPs being approved, +This FLIP proposes multiple updates to the Flow Non-Fungible Token +Standard contracts as part of the Cadence 1.0 upgrade, +primarily about adding support for defining multiple token types +in one contract, adding standard events, integrating `ViewResolver`, +adding support for updating NFTs, adding support for sub-NFTs, and adding entitlements + +Some of these changes are dependent on other Cadence FLIPs that +made fundamental changes to the language +that have been approved and implemented, primarily [interface inheritance](https://github.com/onflow/flips/pull/40), removal of nested type requirements, +[removal of custom destructors](https://github.com/onflow/flips/blob/main/cadence/20230811-destructor-removal.md), and [allowing interfaces to emit events](https://github.com/onflow/cadence/issues/2069). -Most of the changes proposed here would be breaking for all non-fungible token implementations -on the Flow blockchain, but should not be for third-party integrations such as -event listeners and apps that interface with the contracts. +The changes proposed here will be breaking for all non-fungible token implementations +on the Flow blockchain, as well as contracts that utilize tokens based on the standard, +third-party integrations such as event listeners, and apps that interface with the contracts. ## Motivation @@ -30,30 +34,31 @@ The current non-fungible token standard for Flow was designed in mid 2019, at a time when Cadence itself was still being designed. The current standard, though functional, leaves much to be desired. -The current token standard uses contract interfaces. -They are designed in a way which requires each concrete contract -to provide exactly one `NFT` type and one `Collection` type. +The current token standard uses contract interfaces and nested type requirements. +They are designed in a way that requires each concrete contract +to provide exactly one `NFT` and one `Collection` type. This means that any project that needs multiple tokens must deploy multiple contracts. In the case of very simple tokens, this is a lot of complexity for very little value. Related to this problem, functionality and metadata associated with some tokens, -such as paths, events, and empty collection creation methods, +such as paths and empty vault creation methods is only accessible directly through the contract itself, when it should also be accessible directly through an instance of a token resource and/or interface. +Many contracts also do not implement MetadataViews properly because +there is no requirement for it in the standard. + In the case of events, currently there is no way for the standard to ensure that implementations are emitting standardized and correct events, which this upgrade will address. -Additionally, the usage of nested type requirements creates some confusing -interactions, which are not useful and make learning about -and interacting with token smart contracts more difficult. - ## User Benefit With these upgrades, users will now be able to: -* Define multiple tokens in a single contract -* Query all the data about a token directly through the token resource and core interfaces -* Have standard events emitted correctly for important operations +* Define multiple tokens in a single contract. +* Query all the data about a token directly through the contract as well as the `Vault` resource and core interfaces. +* Have standard events emitted correctly for important operations (`Withdrawn`, `Deposited`, `Updated`, and `NonFungibleToken.NFT.ResourceDestroyed`) +* Support querying information about NFTs that an NFT directly owns. +* Check if a `Receiver` accepts a given token type. ## Design Proposal @@ -63,77 +68,79 @@ A [pull request with the suggested code changes](https://github.com/onflow/flow- is in the flow fungible token github repository. The main code changes and their implications are described here. -The linked proposals provide more context. +The linked proposals provide more context, though the proposal in the forum post +is quite out of date. -### Move Event Definitions and emissions to resource interfaces +### Add support for multiple token types in a single contract -Instead of requiring events to be defined in the token implementations contracts, -they will only be defined in the non-fungible token standard smart contract and will -be emitted in post-conditions from the correct methods -in the resource interfaces defined in the standard. +Now that nested type requirements have been removed, contracts can define +multiple token types. The type information can be accessed by using the built-in +`contract.publicTypes(): [Type]` method. A return value of this is now required +in the `NonFungibleToken.createEmptyCollection(nftType: Type)` method because +the caller needs to specify which type they are looking for. -This feature is still being designed, so there is not a code sample yet. -See https://github.com/onflow/cadence/issues/2069 for potential examples +`ViewResolver` has also been updated to require type information +when querying views from a contract: -### Add Type and Metadata parameters to events +```cadence +access(all) contract interface ViewResolver { + + /// Function that returns all the Metadata Views implemented by the resolving contract. + /// Some contracts may have multiple resource types that support metadata views + /// so there there is an optional parameter for specify which resource type the caller + /// is looking for views for. + /// Some contract-level views may be type-agnostic. In that case, the contract + /// should return the same views regardless of what type is passed in. + /// + /// @param resourceType: An optional resource type to return views for + /// @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] + + /// Function that resolves a metadata view for this token. + /// Some contracts may have multiple resource types that support metadata views + /// so there there is an optional parameter for specify which resource type the caller + /// is looking for views for. + /// Some contract-level views may be type-agnostic. In that case, the contract + /// should return the same views regardless of what type is passed in. + /// + /// @param resourceType: An optional resource type to return views for + /// @param view: The Type of the desired view. + /// @return A structure representing the requested view. + /// + access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? +} +``` -Standard events contain more information about the NFT that is being transferred, -such as the type of the NFT and important metadata about the NFT. +While all contracts that implement the `NonFungibleToken` standard +will need to implement these functions, many of them will still +only have a single token type in their contract. +Therefore, they can ignore the `resourceType` parameter. -Here is a simple example of what this could look like: +In addition to these changes to `ViewResolver`, the `MetadataViews.Resolver` +resource interface has been moved to the `ViewResolver` contract because +this is the logical place for it to live and it also avoids a circular dependency. -```cadence -pub event Deposit(id: UInt64, to: Address?, type: Type, displayView: MetadataViews.Display?, serialView: MetadataViews.Serial?) -``` -Or a more involved example: (from [find](https://f.dnz.dev/4fd2a0ccdc3a6cdb2774872b4ac5b32c167e60ae5850ba2b34ef4a749433fe4e)): +### Move Event Definitions and emissions to resource interfaces -``` -from: 0x46625f59708ec2f8 -fromName: null -to: 0x1eef2d5df9d6dd7a -toName: null -uuid: "122520380" -nftInfo: - id: "122520380" - name: "Alphonso Davies - CH01P01 #308" - thumbnail: "ipfs://bafybeidfyk5boobk3n767gz3dc3sma2do6b43k7qsu3s6dlob4h7mmnb2y" - type: "A.46625f59708ec2f8.AeraPanel3.NFT" - rarity: "Common" - editionNumber: null - totalInEdition: null - scalars: - panel_id: "1.00000000" - player_number: "19.00000000" - edition_set_max: "455.00000000" - chapter_id: "1.00000000" - slot: "1.00000000" - chapter_index: "1.00000000" - edition_set_number: "307.00000000" - player_id: "23540953.00000000" - tags: - player_jersey_name: "Davies" - panel_description: "Alphonso Davies’ parents, Victoria and Debeah Davies, decide to escape the horrors of civil war by gathering all their possessions and leaving Liberia with their children in search of a better life." - player_position: "LB" - player_nationality: "Canada" - collectionName: "Aera" - collectionDescription: "Aera by OneFootball" -context: - tenant: "onefootball" - challenge: "test1" -remark: null -``` +Instead of requiring events to be defined in the token implementations contracts, +they will only be [defined in the non-fungible token standard smart contract](https://github.com/onflow/flow-nft/blob/standard-v2/contracts/NonFungibleToken.cdc#L86) and will +be [emitted in post-conditions](https://github.com/onflow/flow-nft/blob/standard-v2/contracts/NonFungibleToken.cdc#L185) +from the correct methods in the resource interfaces defined in the standard. + +### Add Type and Metadata parameters to events -### Include Transferable Interface with transfer method +Standard events contain more information about the NFT that is being transferred, +such as the type of the NFT and important metadata about the transfer. +This includes UUIDs of the collections involved in the token movements +in case this information is useful. -For managing simple transfers, tokens can now implement the transferable interface -and the transfer method. +Here is an example of the proposal: ```cadence - pub resource interface Transferable { - /// Function for a direct transfer instead of having to do a deposit and withdrawal - /// Returns Bool to indicate if the transfer succeeded or not - pub fun transfer(id: UInt64, receiver: Capability<&AnyResource{Receiver}>): Bool - } +/// The event that is emitted when tokens are withdrawn from a Vault +access(all) event Withdrawn(type: String, amount: UFix64, from: Address?, fromUUID: UInt64, withdrawnUUID: UInt64) ``` ### Add `getAcceptedTypes()` method to `Receiver` interface @@ -142,15 +149,18 @@ It is useful to be able to query a non-fungible token receiver to see what types it can accept. ```cadence - pub resource interface Receiver { - - /// deposit takes an NFT as an argument and adds it to the Collection - /// - pub fun deposit(token: @AnyResource{NFT}) - - /// getAcceptedTypes returns a list of NFT types that this receiver accepts - pub fun getAcceptedTypes(): {Type: Bool} - } +access(all) resource interface Receiver { + /// getSupportedNFTTypes returns a list of NFT types that this receiver accepts + /// @return A dictionary of types mapped to booleans indicating if this + /// reciever supports it + access(all) view fun getSupportedNFTTypes(): {Type: Bool} + + /// 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 + /// @param type: An NFT type + /// @return A boolean indicating if this receiver can recieve the desired NFT type + access(all) view fun isSupportedNFTType(type: Type): Bool +} ``` ### Change the return type of `borrowNFT()` to an optional @@ -183,9 +193,10 @@ pub resource interface Collection { /// createEmptyCollection creates an empty Collection /// and returns it to the caller so that they can own NFTs - pub fun createEmptyCollection(): @{Collection} { + access(all) fun createEmptyCollection(): @{Collection} { post { - result.getIDs().length == 0: "The created collection must be empty!" + result.getType() == self.getType(): "The created collection does not have the same type as this collection" + result.getLength() == 0: "The created collection must be empty!" } } } @@ -195,60 +206,70 @@ pub resource interface Collection { Metadata Views for non-fungible tokens should be easily accessible from interfaces defined by the standard. -This propsal adds the metadata methods to the `NFT` interface and to the -`CollectionPublic` interface. +This propsal enforces that implementations implement the `ViewResolver` contract interface +and `NFT` implementations implement the `ViewResolver.Resolver` interface. -```cadence - /// Interface that the NFTs must conform to - /// - pub resource interface NFT: MetadataViews.Resolver { - /// The unique ID that each NFT has - pub fun getID(): UInt64 +### Add support for getting information about Sub-NFTs - pub fun getViews(): [Type] { - return [] - } - pub fun resolveView(_ view: Type): AnyStruct? { - return nil +A powerful feature of Cadence is that resources can own other resources. +Many `NonFungibleToken` implementations make it possible for their NFTs to own other +NFTs. The standard is being updated to support +[querying information about these sub-NFTs](https://github.com/onflow/flow-nft/blob/standard-v2/contracts/NonFungibleToken.cdc#L113-L129). + +It is not required though, so default implementations +that return empty values are provided. + +```cadence +access(all) resource interface NFT { + /// Gets all the NFTs that this NFT directly owns + /// @return A dictionary of all subNFTS keyed by type + access(all) view fun getAvailableSubNFTS(): {Type: UInt64} { + return {} } - } - pub resource interface CollectionPublic: MetadataViews.ResolverCollection { - ... - pub fun borrowViewResolver(id: UInt64): &{MetadataViews.Resolver}? { + /// Get a reference to an NFT that this NFT owns + /// Both arguments are optional to allow the NFT to choose + /// how it returns sub NFTs depending on what arguments are provided + /// For example, if `type` has a value, but `id` doesn't, the NFT + /// can choose which NFT of that type to return if there is a "default" + /// If both are `nil`, then NFTs that only store a single NFT can just return + /// that. This helps callers who aren't sure what they are looking for + /// + /// @param type: The Type of the desired NFT + /// @param id: The id of the NFT to borrow + /// + /// @return A structure representing the requested view. + access(all) fun getSubNFT(type: Type, id: UInt64) : &{NonFungibleToken.NFT}? { return nil } - ... - } +} ``` -In addition to these methods, the proposal also includes methods in the contract -to indicate which NFT types and Collection types the contract defines, -as well as return shared metadata about the project and types. +### Add support for emitting an event when an NFT is updated +Many projects support updating metadata or stored fields for their NFTs. +It is important that these projects have a standard way to indicate +that an NFT has been updated. The standard defines an +[`Update` event](https://github.com/onflow/flow-nft/blob/standard-v2/contracts/NonFungibleToken.cdc#L65) +that any project can access to say that an NFT is updated: ```cadence - /// Return the types that the contract defines - pub fun getNFTTypes(): [Type] { - post { - result.length > 0: "Must indicate what non-fungible token types this contract defines" - } - } - - /// get a list of all the NFT collection types that the contract defines - /// could include a post-condition that verifies that each Type is an NFT collection type - pub fun getCollectionTypes(): [Type] - - /// tells what collection type should be used for the specified NFT type - /// return `nil` if no collection type exists for the specified NFT type - pub fun getCollectionTypeForNftType(nftType: Type): Type? +access(all) event Updated(type: String, id: UInt64, uuid: UInt64, owner: Address?) +access(contract) view fun emitNFTUpdated(_ nftRef: auth(Update | Owner) &{NonFungibleToken.NFT}) +{ + emit Updated(type: nftRef.getType().identifier, id: nftRef.id, uuid: nftRef.uuid, owner: nftRef.owner?.address) +} +``` - /// resolve a type to its CollectionData so you know where to store it - /// Returns `nil` if no collection type exists for the specified NFT type - pub fun getCollectionData(nftType: Type): MetadataViews.NFTCollectionData? +The `emitNFTUpdated` event is used to emit the event and can only be called +with an `Update` or `Owner`-entitled reference to the NFT that has been updated. - /// Returns the CollectionDisplay view for the NFT type that is specified - pub fun getCollectionDisplay(nftType: Type): MetadataViews.NFTCollectionDisplay? +Projects should NOT override the default implementation of `emitNFTUpdated()` +or they will lose access to the standard event. +They still will call it using the name of their contract though. +So if Top Shot were to want to emit the event, the syntax would look like this: +```cadence +TopShot.emitNFTUpdated(nftReference) ``` ### Drawbacks @@ -267,6 +288,11 @@ Please share any other drawbacks that you may discover with these changes. 1. Keep the standard the same: * If nested type requirements are removed, this may not be possible * This would avoid the breaking changes, which would be nice in the short term, but would not be setting up cadence developers for success in the long term. +2. Make `NonFungibleToken` a contract instead of an interface: + * This would allow the contract to have utility methods to have more fine-grained control + over how standard events are emitted, but would not allow the standard + to enforce that implementations use the `ViewResolver` interface + and have the `createEmptyCollection(vaultType: Type)` function. ### Performance Implications @@ -280,10 +306,6 @@ that returns a paginated list of IDs instead of the whole list. Any proposals for that would be appreciated, but it will likely require a Cadence language update. -Adding metadata parameters to the standard events could cause event payloads -to be quite large if the metadata contained in the events is large. -We don't believe this will be a significant problem though. - ### Dependencies * Adds no new dependencies @@ -303,7 +325,7 @@ within resources, avoiding public fields, and giving developers flexibility to w ### Tutorials and Examples -* Coming soon once the proposal reaches a more final state +* Coming soon once the proposal reaches a more final state. ### Compatibility @@ -334,9 +356,8 @@ Each NFT implementor also needs to implement their own Collection resource. The Collection implementation of the Example NFT contract is 5x as long as the NFT implementation itself. Almost all collection code is copied, so it could be very nice to have a universal collection that can hold any type of NFT. -This is out of scope of this proposal and can be introduced later. -There are still many more considerations for the universal collection -that need to be discussed. +There is a universal collection implementation in the branch where the new standard is, +but that will be discussed an another place. ## Prior Art From 926590d78e599ae7273d8f68a8e7ce766039dac5 Mon Sep 17 00:00:00 2001 From: Josh Hannan Date: Tue, 20 Feb 2024 09:03:03 -0600 Subject: [PATCH 3/5] change status to implemented --- application/20221219-nft-v2.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/application/20221219-nft-v2.md b/application/20221219-nft-v2.md index f08fb403..7d278f53 100644 --- a/application/20221219-nft-v2.md +++ b/application/20221219-nft-v2.md @@ -1,9 +1,9 @@ --- -status: draft -flip: NNN (do not set) +status: Implemented +flip: 56 authors: Joshua Hannan (joshua.hannan@dapperlabs.com) sponsor: Joshua Hannan (joshua.hannan@dapperlabs.com) -updated: 2024-01-26 +updated: 2024-02-20 --- # Non-Fungible Token Standard V2 From a4e22a9fb41b07f7a992f320e91b4f8513150d61 Mon Sep 17 00:00:00 2001 From: Josh Hannan Date: Fri, 8 Mar 2024 15:42:12 -0600 Subject: [PATCH 4/5] toms comments --- application/20221219-nft-v2.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/application/20221219-nft-v2.md b/application/20221219-nft-v2.md index 7d278f53..7412e795 100644 --- a/application/20221219-nft-v2.md +++ b/application/20221219-nft-v2.md @@ -56,7 +56,7 @@ implementations are emitting standardized and correct events, which this upgrade With these upgrades, users will now be able to: * Define multiple tokens in a single contract. * Query all the data about a token directly through the contract as well as the `Vault` resource and core interfaces. -* Have standard events emitted correctly for important operations (`Withdrawn`, `Deposited`, `Updated`, and `NonFungibleToken.NFT.ResourceDestroyed`) +* Have standard events emitted correctly for important operations (`Withdrawn`, `Deposited`, `Updated`, and `NonFungibleToken.NFT.ResourceDestroyed`). * Support querying information about NFTs that an NFT directly owns. * Check if a `Receiver` accepts a given token type. @@ -87,7 +87,7 @@ access(all) contract interface ViewResolver { /// Function that returns all the Metadata Views implemented by the resolving contract. /// Some contracts may have multiple resource types that support metadata views - /// so there there is an optional parameter for specify which resource type the caller + /// so there is an optional parameter for specify which resource type the caller /// is looking for views for. /// Some contract-level views may be type-agnostic. In that case, the contract /// should return the same views regardless of what type is passed in. @@ -168,7 +168,7 @@ access(all) resource interface Receiver { In the current standard, `borrowNFT()` is meant to panic if the NFT to borrow is not found in the collection. This is clearly the wrong choice, because it is a best practice to have getter functions return `nil` if something goes wrong -instead of panicing and reverting the transaction. +instead of panicking and reverting the transaction. ```cadence pub fun borrowNFT(id: UInt64): &AnyResource{NFT}? From a1fcf1c30ed864b761c118ab266526be4beb507c Mon Sep 17 00:00:00 2001 From: Josh Hannan Date: Mon, 11 Mar 2024 11:43:04 -0500 Subject: [PATCH 5/5] address gio and Jerome's comments --- application/20221219-nft-v2.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/application/20221219-nft-v2.md b/application/20221219-nft-v2.md index 7412e795..605e99c0 100644 --- a/application/20221219-nft-v2.md +++ b/application/20221219-nft-v2.md @@ -1,9 +1,9 @@ --- status: Implemented flip: 56 -authors: Joshua Hannan (joshua.hannan@dapperlabs.com) -sponsor: Joshua Hannan (joshua.hannan@dapperlabs.com) -updated: 2024-02-20 +authors: Joshua Hannan (joshua.hannan@flowfoundation.org) +sponsor: Joshua Hannan (joshua.hannan@flowfoundation.org) +updated: 2024-03-11 --- # Non-Fungible Token Standard V2 @@ -100,8 +100,8 @@ access(all) contract interface ViewResolver { /// Function that resolves a metadata view for this token. /// Some contracts may have multiple resource types that support metadata views - /// so there there is an optional parameter for specify which resource type the caller - /// is looking for views for. + /// so there there is an optional parameter to specify which resource type the caller + /// is requesting views for. /// Some contract-level views may be type-agnostic. In that case, the contract /// should return the same views regardless of what type is passed in. /// @@ -171,7 +171,7 @@ it is a best practice to have getter functions return `nil` if something goes wr instead of panicking and reverting the transaction. ```cadence -pub fun borrowNFT(id: UInt64): &AnyResource{NFT}? +access(all) view fun borrowNFT(_ id: UInt64): &{NFT}? ``` ### Remove the requirement for the `ownedNFTs` field in `Collection` @@ -182,14 +182,14 @@ to store tokens in a unique way that is different what what could be specified. This proposal removes `ownedNFTs` and only requires a `getIDs()` method for the collection to indicate which IDs it contains. -### Move `createEmptyCollection()` to inside the Collection definition in addition to the contract +### Move `createEmptyCollection()` to inside the NFT and Collection definitions in addition to the contract It is useful to be able to create a new empty collection -directly from a `Collection` object instead of having to import +directly from an `NFT` or a `Collection` object instead of having to import the contract and call the method from the contract. ```cadence -pub resource interface Collection { +access(all) resource interface Collection { /// createEmptyCollection creates an empty Collection /// and returns it to the caller so that they can own NFTs @@ -206,8 +206,9 @@ pub resource interface Collection { Metadata Views for non-fungible tokens should be easily accessible from interfaces defined by the standard. -This propsal enforces that implementations implement the `ViewResolver` contract interface -and `NFT` implementations implement the `ViewResolver.Resolver` interface. +This proposal enforces that implementations implement the `ViewResolver` contract interface, +`NFT` implementations implement the `ViewResolver.Resolver` interface, +and `Collection` implementations implement the `ViewResolver.ResolverCollection` interface. ### Add support for getting information about Sub-NFTs @@ -325,7 +326,8 @@ within resources, avoiding public fields, and giving developers flexibility to w ### Tutorials and Examples -* Coming soon once the proposal reaches a more final state. +* Check out the Cadence 1.0 migration guide for instructions on how to update contracts to the new standards: + * https://cadence-lang.org/docs/cadence_migration_guide/ ### Compatibility @@ -348,7 +350,7 @@ to create scoped providers. This feature is out of the scope of this proposal, but should definitely be a standard that lives alongside the main fungible token standard. -We hope to shepard a proposal for these soon. +We hope to shepherd a proposal for these soon. ### Universal Collection