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 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,making them perfect for representing digital objects with real value.
You can learn more about resources in the Cadence documentationand 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 balancesdirectly in their account storage. This is opposed to languagesthat track user balances in a central ledger smart contract.
When you transfer tokens from one vault to another:
- The transferor's vault creates a temporary vault holding the transfer amount.
- The original vault's balance decreases by the transfer amount.
- The recipient's vault receives the tokens from the temporary vaultand adds the temporary vault's balance to the its own balance.
- 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 standardand learn about specific features and requirements.
Learn more about interfaces here.
Setting Up a Project
To start creating a Fungible Token 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 projectusing the flow setup
command. This command initializesthe 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 deployedto 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 emulatesa 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 contractwill 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 }
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:
).We'll also include fields for standard storage and public pathsfor 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 starting 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 }
Creating 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 interfaceswhich 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 functionthat creates a vault of our FooToken type and returns it to the account.This function takes a vaultType: Type
argument that allows the callerto specify which type of Vault
they want to create.Contracts that implement multiple Vault
types can use this argument,but since your contract is only implementing one Vault
type,it can ignore the argument.
A simpler version of this function with no parametershould also be added 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 vaultwith the transfer amount and decrements the existing 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.Entitlementsare a way for developers to restrict access to privileged fields and functionsin a composite type like a resource when a reference is created for it.In this example, the withdraw()
function is always accessible to code thatcontrols the full Vault
object, but if a reference is created for it,the withdraw()
function can only be called if the referenceis authorized by the owner with FungibleToken.Withdraw
,which is a standard entitlementdefined by the FungibleToken contract:
_10 // Example of an authorized entitled reference to a FungibleToken.Vault _10 <auth(FungibleToken.Withdraw) &{FungibleToken.Vault}>
Entitlements are important to understand because they are what protectsprivileged functionality in your resource objects from being accessed by third-parties.It is recommended to read the entitlements documentationto understand how to use the feature properly.
References can be freely up-casted and down-casted in Cadence, so it is importantfor privileged functionality to be protected by an entitlement so that it canonly be accessed if it is authorized.
In addition to withdrawing, the vault also needs a way to deposit.We'll typecastto 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 emissionof events for projects, so there is no need for you to add any eventsto 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
interfacein the FungibleToken
contract whenever the relevant function is called on any implementation.
One important piece to understand about the Burned
event in particularis that in order for it to be emitted when a Vault
is burned, it needs tobe burnt via the Burner
contract's burn()
method.
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 }
Adding Support for Metadata Views
The Fungible Token standard also enforces that implementationsprovide functionality to return a set of standard views about the tokensvia the ViewResolverand FungibleTokenMetadataViews definitions.(You will need to add these imports to your contract now)These provide developers with standard ways of representing metadataabout a given token such as supply, token symbols, website links, and standardaccount paths and types that third-parties can access in a standard way.You can see the metadata views documentationfor a more thorough guide using a 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/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 }
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.
_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 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 supplyso 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 should 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/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 }
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 networkwe 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 get_total_supply.cdc
using 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 using 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 should see Result: 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 createa Vault
(which acts as a receiver) on each account that we wantto have the ability to receive tokens. To do this, we'll need to run a transactionwhich will create the vault and set it in their storageusing the createEmptyVault()
function we created earlier on the contract.
Let's first create the file at cadence/transactions/setup_ft_account.cdc
using 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 docs here).
_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 transactionsthat you can use to setup an account for ANY fungible token using metadata views!You should check those out and try to use generic transactions whenever it is possible.
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 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 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/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:
_13 import "FungibleToken" _13 import "FooToken" _13 import "FungibleTokenMetadataViews" _13 _13 access(all) fun main(address: Address): UFix64 { _13 let vaultData = FooToken.resolveContractView(resourceType: nil, viewType: Type<FungibleTokenMetadataViews.FTVaultData>()) as! FungibleTokenMetadataViews.FTVaultData? _13 ?? panic("Could not get vault data view for the contract") _13 _13 return getAccount(address).capabilities.borrow<&{FungibleToken.Balance}>( _13 vaultData.metadataPath _13 )?.balance _13 ?? panic("Could not borrow Balance reference to the Vault") _13 }
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 CLIin 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 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 itusing 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 capabilityand mint 20 new tokens into the receivers account.
_30 import "FungibleToken" _30 import "FooToken" _30 _30 transaction(recipient: Address, amount: UFix64) { _30 _30 /// Reference to the Example Token Minter Resource object _30 let tokenMinter: &FooToken.Minter _30 _30 /// Reference to the Fungible Token Receiver of the recipient _30 let tokenReceiver: &{FungibleToken.Receiver} _30 _30 prepare(signer: auth(BorrowValue) &Account) { _30 _30 // Borrow a reference to the admin object _30 self.tokenMinter = signer.storage.borrow<&FooToken.Minter>(from: FooToken.MinterStoragePath) _30 ?? panic("Signer is not the token admin") _30 _30 self.tokenReceiver = getAccount(recipient).capabilities.borrow<&{FungibleToken.Receiver}>(FooToken.VaultPublicPath) _30 ?? panic("Could not borrow receiver reference to the Vault") _30 } _30 _30 execute { _30 _30 // Create mint tokens _30 let mintedVault <- self.tokenMinter.mintTokens(amount: amount) _30 _30 // Deposit them to the receiever _30 self.tokenReceiver.deposit(from: <-mintedVault) _30 } _30 }
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/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 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_footoken.cdc
transaction file:
_10 flow generate transaction transfer_footoken
Let's add the code which states that the signer of the transactionwill withdraw from their vault and put it into the receiver's vaultwhich will be passed as a transaction argument.
_31 import "FungibleToken" _31 import "FooToken" _31 _31 transaction(to: Address, amount: UFix64) { _31 _31 // The Vault resource that holds the tokens that are being transferred _31 let sentVault: @{FungibleToken.Vault} _31 _31 prepare(signer: auth(BorrowValue) &Account) { _31 _31 // Get a reference to the signer's stored vault _31 let vaultRef = signer.storage.borrow<auth(FungibleToken.Withdraw) &FooToken.Vault>(from: FooToken.VaultStoragePath) _31 ?? panic("Could not borrow reference to the owner's Vault!") _31 _31 // Withdraw tokens from the signer's stored vault _31 self.sentVault <- vaultRef.withdraw(amount: amount) _31 } _31 _31 execute { _31 _31 // Get the recipient's public account object _31 let recipient = getAccount(to) _31 _31 // Get a reference to the recipient's Receiver _31 let receiverRef = recipient.capabilities.borrow<&{FungibleToken.Receiver}>(FooToken.VaultPublicPath) _31 ?? panic("Could not borrow receiver reference to the recipient's Vault") _31 _31 // Deposit the withdrawn tokens in the recipient's receiver _31 receiverRef.deposit(from: <-self.sentVault) _31 } _31 }
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 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_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 should now see 1 token in test-acct-2
account!
The transfer transaction also has a generic version that developers are encouraged to use!