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:
Contract | Emulator Import Address |
---|---|
FungibleToken | 0xee82856bf20e2aa6 |
ViewResolver | 0xf8d6e0586b0a20c7 |
Burner | 0xf8d6e0586b0a20c7 |
MetadataViews | 0xf8d6e0586b0a20c7 |
FungibleTokenMetadataViews | 0xee82856bf20e2aa6 |
FungibleTokenSwitchboard | 0xee82856bf20e2aa6 |
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
:
_10access(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_10access(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:
_10access(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_10let 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_10access(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_10access(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///_15case 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!