Get an ERC-20 balance via HTTP API, no RPC key
To read a token balance you normally need an RPC endpoint, an ABI fragment and decimals handling. For an agent or a script that just wants a number, one GET is simpler.
The problem
A balance read is three steps: call balanceOf(address) on the token contract, call decimals(), and divide the integer by ten to the power of the decimals, being careful not to lose precision in floating point. Add native ETH, a USD value and a second chain, and a small helper turns into an RPC key to rotate, rate limits to respect and a failover to write.
How /balance answers it
GET /balance?chain=base&address=0x...&tokens=0x... returns the native ETH balance as an exact decimal string plus a USD value from the Chainlink ETH/USD feed, and for each requested token the symbol, decimals, the raw integer balanceRaw and the human-readable balance. Pass up to 20 token addresses (comma-separated in a GET, an array in a POST); with none, it reports common tokens for the chain. chain is base (default) or ethereum. A token address that is not an ERC-20 on that chain is reported inline with an error instead of failing the call. To check one token across up to 20 wallets in a single Multicall3 request, use /multicall-balances.
Request and response
Example: the USDC balance of the WETH contract on Base.
$ curl -i "https://chainread.imac2014ville.workers.dev/balance?chain=base&address=0x4200000000000000000000000000000000000006&tokens=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
HTTP/2 402
payment-required: eyJ4NDAyVmVyc2lvbiI6Mi4uLn0= # base64 JSON: scheme "exact", network eip155:8453,
# asset USDC, amount 2000 (= $0.002)
# An x402 client signs the payment, then retries with a PAYMENT-SIGNATURE header.
The paid response (abridged; balances change constantly):
{
"ok": true,
"chain": "base",
"address": "0x4200000000000000000000000000000000000006",
"native": { "symbol": "ETH", "balance": "274905.481287619079659259", "usd": 683383808.73 },
"ethUsd": 2485.88,
"tokens": [{
"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"symbol": "USDC",
"decimals": 6,
"balanceRaw": "167551481",
"balance": "167.551481"
}]
}
JavaScript with @x402/fetch
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
const signer = privateKeyToAccount(process.env.PRIVATE_KEY); // wallet holding USDC on Base
const pay = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(signer) }],
});
const url = `https://chainread.imac2014ville.workers.dev/balance?chain=base&address=${wallet}&tokens=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`;
const { native, tokens } = await (await pay(url)).json();
console.log(native.balance, "ETH", tokens[0].balance, tokens[0].symbol);
Pricing
/balance costs $0.002 per call; /multicall-balances costs $0.005 for up to 20 addresses. There is no API key and no account: each request is paid in USDC on Base over x402. Malformed input returns 400 and failed lookups return a non-2xx status, so those are not charged.
Limitations
- Base and Ethereum mainnet only.
- Balances are read at the latest block from public RPC providers and are not historical.
- Tokens with unusual accounting (rebasing or fee-on-transfer) report what
balanceOfreturns, which may differ from the amount you expect.
More guides
ChainRead overview/openapi.jsonllms.txt
Sister services
- BaseLens: Base chain tools: tx explainer, wallet snapshot, x402 endpoint check, web-to-markdown
- DepVet: npm and PyPI package vetting before install
- TokenGuard: honeypot and rug pull checks for Base tokens, plus pre-screened new launches
- MacroLens: country macro statistics and company registries (Norway, France)
- SkyFeed: weather forecasts, US alerts, earthquakes and public holidays