In blockchain development, checking an address's balance at a specific historical moment is a common requirement, whether for auditing, tax reporting, or transaction analysis. This guide explains how to use the Ethers.js library to retrieve the balance of Ethereum (ETH) and ERC-20 tokens for any address at a particular Unix timestamp.
Prerequisites
Before starting, ensure you have the following:
- Basic knowledge of JavaScript and Node.js.
- An Infura account and API key for accessing Ethereum network data.
- Ethers.js library installed in your project.
Setting Up the Environment
To begin, initialize your project and install the necessary dependencies. Create a new JavaScript file and import the Ethers.js library.
const { ethers } = require('ethers');Configuring the Infura Provider
Ethers.js requires a provider to interact with the Ethereum blockchain. Infura offers a reliable JSON-RPC endpoint. Replace the placeholder with your Infura project ID.
const infuraUrl = 'https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID';
const provider = new ethers.providers.JsonRpcProvider(infuraUrl);Defining the Target Address and Timestamp
Specify the Ethereum address you want to query and the Unix timestamp representing the exact moment for the balance check. Unix time is the number of seconds since January 1, 1970.
const address = 'TARGET_ETHEREUM_ADDRESS';
const timestamp = SPECIFIC_UNIX_TIMESTAMP;Retrieving the ETH Balance
Use the getBalance method provided by Ethers.js. This function accepts the address and an optional block parameter, which can be a block number or timestamp.
const ethBalancePromise = provider.getBalance(address, timestamp);Checking ERC-20 Token Balances
For ERC-20 tokens, you need the token's contract address and its Application Binary Interface (ABI). The ABI defines the balanceOf function needed to query holdings.
const tokenAddress = 'ERC20_TOKEN_CONTRACT_ADDRESS';
const tokenAbi = ['function balanceOf(address) view returns (uint256)'];
const tokenContract = new ethers.Contract(tokenAddress, tokenAbi, provider);
const tokenBalancePromise = tokenContract.balanceOf(address, timestamp);Executing the Balance Queries
Combine both balance queries using Promise.all to handle asynchronous operations efficiently. This approach ensures both balances are retrieved simultaneously.
Promise.all([ethBalancePromise, tokenBalancePromise])
  .then(([ethBalance, tokenBalance]) => {
    console.log(`ETH balance at timestamp ${timestamp}: ${ethers.utils.formatEther(ethBalance)} ETH`);
    console.log(`Token balance at timestamp ${timestamp}: ${ethers.utils.formatUnits(tokenBalance, TOKEN_DECIMALS)}`);
  })
  .catch((error) => {
    console.error('Error retrieving balances:', error);
  });Handling Results and Errors
After obtaining the balances, format them for readability. ETH balances are in wei by default; use formatEther to convert to ETH. Token balances may require specifying decimals, which vary per token.
Error handling is crucial for debugging issues like invalid addresses, provider errors, or incorrect timestamps.
Important Considerations
- Timestamp Accuracy: Blockchain data is block-based. The balance returned corresponds to the first block at or before the specified timestamp.
- Provider Reliability: Use a reliable provider like Infura or Alchemy to avoid rate limiting or downtime.
- Token Decimals: Always confirm the decimal places of the ERC-20 token to format balances correctly.
👉 Explore advanced blockchain query techniques
Frequently Asked Questions
Can I retrieve balances for multiple tokens simultaneously?
Yes, create multiple contract instances and use Promise.all to handle multiple balanceOf calls. Ensure your provider can handle the increased load.
What if the timestamp is before the address existed?
The balance will be zero. If the timestamp predates the token's deployment, the query might fail unless the contract handles historical calls.
How do I convert a human-readable date to Unix timestamp?
Use JavaScript's Date.getTime() method, dividing by 1000 to get seconds. For example: Math.floor(new Date('2023-01-01').getTime() / 1000).
Why is my balance query returning zero?
Possible reasons include incorrect address, timestamp outside the blockchain history, or the address having no balance at that time.
Is this method compatible with testnets?
Yes, replace the Infura endpoint with the testnet URL (e.g., Ropsten or Goerli) and use testnet addresses.
Can I use this for other EVM-compatible blockchains?
Absolutely. Adjust the Infura URL to the respective blockchain (e.g., Polygon or BNB Smart Chain) and use the correct network settings.
Conclusion
Querying historical balances is straightforward with Ethers.js and a provider like Infura. This method is essential for developers building analytics tools, financial reports, or audit systems. Always test with known addresses and timestamps to verify accuracy. For more complex queries, consider using specialized indexers or archives.