How to Create a Fungible Token on Flow
This guide is an in-depth tutorial on launching a Fungible Token contract from scratch. To launch in 2 minutes using a tool check out Toucans
What are Fungible Tokens?​
Fungible tokens are digital assets that are interchangeable and indistinguishable with other tokens of the same type. This means that each token is identical in specification to every other token in circulation. Think of them like traditional money; every dollar bill has the same value as every other dollar bill. Fungible tokens play a crucial role in web3 ecosystems, serving as both a means of payment and an incentive for network participation. They can take on various roles including currencies, structured financial instruments, shares of index funds, and even voting rights in decentralized autonomous organizations.
Vaults on Flow​
On the Flow blockchain, fungible tokens are stored in structures called vaults. Think of a vault as a digital piggy bank. When you transfer tokens from one vault to another:
- A temporary vault (or a temporary piggy bank) is created holding the transfer amount.
- The original vault's balance decreases by the transfer amount.
- The recipient's vault receives the tokens from the temporary vault.
- The temporary vault is then deleted.
This process ensures secure and accurate token transfers on the Flow blockchain.
Fungible Token Standard​
The Fungible Token Standard defines what a fungible token should look like on Flow. Wallets and other platforms need to recognize these tokens, so they adhere to a specific interface, which defines fields like balance, totalSupply, withdraw functionality, and more. This interface ensures that all fungible tokens on Flow have a consistent structure and behavior. Learn more about interfaces here.
Setting Up a Project​
To start creating an NFT on the Flow blockchain, you'll first need some tools and configurations in place.
Installing Flow CLI​
The Flow CLI (Command Line Interface) provides a suite of tools that allow developers to interact seamlessly with the Flow blockchain.
If you haven't installed the Flow CLI yet and have Homebrew installed, you can run brew install flow-cli
. If you don’t have Homebrew, please follow the installation guide here.
Initializing a New Project​
💡 Note: Here is a link to the completed code if you want to skip ahead or reference as you follow along.
Once you have the Flow CLI installed, you can set up a new project using the flow setup
command. This command initializes the necessary directory structure and a flow.json
configuration file (a way to configure your project for contract sources, deployments, accounts, and more):
_10flow setup FooToken
Upon execution, the command will generate the following directory structure:
_10/cadence_10 /contracts_10 /scripts_10 /transactions_10 /tests_10flow.json
Now, navigate into the project directory:
_10cd FooToken
In our configuration file, called flow.json
, for the network we want to use, we are going to state the address the FungibleToken
contract is deployed to via aliases
in a new contracts
section. Since it is a standard contract, it has already been deployed to the emulator, a tool that runs and emulates a local development version of the Flow Blockchain, for us. You can find addresses for other networks, like Testnet and Mainnet, on the Fungible Token Standard repo.
_10"contracts": {_10 "FungibleToken": {_10 "aliases": {_10 "emulator": "0xee82856bf20e2aa6"_10 }_10 }_10}
Writing Our Token Contract​
Next let's create a FooToken
contract at cadence/contract/FooToken.cdc
using the boilerplate generate
command from the Flow CLI:
_10flow generate contract FooToken
This will create a new file called FooToken.cdc
in the contracts
directory. Let's open it up and add some code.
In this contract file, we want to import our FungibleToken
contract that we've defined in flow.json
.
_10import "FungibleToken"
In this same file, let's create our contract which implements the FungibleToken
contract Interface (it does so by setting it following the FooToken:
). In our init
— which runs on the contracts first deployment and is used to set initial values — let’s set an starting total supply of 1,000 tokens for this example.
_10// ...previous code_10_10access(all) contract FooToken: FungibleToken {_10 pub var totalSupply: UFix64_10_10 init() {_10 self.totalSupply = 1000.0_10 }_10}
Creating a Vault​
Inside of this contract, we'll need to create a resource for a Vault. A resource is a special type to Cadence (our smart contract language) which represents a unique value. We also want to have it implement the Provider, Receiver, and Balance sections of the FungibleToken contract, as well as allow us to set the initial balance of the vault.
_15import "FungibleToken"_15_15access(all) contract FooToken: FungibleToken {_15 // ...totalSupply code_15_15 pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance {_15 pub var balance: UFix64_15_15 init(balance: UFix64) {_15 self.balance = balance_15 }_15 }_15_15 // ...init code_15}
In order to give an account a vault, we need to create a function that creates a vault of our FooToken type and returns it to the account.
_11import "FungibleToken"_11_11access(all) contract FooToken: FungibleToken {_11 // ...other code_11_11 pub fun createEmptyVault(): @FooToken.Vault {_11 return <- create Vault(balance: 0.0)_11 }_11_11 // ...init code_11}
The standard also wants us to implement the events required for creating a token. These will tell us when a token has been initialized, withdrawn, or deposited. Let's add these to the top of the contract.
_10import "FungibleToken"_10_10access(all) contract FooToken: FungibleToken {_10 _10 pub event TokensInitialized(initialSupply: UFix64)_10 pub event TokensWithdrawn(amount: UFix64, from: Address?)_10 pub event TokensDeposited(amount: UFix64, to: Address?)_10_10 // ...all other code_10}
Inside our Vault
resource, we also need a way to withdraw balances. To do that, we need to create a new vault with the transfer amount and decrement the existing balance. Let’s also emit an event for this action:
_22import "FungibleToken"_22_22access(all) contract FooToken: FungibleToken {_22_22 // ...previous code_22_22 pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance {_22_22 // ...other vault code_22_22 pub fun withdraw(amount: UFix64): @Vault {_22 self.balance = self.balance - amount_22 emit TokensWithdrawn(amount: amount, from: self.owner?.address)_22 return <- create Vault(balance: amount)_22 }_22_22 // ...vault init code_22_22 }_22_22 // ...additional code_22}
In addition to withdrawing, the vault also needs a way to deposit. We'll again emit the appropriate event, as well as typecast to make sure we are dealing with the correct token, update the vault balance, and destroy the vault. We also need to set the balance to 0 in the current vault so that the destroy method is not triggered. Add this code to your resource:
_26import "FungibleToken"_26_26access(all) contract FooToken: FungibleToken {_26_26 // ...previous code_26_26 pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance {_26_26 // ...other vault code_26_26 pub fun deposit(from: @FungibleToken.Vault) {_26 let vault <- from as! @FooToken.Vault // typecast to make sure we are using the correct token type_26_26 emit TokensDeposited(amount: vault.balance, to: self.owner?.address)_26_26 self.balance = self.balance + vault.balance_26 vault.balance = 0.0 // Before the vault is destroyed, set the balance to zero so the totalSupply isn't affected in the destroy method_26 destroy vault_26 }_26_26 // ...vault init_26_26 }_26_26 // ...additional code_26}
The destroy call is an important thing to handle though since if anyone ever does destroy their vault, we'll want to change the total supply of the token. You can add this inside of your Vault
resource as well.
_18import "FungibleToken"_18_18access(all) contract FooToken: FungibleToken {_18_18 // ...previous code_18_18 pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance {_18_18 // ...other vault code_18_18 destroy() {_18 FooToken.totalSupply = FooToken.totalSupply - self.balance_18 }_18_18 }_18_18 // ...additional code_18}
Creating a Minter​
Let's create a minter resource which is used to mint vaults that have tokens in them. We can keep track of tokens we are minting with totalSupply
If we want the ability to create new tokens, we'll need a way to mint them. To do that, let's create another resource on the FooToken
contract. This will have a mintToken
function which can increase the total supply of the token.
_17import "FungibleToken"_17_17access(all) contract FooToken: FungibleToken {_17_17 // ...additional contract code_17_17 pub resource Minter {_17 pub fun mintToken(amount: UFix64): @FungibleToken.Vault {_17 FooToken.totalSupply = FooToken.totalSupply + amount_17 return <- create Vault(balance: amount)_17 }_17_17 init() {}_17 }_17_17 // ...additional contract code_17}
We also want to decide which account/s we want to give this ability to. In our example, we'll give it to the account where the contract is deployed. We can set this in the contract init function below the setting of total supply so that when the contract is created the minter is stored on the same account.
_11import "FungibleToken"_11_11access(all) contract FooToken: FungibleToken {_11_11 // ...additional contract code_11_11 init() {_11 self.totalSupply = 1000.0 // existed before_11 self.account.save(<- create Minter(), to: /storage/Minter)_11 }_11}
After each of these steps, your FooToken.cdc
contract file should now look like this:
_53import "FungibleToken"_53_53access(all) contract FooToken: FungibleToken {_53_53 pub event TokensInitialized(initialSupply: UFix64)_53 pub event TokensWithdrawn(amount: UFix64, from: Address?)_53 pub event TokensDeposited(amount: UFix64, to: Address?)_53 pub var totalSupply: UFix64_53_53 pub resource Vault: FungibleToken.Provider, FungibleToken.Receiver, FungibleToken.Balance {_53 pub var balance: UFix64_53_53 pub fun deposit(from: @FungibleToken.Vault) {_53 let vault <- from as! @FooToken.Vault_53 emit TokensDeposited(amount: vault.balance, to: self.owner?.address)_53 self.balance = self.balance + vault.balance_53 vault.balance = 0.0_53 destroy vault_53 }_53_53 pub fun withdraw(amount: UFix64): @Vault {_53 self.balance = self.balance - amount_53 emit TokensWithdrawn(amount: amount, from: self.owner?.address)_53 return <- create Vault(balance: amount)_53 }_53_53 destroy() {_53 FooToken.totalSupply = FooToken.totalSupply - self.balance_53 }_53_53 init(balance: UFix64) {_53 self.balance = balance_53 }_53 }_53_53 pub resource Minter {_53 pub fun mintToken(amount: UFix64): @FungibleToken.Vault {_53 FooToken.totalSupply = FooToken.totalSupply + amount_53 return <- create Vault(balance: amount)_53 }_53_53 init(){}_53 }_53_53 pub fun createEmptyVault(): @FooToken.Vault {_53 return <- create Vault(balance: 0.0)_53 }_53_53 init() {_53 self.totalSupply = 1000.0_53 self.account.save(<- create Minter(), to: /storage/Minter)_53 }_53}
Deploying the Contract​
In order to use the contract, we need to deploy it to the network we want to use it on. In our case we are going to deploy it to emulator while developing.
Back in our flow.json
, let's add our FooToken
to the contracts
after FungibleToken
with the path of the source code:
_10"FooToken": "cadence/contracts/FooToken.cdc"
Let's also add a new deployments
section to flow.json
with the network we want to deploy it to, emulator
, the account we want it deployed to emulator-account
, and the list of contracts we want deployed in the array.
_10"deployments": {_10 "emulator": {_10 "emulator-account": ["FooToken"]_10 }_10}
Next, using the Flow CLI, we will start the emulator. As mentioned, this will give us a local development environment for the Flow Blockchain.
_10flow emulator start
Open a new terminal and run the following to deploy your project:
_10flow project deploy
Congrats, you've deployed your contract to the Flow Blockchain emulator. To read more about deploying your project to other environments, see the CLI docs.
Reading the Token’s Total Supply​
Let's now check that our total supply was initialized with 1,000 FooTokens. Go ahead and create a script called TotalSupply.cdc
using the generate
command.
_10flow generate script TotalSupply
In cadence/scripts/TotalSupply.cdc
(which was just created), let's add this code which will log the totalSupply
value from the FooToken
contract:
_10import "FooToken"_10_10access(all) fun main() {_10 log(FooToken.totalSupply)_10}
To run this using the CLI, enter this in your terminal:
_10flow scripts execute cadence/scripts/TotalSupply.cdc
In the terminal where you started the emulator, you should see 1000.0
To learn more about running scripts using Flow CLI, see the docs.
Giving Accounts the Ability to Receive Tokens​
On Flow, newly created accounts cannot receive arbitrary assets. They need to be initialized to receive resources. In our case, we want to give accounts tokens and we’ll need to create a Vault
(which acts as a receiver) on each account that we want to have the ability to receive tokens. To do this, we'll need to run a transaction which will create the vault and set it in their storage using the createEmptyVault
function we created earlier on the contract.
Let's first create the file at cadence/transactions/CreateVault.cdc
using the generate
command:
_10flow generate transaction CreateVault
Then add this code to it. This will call the createEmptyVault
function, save it in storage, and create a capability for the vault which will later allow us to read from it (To learn more about capabilities, see the Cadence docs here).
_13import "FooToken"_13import "FungibleToken"_13_13transaction {_13 prepare(acct: AuthAccount) {_13 acct.save(<- FooToken.createEmptyVault(), to: /storage/Vault)_13 acct.link<&FooToken.Vault{FungibleToken.Balance, FungibleToken.Receiver}>(/public/Vault, target: /storage/Vault)_13 }_13_13 execute {_13 log("Created")_13 }_13}
Next let's create a new emulator account using the CLI. We'll use this account to create a new vault and mint tokens into it. Run:
_10flow accounts create
Let's call it test-acct
and select "Emulator" for the network:
_10test-acct
This will have added a new account, called test-acct
to your flow.json
.
To call our create vault transaction from the CLI, we'll run the following:
_10flow transactions send ./cadence/transactions/CreateVault.cdc --signer test-acct --network emulator
To learn more about running transactions using CLI, see the docs.
Reading a Vault’s Balance​
Let's now read the balance of the newly created account (test-acct
) to check it's zero.
Create this new script file cadence/scripts/ReadVaultBalance.cdc
:
_10flow generate script ReadVaultBalance
Add this code which attempts to borrow the capability from the account requested and logs the vault balance if permitted:
_10import "FooToken"_10import "FungibleToken"_10_10access(all) fun main(account: Address) {_10 let vault = getAccount(account).getCapability(/public/Vault)_10 .borrow<&FooToken.Vault{FungibleToken.Balance}>()_10 ?? panic("Can't borrow public Vault")_10_10 log(vault.balance)_10}
To run this script using the CLI, enter the following in your terminal. Note: you'll need to replace 123
with the address created by CLI in your flow.json
for the test-acct
address.
_10flow scripts execute cadence/scripts/ReadVaultBalance.cdc 123 // change "123" to test-acct address
You should see a balance of zero logged.
Minting More Tokens​
Now that we have an account with a vault, let's mint some tokens into it using the Minter we created on the contract account.
To do this, let's create a new transaction file cadence/transactions/Minter.cdc
:
_10flow generate transaction Minter
Next, let's add the following code to the Minter.cdc
file. This code will attempt to borrow the minting capability and mint 20 new tokens into the receivers account.
_19import "FooToken"_19import "FungibleToken"_19_19transaction(receiverAccount: Address) {_19 prepare(acct: AuthAccount) {_19 let minter = acct.borrow<&FooToken.Minter>(from: /storage/Minter)_19 ?? panic("Can't borrow Minter")_19_19 let newVault <- minter.mintToken(amount: 20.0)_19_19 let receiverVault = getAccount(receiverAccount).getCapability(/public/Vault)_19 .borrow<&FooToken.Vault{FungibleToken.Receiver}>()_19 ?? panic("Could not get public Vault")_19_19 receiverVault.deposit(from: <- newVault)_19 }_19_19 execute {}_19}
To run this transaction, enter this in your terminal. Note: 123
should be replaced with address of test-acct
found in your flow.json
. This command also states to sign with our emulator-account
on the Emulator network.
_10flow transactions send ./cadence/transactions/Minter.cdc 123 --signer emulator-account --network emulator
Let's go ahead and read the vault again. Remember to replace 123
with the correct address.
_10flow scripts execute cadence/scripts/ReadVaultBalance.cdc 123
It should now say 20 tokens are in the vault.
Transferring Tokens Between Accounts​
The final functionality we'll add is the ability to transfer tokens from one account to another.
To do that, create a new cadence/transactions/Transfer.cdc
transaction file:
_10flow generate transaction Transfer
Let's add the code which states that the signer of the transaction will withdraw from their vault and put it into the receiver's vault which will be passed as a transaction argument.
_19import "FooToken"_19import "FungibleToken"_19_19transaction(receiverAccount: Address, amount: UFix64) {_19 prepare(acct: AuthAccount) {_19 let signerVault = acct.borrow<&FooToken.Vault>(from: /storage/Vault)_19 ?? panic("Couldn't get signer's Vault")_19_19 let receiverVault = getAccount(receiverAccount).getCapability(/public/Vault)_19 .borrow<&FooToken.Vault{FungibleToken.Receiver}>()_19 ?? panic("Could not get public Vault")_19_19 receiverVault.deposit(from: <- signerVault.withdraw(amount: amount))_19 }_19_19 execute {_19 log("Transferred")_19 }_19}
To send our tokens, we'll need to create a new account to send them to. Let's make one more account on emulator. Run:
_10flow accounts create
And pick the name:
_10test-acct-2
Make sure to select Emulator as the network.
Don't forget the new account will need a vault added, so let's run the following transaction to add one:
_10flow transactions send ./cadence/transactions/CreateVault.cdc --signer test-acct-2 --network emulator
Now, let's send 1 token from our earlier account to the new account. Remember to replace 123
with account address of test-acct-2
.
_10flow transactions send ./cadence/transactions/Transfer.cdc 123 "1.0" --signer test-acct --network emulator
After that, read the balance of test-acct-2
(replace the address 123
).
_10flow scripts execute cadence/scripts/ReadVaultBalance.cdc 123
You should now see 1 token in test-acct-2
account!