Checking ERC20 token balances is a routine task for blockchain developers, traders, and enthusiasts. Most wallets automatically display token balances once tokens are imported. But what if you need to check unimported tokens, verify someone else’s holdings, or audit historical balances on a specific date? This guide covers how to check ERC20 token balances for any wallet, across multiple blockchains, and at any point in time — including practical tools and code examples.
We’ll walk you through the technical process using blockchain nodes, smart contract methods, and APIs — and introduce a ready-to-use CLI tool that simplifies everything.
Understanding the ERC20 balanceOf Method
All ERC20 tokens implement a public function called balanceOf, which returns the token balance of a given wallet address. To use it, you need three things:
- The ERC20 token contract address
- The wallet address to query
- Access to a blockchain node via an RPC endpoint
This method is read-only and accessible to anyone, making it ideal for balance verification without requiring private keys.
How to Find an ERC20 Token Contract Address
Before checking a balance, you must know the token’s contract address. For popular tokens like USDC or DAI, you can:
- Search on blockchain explorers like Etherscan
- Refer to official project documentation
- Use trusted token lists
For less common tokens, always verify the address across multiple sources to avoid scams.
How to Retrieve a List of ERC20 Tokens by Blockchain
Manually collecting contract addresses becomes impractical when dealing with dozens or hundreds of tokens. Instead, use curated token lists.
A reliable source is the Via Protocol TokenLists GitHub repository, which provides JSON files for major chains like:
- Ethereum
- BNB Chain
- Polygon
- Avalanche
Here’s a JavaScript function to fetch token lists dynamically:
const axios = require('axios');
const TOKEN_LISTS = {
Ethereum: 'https://raw.githubusercontent.com/viaprotocol/tokenlists/main/tokenlists/ethereum.json',
Avalanche: 'https://raw.githubusercontent.com/viaprotocol/tokenlists/main/tokenlists/avax.json',
Binance: 'https://raw.githubusercontent.com/viaprotocol/tokenlists/main/tokenlists/bsc.json',
Polygon: 'https://raw.githubusercontent.com/viaprotocol/tokenlists/main/tokenlists/polygon.json',
};
const getTokens = async (chain) => {
const tokenSource = TOKEN_LISTS[chain];
const res = await axios.get(tokenSource);
return res.data;
};This approach enables scalable token balance checks across multiple networks.
Checking Current vs. Historical Token Balances
Current Balance
To get the current balance of a token, call balanceOf using a provider like ethers.js:
const { ethers } = require('ethers');
const provider = new ethers.providers.JsonRpcProvider('YOUR_NODE_ENDPOINT');
const getSingleTokenBalance = async (token, wallet) => {
const contract = new ethers.Contract(token.address, ERC20_ABI, provider);
const res = await contract.balanceOf(wallet);
return res;
};Historical Balance
To check a balance at a past date, use the blockTag parameter:
const getSingleTokenBalanceByBlock = async (token, wallet, block) => {
const contract = new ethers.Contract(token.address, ERC20_ABI, provider);
const res = await contract.balanceOf(wallet, { blockTag: block });
return res;
};⚠️ Note: Historical queries require an archive node, not just a full node. Archive nodes store all historical states, enabling queries from any block.
How to Get the Block Number for a Specific Date
Most blockchains don’t offer built-in date-to-block conversion. However, explorers like Etherscan, Snowtrace, and BscScan provide APIs with getblocknobytime.
Here’s how to fetch the block number for a given timestamp:
const axios = require('axios');
const ETHERSCAN_API = 'https://api.etherscan.io/api';
const ETHERSCAN_API_KEY = process.env.API_KEY;
const getBlockByTimestamp = async (dateFrom) => {
const timestamp = Math.floor(new Date(dateFrom).getTime() / 1000);
const queryParams = `?module=block&action=getblocknobytime×tamp=${timestamp}&closest=before&apikey=${ETHERSCAN_API_KEY}`;
const endpoint = `${ETHERSCAN_API}${queryParams}`;
const res = await axios.get(endpoint);
return res.data.result;
};Replace the base URL with chain-specific explorers (e.g., api.snowtrace.io for Avalanche).
Fetching Multiple Token Balances Efficiently
Querying balances one by one is slow. Use Promise.allSettled() to send all requests in parallel:
const getAllTokenBalances = async (tokenList, wallet, block) => {
let proms = [];
for (const tkn of tokenList) {
const erc20 = new ethers.Contract(tkn.address, ERC20_ABI, provider);
proms.push(erc20.balanceOf(wallet, { blockTag: block }));
}
const promiseResults = await Promise.allSettled(proms);
let results = [];
for (let i = 0; i < promiseResults.length; i++) {
const bal = convertToNumber(promiseResults[i].value, tokenList[i].decimals);
results.push({
name: tokenList[i].name,
symbol: tokenList[i].symbol,
balance: bal,
});
}
return results;
};This reduces execution time from minutes to seconds.
Retrieving Token Metadata: Name, Symbol, Decimals
ERC20 contracts expose methods to retrieve metadata:
const getTokenDetails = async (address) => {
const contract = new ethers.Contract(address, ERC20_ABI, provider);
const decimals = await contract.decimals();
const name = await contract.name();
const symbol = await contract.symbol();
return { name, decimals, symbol };
};Always fetch decimals dynamically — they vary by token (e.g., USDC uses 6, most others use 18).
Formatting Balances Correctly
The balanceOf method returns a BigNumber in wei. Convert it using formatUnits:
const convertToNumber = (hex, decimals = 18) => {
if (!hex) return 0;
return ethers.utils.formatUnits(hex, decimals);
};This ensures readable outputs like 15.0 instead of 15000000.
CLI Tool for Bulk Token Balance Checks
A ready-to-use CLI tool is available on GitHub: token-balance-ultimate.
It supports:
- Multiple blockchains (Ethereum, Polygon, BNB Chain, etc.)
- Single or bulk token queries
- Historical balance checks by date
- Automatic token list fetching
Setup requires:
- A blockchain node (e.g., from Chainstack)
- API keys from Etherscan or equivalent explorers
- Environment variables configured in
.env
Follow the repo instructions to get started.
Frequently Asked Questions (FAQ)
Can I check someone else’s ERC20 token balance?
Yes. Since balanceOf is a public method, you can query any wallet address as long as you have the token contract address and RPC access.
Do I need an archive node to check past balances?
Yes. Full nodes only store recent state data (usually up to 128 blocks back). To query balances from earlier dates, you need an archive node.
How accurate is block-by-date conversion?
It’s approximate. The getblocknobytime API returns the closest block before the given timestamp. For precise audits, cross-check with block explorers.
What if a token isn’t in the token list?
Manually add the contract address. Always verify it through official sources or on-chain verification tools.
Can I use this method for non-ERC20 tokens?
No. This applies only to ERC20-compatible tokens. For NFTs (ERC721/ERC1155), use their respective standards’ balance methods.
Is it safe to use third-party APIs for balance checks?
Yes — as long as you use HTTPS and trusted providers. Since no private keys are involved, there’s no security risk in querying public data.
Final Thoughts
Checking ERC20 token balances goes beyond wallet interfaces. Whether you're auditing DeFi positions, analyzing on-chain activity, or building dApps, understanding how to programmatically retrieve current and historical balances is essential.
With the right tools — from blockchain nodes to explorer APIs and CLI utilities — you can automate and scale these checks efficiently.
Core Keywords: ERC20 token balance, check token balance, blockchain node, archive node, ethers.js, token contract address, historical balance, balanceOf method