Web3.js: How to Retrieve the Balance of an ERC-20 Token

Most of the tokens we all know and love deployed on the Ethereum network follow the same specification that we call ERC-20.

Having this standard that most tokens follow allows us to create web3 applications offering interoperability, so the same code will enable us to interact with almost every token contract.

Every single ERC-20 token contract will have at least the following functions;

  • totalSupply(); returns the total tokens issued for this token
  • balanceOf(address _owner); returns the balance of an account
  • transfer(address _to, uint _value); creates a transfer of _value amount of tokens to another account
  • transferFrom(address _from, address _to, uint _value); transfers _value amount of tokens to another address per its allowance
  • approve(address _spender, uint _value); allows _spender to transfer _value tokens from the sender’s address
  • allowance(address _owner, address _spender); returns the remaining number of tokens that _spender is still allowed to spend

Some ERC-20 token contracts might have additional functionality, but this would differ from the standard, and we can’t rely on every token contract having these other functions.

This tutorial will utilise the balanceOf() function to retrieve the token balance of an address. We’re going to use web3js to do so.

Installing Web3js

Create a new folder where we will create our new project. Next, install web3.js using npm.

npm install web3

You’ll notice that npm will create a package.json and package-lock.json file in your working folder. Additionally, it will make a folder called node_modules.

Next, create a .js file in which we can write our code; we’ll call ours main.js. You can use any text editor or IDE to work in your .js file.

Connecting to Infura

Ensure you got your Infura account set up and have access to your endpoint URL. Feel free to read more about getting started with Infura.

At the top of our new .js file, we can add the following to import the web3js library that we just installed:

const Web3 = require('web3');

Next, we can add the following to connect to our Infura endpoint so that we can interact with the Ethereum blockchain:

const web3 = new Web3(new Web3.providers.HttpProvider('https://mainnet.infura.io/v3/YOUR_PROJECT_ID'));

Make sure to replace YOUR_PROJECT_ID with your actual Infura project ID.

Setting the ABI

The ABI, or Application Binary Interface, reads the bytecode of smart contracts on the Ethereum network. This might sound confusing and daunting - don’t worry about it too much at this time.

Bytecode?!

However, if you want to learn more, Bytecode is compiled code (usually from Solidity), the actual smart contract code. It’s unreadable for you and me but readable for a machine. Here’s a small example of bytecode:

6101406040527f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9610120908152503480156200003a57600080fd5b5060405162005bf838038062005bf8833981810160405281019062000060

An ABI is an interface which defines a scheme of how we can call specific functions in our specified smart contract.

Since we will only use the balanceOf method, we don’t need the entire ABI for ERC-20 smart contracts. Hence, we’re only going to define the ABI for the balanceOf method. We can add the following to our script:

const balanceOfABI = [
    {
        "constant": true,
        "inputs": [
            {
                "name": "_owner",
                "type": "address"
            }
        ],
        "name": "balanceOf",
        "outputs": [
            {
                "name": "balance",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
];

If you want to call additional functions, you can find the entire ERC-20 ABI here.

Selecting a Token Address

Every token on the Ethereum network has its own contract address. For each token that you would like the request one’s balance for, you need to have its token contract address. You can easily find these with a block explorer such as etherscan.io.

For this tutorial, we’ll use the DAI token contract. However, you can choose to use any ERC-20 token address. Copy the address you wish to use, and save it somewhere as we will use this in our code later.

External Image

Requesting Our Token Balance

Now that we’ve installed Web3js, connected it to our Infura endpoint, learned about ABIs, and found the contract address of our token, we’re ready to actually request our token balance.

Let’s define the addresses we’re going to use in our code:

const tokenContract = "0x6b175474e89094c44da98b954eedeac495271d0f"
const tokenHolder = "0xf326e4de8f66a0bdc0970b79e0924e33c79f1915"

tokenContract holds the address of the token contract we’re going to call the balanceOf function on, and tokenHolder holds the account address we’re requesting the token balance of.

We can define our contract with web3.eth.Contract(), and passing the ABI and contract address as parameters:

const contract = new web3.eth.Contract(balanceOfABI, tokenContract)

Next, we can create an async function in which we actually interact with the smart contract.

We can call methods.balanceOf() on our contract, which will send a request to our Infura endpoint to request the token balance.

async function getTokenBalance() {
    let result = await.contract.methods.balanceOf(walletAddress).call();

    console.log(result)
}

getTokenBalance();

We can already execute our code by running the following command in a terminal:

node main.js

Result:

➜  retrieve-erc-20-balance node main.js
3142422965167994254806984

The address I specified doesn’t actually hold this amount of DAI in its wallet, so why is this number so large? By default, calling balanceOf will return the balance value in wei, which is the smallest unit in Ethereum (equal to 0.000000000000000001 Ether). You can read more about Ethereum’s units here.

To get the actual number of DAI tokens, we can convert this number with web3.utils.fromWei(result, "ether"):

const formattedResult = web3.utils.fromWei(result, "ether");

Make sure to update your console.log(format) too:

console.log(formattedResult);

If we run node main.js again, we get the proper DAI token balance of our specified address:

3142422.965167994254806984

Complete Code Overview

// Import the web3js library, set Infura as our provider
const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider('https://mainnet.infura.io/v3/<YOUR_PROJECT_ID>'));

// Set the ERC-20 balanceOf() ABI
const balanceOfABI = [
    {
        "constant": true,
        "inputs": [
            {
                "name": "_owner",
                "type": "address"
            }
        ],
        "name": "balanceOf",
        "outputs": [
            {
                "name": "balance",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
];

const tokenContract = "0x6b175474e89094c44da98b954eedeac495271d0f"
const tokenHolder = "0xf326e4de8f66a0bdc0970b79e0924e33c79f1915"

// Define the ERC-20 token contract
const contract = new web3.eth.Contract(balanceOfABI, tokenContract)

async function getTokenBalance() {
    // Execute balanceOf() to retrieve the token balance
    const result = await contract.methods.balanceOf(tokenHolder).call(); // 29803630997051883414242659

    // Convert the value from Wei to Ether
    const formattedResult = web3.utils.fromWei(result, "ether"); // 29803630.997051883414242659

    console.log(formattedResult);
}

getTokenBalance();
8 Likes