Skip to main content
Version: 0.42

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 Fungible Token (FT) standard and gives a step-by-step guide for how to upgrade your FT contract from Cadence 0.42 to Cadence 1.0.

We'll be using the ExampleToken contract as an example. Many projects have used ExampleToken as a starting point for their projects, so it is widely applicable to most NFT developers on Flow. The upgrades required for ExampleToken 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 ExampleToken, 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.

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 FungibleToken standard first.

The updated code for the V2 Fungible Token standard is located in the v2-standard branch of the flow-ft repo. Please look at the changes there to understand how the standard and examples have changed. This branch also includes the updated versions of FungibleTokenMetadataViews, Burner, FungibleTokenSwitchboard, and TokenForwarding.

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 fungible tokens:

ContractEmulator Import Address
FungibleToken0xee82856bf20e2aa6
ViewResolver0xf8d6e0586b0a20c7
Burner0xf8d6e0586b0a20c7
MetadataViews0xf8d6e0586b0a20c7
FungibleTokenMetadataViews0xee82856bf20e2aa6
FungibleTokenSwitchboard0xee82856bf20e2aa6

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:

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.

Migration Guide

Please see the NFT Cadence 1.0 migration guide. While the contracts aren't exactly the same, they share a huge amount of functionality, and the changes described in that guide will cover 90% of the changes that are needed for fungible tokens, so if you just follow those instructions for your fungible token contract, you'll be most of the way there.

Here, we will only describe the changes that are specific to the fungible token standard.

Vault implements FungibleToken.Vault

FungibleToken.Vault is no longer a resource type specification. It is now an interface that inherits from Provider, Receiver, Balance, ViewResolver.Resolver, and Burner.Burnable.

To ensure compatibility, update your Vault interface conformance list to only implement FungibleToken.Vault:


_10
access(all) resource Vault: FungibleToken.Vault {

In addition, since Vault is an interface, you will need to update every instance in your code that refers to @FungibleToken.Vault or &FungibleToken.Vault to @{FungibleToken.Vault} or &{FungibleToken.Vault} respectively to show that it is now an interface specification instead of a concrete type specification. Example in deposit():


_10
/// deposit now accepts a resource that implements the `FungibleToken.Vault` interface type
_10
access(all) fun deposit(from: @{FungibleToken.Vault})

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

Add Withdraw 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(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @ExampleToken.Vault {

This means that you can only call the withdraw method if you control the actual object or if you have an auth(FungibleToken.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
// Get a reference to the signer's stored vault
_10
let vaultRef = signer.storage.borrow<auth(FungibleToken.Withdraw) &ExampleToken.Vault>(from: self.vaultData.storagePath)
_10
?? panic("Could not borrow reference to the owner's Vault!")

From the flow-ft transfer_tokens.cdc transaction.

Use the new Burner contract if desired

Custom destructors were removed as part of Cadence 1.0, so destroy blocks in resource definitions are no longer allowed. If you were using the destroy block to emit a custom event or subtract the destroyed tokens' supply from your token's total supply and still want that functionality, you'll need to use the burnCallback() method from the Burner smart contract:


_10
/// Called when a fungible token is burned via the `Burner.burn()` method
_10
access(contract) fun burnCallback() {
_10
if self.balance > 0.0 {
_10
ExampleToken.totalSupply = ExampleToken.totalSupply - self.balance
_10
}
_10
self.balance = 0.0
_10
}

This will automatically be executed if a Vault is destroyed via the Burner.burn() method. It will emit a standard event to indicate the destruction, so no need to include one yourself unless you need to emit other information besides the balance and type.

As shown above, this is also where you can subtract the destroyed tokens from the total supply. This function requires you to set the balance of the vault to zero before the function execution completes though. This is to prevent spam.

Add isAvailableToWithdraw method

Some more complex types that implement Provider may want a more efficient way to describe if a desired amount of tokens can be withdrawn. isAvailableToWithdraw allows that.

The Vault implementation is simple though:


_10
/// In `ExampleToken.Vault`
_10
/// Asks if the amount can be withdrawn from this vault
_10
access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
_10
return amount <= self.balance
_10
}

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 FTVaultData:


_15
/// In `ExampleToken.resolveContractView()`
_15
///
_15
case Type<FungibleTokenMetadataViews.FTVaultData>():
_15
return FungibleTokenMetadataViews.FTVaultData(
_15
storagePath: /storage/exampleTokenVault,
_15
receiverPath: /public/exampleTokenReceiver,
_15
metadataPath: /public/exampleTokenVault,
_15
/// REMOVED: providerPath
_15
receiverLinkedType: Type<&ExampleToken.Vault>(),
_15
metadataLinkedType: Type<&ExampleToken.Vault>(),
_15
/// REMOVED: providerLinkedType
_15
createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
_15
return <-ExampleToken.createEmptyVault(vaultType: Type<@ExampleToken.Vault>())
_15
})
_15
)

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.

Conclusion

This guide briefly covered the Cadence 1.0 changes that are specific to Fungible Tokens. If you have any more questions or would like additional sections to be added to the guide, please create an issue in the cadence-lang.org repo or ask in discord and the flow team will be happy to assist!