How to Get ERC-20 Token Balance at a Given Block

Learn how to get the balance of a single token being held by a specific contract address at a given block or point in time.

This tutorial uses the alchemy_getTokenBalances endpoint.

The token balances for a user address is a critical data point for almost any web3 use case - be it DeFi platforms, wallets, analytics, exchanges, and many others. A common use case would be displaying the balances as below in your wallet app.

359

Sample wallet screen displaying coin balances

To fetch this data you have 2 options:

  1. Fetch the Transaction History for the token’s contract, for example take USDT, which is millions of records. Then search through for your user’s address among those records (will take hours) and then show that to your user. Not to forget, do this indexing process every second forever to keep it updated for every user.
  2. OR just use Alchemy’s getTokenBalances endpoint.

If you’re like most web3 developers, you probably want to go for the second option!

About this Tutorial


We will write a simple script in Node to gets us the balance of USDT for a particular wallet address. We will get the latest balance, the balance at a a particular block (in our case, 1000000), and at a particular human-readable time using Alchemy’s Token API Quickstart.

Creating the Token Balances Script


Step 1: Install Node and npm

In case you haven’t already, install node and npm on your local machine.

Make sure that node is at least v14 or higher by typing the following in your terminal:

shell
$node -v

Step 2: Create an Alchemy app


In case you haven’t already, sign up for a free Alchemy account.

2880

Alchemy’s account dashboard where developers can create a new app on the Ethereum blockchain.

Next, navigate to the Alchemy Dashboard and create a new app.

Make sure you set the chain to Ethereum and the network to Mainnet.

Once the app is created, click on your app’s View Key button on the dashboard.

Take note of the HTTP URL.

The URL will be in this form: https://eth-mainnet.g.alchemy.com/v2/xxxxxxxxx

You will need this later.


Step 3: Create a node project

Let’s now create an empty repository and install all node dependencies.

To make requests to the Token API, you can use direct HTTP requests or Web3 libraries.

You can also use axios or fetch alternatively.

$mkdir token-balances && cd token-balances
>npm init -y
>npm install --save viem ethers
>touch main.js

This will create a repository named token-balances that holds all your files and dependencies.

Next, open this repo in your favorite code editor.

We will be writing all our code in the main.js file.

Step 4: Get the latest USDT balance

To get the USDT balance of a particular wallet, we will use the getTokenBalances method.

This method takes in two arguments:

  1. DATA: The wallet address whose USDT balance we want to check
  2. Array: An array containing the list of token contract addresses we are interested in. In our case, it’s just one address (i.e of USDT).

Add the following code to the main.js file.

1// Replace with your Alchemy API Key
2const apiKey = "<-- ALCHEMY API KEY -->";
3const baseURL = `https://eth-mainnet.g.alchemy.com/v2/${apiKey}`;
4
5const main = async () => {
6 // Wallet address
7 const walletAddress = "0xef0dcc839c1490cebc7209baa11f46cfe83805ab";
8
9 // USDT contract address
10 const contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
11 const numDecimals = 6;
12
13 // Get latest USDT balance using alchemy_getTokenBalances
14 const requestBody = {
15 "jsonrpc": "2.0",
16 "method": "alchemy_getTokenBalances",
17 "params": [
18 walletAddress,
19 [contractAddress]
20 ],
21 "id": 1
22 };
23
24 try {
25 const response = await fetch(baseURL, {
26 method: 'POST',
27 headers: {
28 'Content-Type': 'application/json'
29 },
30 body: JSON.stringify(requestBody)
31 });
32
33 const data = await response.json();
34
35 if (data.error) {
36 console.error('Error:', data.error);
37 return;
38 }
39
40 let balance = data.result.tokenBalances[0].tokenBalance;
41 balance = (parseInt(balance) / 10 ** numDecimals).toFixed(2);
42 console.log("Balance:", balance, "USDT");
43 } catch (error) {
44 console.error('Request failed:', error);
45 }
46};
47
48main();

You can find the contract address of any token by searching its ticker or name on Etherscan.

In order to format a token into its human-readable format, you simply convert the hexadecimal result received from the API response into a decimal, and then divide by the number of decimal places that that particular token has. USDT, for instance, has 6. You can find out how many decimal places any currency has by reading the decimals value from the contract’s page on Etherscan.

Solidity does not support decimal point numbers. So, If you want an ERC20 token to have the ability to be subdivided with a precision of 2 decimal places, you need to represent 1 token as 100 (set its decimal places to 2). Similarly if I you want an ERC20 token to have the ability to subdivided with a precision of 18 decimal places, you need to represent 1 token as 1000000000000000000 (set its decimal places to 18).

So, the formula for number of tokens becomes:

tokenSupplyWithDecimalZeros = actualTokenSupply * (10 ** decimals)

That is why we need to divide the decimal value by the number of decimal places, to get the actual token value.

Run the script using the following command:

javascript
$node main.js

If all goes well, you should see output that looks something like this:

shell
$Balance: 61900000.01 USDT

Step 5: Get USDT balance at a particular block

In order to get the balance of a token at a particular block, we need to leverage the eth_call method. This will allow us to query USDT contract’s balanceOf method at a particular block number.

Create a new file called historical_balance.js and add the following code:

1const { ethers } = require('ethers');
2
3const main = async () => {
4 // Replace with your Alchemy API Key
5 const apiKey = "demo";
6 const provider = new ethers.JsonRpcProvider(`https://eth-mainnet.g.alchemy.com/v2/${apiKey}`);
7
8 // Wallet address
9 const walletAddress = "0xef0dcc839c1490cebc7209baa11f46cfe83805ab";
10
11 // USDT contract address
12 const contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
13 const numDecimals = 6;
14
15 // Block number
16 const blockNum = 12026456;
17
18 // ABI
19 const abi = [
20 'function balanceOf(address account) view returns (uint256)'
21 ];
22
23 // Create function call data -- eth_call
24 const iface = new ethers.Interface(abi);
25 const data = iface.encodeFunctionData("balanceOf", [walletAddress]);
26
27 // Get balance at a particular block -- usage of eth_call
28 const balance = await provider.call({
29 to: contractAddress,
30 data: data,
31 }, blockNum);
32
33 const formattedBalance = (parseInt(balance) / 10 ** numDecimals).toFixed(2);
34 console.log("Balance:", formattedBalance, "USDT");
35};
36
37const runMain = async () => {
38 try {
39 await main();
40 process.exit(0);
41 } catch (error) {
42 console.log(error);
43 process.exit(1);
44 }
45};
46
47runMain();

Run this script using:

shell
$node historical_balance.js

If all goes well, you should see output that looks something like this:

$Balance: 61933384.84 USDT

Step 6: Get USDT balance at a particular timestamp

The process to get the USDT balance at a particular timestamp is identical to the previous, except that we need to figure out a way to convert a timestamp into a block number.

In order to do this, we will use a library called ethereum-block-by-date.

Install this package by running the following command in your terminal:

shell
$npm install ethereum-block-by-date

Replace the contents of historical_balance.js with the following:

1const { ethers } = require('ethers');
2const EthDater = require('ethereum-block-by-date');
3
4const main = async () => {
5 // Replace with your Alchemy API Key
6 const apiKey = "demo";
7 const provider = new ethers.JsonRpcProvider(`https://eth-mainnet.g.alchemy.com/v2/${apiKey}`);
8
9 const dater = new EthDater(
10 provider // Ethers provider, required.
11 );
12
13 // Wallet address
14 const walletAddress = "0xef0dcc839c1490cebc7209baa11f46cfe83805ab";
15
16 // USDT contract address
17 const contractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
18 const numDecimals = 6;
19
20 // Set timestamp
21 const timestamp = '2021-02-20T13:20:40Z';
22
23 // Get blocknumber
24 const block = await dater.getDate(timestamp);
25 const blockNum = block['block'];
26
27 // ABI
28 const abi = [
29 'function balanceOf(address account) view returns (uint256)'
30 ];
31
32 // Create function call data
33 const iface = new ethers.Interface(abi);
34 const data = iface.encodeFunctionData("balanceOf", [walletAddress]);
35
36 // Get balance at a particular block -- usage of eth_call
37 const balance = await provider.call({
38 to: contractAddress,
39 data: data,
40 }, blockNum);
41
42 const formattedBalance = (parseInt(balance) / 10 ** numDecimals).toFixed(2);
43 console.log("Balance:", formattedBalance, "USDT");
44};
45
46const runMain = async () => {
47 try {
48 await main();
49 process.exit(0);
50 } catch (error) {
51 console.log(error);
52 process.exit(1);
53 }
54};
55
56runMain();

Running the same command as in step 5 should produce the same output.

$Balance: 61933384.84 USDT

Conclusion


Congratulations! You now know how to use the Alchemy Ethereum API and Token API to get the token balance of a wallet at present, a particular block height or timestamp.

If you enjoyed this tutorial on how to get ETH balance at a point in time, give us a tweet @Alchemy.

If you have any questions or feedback, please contact us at [email protected] or open a ticket in the dashboard.

Ready to start using the Alchemy Token API?

Create a free Alchemy account and share your project with us!