Skip to main content
Version: 1.0

Non-Fungible Tokens in Cadence 1.0

In 2024, the network will be upgrading to Cadence 1.0. In addition to many changes to the Cadence programming language, the Cadence token standards are also being streamlined and improved. All applications will need to prepare and migrate their existing Cadence smart contracts, scripts, and transactions for the update. If you do not update your code, your applications will become non-functional after the network upgrade.

This document describes the changes to the Cadence Non-Fungible Token (NFT) standard and gives a step-by-step guide for how to upgrade your NFT contract from Cadence 0.42 to Cadence 1.0.

We'll be using the ExampleNFT contract as an example. Many projects have used ExampleNFT as a starting point for their projects, so it is widely applicable to most NFT developers on Flow. The upgrades required for ExampleNFT will cover 90%+ of what you'll need to do to update your contract. Each project most likely has additional logic or features that aren't included in ExampleNFT, but hopefully after reading this guide, you'll understand Cadence 1.0 well enough that you can easily make any other changes that are necessary.

Additionally, most of the changes described here also apply to anyone who is updating a Fungible Token contract or interacting with one, so keep that in mind while reading if that applies to you.

As always, there are plenty of people on the Flow team and in the community who are happy to help answer any questions you may have, so please reach out in Discord if you need any help.

Important Info

Please read the FLIP that describes the changes to the NonFungibleToken standard first.

The updated code for the V2 Non-Fungible Token standard is located in the standard-v2 branch of the flow-nft repo. Please look at the changes there to understand how the standard and examples have changed. This branch includes the updated versions of NonFungibleToken, MetadataViews, ViewResolver, and NFTForwarding.

Please see the latest post in this forum thread to find the latest version of the CLI and emulator that you should be testing with.

It is also important to remember that after you've made your changes to your contracts, you will have to stage the upgrades on testnet and mainnet in order for them to be upgraded and migrated properly. You can find informaion about how to do that here: https://github.com/onflow/contract-updater

Additionally, here are the import addresses for all of the important contracts related to non-fungible tokens:

ContractEmulator Import Address
NonFungibleToken0xf8d6e0586b0a20c7
FungibleToken0xee82856bf20e2aa6
ViewResolver0xf8d6e0586b0a20c7
Burner0xf8d6e0586b0a20c7
MetadataViews0xf8d6e0586b0a20c7

See the other guides in this section of the docs for the import addresses of other important contracts in the emulator.

As for contracts that are important for NFT developers but aren't "core contracts", here is information about where to find the Cadence 1.0 Versions of Each:

NFT Catalog: See the feature/cadence-1.0 branch of the NFT Catalog Repo for the updated versions of NFT Catalog contracts.

NFT Storefront: See the cadence-1.0 branch in the NFT Storefront Repo for the updated versions of the NFTStorefront and NFTStorefrontV2 contracts.

USDC: The USDC contract is still being updated for Cadence 1.0 and is currently not available.

Account Linking and Hybrid Custody: The account linking and hybrid custody contracts are still being updated for Cadence 1.0 and are currently not available.

For any other contracts, search for their github repo and there will likely be a PR or feature branch with the Cadence 1.0 changes. If there isn't, please create an issue in the repo or reach out to that team directly via their support or Discord channel to ask them about their plans to update their contracts.

A note for newcomers​

This guide is primarily for developers who have existing contracts deployed to Flow mainnet that they need to update for Cadence 1.0. If you don't have any contracts deployed yet, it is recommended that you start an NFT contract from scratch by either copying the ExampleNFT contract or the BasicNFT contract from the standard-v2 branch of the flow-nft github repo and wait to deploy it until Flow has been upgraded for Cadence 1.0.

BasicNFT and UniversalCollection​

As part of the improvements to the NFT standard, there is now a new NFT contract example in the flow-nft github repo: BasicNFT.

BasicNFT defines a Cadence NFT in as few lines of code as possible, 137 at the moment! This is possible because the contract basically only defines the NFT resource, the essential metadata views, and a minter resource. It doesn't have to define a collection! Most collection resources are 99% boilerplate code, so it really doesn't make sense for most projects to have to define their own collection.

Instead, BasicNFT uses UniversalCollection, a contract that defines a collection resource that has all of the standard functionality that a collection needs and nothing else. From now on, any project that doesn't want to do anything unique with their collection can just import UniversalCollection and call it from their createEmptyCollection function:


_10
access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
_10
return <- UniversalCollection.createEmptyCollection(identifier: "flowBasicNFTCollection", type: Type<@BasicNFT.NFT>())
_10
}

All they have to provide is a type and an identifier for the collection. UniversalCollection.Collection will enforce that only NFTs of the given type can be accepted by the collection:


_10
access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
_10
if self.supportedType != token.getType() {
_10
panic("Cannot deposit an NFT of the given type")
_10
}

It also constructs standard paths based on the identifier provided.

UniversalCollection will be deployed to all the networks soon after the Cadence 1.0 upgrade, so developers will be able to import from it after that point.

We'll be putting out more information and guides for BasicNFT and UniversalCollection in the near future, but keep it in mind if you are thinking about deploying any new NFT contracts in the future!

Migration Guide

This guide will cover changes that are required because of upgrades to the Cadence Language as well as the token standard. The improvements will be described here as they apply to specific changes that projects need to make in order to be ready for the upgrade, but it is good to read all sources to fully understand the changes.

Please read the motivation section of the NFT-V2 FLIP to learn about why most of the changes to the standard were needed or desired.

First, we will cover the changes that come from the new token standards and then we will cover the changes that come from Cadence.

Previous Non-Permitted Changes​

Until now, there were a lot of restrictions on what changes are allowed in upgrades to Cadence smart contracts, like not being allowed to change the type of fields, not being able to change interface conformance, and more. Many of the Cadence 1.0 changes require updates that break some of these rules, but the upgrade checker will be relaxed in order to allow these changes to be possible. All of these changes have already been tested with the Cadence 1.0 upgrades for all of the token standards and all of the protocol smart contracts which cover a huge amount of changes that contracts can go through, so the Flow team is confident that the restrictions have been relaxed enough to allow the upgrades.

Automatic State Migrations​

Some of these changes require that types for stored values are updated to more or less restrictive types. The work for updating the code that refers to these types is up to the developer, but the work for migrating the stored state to reflect these two types will be handled automatically by the Flow teams custom state migrations. These migrations only expect a restricted subset of type changes, but it is important to make sure that you don't introduce any new business logic as part of your Cadence 1.0 upgrades because the migrations will only understand how to migrate state for existing code. More information will be given about this in the sections of this doc that will be affected by the custom migrations, such as with entitlements and the removal of nested type requirements and restricted types.

Token Standard Changes​

Continue to implement NonFungibleToken​

Make sure your contract still implements the NonFungibleToken interface:


_10
access(all) contract YourContract: NonFungibleToken {

This won't be a change for most contracts because this is how tokens are implemented currently, but there was a period of time when the new standards defined NonFungibleToken as a contract instead of an interface, so anyone who was testing with those early versions should make sure that their contract still implements NonFungibleToken. This will ensure that the correct metadata view methods are enforced from ViewResolver and that your contract has the correct createEmptyCollection() method defined.

It is important to understand that the new NonFungibleToken no longer specifies types that you need to define like NFT and Collection. These were changed to interfaces because now the token standards support defining multiple token types in a single contract. Most projects won't utilize this, but it is important to understand in relation to some of the other changes that are needed, like the next one for example.

Add type argument to contract.createEmptyCollection()​

Because contracts can now define multiple token types, all contract.createEmptyCollection() functions now have a nftType argument:


_10
/// createEmptyCollection creates an empty Collection for the specified NFT type
_10
/// and returns it to the caller so that they can own NFTs
_10
access(all) fun createEmptyCollection(nftType: Type): @{NonFungibleToken.Collection} {
_10
return <- create Collection()
_10
}

As is shown here, if you only have a single collection type defined in your contract, you can just return that collection type regardless of what the type argument is, but you could also make sure that the caller provides the correct type before returning the collection.

Your NFT implements NonFungibleToken.NFT​

NonFungibleToken.NFT used to be a nested type specification, but now it is an interface! This means that in your contract, your NFT resource needs to implement it in order to be considered compatible with the standard!


_10
access(all) contract ExampleNFT: NonFungibleToken {
_10
/// We choose the name NFT here, but this type can have any name now
_10
/// because the interface does not require it to have a specific name any more
_10
access(all) resource NFT: NonFungibleToken.NFT {

This will ensure that your NFT resource has all the correct fields and functions.

As part of this upgrade, you should remove the NonFungibleToken.INFT implementation specification from the declaration of your NFT because the INFT interface has been removed.

Note for Custom Migrations: All stored objects that currently use the concrete type NonFungibleToken.NFT will be automatically migrated to use the interface type {NonFungibleToken.NFT} as part of the Flow team's custom state migrations.

Your Collection implements NonFungibleToken.Collection​

Similar to NFT, NonFungibleToken.Collection is now an interface, so your Collection resource type needs to implement it in order to be conformant.


_10
/// In the `ExampleToken` smart contract
_10
access(all) resource Collection: NonFungibleToken.Collection {

Note for Custom Migrations: All stored objects that currently use the concrete type NonFungibleToken.Collection will be automatically migrated to use the interface type {NonFungibleToken.Collection} as part of the Flow team's custom state migrations.

Remove Project-Specific Events​

Standard events are being added to the token standards! These are events that are defined in the contract interface and are emitted during pre and post-conditions every time an important event happens like a Deposit or a Withdrawal. The events include all the important information and metadata about the action, are emitted automatically from the interface, and are unable to be spoofed!

This means that you can get rid of the Deposit and Withdraw events in your contracts completely! You don't have to obviously, but the standard events will be much more reliable and will be emitted anyway, so your custom events are redundant unless they contain some information that is not included in the standard events.

The definitions and emissions for these standard events is in the new NonFungibleToken standard.


_15
/// Event that is emitted when a token is withdrawn,
_15
/// indicating the type, id, uuid, the owner of the collection that it was withdrawn from,
_15
/// and the UUID of the resource it was withdrawn from, usually a collection.
_15
///
_15
/// If the collection is not in an account's storage, `from` will be `nil`.
_15
///
_15
access(all) event Withdrawn(type: String, id: UInt64, uuid: UInt64, from: Address?, providerUUID: UInt64)
_15
_15
/// Event that emitted when a token is deposited to a collection.
_15
/// Indicates the type, id, uuid, the owner of the collection that it was deposited to,
_15
/// and the UUID of the collection it was deposited to
_15
///
_15
/// If the collection is not in an account's storage, `from`, will be `nil`.
_15
///
_15
access(all) event Deposited(type: String, id: UInt64, uuid: UInt64, to: Address?, collectionUUID: UInt64)

As you can see in the ExampleNFT diff, the events have been removed completely.

For event listeners, the events will have this format:


_10
A.f8d6e0586b0a20c7.NonFungibleToken.Deposited(...)

Where the address is whatever address the NonFungibleToken contract is deployed to.

Implement ViewResolver​

The new standard enforces that implementations also implement the ViewResolver functions, which are standard functions for returning metadata about a given token or smart contract.

If you were using these contract-level functions before, you will need to update them to have the correct names and arguments. They were changed because now that contracts can define multiple token types, the metadata getter functions need to be able to return information about any of the token types. Therefore, here are the new definitions. (The comments explain the design and some suggestions for how to implement them, so it is recommend that you read them.)


_25
/// Function that returns all the Metadata Views implemented by the resolving contract.
_25
/// Some contracts may have multiple resource types that support metadata views
_25
/// so there there is an optional parameter for specify which resource type the caller
_25
/// is looking for views for.
_25
/// Some contract-level views may be type-agnostic. In that case, the contract
_25
/// should return the same views regardless of what type is passed in.
_25
///
_25
/// @param resourceType: An optional resource type to return views for
_25
/// @return An array of Types defining the implemented views. This value will be used by
_25
/// developers to know which parameter to pass to the resolveView() method.
_25
///
_25
access(all) view fun getContractViews(resourceType: Type?): [Type]
_25
_25
/// Function that resolves a metadata view for this token.
_25
/// Some contracts may have multiple resource types that support metadata views
_25
/// so there there is an optional parameter for specify which resource type the caller
_25
/// is looking for views for.
_25
/// Some contract-level views may be type-agnostic. In that case, the contract
_25
/// should return the same views regardless of what type is passed in.
_25
///
_25
/// @param resourceType: An optional resource type to return views for
_25
/// @param view: The Type of the desired view.
_25
/// @return A structure representing the requested view.
_25
///
_25
access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct?

You can see how ExampleNFT implements them here.

Keep NFT ID Usage Consistent​

In the new standard examples, we often use UUID for NFT IDs. Many early Flow projects used a project-specific ID system for their NFTs. It is important that you stick with whatever ID system your project used from the beginning so NFT IDs don't get mixed up.

Add createEmptyCollection() to NFT and Collection.​

These function requirements were added to NFT and Collection so that holders of any of those objects could create a new collection of the correct type, no matter if they imported the contract or knew the type ahead of time.

Add getSupportedNFTTypes() and isSupportedNFTType()​

All resources that implement NonFungibleToken.Receiver now have to include these two functions that indicate which types they are able to receive in their deposit() calls. Since Collection implements Receiver, your Collection will need implementations for both of these functions.

As is done in the ExampleNFT.Collection, if your Collection can only accept a single NFT type, then the implementation is simple.

Add getLength()​

Add a getLength() function to your Collection resource so that callers can quickly get an idea of the size of your collection.


_10
/// Gets the amount of NFTs stored in the collection
_10
access(all) view fun getLength(): Int {
_10
return self.ownedNFTs.keys.length
_10
}

Update borrowNFT() to Return an Optional​

The borrowNFT() method is used to get a reference to any NFT in the collection. It is a common best practice in Cadence smart contracts for getter functions (functions that only return a piece of informaion instead of modifying state) to never panic or revert if the request is invalid. Getter functions should just return nil if the request is invalid.

Therefore. The borrowNFT method should be modified to return an optional reference and return nil if the NFT ID doesn't exist in the collection.


_10
/// In the Collection resource
_10
access(all) view fun borrowNFT(_ id: UInt64): &{NonFungibleToken.NFT}? {
_10
return (&self.ownedNFTs[id] as &{NonFungibleToken.NFT}?)
_10
}

Additionally, any projects that have borrowNFTSafe or a project-specific borrow like borrowMoment() in NBA Top Shot can safely remove those and also remove any usage of them from transactions and scripts.

Remove Private Path and Type fields​

Since private paths were removed in Cadence 1.0, these fields are no longer needed, so remove the code that returns them in your resolveView method for NFTCollectionData:


_13
case Type<MetadataViews.NFTCollectionData>():
_13
let collectionData = MetadataViews.NFTCollectionData(
_13
storagePath: /storage/cadenceExampleNFTCollection,
_13
publicPath: /public/cadenceExampleNFTCollection,
_13
// REMOVED: providerPath
_13
publicCollection: Type<&ExampleNFT.Collection>(),
_13
publicLinkedType: Type<&ExampleNFT.Collection>(),
_13
// REMOVED: providerLinkedType
_13
createEmptyCollectionFunction: (fun(): @{NonFungibleToken.Collection} {
_13
return <-ExampleNFT.createEmptyCollection(nftType: Type<@ExampleNFT.NFT>())
_13
})
_13
)
_13
return collectionData

Private paths are no longer able to be used in Cadence across the board, so you'll need to find other ways to do what you were doing with them before. This will likely involve Capability Controllers.

Use the NonFungibleToken.emitNFTUpdated() function​

This is an optional change and only applies to projects that have functionality that updates the metadata of NFTs periodically. It allows those projects to emit the standard Updated event so that event listeners can know when NFTs have been updated so they can query collections to get the updated metadata to show in their user interfaces.


_10
access(all) event Updated(type: String, id: UInt64, uuid: UInt64, owner: Address?)
_10
access(all) view fun emitNFTUpdated(_ nftRef: auth(Update | Owner) &{NonFungibleToken.NFT})
_10
{
_10
emit Updated(type: nftRef.getType().identifier, id: nftRef.id, uuid: nftRef.uuid, owner: nftRef.owner?.address)
_10
}

As you can see, it requires an authorized reference to an NFT, so only the owner of and NFT can call this to emit an event. Additionally, as is noted in the example below, you have to use your own contract's name to call the function because the code that emits the event is a default implementation that can only be accessed from an implementation.

DO NOT Re-implement the emitNFTUpdated function in your contract or you will lose access to the ability to emit the standard event.

This function could be called from within a Collection resource when a piece of metadata on an owned NFT is updated. For example, if a developer wanted to track the time of the latest transfer for each NFT, they could do it in the deposit() function:


_36
access(all) contract ExampleNFT {
_36
access(all) resource Collection: NonFungibleToken.Collection {
_36
_36
access(contract) var ownedNFTs: @{UInt64: ExampleNFT.NFT}
_36
_36
...
_36
_36
/// deposit takes a NFT and adds it to the collections dictionary
_36
/// and adds the ID to the id array
_36
access(all) fun deposit(token: @{NonFungibleToken.NFT}) {
_36
let token <- token as! @ExampleNFT.NFT
_36
_36
let id = token.id
_36
_36
// add the new token to the dictionary which removes the old one
_36
let oldToken <- self.ownedNFTs[token.id] <- token
_36
destroy oldToken
_36
_36
// Get an authorized reference to the NFT so that
_36
// the update transfer date function can be called
_36
// and the emitNFTUpdated function can be called
_36
let authTokenRef = (&self.ownedNFTs[id] as auth(NonFungibleToken.Update) &{NonFungibleToken.NFT}?)!
_36
authTokenRef.updateTransferDate(date: getCurrentBlock().timestamp)
_36
_36
// EMIT THE UPDATED EVENT
_36
// Note: You have to use your own contract's name for the call
_36
// because the code that emits the event is a default implementation
_36
// DO NOT Re-implement the `emitNFTUpdated` function or you will lose
_36
// access to the ability to emit the standard event
_36
ExampleNFT.emitNFTUpdated(authTokenRef)
_36
}
_36
_36
...
_36
}
_36
...
_36
}

Cadence Changes​

Update all pub access modfiers​

The pub access modifier was removed from the language to better support unified representation of access control, especially now that entitlements exist.

Please familiarize yourself with the new entitlements feature because it is extremely important for you to understand in order to build safe smart contracts.

Most contracts can update and pub access modifiers to access(all), but there are some functions, such as withdraw, that need to have entitled access.

A good rule to follow is that if there is a resource that will ever have a reference created for it (such as for a public or private capability), any functions that you don't want everyone in the network to be able to have access to should be restricted by an entitlement so that people cannot downcast the reference to access these privledged functions.

Add Withdraw and Owner Entitlements to withdraw()​

Now that unrestricted casting is possible in Cadence, it is necessary to use entitlements to restrict access to privledged functions in any composite type.

The only default method that needs to be restricted is the withdraw method:


_10
access(NonFungibleToken.Withdraw) fun withdraw(withdrawID: UInt64): @ExampleNFT.NFT {

This means that you can only call the withdraw method if you control the actual object or if you have an auth(NonFungibleToken.Withdraw) entitled reference to it.

So in a typical transfer transaction when you need to withdraw from a vault, you would get the reference like this:


_10
// borrow a reference to the signer's NFT collection
_10
self.withdrawRef = signer.storage.borrow<auth(NonFungibleToken.Withdraw) &{NonFungibleToken.Collection}>(
_10
from: collectionData.storagePath
_10
) ?? panic("Account does not store an object at the specified path")

From the flow-nft transfer_nft.cdc transaction.

In addition, since Collection is an interface, you will need to update every instance in your code that refers to @NonFungibleToken.Collection or &NonFungibleToken.Collection to @{NonFungibleToken.Collection} or &{NonFungibleToken.Collection} respectively to show that it is now an interface specification instead of a concrete type specification.

This also applies to NonFungibleToken.NFT. Any instance that refers to @NonFungibleToken.NFT or &NonFungibleToken.NFT need to be updated to @{NonFungibleToken.NFT} or &{NonFungibleToken.NFT} respectively.

Example in deposit():


_10
/// deposit now accepts a resource that implements the `NonFungibleToken.NFT` interface type
_10
access(all) fun deposit(token: @{NonFungibleToken.NFT})

Note on Custom State Migrations: You may be wondering how you can get these entitlements onto capabilities that already exist in a contract or in an account. As part of the automatic migrations, all existing capabilities will be automatically migrated to use a type that offers the same level of access. In the case of Capabilities that provide access to entitled functions, the relevant entitlements will be added.

Update all getter functions to view​

Cadence 1.0 introduces view functions which enforce that a function does not modify any state.

The default view functions will be enforced by the token standard, but if your project has any other getter functions that aren't in the standard and don't modify any state, then you should add view to these functions.

Style Tip: The recommended style for view functions is to put the view keyword after the access specification instead of before, like this:


_10
/// Recommended
_10
access(all) view fun getIDs(): [UInt64] {
_10
_10
/// NOT Recommended
_10
view access(all) fun getIDs(): [UInt64] {

Remove Restricted Types​

Cadence 1.0 makes it so restricted types (for example: @ExampleNFT.Collection{NonFungibleToken.Receiver} instead of @ExampleNFT.Collection or {NonFungibleToken.Receiver}) are no longer permitted.

See the FLIP to get more context on why these were removed.

Note on Custom State Migrations: Developers are required to update any code that refers to a restricted type to either refer to the resource type OR the interface type, but the migration of any stored values that use a restricted type will be handled by the Flow team's custom migrations. Restricted types will be migrated to be the unrestricted type. For example, a capability with the type &ExampleNFT.Collection{NonFungibleToken.Receiver} will be changed to have the type &ExampleNFT.Collection.

Use Correct Capability Syntax​

Cadence 1.0 introduces Capability Controllers a more sophisticated and easy to use way of handling capabilities. It is important to understand how these work in order to use them properly.

As part of these changes, projects need to update the syntax for how they manage capabilities. You can see the setup_account.cdc transaction for the proper syntax for creating public capabilities for example.


_10
// create a public capability for the collection
_10
signer.capabilities.unpublish(collectionData.publicPath)
_10
let collectionCap = signer.capabilities.storage.issue<&ExampleNFT.Collection>(collectionData.storagePath)
_10
signer.capabilities.publish(collectionCap, at: collectionData.publicPath)

Additionally, private paths have been removed, so any code that references private paths needs to be changed to use capability controllers instead.

Use Proper Entitlements for Accounts​

AuthAccount objects are not referred to as Account and there are now more restrictions on how accounts can be used.

See the Cadence 1.0 Account documentation for more information.

Most of the functionality on Account objects is now hidden by entitlements, so a transaction has to declare what Account functionality the transaction will access in the prepare block.

For example, in the transfer_nft transaction, these are the entitlements that are required:


_10
prepare(signer: auth(BorrowValue) &Account) {

The transaction needs to borrow a value from storage to withdraw the NFT, so the BorrowValue entitlement is required.

Each transaction is different, so different entitlements will be required depending on what is happening. It is important for developers to make sure that only the minimum set of entitlements that are required for the transaction are given. This allows wallets to more accurately show users what the transactions they are signing will have acccess to, which helps users have more confidence and safety about what transactions they sign.

Conclusion​

This guide covered the most important changes that are required for the Cadence 1.0 upgrades to NFT contracts. Please ask any questions about the migrations in the #developer-questions channel in discord and good luck with your upgrades!