LLM Notice: This documentation site supports content negotiation for AI agents. Request any page with Accept: text/markdown or Accept: text/plain header to receive Markdown instead of HTML. Alternatively, append ?format=md to any URL. All markdown files are available at /md/ prefix paths. For all content in one file, visit /llms-full.txt
Skip to main content

Creating a Fungible Token

info

This guide is an in-depth tutorial on launching a Fungible Token contract from scratch. To launch in two minutes with 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. They serve as both a means of payment and an incentive for network participation. They can take on various roles, such as currencies, structured financial instruments, shares of index funds, and even voting rights in decentralized autonomous organizations.

Vaults on Flow

On the Flow blockchain and in the Cadence programming language, fungible tokens are stored in structures called resources. Resources are objects in Cadence that store data, but have special restrictions about how they can be stored and transferred, which makes them perfect to represent digital objects with real value.

You can learn more about resources in the Cadence documentation and Cadence tutorials.

For fungible tokens specifically, tokens are represented by a resource type called a Vault:


_10
access(all) resource interface Vault {
_10
_10
/// Field that tracks the balance of a vault
_10
access(all) var balance: UFix64
_10
_10
}

Think of a Vault as a digital piggy bank. Users who own fungible tokens store vault objects that track their balances directly in their account storage. This is opposed to languages that track user balances in a central ledger smart contract.

When you transfer tokens from one vault to another:

  1. The transferor's vault creates a temporary vault that contains 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 and adds the temporary vault's balance to the its own balance.
  4. The temporary vault is then destroyed.

This process ensures secure and accurate token transfers on the Flow blockchain.

Fungible token standard

The [Fungible Token Standard] defines how a fungible token should behave 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.

Clink the link to the fungible token standard to see the full standard and learn about specific features and requirements.

Learn more about interfaces here.

Set up a project

To create a fungible token on the Flow blockchain, you'll first need some tools and configurations in place.

Install 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, follow the Flow CLI installation guide.

Initialize a new project

info

💡 Here is a link to the completed code if you want to skip ahead or reference as you follow along.

After you've installed the Flow CLI, you can set up a new project with the flow init 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 init FooToken

Select Basic Cadence project (no dependencies).

When you execute the command, it generates 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'll 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.

We'll also need to add the addresses for ViewResolver, MetadataViews, and FungibleTokenMetadataViews, which are other important contracts to use. These contracts are deployed to the Flow emulator by default, so there is not need to copy their code into your repo. The addresses below are the addresses in the emulator that your contract will import them from.


_22
"contracts": {
_22
"FungibleToken": {
_22
"aliases": {
_22
"emulator": "0xee82856bf20e2aa6"
_22
}
_22
},
_22
"FungibleTokenMetadataViews": {
_22
"aliases": {
_22
"emulator": "0xee82856bf20e2aa6"
_22
}
_22
},
_22
"ViewResolver": {
_22
"aliases": {
_22
"emulator": "0xf8d6e0586b0a20c7"
_22
}
_22
},
_22
"MetadataViews": {
_22
"aliases": {
_22
"emulator": "0xf8d6e0586b0a20c7"
_22
}
_22
}
_22
}

Write Our token contract

Next let's create a FooToken contract at cadence/contract/FooToken.cdc with 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 (to do this, it sets it after the FooToken:). We'll also include fields for standard storage and public paths for our resource definitions.

In our init — which runs on the contract's first deployment and is used to set initial values — let's set an initial total supply of 1,000 tokens for this example.


_16
// ...previous code
_16
_16
access(all) contract FooToken: FungibleToken {
_16
access(all) var totalSupply: UFix64
_16
_16
access(all) let VaultStoragePath: StoragePath
_16
access(all) let VaultPublicPath: PublicPath
_16
access(all) let MinterStoragePath: StoragePath
_16
_16
init() {
_16
self.totalSupply = 1000.0
_16
self.VaultStoragePath = /storage/fooTokenVault
_16
self.VaultPublicPath = /public/fooTokenVault
_16
self.MinterStoragePath = /storage/fooTokenMinter
_16
}
_16
}

Create a vault

Inside of this contract, we'll need to create a resource for a Vault. The FungibleToken standard requires that your vault implements the FungibleToken.Vault interface. This interface inherits from many other interfaces, which enforce different functionality that you can learn about in the standard.


_16
import "FungibleToken"
_16
_16
access(all) contract FooToken: FungibleToken {
_16
// ...totalSupply and path code
_16
_16
access(all) resource Vault: FungibleToken.Vault {
_16
_16
access(all) var balance: UFix64
_16
_16
init(balance: UFix64) {
_16
self.balance = balance
_16
}
_16
}
_16
_16
// ...init code
_16
}

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. This function takes a vaultType: Type argument that allows the caller to specify which type of Vault they want to create. Contracts that implement multiple Vault types can use this argument, but since your contract only implements one Vault type, it can ignore the argument.

You should also add a simpler version of this function with no parameter to your Vault implementation.


_24
import "FungibleToken"
_24
_24
access(all) contract FooToken: FungibleToken {
_24
// ...other code
_24
_24
access(all) resource Vault: FungibleToken.Vault {
_24
_24
// ...other vault code
_24
_24
access(all) fun createEmptyVault(): @FooToken.Vault {
_24
return <-create Vault(balance: 0.0)
_24
}
_24
_24
// ...vault init code
_24
}
_24
_24
// ...other code
_24
_24
access(all) fun createEmptyVault(vaultType: Type): @FooToken.Vault {
_24
return <- create Vault(balance: 0.0)
_24
}
_24
_24
// ...FooToken.init() code
_24
}

Inside our Vault resource, we also need a way to withdraw balances. To do that, we need to add a withdraw() function that returns a new vault with the transfer amount and decrements the current balance.


_20
import "FungibleToken"
_20
_20
access(all) contract FooToken: FungibleToken {
_20
_20
// ...previous code
_20
_20
access(all) resource Vault: FungibleToken.Vault {
_20
_20
// ...other vault code
_20
_20
access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @FooToken.Vault {
_20
self.balance = self.balance - amount
_20
return <-create Vault(balance: amount)
_20
}
_20
_20
// ...vault init code
_20
}
_20
_20
// ...additional code
_20
}

As you can see, this function has an access(FungibleToken.Withdraw) access modifier. This is an example of entitlements in Cadence. Entitlements are a way for developers to restrict access to privileged fields and functions in a composite type like a resource when a reference is created for it. They are what protects third-party access to the privileged functionality in your resource objects. We recommend that you read the Entitlements documentation to understand how to use the feature properly.

Referencescan be freely up-casted and down-casted in Cadence, so it is important for privileged functionality to be protected by an entitlement so that it can only be accessed if it is authorized.

In this example, the withdraw() function is always accessible to code that controls the full Vault object, but if a reference is created for it, the withdraw() function can only be called if the reference is authorized by the owner with FungibleToken.Withdraw, which is a standard entitlement defined by the FungibleToken contract:


_10
// Example of an authorized entitled reference to a FungibleToken.Vault
_10
<auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>

In addition to withdrawl, the vault also needs a way to deposit. We'll typecast to make sure we are dealing with the correct token, update the vault balance, and destroy the vault. Add this code to your resource:


_22
import "FungibleToken"
_22
_22
access(all) contract FooToken: FungibleToken {
_22
_22
// ...previous code
_22
_22
access(all) resource Vault: FungibleToken.Vault {
_22
_22
// ...other vault code
_22
_22
access(all) fun deposit(from: @{FungibleToken.Vault}) {
_22
let vault <- from as! @FooToken.Vault
_22
self.balance = self.balance + vault.balance
_22
destroy vault
_22
}
_22
_22
// ...vault init
_22
_22
}
_22
_22
// ...additional code
_22
}

Many projects rely on events the signal when withdrawals, deposits, or burns happen. Luckily, the FungibleToken standard handles the definition and emission of events for projects, so there is no need for you to add any events to your implementation for withdraw, deposit, and burn.

Here are the FungibleToken event definitions:


_10
/// The event that is emitted when tokens are withdrawn from a Vault
_10
access(all) event Withdrawn(type: String, amount: UFix64, from: Address?, fromUUID: UInt64, withdrawnUUID: UInt64, balanceAfter: UFix64)
_10
_10
/// The event that is emitted when tokens are deposited to a Vault
_10
access(all) event Deposited(type: String, amount: UFix64, to: Address?, toUUID: UInt64, depositedUUID: UInt64, balanceAfter: UFix64)
_10
_10
/// Event that is emitted when the global burn method is called with a non-zero balance
_10
access(all) event Burned(type: String, amount: UFix64, fromUUID: UInt64)

These events are emitted by the Vault interface in the FungibleToken contract whenever the relevant function is called on any implementation.

One important piece to understand about the Burned event in particular is that for it to be emitted when a Vault is burned, it needs to be burnt via the Burner contract's burn() method.

The Burner contract defines a standard that all projects should use to handle the destruction of any resource. It allows projects to define custom logic that can be executed when a resource is destroyed, like emitting events, or update a field in the contract to show that the resource was destroyed.

This will call the resource's burnCallback() function, which emits the event. You'll need to also add this function to your token contract now:


_24
import "FungibleToken"
_24
_24
access(all) contract FooToken: FungibleToken {
_24
_24
// ...previous code
_24
_24
access(all) resource Vault: FungibleToken.Vault {
_24
_24
// ...other vault code
_24
_24
/// Called when a fungible token is burned via the `Burner.burn()` method
_24
access(contract) fun burnCallback() {
_24
if self.balance > 0.0 {
_24
FooToken.totalSupply = FooToken.totalSupply - self.balance
_24
}
_24
self.balance = 0.0
_24
}
_24
_24
// ...vault init
_24
_24
}
_24
_24
// ...additional code
_24
}

If you ever need to destroy a Vault with a non-zero balance, you should destroy it via the Burner.burn method so this important function can be called.

There are three other utility methods that need to be added to your Vault to get various information:


_33
import "FungibleToken"
_33
_33
access(all) contract FooToken: FungibleToken {
_33
_33
// ...previous code
_33
_33
access(all) resource Vault: FungibleToken.Vault {
_33
_33
// ...other vault code
_33
_33
/// getSupportedVaultTypes optionally returns a list of vault types that this receiver accepts
_33
access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
_33
let supportedTypes: {Type: Bool} = {}
_33
supportedTypes[self.getType()] = true
_33
return supportedTypes
_33
}
_33
_33
/// Says if the Vault can receive the provided type in the deposit method
_33
access(all) view fun isSupportedVaultType(type: Type): Bool {
_33
return self.getSupportedVaultTypes()[type] ?? false
_33
}
_33
_33
/// Asks if the amount can be withdrawn from this vault
_33
access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
_33
return amount <= self.balance
_33
}
_33
_33
// ...vault init
_33
_33
}
_33
_33
// ...additional code
_33
}

Add support for metadata views

The Fungible Token standard also enforces that implementations provide functionality to return a set of standard views about the tokens via the ViewResolver and FungibleTokenMetadataViews definitions. (You will need to add these imports to your contract now.) These provide developers with standard ways to represent metadata about a given token such as supply, token symbols, website links, and standard account paths and types that third-parties can access in a standard way.

You can see the metadata views documentation for a more thorough guide that uses an NFT contract as an example. For now, you can add this code to your contract to support the important metadata views:


_83
import "FungibleToken"
_83
_83
// Add these imports
_83
import "MetadataViews"
_83
import "FungibleTokenMetadataViews"
_83
_83
access(all) contract FooToken: FungibleToken {
_83
// ...other code
_83
_83
access(all) view fun getContractViews(resourceType: Type?): [Type] {
_83
return [
_83
Type<FungibleTokenMetadataViews.FTView>(),
_83
Type<FungibleTokenMetadataViews.FTDisplay>(),
_83
Type<FungibleTokenMetadataViews.FTVaultData>(),
_83
Type<FungibleTokenMetadataViews.TotalSupply>()
_83
]
_83
}
_83
_83
access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
_83
switch viewType {
_83
case Type<FungibleTokenMetadataViews.FTView>():
_83
return FungibleTokenMetadataViews.FTView(
_83
ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?,
_83
ftVaultData: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
_83
)
_83
case Type<FungibleTokenMetadataViews.FTDisplay>():
_83
let media = MetadataViews.Media(
_83
file: MetadataViews.HTTPFile(
_83
// Change this to your own SVG image
_83
url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg"
_83
),
_83
mediaType: "image/svg+xml"
_83
)
_83
let medias = MetadataViews.Medias([media])
_83
return FungibleTokenMetadataViews.FTDisplay(
_83
// Change these to represent your own token
_83
name: "Example Foo Token",
_83
symbol: "EFT",
_83
description: "This fungible token is used as an example to help you develop your next FT #onFlow.",
_83
externalURL: MetadataViews.ExternalURL("https://developers.flow.com/build/cadence/guides/fungible-token"),
_83
logos: medias,
_83
socials: {
_83
"twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain")
_83
}
_83
)
_83
case Type<FungibleTokenMetadataViews.FTVaultData>():
_83
return FungibleTokenMetadataViews.FTVaultData(
_83
storagePath: self.VaultStoragePath,
_83
receiverPath: self.VaultPublicPath,
_83
metadataPath: self.VaultPublicPath,
_83
receiverLinkedType: Type<&FooToken.Vault>(),
_83
metadataLinkedType: Type<&FooToken.Vault>(),
_83
createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
_83
return <-FooToken.createEmptyVault(vaultType: Type<@FooToken.Vault>())
_83
})
_83
)
_83
case Type<FungibleTokenMetadataViews.TotalSupply>():
_83
return FungibleTokenMetadataViews.TotalSupply(
_83
totalSupply: FooToken.totalSupply
_83
)
_83
}
_83
return nil
_83
}
_83
_83
// ...other code
_83
_83
access(all) resource Vault: FungibleToken.Vault {
_83
_83
// ...other vault code
_83
_83
access(all) view fun getViews(): [Type] {
_83
return FooToken.getContractViews(resourceType: nil)
_83
}
_83
_83
access(all) fun resolveView(_ view: Type): AnyStruct? {
_83
return FooToken.resolveContractView(resourceType: nil, viewType: view)
_83
}
_83
_83
// ...other vault code
_83
}
_83
_83
// ...other FooToken code
_83
}

Create 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 mint 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.


_31
import "FungibleToken"
_31
import "MetadataViews"
_31
import "FungibleTokenMetadataViews"
_31
_31
access(all) contract FooToken: FungibleToken {
_31
_31
// ...additional contract code
_31
_31
// Add this event
_31
access(all) event TokensMinted(amount: UFix64, type: String)
_31
_31
/// Minter
_31
///
_31
/// Resource object that token admin accounts can hold to mint new tokens.
_31
///
_31
access(all) resource Minter {
_31
/// mintTokens
_31
///
_31
/// Function that mints new tokens, adds them to the total supply,
_31
/// and returns them to the calling context.
_31
///
_31
access(all) fun mintTokens(amount: UFix64): @FooToken.Vault {
_31
FooToken.totalSupply = FooToken.totalSupply + amount
_31
let vault <-create Vault(balance: amount)
_31
emit TokensMinted(amount: amount, type: vault.getType().identifier)
_31
return <-vault
_31
}
_31
}
_31
_31
// ...additional contract code
_31
}

We also want to decide which accounts 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.


_13
import "FungibleToken"
_13
import "MetadataViews"
_13
import "FungibleTokenMetadataViews"
_13
_13
access(all) contract FooToken: FungibleToken {
_13
_13
// ...additional contract code
_13
_13
init() {
_13
self.totalSupply = 1000.0 // existed before
_13
self.account.save(<- create Minter(), to: self.MinterStoragePath)
_13
}
_13
}

After each of these steps, your FooToken.cdc contract file will now look like this:


_172
import "FungibleToken"
_172
import "MetadataViews"
_172
import "FungibleTokenMetadataViews"
_172
_172
access(all) contract FooToken: FungibleToken {
_172
_172
/// The event that is emitted when new tokens are minted
_172
access(all) event TokensMinted(amount: UFix64, type: String)
_172
_172
/// Total supply of FooTokens in existence
_172
access(all) var totalSupply: UFix64
_172
_172
/// Storage and Public Paths
_172
access(all) let VaultStoragePath: StoragePath
_172
access(all) let VaultPublicPath: PublicPath
_172
access(all) let ReceiverPublicPath: PublicPath
_172
access(all) let MinterStoragePath: StoragePath
_172
_172
access(all) view fun getContractViews(resourceType: Type?): [Type] {
_172
return [
_172
Type<FungibleTokenMetadataViews.FTView>(),
_172
Type<FungibleTokenMetadataViews.FTDisplay>(),
_172
Type<FungibleTokenMetadataViews.FTVaultData>(),
_172
Type<FungibleTokenMetadataViews.TotalSupply>()
_172
]
_172
}
_172
_172
access(all) fun resolveContractView(resourceType: Type?, viewType: Type): AnyStruct? {
_172
switch viewType {
_172
case Type<FungibleTokenMetadataViews.FTView>():
_172
return FungibleTokenMetadataViews.FTView(
_172
ftDisplay: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTDisplay>()) as! FungibleTokenMetadataViews.FTDisplay?,
_172
ftVaultData: self.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
_172
)
_172
case Type<FungibleTokenMetadataViews.FTDisplay>():
_172
let media = MetadataViews.Media(
_172
file: MetadataViews.HTTPFile(
_172
// Change this to your own SVG image
_172
url: "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg"
_172
),
_172
mediaType: "image/svg+xml"
_172
)
_172
let medias = MetadataViews.Medias([media])
_172
return FungibleTokenMetadataViews.FTDisplay(
_172
// Change these to represent your own token
_172
name: "Example Foo Token",
_172
symbol: "EFT",
_172
description: "This fungible token is used as an example to help you develop your next FT #onFlow.",
_172
externalURL: MetadataViews.ExternalURL("https://developers.flow.com/build/cadence/guides/fungible-token"),
_172
logos: medias,
_172
socials: {
_172
"twitter": MetadataViews.ExternalURL("https://twitter.com/flow_blockchain")
_172
}
_172
)
_172
case Type<FungibleTokenMetadataViews.FTVaultData>():
_172
return FungibleTokenMetadataViews.FTVaultData(
_172
storagePath: self.VaultStoragePath,
_172
receiverPath: self.VaultPublicPath,
_172
metadataPath: self.VaultPublicPath,
_172
receiverLinkedType: Type<&FooToken.Vault>(),
_172
metadataLinkedType: Type<&FooToken.Vault>(),
_172
createEmptyVaultFunction: (fun(): @{FungibleToken.Vault} {
_172
return <-FooToken.createEmptyVault(vaultType: Type<@FooToken.Vault>())
_172
})
_172
)
_172
case Type<FungibleTokenMetadataViews.TotalSupply>():
_172
return FungibleTokenMetadataViews.TotalSupply(
_172
totalSupply: FooToken.totalSupply
_172
)
_172
}
_172
return nil
_172
}
_172
_172
access(all) resource Vault: FungibleToken.Vault {
_172
_172
/// The total balance of this vault
_172
access(all) var balance: UFix64
_172
_172
// initialize the balance at resource creation time
_172
init(balance: UFix64) {
_172
self.balance = balance
_172
}
_172
_172
/// Called when a fungible token is burned via the `Burner.burn()` method
_172
access(contract) fun burnCallback() {
_172
if self.balance > 0.0 {
_172
FooToken.totalSupply = FooToken.totalSupply - self.balance
_172
}
_172
self.balance = 0.0
_172
}
_172
_172
access(all) view fun getViews(): [Type] {
_172
return FooToken.getContractViews(resourceType: nil)
_172
}
_172
_172
access(all) fun resolveView(_ view: Type): AnyStruct? {
_172
return FooToken.resolveContractView(resourceType: nil, viewType: view)
_172
}
_172
_172
access(all) view fun getSupportedVaultTypes(): {Type: Bool} {
_172
let supportedTypes: {Type: Bool} = {}
_172
supportedTypes[self.getType()] = true
_172
return supportedTypes
_172
}
_172
_172
access(all) view fun isSupportedVaultType(type: Type): Bool {
_172
return self.getSupportedVaultTypes()[type] ?? false
_172
}
_172
_172
access(all) view fun isAvailableToWithdraw(amount: UFix64): Bool {
_172
return amount <= self.balance
_172
}
_172
_172
access(FungibleToken.Withdraw) fun withdraw(amount: UFix64): @FooToken.Vault {
_172
self.balance = self.balance - amount
_172
return <-create Vault(balance: amount)
_172
}
_172
_172
access(all) fun deposit(from: @{FungibleToken.Vault}) {
_172
let vault <- from as! @FooToken.Vault
_172
self.balance = self.balance + vault.balance
_172
vault.balance = 0.0
_172
destroy vault
_172
}
_172
_172
access(all) fun createEmptyVault(): @FooToken.Vault {
_172
return <-create Vault(balance: 0.0)
_172
}
_172
}
_172
_172
access(all) resource Minter {
_172
/// mintTokens
_172
///
_172
/// Function that mints new tokens, adds them to the total supply,
_172
/// and returns them to the calling context.
_172
///
_172
access(all) fun mintTokens(amount: UFix64): @FooToken.Vault {
_172
FooToken.totalSupply = FooToken.totalSupply + amount
_172
let vault <-create Vault(balance: amount)
_172
emit TokensMinted(amount: amount, type: vault.getType().identifier)
_172
return <-vault
_172
}
_172
}
_172
_172
access(all) fun createEmptyVault(vaultType: Type): @FooToken.Vault {
_172
return <- create Vault(balance: 0.0)
_172
}
_172
_172
init() {
_172
self.totalSupply = 1000.0
_172
_172
self.VaultStoragePath = /storage/fooTokenVault
_172
self.VaultPublicPath = /public/fooTokenVault
_172
self.MinterStoragePath = /storage/fooTokenMinter
_172
_172
// Create the Vault with the total supply of tokens and save it in storage
_172
//
_172
let vault <- create Vault(balance: self.totalSupply)
_172
emit TokensMinted(amount: vault.balance, type: vault.getType().identifier)
_172
self.account.storage.save(<-vault, to: self.VaultStoragePath)
_172
_172
// Create a public capability to the stored Vault that exposes
_172
// the `deposit` method and getAcceptedTypes method through the `Receiver` interface
_172
// and the `balance` method through the `Balance` interface
_172
//
_172
let fooTokenCap = self.account.capabilities.storage.issue<&FooToken.Vault>(self.VaultStoragePath)
_172
self.account.capabilities.publish(fooTokenCap, at: self.VaultPublicPath)
_172
_172
let minter <- create Minter()
_172
self.account.storage.save(<-minter, to: self.MinterStoragePath)
_172
}
_172
}

Deploy the contract

To use the contract, we need to deploy it to the network we want to use it on. In our case, we'll deploy it to emulator while we develop it.

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, via 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 how to deploy your project to other environments, see the Deploy Project Contracts with CLI docs.

Read 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 get_total_supply.cdc with the generate command.


_10
flow generate script get_total_supply

In cadence/scripts/get_total_supply.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(): UFix64 {
_10
return FooToken.totalSupply
_10
}

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


_10
flow scripts execute cadence/scripts/get_total_supply.cdc

In the terminal where you started the emulator, you will see Result: 1000.0

To learn more about how to run scripts with Flow CLI, see the Execute Scripts in Flow CLI docs.

Give 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 with the createEmptyVault() function we created earlier on the contract.

Let's first create the file at cadence/transactions/setup_ft_account.cdc with the generate command:


_10
flow generate transaction setup_ft_account

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 Capabilities docs


_24
import "FungibleToken"
_24
import "FooToken"
_24
_24
transaction () {
_24
_24
prepare(signer: auth(BorrowValue, IssueStorageCapabilityController, PublishCapability, SaveValue) &Account) {
_24
_24
// Return early if the account already stores a FooToken Vault
_24
if signer.storage.borrow<&FooToken.Vault>(from: FooToken.VaultStoragePath) != nil {
_24
return
_24
}
_24
_24
let vault <- FooToken.createEmptyVault(vaultType: Type<@FooToken.Vault>())
_24
_24
// Create a new FooToken Vault and put it in storage
_24
signer.storage.save(<-vault, to: FooToken.VaultStoragePath)
_24
_24
// Create a public capability to the Vault that exposes the Vault interfaces
_24
let vaultCap = signer.capabilities.storage.issue<&FooToken.Vault>(
_24
FooToken.VaultStoragePath
_24
)
_24
signer.capabilities.publish(vaultCap, at: FooToken.VaultPublicPath)
_24
}
_24
}

There are also examples of generic transactions that you can use to setup an account for ANY fungible token with metadata views! Check those out and try to use generic transactions whenever it is possible.

Next let's create a new emulator account with 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 setup account transaction from the CLI, we'll run the following:


_10
flow transactions send ./cadence/transactions/setup_ft_account.cdc --signer test-acct --network emulator

To learn more about how to run transactions with Flow CLI, see the Send a Transaction docs.

Read a vault's balance

Let's now read the balance of the newly-created account (test-acct) to check that it's zero.

Create this new script file cadence/scripts/get_footoken_balance.cdc:


_10
flow generate script get_footoken_balance

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


_15
import "FungibleToken"
_15
import "FooToken"
_15
import "FungibleTokenMetadataViews"
_15
_15
access(all) fun main(address: Address): UFix64 {
_15
let vaultData = FooToken.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData?
_15
?? panic("Could not get FTVaultData view for the FooToken contract")
_15
_15
return getAccount(address).capabilities.borrow<&{FungibleToken.Balance}>(
_15
vaultData.metadataPath
_15
)?.balance
_15
?? panic("Could not borrow a reference to the FooToken Vault in account "
_15
.concat(address.toString()).concat(" at path ").concat(vaultData.metadataPath.toString())
_15
.concat(". Make sure you are querying an address that has an FooToken Vault set up properly."))
_15
}

To run this script with the CLI, enter the following in your terminal. You'll need to replace 123 with the address created by Flow CLI in your flow.json for the test-acct address.


_10
flow scripts execute cadence/scripts/get_footoken_balance.cdc 123 // change "123" to test-acct address

You will see a balance of zero logged.

Mint more tokens

Now that we have an account with a vault, let's mint some tokens into it with the Minter we created on the contract account.

To do this, let's create a new transaction file cadence/transactions/mint_footoken.cdc:


_10
flow generate transaction mint_footoken

Next, let's add the following code to the mint_footoken.cdc file. This code will attempt to borrow the minting capability and mint 20 new tokens into the receivers account.


_33
import "FungibleToken"
_33
import "FooToken"
_33
_33
transaction(recipient: Address, amount: UFix64) {
_33
_33
/// Reference to the Example Token Minter Resource object
_33
let tokenMinter: &FooToken.Minter
_33
_33
/// Reference to the Fungible Token Receiver of the recipient
_33
let tokenReceiver: &{FungibleToken.Receiver}
_33
_33
prepare(signer: auth(BorrowValue) &Account) {
_33
_33
// Borrow a reference to the admin object
_33
self.tokenMinter = signer.storage.borrow<&FooToken.Minter>(from: FooToken.MinterStoragePath)
_33
?? panic("Cannot mint: Signer does not store the FooToken Minter in their account!")
_33
_33
self.tokenReceiver = getAccount(recipient).capabilities.borrow<&{FungibleToken.Receiver}>(FooToken.VaultPublicPath)
_33
?? panic("Could not borrow a Receiver reference to the FungibleToken Vault in account "
_33
.concat(recipient.toString()).concat(" at path ").concat(FooToken.VaultPublicPath.toString())
_33
.concat(". Make sure you are sending to an address that has ")
_33
.concat("a FungibleToken Vault set up properly at the specified path."))
_33
}
_33
_33
execute {
_33
_33
// Create mint tokens
_33
let mintedVault <- self.tokenMinter.mintTokens(amount: amount)
_33
_33
// Deposit them to the receiever
_33
self.tokenReceiver.deposit(from: <-mintedVault)
_33
}
_33
}

To run this transaction, enter this in your terminal. Replace 123 with the test-acct address 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/mint_footoken.cdc 123 20.0 --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/get_footoken_balance.cdc 123

It will now say 20 tokens are in the vault.

Transfer 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_footoken.cdc transaction file:


_10
flow generate transaction transfer_footoken

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.


_36
import "FungibleToken"
_36
import "FooToken"
_36
_36
transaction(to: Address, amount: UFix64) {
_36
_36
// The Vault resource that holds the tokens that are being transferred
_36
let sentVault: @{FungibleToken.Vault}
_36
_36
prepare(signer: auth(BorrowValue) &Account) {
_36
_36
// Get a reference to the signer's stored vault
_36
let vaultRef = signer.storage.borrow<auth(FungibleToken.Withdraw) &FooToken.Vault>(from: FooToken.VaultStoragePath)
_36
?? panic("The signer does not store an FooToken.Vault object at the path "
_36
.concat(FooToken.VaultStoragePath.toString())
_36
.concat(". The signer must initialize their account with this vault first!"))
_36
_36
// Withdraw tokens from the signer's stored vault
_36
self.sentVault <- vaultRef.withdraw(amount: amount)
_36
}
_36
_36
execute {
_36
_36
// Get the recipient's public account object
_36
let recipient = getAccount(to)
_36
_36
// Get a reference to the recipient's Receiver
_36
let receiverRef = recipient.capabilities.borrow<&{FungibleToken.Receiver}>(FooToken.VaultPublicPath)
_36
?? panic("Could not borrow a Receiver reference to the FooToken Vault in account "
_36
.concat(recipient.toString()).concat(" at path ").concat(FooToken.VaultPublicPath.toString())
_36
.concat(". Make sure you are sending to an address that has ")
_36
.concat("a FooToken Vault set up properly at the specified path."))
_36
_36
// Deposit the withdrawn tokens in the recipient's receiver
_36
receiverRef.deposit(from: <-self.sentVault)
_36
}
_36
}

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/setup_ft_account.cdc --signer test-acct-2 --network emulator

Now, let's send one 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_footoken.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/get_footoken_balance.cdc 123

You will now see one token in test-acct-2 account!

The transfer transaction also has a generic version that developers are encouraged to use!

More