Read a token balance, pool reserve, or any other view function without sending a transaction.
This guide reads the USDC balance of the Uniswap V2 USDC/WETH pair at block 18000000 with
eth_call, then pins several reads to one block so their results describe the same chain state.
Prerequisites
- An RPC API key.
- Ethereum mainnet endpoint access.
- A contract address, ABI or encoded call data, and a block reference.
Request
The data field is balanceOf(address) encoded with selector 0x70a08231 and the target address
left-padded to 32 bytes.
Prerequisites
Set GRAPHOPS_API_KEY to your GraphOps API key.
curl
# data is the balanceOf(address) selector 0x70a08231 followed by the holder address
# left-padded to 32 bytes. 0x112a880 is block 18,000,000.
curl --fail-with-body --silent --show-error "https://rpc.graphops.xyz/v1/evm/1" \
-H "Authorization: Bearer ${GRAPHOPS_API_KEY}" \
-H "Content-Type: application/json" \
--data '{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_call",
"params": [
{
"to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"data": "0x70a08231000000000000000000000000b4e16d0168e52d35cacd2c6185b44281ec28c9dc"
},
"0x112a880"
]
}'
Output
{ "jsonrpc": "2.0", "id": 1,Matches the id you sent "result": "0x0000000000000000000000000000000000000000000000000000197016bc99ff"Token amount: 27,969,208,490,495 base units, 27,969,208.490495 USDC}Read the output
USDC has 6 decimals, so divide the decoded integer by 1,000,000. Client libraries such as viem,
ethers, and web3.py decode the bytes for you and return the integer.
Read several values at one block
Separate latest reads can land on different blocks as the chain advances. When results must
agree, such as two pool balances used to compute a price, resolve one block first and pass its
number to every read.
This viem example resolves the finalized block, then reads the pair's USDC and WETH balances at
that block:
Prerequisites
Install Node.js.
Install tsx and viem.
npm install tsx viemSet GRAPHOPS_API_KEY to your GraphOps API key.
TypeScript
import { type Address, createPublicClient, erc20Abi, http } from "viem";
import { mainnet } from "viem/chains";
const client = createPublicClient({
chain: mainnet,
transport: http("https://rpc.graphops.xyz/v1/evm/1", {
fetchOptions: {
headers: {
Authorization: `Bearer ${process.env.GRAPHOPS_API_KEY}`,
},
},
}),
});
const pair = "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc";
// Pin both reads to one finalized block so the balances are consistent.
const block = await client.getBlock({ blockTag: "finalized" });
const balanceOf = (token: Address) =>
client.readContract({
address: token,
abi: erc20Abi,
functionName: "balanceOf",
args: [pair],
blockNumber: block.number,
});
const [usdc, weth] = await Promise.all([
balanceOf("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
balanceOf("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
]);
console.log(
JSON.stringify({
block: block.number.toString(),
usdc: usdc.toString(),
weth: weth.toString(),
}),
);
Output
{ "block": "26250759",Block in decimal: 26,250,759 "usdc": "9871201835634",USDC balance: 9,871,201,835,634 base units, 9,871,201.835634 USDC "weth": "4143481977178600898550"WETH balance: 4,143,481,977,178,600,898,550 base units, 4,143.48197717860089855 WETH}Use finalized when the result must not change after a reorganization, safe for user-visible
reads that can trail the head slightly, or a block number you stored earlier to repeat a read.
See Consistency and finality for how the tags differ.
Interpret errors
- A revert is a contract execution result, not a transport failure. The JSON-RPC
errorobject carries the revert data when the contract provides it, and retrying the same call at the same block reverts again. - Retry only when the HTTP status or JSON-RPC error indicates a transport or server failure. See Retries and reorgs.
- A historical block may need archive access on your network. See Archive reads.