Skip to main content

How to Create a Fungible Token on Flow

info

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:

  1. A temporary vault (or a temporary piggy bank) is created holding the transfer amount.
  2. The original vault's balance decreases by the transfer amount.
  3. The recipient's vault receives the tokens from the temporary vault.
  4. 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):


_10
flow setup FooToken

Upon execution, the command will generate the following directory structure:


_10
/cadence
_10
/contracts
_10
/scripts
_10
/transactions
_10
/tests
_10
flow.json

Now, navigate into the project directory:


_10
cd 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:


_10
flow 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.


_10
import "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
_10
access(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.


_15
import "FungibleToken"
_15
_15
access(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.


_11
import "FungibleToken"
_11
_11
access(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.


_10
import "FungibleToken"
_10
_10
access(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:


_22
import "FungibleToken"
_22
_22
access(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:


_26
import "FungibleToken"
_26
_26
access(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.


_18
import "FungibleToken"
_18
_18
access(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 mintTokenfunction which can increase the total supply of the token.


_17
import "FungibleToken"
_17
_17
access(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.


_11
import "FungibleToken"
_11
_11
access(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:


_53
import "FungibleToken"
_53
_53
access(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.


_10
flow emulator start

Open a new terminal and run the following to deploy your project:


_10
flow 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.


_10
flow 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:


_10
import "FooToken"
_10
_10
access(all) fun main() {
_10
log(FooToken.totalSupply)
_10
}

To run this using the CLI, enter this in your terminal:


_10
flow 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:


_10
flow 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).


_13
import "FooToken"
_13
import "FungibleToken"
_13
_13
transaction {
_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:


_10
flow accounts create

Let's call it test-acct and select "Emulator" for the network:


_10
test-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:


_10
flow 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:


_10
flow generate script ReadVaultBalance

Add this code which attempts to borrow the capability from the account requested and logs the vault balance if permitted:


_10
import "FooToken"
_10
import "FungibleToken"
_10
_10
access(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.


_10
flow 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:


_10
flow 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.


_19
import "FooToken"
_19
import "FungibleToken"
_19
_19
transaction(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.


_10
flow 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.


_10
flow 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:


_10
flow 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.


_19
import "FooToken"
_19
import "FungibleToken"
_19
_19
transaction(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:


_10
flow accounts create

And pick the name:


_10
test-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:


_10
flow 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.


_10
flow 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).


_10
flow scripts execute cadence/scripts/ReadVaultBalance.cdc 123

You should now see 1 token in test-acct-2 account!

More