The following article is part of a series of articles that will help you build a DAO on the Flow blockchain using Cadence.
As the Flow ecosystem continues to grow, the need to assuring contract and external apps interoperability increases. The development of the FT Metadata standard showed how important it is for FTs on Flow to be able to communicate their features to other dApps, and the advantages of doing so in a standardized way.
The creation of a Metadata standard for fungible tokens has brought two major user benefits: discoverability and interoperability. It makes it easier for any FT to be presented in any app that displays fungible tokens, and it also allows those FTs to communicate how they can be used programmatically. In this article, I will explain the core philosophy behind Flow’s Fungible Token standard, and then we will implement it in conjunction with the MetadataViews to deploy our governance token.
What makes Flow’s Fungible Tokens different
How Flow implements fungible tokens is different from other blockchains. As a result:
Ownership is decentralized and does not rely on a central ledger
Bugs and exploits present less risk for users and less opportunity for attackers
There is no risk of integer underflow or overflow
Assets cannot be duplicated, and it is very hard for them to be lost, stolen, or destroyed
Code can be composable
Rules can be immutable
Code is not unintentionally made public
How does Flow achieve all of these? It’s all thanks to Cadence and its resource-oriented approach to smart contract development. A resource is a composite type (like a struct) that has its own defined fields and functions. The difference is that resource objects have special rules that keep them from being copied or lost.
This approach simplifies access control because instead of a central contract having to check the sender of a function call(like in Solidity), most function calls happen on resource objects stored in users’ accounts, and each user controls who is able to call the functions on resources in their account. This concept is called Capability-based security.
I’m going to implement a governance token that’s exactly like the one in this example, and I’m going to explain each section of the smart contract individually so we have a deeper understanding of it, and then we’ll run some tests
Disecting the BlockVersityToken Contract
Variables.
This is the easiest part
TotalSupply
is to track the total supply of tokens in existence at a given time, and then we have the Storage and Public Paths variables.
On Flow, accounts can store their own data and we access them through the AuthAccount
type. You can think of account storage as a “container” of data that lives at a specific path: /storage/. In a Flow account, there are 3 paths to get to certain data:
1. /storage/ — only the account owner can access this. ALL of your data lives here.
2. /public/ — available to everybody
3. /private/ — only available to the account owner and the people that the account owner gives access to
The BlockVersityToken contract implements two storage paths and two public paths, and the variables containing these paths are declared here.
Vault Resource
Each user stores an instance of the Vault
in their storage. Look at it as a resource that contains fungible tokens. Resources can only be created in the context of the contract that they are defined in, so there is no way for a malicious user to create Vaults out of thin air. A special Minter
resource needs to be defined to mint new tokens.
Inside our Vault
resource we define functions to withdraw and deposit tokens. The vault
resource keeps track of its own balance. The way tokens are transferred between vaults is by creating vault
resources that temporarily hold the number of tokens to be transferred, and then are destroyed at the end of the process. The BlockVersityToken contract implements a createEmptyVault
function that must be called by a user and store the returned Vault
in their storage in order to allow their account to be able to receive deposits of this token type.
MetadataViews
Before, there was no standard for defining FT/NFT metadata. Most implementations employed a simple {String: AnyStruct} dictionary. Due to the various methods that NFT authors use, it was impossible to create applications that use NFTs whether it be for something as simple as viewing them or as complex as integrating them into a game or application.
Now, we have the MetadataViews standard. Authors can implement it for easy consumption of metadata. This makes NFTs and FTs intrinsically more useful and valuable to the end users holding them.
The core of this standard is to add the following interface and have any FungibleToken.Vault
implement it.
Views Resolver Interface
It is required to provide an FTView view, which provides a full picture of the FT. It wraps the FTDisplay and FTVaultData views.
The FTView
contains two sub-views:
1. FTView
: A view that wraps the two other views that actually contain the data.
2. FTDisplay
: The view that contains all the information that will be needed by other dApps to display the fungible token: name, symbol, description, external URL, logos, and links to social media.
3. FTVaultData
: The view that can be used by other dApps to interact programmatically with the fungible token, providing the information about the public and private paths used by default by the token, the public and private linked types for exposing capabilities, and the function for creating new empty vaults. You can use this view to set up an account using the vault stored in another account without the need of importing the actual token contract.
Let’s see how this is implemented inside the BlockVersityToken contract.
The resolveView
function receives a type of View as a parameter and returns a structure representing the requested View.
When implementing MetadataViews
, there’s is little to change from this example. You just need to replace the variables inside MetadataViews.Media
,
FungibleTokenMetadataViews.FTDisplay
, and FungibleTokenMetadataViews.FTVaultData
. Here’s an example of how blockVersity implemented its own metadata into its governance token.
Views do not specify or require how to store your metadata, they only specify the format to query and return them, so projects can still be flexible with how they store their data.
How to read Metadata
Here are a few examples of how to read metadata. The first step will be to borrow a reference to the token’s vault stored in some account:
Using that reference you can call methods defined in the FungibleTokenMetadataViews
contract that will return you the structure containing the desired information:
Alternatively, you could call directly the resolveView(_ view: Type): AnyStruct?
method on the YourToken.Vault, but the getFTView(viewResolver: &{MetadataViews.Resolver}): FTView
, getFTDisplay(_ viewResolver: &{MetadataViews.Resolver}): FTDisplay?
and getFTVaultData(_ viewResolver: &{MetadataViews.Resolver}): FTVaultData?
defined on the FungibleMetadataViews
contract will ease the process of dealing with optional types when retrieving these views.
Finally, you can return the whole structure or just log some values from the views depending on what you are aiming for:
Minting and Burning Fungible Tokens
Let’s illustrate what minting and burning might look like for a token in Flow.
Minting or Burning a specific amount of tokens using a specific minter resource that an owner(using the Administrator resource) can control
MintandBurn Resource
function to mintTokens
tokens minted event
Each minter has a set amount of tokens that they are allowed to mint. This cannot be changed and a new minter needs to be created to add more allowance.
function to burnTokens
tokens Burnt event
Each time tokens are minted or burnt, that value is added or subtracted to or from the total supply.
After this, there’s only the initialization left.
Here’s an example of a completed implementation of a Fungible Token smart contract. In another article, we’ll learn how to display the Metadata View from our dApp.