Overview
Fungible tokens are often used as a means of payment - for products or services. In this article we will guide you how to:
- get current balance of FLOW on a specific account
- compose FLOW transfer transaction
- sign and send it using Blocto or Lilico wallet
Theory
FLOW token is a contract, which implements Fungible Token Standard interface. If you take a look in the code, you will see that on the higher level totalSupply is defined on implementing contract, but it doesn’t know how much tokens holds each individual account. Each account that would want to own tokens would need to have an instance of the Vault resource stored in their account storage. The Vault
resource has methods that the owner and other users can call. Those methods are limited byProvider
, Receiver
, and Balance
resource interfaces. These interfaces declare pre-conditions and post-conditions that restrict the execution of the functions in the Vault.
They are separate because it gives the user the ability to share a reference to their Vault that only exposes the fields functions in one or more of the interfaces.
It also gives users the ability to make custom resources that implement these interfaces to do various things with the tokens. For example, a faucet can be implemented by conforming
to the Provider interface.
By using resources and interfaces, users of Fungible Token contracts can send and receive tokens peer-to-peer, without having to interact with a central ledger smart contract. To send tokens to another user, a user would simply withdraw the tokens from their Vault, then call the deposit function on another user's Vault to complete the transfer.
Step 1 - Installation
Add "@onflow/fcl": "1.0.0"
as your dependency
Step 2 - FCL Config
Let’s create testnet-config.js
file, so we can reuse it in later projects. It will also make our index.js
file cleaner and allow for an easy swap to mainnet version. The content of the file will be pretty much the same as we covered in previous article:
// Configure FCL
import { config } from "@onflow/fcl";
config({
// Use Testnet Access Node
"accessNode.api": "https://rest-testnet.onflow.org",
// We will also specify the network as some of the FCL parts need it to properly do it's work
"flow.network": "testnet",
// This will be the title of our DApp
"app.detail.title": "Meow DApp",
// This is just a kitten photo, we will use for the icon
"app.detail.icon": "https://placekitten.com/g/200/200",
// Next two will define where Wallet Discovery is located
"discovery.wallet": "https://fcl-discovery.onflow.org/testnet/authn",
"discovery.authn.endpoint":
"https://fcl-discovery.onflow.org/api/testnet/authn",
// We will also set aliases for the contracts we will use in this example
"0xFLOW": "0x7e60df042a9c0868",
"0xFT": "0x9a0766d93b6608b7"
});
If you take a look at the end of the config you can see that we’ve defined two aliases for the contracts we will import in our Cadence code:
-
0xFLOW
forFlowToken
contract -
0xFT
forFungibleToken
contract
Step 4 - Imports
Back to our index.js
file, where we will add all the necessary imports:
import { query, mutate, tx, reauthenticate } from "@onflow/fcl";
import "./testnet-config";
Step 3 - Implement getFlowBalance
function
Next, let’s create a function, which will check current balance of the account, defined by it’s address.
It will accept single argument address
, which will be passed to Cadence script and return UFix64
value, represented as string.
💡If you just arrived here and don’t quite know what is happening - don’t be afraid! We’ve got you covered! You can check earlier articles in this series:
- How to Execute Scripts
- How to Send Transactions
const getFlowBalance = async (address) => {
const cadence = `
import FlowToken from 0xFLOW
import FungibleToken from 0xFT
pub fun main(address: Address): UFix64 {
let account = getAccount(address)
let vaultRef = account.getCapability(/public/flowTokenBalance)
.borrow<&FlowToken.Vault{FungibleToken.Balance}>()
?? panic("Could not borrow Balance reference to the Vault")
return vaultRef.balance
}
`;
const args = (arg, t) => [arg(address, t.Address)];
const balance = await query({ cadence, args });
console.log({ balance });
};
🧑🏫 If you are wondering where we’ve got the code for this transaction - you can check Fungible Token Standart repository on Github:
The “problem” is - you won’t be able to find BalancePublicPath
field set on FlowToken
contract deployed to 0x9a0766d93b6608b7. The contract was deployed before this useful partern of storing common path became widely used. That’s why we are hard-coding /public/flowTokenBalance
value which we sourecd from init
method of the contract. We will also use /public/flowTokenReceiver
defined there to get capability of the Recepient’s Vault.
While Javacript code is self-explanatory, let’s cover whats is happening in Cadence script. It accepts single argument address
of type Address
and returns UFix64
value:
pub fun main(address: Address): UFix64 {
First we take account, we are trying to “investigate” by calling getAccount
function, which will return us PublicAccount struct.
let account = getAccount(address)
This struct gives us the ability to call getCapability
method to retrieve necessary capability to FLOW Vault. We know that capability should be exposes on /public/flowTokenBalance
public path. And we also know that it’s a limited capability FungibleToken.Balance
, which will only alow us to read the balance of the Vault.
We can check init
method of FlowToken contract (line #187) to get the correct type we need to borrow:
<&FlowToken.Vault{FungibleToken.Balance}>
In order to operate said capability, we need to get a reference to it with .borrow
method. We can set the type are are expecting to borrow using angular brackets - and paste the same type as it was defined in init
method.
let vaultRef = account.getCapability(/public/flowTokenBalance)
.borrow<&FlowToken.Vault{FungibleToken.Balance}>()
?? panic("Could not borrow Balance reference to the Vault")
If the reference can’t be created (for example stored Capability have different type or can’t be casted to this specific type) - transaction will stop the execution with panic
message.
Step 4 - Implement sendFlow
function
Next on the list is a function, which will accept Recepient’s address and amount of FLOW tokens we want to transfer.
const sendFlow = async (recepient, amount) => {
const cadence = `
import FungibleToken from 0xFT
import FlowToken from 0xFLOW
transaction(recepient: Address, amount: UFix64){
prepare(signer: AuthAccount){
let sender = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)
?? panic("Could not borrow Provider reference to the Vault")
let receiverAccount = getAccount(recepient)
let receiver = receiverAccount.getCapability(/public/flowTokenReceiver)
.borrow<&FlowToken.Vault{FungibleToken.Receiver}>()
?? panic("Could not borrow Receiver reference to the Vault")
let tempVault <- sender.withdraw(amount: amount)
receiver.deposit(from: <- tempVault)
}
}
`;
const args = (arg, t) => [arg(recepient, t.Address), arg(amount, t.UFix64)];
const limit = 500;
const txId = await mutate({ cadence, args, limit });
console.log("Waiting for transaction to be sealed...");
const txDetails = await tx(txId).onceSealed();
console.log({ txDetails });
};
Cadence code is pretty similar to one we’ve used above to query FLOW balance.
- we are getting full reference to a Sender’s Vault
- we are geting limited
Receiver
reference to Recepient’s Vault - we are creating temporary Vault via
withdraw
method and passingamount
as argument - we are depositing into Recepient’s Vault via
deposit
method andtempVault
Notice the syntax of full reference - we don’t specify limiting interface in curly braces and we also specify storage path /storage/flowTokenVault
. Every account is initialized with FLOW Vault on that path, so it’s safe to assume it’s there for every signer:
let sender = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)
Finally
Let's add an IIFE at the end of the file and populate it with methods we have just defined:
(async () => {
console.clear();
// "reauthenticate" will ensure your session works properly
// and present you a popup to sign in
await reauthenticate();
// This is an example account we've created to this exibition
// You can replace it with one of your addresses
const recepient = "0x3e68d80ca405bbac";
// Check "initial" balance first
await getFlowBalance(recepient);
// Send some FLOW tokens to Recepient
await sendFlow(recepient, "1.337");
// Ensure that Recepient's balance has been changed
await getFlowBalance(recepient);
})();
After a series of popups and confirmation your console should have the proof that balance of Recepient’s Vault have been changed:
{balance: "1002.67500000"}
Waiting for transaction to be sealed...
txDetails: {
blockId: "b555b7ca17fac5ad170e3bfc4a85afd3325eb5d4fb56e4be49550dce9413ffad"
status: 4
statusString: "SEALED"
statusCode: 0
errorMessage: ""
events: Array(5)
}
{balance: "1004.01200000"}
You know what to do next 😉
Until next time! 👋
Resources
- Full Source Code - https://codesandbox.io/s/dev-to-16-transfer-flow-tokens-pjq98u
- Fungible Token Standard - https://github.com/onflow/flow-ft
-
FCL -
mutate
- https://docs.onflow.org/fcl/reference/api/#mutate -
FCL -
query
- https://docs.onflow.org/fcl/reference/api/#query - Cadence - Capabilities - https://docs.onflow.org/cadence/language/capability-based-access-control
- Cadence - References - https://docs.onflow.org/cadence/language/references
Other resources you might find useful:
- Flow Docs Site - https://docs.onflow.org/ - More detailed information about Flow blockchain and how to interact with it
- Flow Portal - https://flow.com/ - your entry point to Flow
- FCL JS - https://github.com/onflow/fcl-js - Source code and ability to contribute to the FCL JS library
- Cadence - https://docs.onflow.org/cadence/ - Introduction to Cadence
- Codesandbox - https://codesandbox.io - An amazing in-browser IDE enabling quick prototyping
Top comments (0)