ChainRead / Guides

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

More guides

Sister services