DEV Community

Max Daunarovich for Flow Blockchain

Posted on

Build on Flow | Learn FCL - 16. How to Send FLOW Tokens to Another Account

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"
});
Enter fullscreen mode Exit fullscreen mode

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 for FlowToken contract
  • 0xFT for FungibleToken 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";
Enter fullscreen mode Exit fullscreen mode

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 });
};
Enter fullscreen mode Exit fullscreen mode

🧑‍🏫 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 FlowTokencontract 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 {
Enter fullscreen mode Exit fullscreen mode

First we take account, we are trying to “investigate” by calling getAccount function, which will return us PublicAccount struct.

let account = getAccount(address)
Enter fullscreen mode Exit fullscreen mode

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}>
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

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 });
};
Enter fullscreen mode Exit fullscreen mode

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 passing amount as argument
  • we are depositing into Recepient’s Vault via deposit method and tempVault

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)
Enter fullscreen mode Exit fullscreen mode

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);
})();
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

https://media.giphy.com/media/1BPvv1QvbKV156jyul/giphy-downsized-large.gif

You know what to do next 😉

Until next time! 👋

Resources

Other resources you might find useful:

Top comments (0)