
Security News
Package Maintainers Call for Improvements to GitHub’s New npm Security Plan
Maintainers back GitHub’s npm security overhaul but raise concerns about CI/CD workflows, enterprise support, and token management.
@onflow/flow-ft
Advanced tools
This is a description of the Flow standard for fungible token contracts. It is meant to contain the minimum requirements to implement a safe, secure, easy to understand, and easy to use fungible token contract. It also includes an example implementation to show how a concrete smart contract would actually implement the interface.
Flow is a new blockchain for open worlds. Read more about it here.
Cadence is a new Resource-oriented programming language for developing smart contracts for the Flow Blockchain. Read more about it here and see its implementation here
We recommend that anyone who is reading this should have already completed the Cadence Tutorials so they can build a basic understanding of the programming language.
Resource-oriented programming, and by extension Cadence, is the perfect programming environment for currencies, because users are able to store their tokens directly in their accounts and transact peer-to-peer. Please see the blog post about resources to understand why they are perfect for digital assets.
Flow and Cadence are both still in development, so this standard will still be going through a lot of changes as the protocol and language evolves, and as we receive feedback from the community about the standard.
We'd love to hear from anyone who has feedback. Main feedback we are looking for is:
The feedback we are looking for is:
The code for the standard is in contracts/FungibleToken.cdc
. An example implementation of the standard that simulates what a simple token would be like is in contracts/ExampleToken.cdc
.
The exact smart contract that is used for the official Flow Network Token is in contracts/FlowToken.cdc
Example transactions that users could use to interact with fungible tokens are located in the transactions/
directory. These templates are mostly generic and can be used with any fungible token implementation by providing the correct addresses, names, and values.
The standard consists of a contract interface called FungibleToken
that requires implementing contracts to define a Vault
resource that represents the tokens that an account owns. Each account that owns tokens will have a Vault
stored in its account storage. Users call functions on each other's Vault
s to send and receive tokens.
Right now we are using unsigned 64-bit fixed point numbers UFix64
as the type to represent token balance information. This type has 8 decimal places and cannot represent negative numbers.
1- Getting metadata for the token smart contract via the fields of the contract:
pub var totalSupply: UFix64
pub event TokensInitialized(initialSupply: UFix64)
2- Retrieving the token fields of a Vault
in an account that owns tokens.
pub var balance: UFix64
Vault
type3- Withdrawing a specific amount of tokens amount using the withdraw function of the owner's Vault
pub fun withdraw(amount: UFix64): @FungibleToken.Vault
Vault
cast as a Provider
to allow them to withdraw and send tokens for them. A contract can define any custom logic to govern the amount of tokens that can be withdrawn at a time with a Provider
. This can mimic the approve
, transferFrom
functionality of ERC20.Vault
is stored in.
If the Vault
is not in account storage when the event is emitted,
from
will be nil
.pub event TokensWithdrawn(amount: UFix64, from: Address?)
4 - Depositing a specific amount of tokens from using the deposit function of the recipient's Vault
Receiver
interface
pub fun deposit(from: @FungibleToken.Vault)
from
balance must be non-zerofrom
deposit event
Vault
is stored in.
If the Vault
is not in account storage when the event is emitted,
to
will be nil
.pub event TokensDeposited(amount: UFix64, to: Address?)
Users could create custom Receiver
s to trigger special code when transfers to them happen, like forwarding the tokens
to another account, splitting them up, and much more.
It is important that if you are making your own implementation of the fungible token interface that
you cast the input to deposit
as the type of your token.
let vault <- from as! @ExampleToken.Vault
The interface specifies the argument as @FungibleToken.Vault
, any resource that satisfies this can be sent to the deposit function. The interface checks that the concrete types match, but you'll still need to cast the Vault
before storing it.
5 - Creating an empty Vault resource
pub fun createEmptyVault(): @FungibleToken.Vault
Vault
, the caller calls the function in the contract and stores the Vault in their storage.6 - Destroying a Vault
If a Vault
is explicitly destroyed using Cadence's destroy
keyword, the balance of the destroyed vault must be subracted from the total supply.
7 - Standard for Token Metadata
This spec covers much of the same ground that a spec like ERC-20 covers, but without most of the downsides.
Vault
in its storage to receive tokens. No safetransfer
is needed.Vault
, the tokens can just be deposited to that Vault without having to do a clunky approve
, transferFrom
approve
, transferFrom
pattern is not included, so double spends are not permittedReceivers
to execute certain code when a token is sent.SafeMath
-equivalent library is not needed.A standard for token metadata is still an unsolved problem in the general blockchain world and we are still thinking about ways to solve it in Cadence. We hope to be able to store all metadata on-chain and are open to any ideas or feedback on how this could be implemented.
Minting and Burning are not included in the standard but are included in the FlowToken example contract to illustrate what minting and burning might look like for a token in Flow.
8 - Minting or Burning a specific amount of tokens using a specific minter resource that an owner can control
MintandBurn
Resource
The following features could each be defined as a separate interface. It would be good to make standards for these, but not necessary to include in the main standard interface and are not currently defined in this example.
9 - Withdrawing a specific amount of tokens from someone else's Vault
by using their provider
reference.
11 - Pausing Token transfers (maybe a way to prevent the contract from being imported)
12 - Cloning the token to create a new token with the same distribution
13 - Restricted ownership (For accredited investors and such)
To use the Flow Token contract as is, you need to follow these steps:
FungibleToken
definition to account 1 yourself and import it in ExampleToken
. It is a predeployed interface in the emulator, testnet, and mainnet and you can import definition from those accounts:
0xee82856bf20e2aa6
on emulator0x9a0766d93b6608b7
on testnet0xf233dcee88fe0abe
on mainnetExampleToken
definitionget_balance.cdc
or get_supply.cdc
scripts to read the
balance of a user's Vault
or the total supply of all tokens, respectively.setupAccount.cdc
on any account to set up the account to be able to
use FlowTokens
.transfer_tokens.cdc
transaction file to send tokens from one user with
a Vault
in their account storage to another user with a Vault
in their account storage.mint_tokens.cdc
transaction with the admin account to mint new tokens.burn_tokens.cdc
transaction with the admin account to burn tokens.create_minter.cdc
transaction to create a new MintandBurn resource
and store it in a new Admin's account.FungibleTokenSwitchboard.cdc
, allows users to receive payments in different fungible tokens using a single &{FungibleToken.Receiver}
placed in a standard receiver path /public/GenericFTReceiver
.
Users willing to use the Fungible Token Switchboard will need to setup their accounts by creating a new FungibleTokenSwitchboard.Switchboard
resource and saving it to their accounts at the FungibleTokenSwitchboard.StoragePath
path.
This can be acomplished by executing the transaction found in this repository transactions/switchboard/setup_account.cdc
. This transaction will create and save a Switchboard resource to the signer's account,
and it also will create the needed public capabilities to access it. After setting up their switchboard, in order to make it support receiving a certain token, users will need to add the desired token's receiver capability to their switchboard resource.
When a user wants to receive a new fungible token through their switchboard, they will need to add a new public capability linked to said FT to their switchboard resource. This can be accomplished in two different ways:
Adding a single capability using addNewVault(capability: Capability<&{FungibleToken.Receiver}>)
transactions/switchboard/add_vault_capabilty.cdc
.transaction {
let exampleTokenVaultCapabilty: Capability<&{FungibleToken.Receiver}>
let switchboardRef: &FungibleTokenSwitchboard.Switchboard
prepare(signer: AuthAccount) {
// Get the example token vault capability from the signer's account
self.exampleTokenVaultCapabilty =
signer.getCapability<&{FungibleToken.Receiver}>
(ExampleToken.ReceiverPublicPath)
// Get a reference to the signers switchboard
self.switchboardRef = signer.borrow<&FungibleTokenSwitchboard.Switchboard>
(from: FungibleTokenSwitchboard.StoragePath)
?? panic("Could not borrow reference to switchboard")
}
execute {
// Add the capability to the switchboard using addNewVault method
self.switchboardRef.addNewVault(capability: self.exampleTokenVaultCapabilty)
}
}
This function will panic if is not possible to .borrow()
a reference to a &{FungibleToken.Receiver}
from the passed capability. It will also panic if there is already a capability stored for the same Type
of resource exposed by the capability.
Adding one or more capabilities using the paths where they are stored using addNewVaultsByPath(paths: [PublicPath], address: Address)
PublicPath
objects should be passed along with the Address
of the account from where the vaults' capabilities should be retrieved.transaction (address: Address) {
let exampleTokenVaultPath: PublicPath
let vaultPaths: [PublicPath]
let switchboardRef: &FungibleTokenSwitchboard.Switchboard
prepare(signer: AuthAccount) {
// Get the example token vault path from the contract
self.exampleTokenVaultPath = ExampleToken.ReceiverPublicPath
// And store it in the array of public paths that will be passed to the
// switchboard method
self.vaultPaths = []
self.vaultPaths.append(self.exampleTokenVaultPath)
// Get a reference to the signers switchboard
self.switchboardRef = signer.borrow<&FungibleTokenSwitchboard.Switchboard>
(from: FungibleTokenSwitchboard.StoragePath)
?? panic("Could not borrow reference to switchboard")
}
execute {
// Add the capability to the switchboard using addNewVault method
self.switchboardRef.addNewVaultsByPath(paths: self.vaultPaths,
address: address)
}
}
This function won't panic, instead it will just not add to the @Switchboard
any capability which can not be retrieved from any of the provided PublicPath
s. It will also ignore any type of &{FungibleToken.Receiver}
that is already present on the @Switchboard
If a user no longer wants to be able to receive deposits from a certain FT, or if they want to update the provided capability for one of them, they will need to remove the vault from the switchboard.
This can be accomplished by using removeVault(capability: Capability<&{FungibleToken.Receiver}>)
.
This can be observed in the template transaction transactions/switchboard/remove_vault_capability.cdc
:
transaction {
let exampleTokenVaultCapabilty: Capability<&{FungibleToken.Receiver}>
let switchboardRef: &FungibleTokenSwitchboard.Switchboard
prepare(signer: AuthAccount) {
// Get the example token vault capability from the signer's account
self.exampleTokenVaultCapabilty = signer.getCapability
<&{FungibleToken.Receiver}>(ExampleToken.ReceiverPublicPath)
// Get a reference to the signers switchboard
self.switchboardRef = signer.borrow<&FungibleTokenSwitchboard.Switchboard>
(from: FungibleTokenSwitchboard.StoragePath)
?? panic("Could not borrow reference to switchboard")
}
execute {
// Remove the capability from the switchboard using the
// removeVault method
self.switchboardRef.removeVault(capability: self.exampleTokenVaultCapabilty)
}
}
This function will panic if is not possible to .borrow()
a reference to a &{FungibleToken.Receiver}
from the passed capability.
The Fungible Token Switchboad provides two different ways of depositing tokens to it, using the deposit(from: @FungibleToken.Vault)
method enforced by the {FungibleToken.Receiver}
or using the safeDeposit(from: @FungibleToken.Vault): @FungibleToken
:
&{FungibleToken.Receiver}
. The path for the Switchboard receiver is defined in FungibleTokenSwitchboard.ReceiverPublicPath
,
the generic receiver path /public/GenericFTReceiver
that can also be obtained from the NFT MetadataViews contract.
An example of how to do this can be found in the transaction template on this repo transactions/switchboard/transfer_tokens.cdc
transaction(to: Address, amount: UFix64) {
// The Vault resource that holds the tokens that are being transferred
let sentVault: @FungibleToken.Vault
prepare(signer: AuthAccount) {
// Get a reference to the signer's stored vault
let vaultRef = signer.borrow<&ExampleToken.Vault>
(from: ExampleToken.VaultStoragePath)
?? panic("Could not borrow reference to the owner's Vault!")
// Withdraw tokens from the signer's stored vault
self.sentVault <- vaultRef.withdraw(amount: amount)
}
execute {
// Get the recipient's public account object
let recipient = getAccount(to)
// Get a reference to the recipient's Switchboard Receiver
let switchboardRef = recipient.getCapability
(FungibleTokenSwitchboard.ReceiverPublicPath)
.borrow<&{FungibleToken.Receiver}>()
?? panic("Could not borrow receiver reference to switchboard!")
// Deposit the withdrawn tokens in the recipient's switchboard receiver
switchboardRef.deposit(from: <-self.sentVault)
}
}
safeDeposit(from: @FungibleToken.Vault): @FungibleToken
works in a similar way, with the difference that it will not panic if the desired FT Vault can not be obtained from the Switchboard. The method will return the passed vault, empty if the funds were deposited sucessfully or still containing the funds if the transfer of the funds was not possible. Keep in mind that when using this method on a transaction you will allways have to deal with the returned resource. An example of this can be found on transactions/switchboard/safe_transfer_tokens.cdc
:transaction(to: Address, amount: UFix64) {
// The reference to the vault from the payer's account
let vaultRef: &ExampleToken.Vault
// The Vault resource that holds the tokens that are being transferred
let sentVault: @FungibleToken.Vault
prepare(signer: AuthAccount) {
// Get a reference to the signer's stored vault
self.vaultRef = signer.borrow<&ExampleToken.Vault>(from: ExampleToken.VaultStoragePath)
?? panic("Could not borrow reference to the owner's Vault!")
// Withdraw tokens from the signer's stored vault
self.sentVault <- self.vaultRef.withdraw(amount: amount)
}
execute {
// Get the recipient's public account object
let recipient = getAccount(to)
// Get a reference to the recipient's Switchboard Receiver
let switchboardRef = recipient.getCapability(FungibleTokenSwitchboard.PublicPath)
.borrow<&FungibleTokenSwitchboard.Switchboard{FungibleTokenSwitchboard.SwitchboardPublic}>()
?? panic("Could not borrow receiver reference to switchboard!")
// Deposit the withdrawn tokens in the recipient's switchboard receiver,
// then deposit the returned vault in the signer's vault
self.vaultRef.deposit(from: <- switchboardRef.safeDeposit(from: <-self.sentVault))
}
}
You can find automated tests in the lib/go/test/token_test.go
file. It uses the transaction templates that are contained in the lib/go/templates/transaction_templates.go
file. Currently, these rely on a dependency from a private dapper labs repository to run, so external users will not be able to run them. We are working on making all of this public so anyone can run tests, but haven't completed this work yet.
The works in these folders are under the Unlicense:
FAQs
standard implementation of the fungible token on flow blockchain
The npm package @onflow/flow-ft receives a total of 3 weekly downloads. As such, @onflow/flow-ft popularity was classified as not popular.
We found that @onflow/flow-ft demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 16 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Maintainers back GitHub’s npm security overhaul but raise concerns about CI/CD workflows, enterprise support, and token management.
Product
Socket Firewall is a free tool that blocks malicious packages at install time, giving developers proactive protection against rising supply chain attacks.
Research
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.