Skip to content

Read contract state

Use eth_call to read USDC balanceOf at an explicit Ethereum mainnet block.

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

  1. Set GRAPHOPS_API_KEY to your GraphOps API key.

# 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.

Resolve one block, then pass its number to every read. Both balances describe block N even after latest moves on, so the pool price you compute from them is consistent.

This viem example resolves the finalized block, then reads the pair's USDC and WETH balances at that block:

Prerequisites

  1. Install Node.js.

  2. Install tsx and viem.

    npm install tsx viem
  3. Set 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 error object 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.

Next steps