Checking a wallet balance on all chains means querying each chain's own state, because no single network knows what an address holds elsewhere. The reliable approach is to call the native balance method on every chain's RPC endpoint, then query token contracts separately for ERC-20-style assets. This article gives a runnable procedure using curl, JavaScript, and Python, plus a results-table method so you can record and verify every value yourself. It also covers the failure modes that make multi-chain balance checks look wrong: wrong chain IDs, stale caches, decimals, and rate limits.
What checking a balance across all chains actually means
A blockchain address is not a global account. The same string can exist on Ethereum, BNB Smart Chain, Arbitrum, Optimism, Polygon, and dozens of other networks, but each network stores its own state independently. When you check a wallet balance on all chains, you are running one query per chain and then aggregating the answers off-chain.
This distinction matters because most confusion around multi-chain balance tools comes from treating the result as a single number. A portfolio view is a client-side or server-side aggregation of many independent RPC responses, each denominated in that chain's native unit. The aggregation layer, not the blockchain, decides how values are combined and displayed.
This article focuses on a reproducible method you can run yourself against any set of EVM-compatible chains. The same mental model applies to non-EVM networks, but the RPC methods and token standards differ, so those are treated as a separate integration.
- Native balance: one RPC call per chain, returned in the chain's smallest unit.
- Token balance: one call per token contract per chain, using the token's decimals.
- Aggregation: your code converts units, applies prices if needed, and records provenance.
Prerequisites and assumptions
You need a list of chains you care about, each with a chain ID and an RPC endpoint you are authorized to use. You also need the public address you are inspecting; never a private key, because balance reads are public operations. For token balances you need the token contract addresses and their decimals, which you can read from the contract itself.
The examples use placeholders such as RPC_URL and WALLET_ADDRESS. Replace them with your own endpoint and address. If you do not yet have endpoints for each network, the multi-chain RPC endpoints guide explains how endpoint selection works, and the OnFinality networks directory lists supported networks you can map to your chain list.
- A chain list with chainId, name, RPC_URL, and native symbol.
- A public wallet address in checksum or lowercase hex form.
- Optional: token contract addresses for ERC-20 balances.
- A place to record results, such as a spreadsheet or JSON file.
The mental model: one query per chain, one unit per answer
Every EVM chain exposes eth_getBalance, which returns the native balance in wei as a hexadecimal string. Wei is the smallest unit; one ether is 10^18 wei on Ethereum-like chains, but other chains may use different decimals for their native unit. The RPC response does not tell you the symbol or the decimals, so your chain list must carry that metadata.
Token balances are different. An ERC-20 contract stores a mapping from address to amount, and you read it with the balanceOf function. The returned integer must be divided by 10^decimals, where decimals is read from the contract's decimals() function. A token balance query is therefore two calls per token: one for decimals (cacheable) and one for balanceOf.
This separation is why a wallet balance checker can show a native total quickly but take longer for tokens. Native balances are one call per chain; token balances scale with the number of contracts you check.
Runnable procedure: native balances with curl
Start with a single chain to confirm your endpoint and address format. The JSON-RPC request below asks for the native balance at the latest block. The params array contains the address and the block tag; use "latest" for the current balance or a block number for a historical check.
- A successful response contains a result field with a hex string such as 0x0 or 0xde0b6b3a7640000.
- An error field usually indicates a malformed address, an unsupported method, or an endpoint problem.
- Convert the hex string to an integer, then divide by 10^nativeDecimals to get the human-readable amount.
curl -s -X POST "$RPC_URL" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getBalance",
"params": ["WALLET_ADDRESS", "latest"]
}'Runnable procedure: all chains in JavaScript
The script below iterates over a chain list, calls eth_getBalance on each endpoint, converts the result, and prints a table. It uses fetch, which is available in modern Node.js and browsers. Keep the chain list in one place so the same metadata drives both the query and the display.
This pattern is the core of any multi-chain balance check. The important engineering decisions are how you handle a failed chain, how you avoid rate limits, and how you record which endpoint produced each value.
const chains = [
{ name: "Ethereum", chainId: 1, rpc: "RPC_URL_ETH", symbol: "ETH", decimals: 18 },
{ name: "BNB Smart Chain", chainId: 56, rpc: "RPC_URL_BSC", symbol: "BNB", decimals: 18 },
{ name: "Arbitrum One", chainId: 42161, rpc: "RPC_URL_ARB", symbol: "ETH", decimals: 18 }
];
const address = "WALLET_ADDRESS";
async function getNativeBalance(chain) {
const res = await fetch(chain.rpc, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: chain.chainId,
method: "eth_getBalance",
params: [address, "latest"]
})
});
const json = await res.json();
if (json.error) throw new Error(`${chain.name}: ${json.error.message}`);
const wei = BigInt(json.result);
const divisor = 10n ** BigInt(chain.decimals);
return Number(wei) / Number(divisor);
}
for (const chain of chains) {
try {
const balance = await getNativeBalance(chain);
console.log(`${chain.name}\t${balance}\t${chain.symbol}`);
} catch (err) {
console.log(`${chain.name}\tERROR\t${err.message}`);
}
}Runnable procedure: token balances in Python
Native balances are only part of the picture. To check token balances, call the contract's balanceOf function using eth_call. The function selector for balanceOf(address) is 0x70a08231, followed by the address padded to 32 bytes. The decimals() selector is 0x313ce567.
The Python example below builds the calldata, calls the contract, and converts the result. It caches decimals per contract because decimals rarely change. Run it against one chain first, then extend it to your chain list.
import json, urllib.request
RPC_URL = "RPC_URL"
TOKEN = "TOKEN_CONTRACT_ADDRESS"
WALLET = "WALLET_ADDRESS"
def rpc(method, params):
payload = json.dumps({"jsonrpc": "2.0", "id": 1, "method": method, "params": params}).encode()
req = urllib.request.Request(RPC_URL, data=payload, headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req) as resp:
body = json.loads(resp.read())
if "error" in body:
raise RuntimeError(body["error"]["message"])
return body["result"]
def pad_address(addr):
return addr.lower().replace("0x", "").rjust(64, "0")
decimals_hex = rpc("eth_call", [{"to": TOKEN, "data": "0x313ce567"}, "latest"])
decimals = int(decimals_hex, 16)
balance_hex = rpc("eth_call", [{"to": TOKEN, "data": "0x70a08231" + pad_address(WALLET)}, "latest"])
balance = int(balance_hex, 16) / (10 ** decimals)
print(f"Token balance: {balance}")Recording results in a verification table
A multi-chain balance check is only trustworthy if you can reproduce it. Record one row per chain with the chain name, chain ID, endpoint label, block number, raw hex result, converted amount, and timestamp. The block number is important because balances change; without it, two runs at different times are not comparable.
The table below is a template, not measured data. Fill it in from your own runs. If a value looks wrong, the raw hex and block number let you re-query the same state and confirm whether the issue is in the chain data or in your conversion logic.
- | Chain | Chain ID | Endpoint label | Block | Raw hex | Converted | Timestamp |
- | Ethereum | 1 | primary | (your block) | (your hex) | (your value) | (your time) |
- | BNB Smart Chain | 56 | primary | (your block) | (your hex) | (your value) | (your time) |
- | Arbitrum One | 42161 | primary | (your block) | (your hex) | (your value) | (your time) |
Expected verification result and how to interpret it
A correct run produces one row per chain, each with a block number and a converted amount. Chains where the address has never received funds should return 0x0, which converts to zero. A chain that returns an error is not the same as a zero balance; record it as an error and investigate separately.
If you compare your output with a third-party wallet tracker, expect small differences. Trackers may use different block tags, cache values, or include token and DeFi positions that your native-only query does not cover. The way to reconcile is to compare the same block number and the same asset class, not the headline portfolio total.
- Zero balance: valid response, result 0x0.
- Non-zero balance: confirm decimals and symbol from your chain list.
- Error: check address format, endpoint health, and method support.
- Mismatch with a tracker: align block number and asset scope before concluding anything.
Troubleshooting the common failure modes
Most incorrect multi-chain balance results come from a small set of causes. The first is using the wrong chain ID or endpoint, which returns a valid balance for a different network. The second is decimal confusion, especially on chains whose native unit is not 18 decimals. The third is rate limiting: querying many chains and tokens in parallel can trigger 429 responses, which some scripts silently treat as zero.
A fourth cause is stale caching. If your service caches balances, make sure the cache key includes the chain ID, address, and block tag. A cache keyed only by address will return one chain's balance for every chain. Finally, confirm that your endpoint actually supports the methods you call; some restricted endpoints allow eth_getBalance but not eth_call.
- Validate the returned chain ID with eth_chainId before trusting a balance.
- Never treat an RPC error as a zero balance.
- Add retry with backoff for 429 and 5xx responses.
- Include chain ID and block tag in every cache key.
- Log the endpoint label with each result for auditability.
Limitations and tradeoffs of direct RPC balance checks
Direct RPC queries give you the most control and the clearest provenance, but they are not free of tradeoffs. Querying many chains means many endpoints, each with its own reliability profile, rate limits, and latency. Token balances multiply the call count, and historical balances require archive-capable nodes, which are a different class of service.
Public endpoints are convenient for occasional checks but are often rate-limited and may not support archive queries. For repeated or production use, a dedicated node or a managed RPC service provides more predictable throughput. The blockchain node hosting guide explains the operational differences, and the RPC pricing page describes how request volume maps to plans.
- Latency: the slowest chain determines the total response time unless you parallelize.
- Cost: token queries scale with contracts, not just chains.
- Archive data: historical balances need archive nodes.
- Consistency: different chains advance at different rates, so a snapshot is approximate.
Next steps: from script to reliable multi-chain balance service
Once the basic script works, the next step is to make it resilient. Parallelize chain queries with a concurrency limit, retry transient errors, and persist results with block numbers. If you need token and DeFi positions, add a token list per chain and cache decimals. If you need historical balances, route those queries to archive-capable endpoints.
For endpoint selection across many networks, the RPC Assistant's multi-chain RPC endpoints guide covers the decision criteria, and the API service page describes managed access. If you are choosing a provider for a specific chain, start with the RPC Assistant rather than hard-coding a public endpoint into production.
- Add a concurrency limit and retry policy.
- Persist raw hex, block number, and endpoint label.
- Separate native, token, and historical query paths.
- Review the multi-chain RPC endpoints guide before scaling.