Reading Ethereum Balances Safely with Web3.js: Avoiding BigInt Pitfalls
Reading an Ethereum address's balance with web3.eth.getBalance() returns wei as a string. Converting that string to JavaScript Number causes precision loss. Keep it as a string or use BigInt, then convert with web3.utils.fromWei() for display.
05 Sept 2025, 09:26 UTC

The Problem: Balance Values Break JavaScript Numbers
When reading an Ethereum address's native balance with web3.eth.getBalance(), the result comes back as a wei string. That string can easily exceed JavaScript's Number.MAX_SAFE_INTEGER, causing silent precision loss if you convert it directly to a number. This isn't just a theoretical concern—addresses holding even modest amounts of ETH (like 100 ETH) produce 100,000,000,000,000,000 wei, which JavaScript truncates if mishandled.
The Solution: Keep Wei as Strings or Use BigInt
The safest approach is to treat the wei value as a string throughout your application. Convert to ether only when displaying to users, using web3.utils.fromWei() which returns a string representation of the decimal value. In modern environments, you can also use native BigInt for arithmetic, but never use Number for the raw wei value.
Provider Setup Matters
Your choice of provider determines connection behavior. HTTP providers work well for simple read operations, while WebSocket providers enable subscriptions and persistent connections useful for real-time applications.
Block Tag Affects What You See
The optional blockTag parameter controls which block's state you query. 'latest' (the default) returns the most recent mined block, while 'pending' includes unconfirmed transactions in the mempool. This distinction matters for applications needing to account for transactions not yet mined.
Worked Example: Safe Balance Reading
Here's a minimal pattern that avoids common pitfalls:
// Check your web3 version first - syntax differs between v1, v3, and v4
const Web3 = require('web3');
const web3 = new Web3('https://mainnet.infura.io/v3/YOUR-PROJECT-ID');
async function getBalanceSafely(address) {
try {
// getBalance returns a Promise resolving to a wei string
const weiBalance = await web3.eth.getBalance(address);
// Convert to ether string for display - never use Number(weiBalance)
const etherBalance = web3.utils.fromWei(weiBalance, 'ether');
console.log(`Address: ${address}`);
console.log(`Balance: ${etherBalance} ETH`);
console.log(`Raw wei: ${weiBalance}`);
return { wei: weiBalance, ether: etherBalance };
} catch (error) {
console.error('Failed to fetch balance:', error.message);
throw error;
}
}
// Usage
getBalanceSafely('0x742d35Cc6634C0532925a3b844Bc454e4438f44e')
.catch(() => process.exit(1));
Version Compatibility Notes
- web3.js v1.x: Uses
Web3constructor directly, utilities underweb3.utils - web3.js v3.x: Similar structure but different default providers
- web3.js v4.x: May expose utilities under different paths; check documentation for your installed version
Verification and Limitations
To verify your setup works correctly:
- Run
npm list web3to identify your major version - Query a known address (like the Ethereum Foundation's) and compare against a block explorer
- Test with a small balance address first to confirm formatting
Important limitations:
- The
blockTagparameter defaults to'latest', meaning pending transactions won't appear until confirmed - RPC endpoints may have rate limits; handle errors gracefully
- Hardcoding RPC URLs in frontend code exposes your project ID to users
Actionable Takeaways
- Never convert wei to
Number—use strings orBigInt - Always verify your web3.js version before implementing
- Test against known addresses to validate your balance reads
- Handle errors explicitly—network issues and rate limits are common
For production applications, consider using a dedicated BigNumber library like ethers.js's BigNumber or bignumber.js for complex arithmetic operations.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.