# Read contract state

Source: https://docs.graphops.xyz/rpc/guides/read-contract-state

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 API key that includes RPC.
* 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.

CLI · curl

Prerequisites

1. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

[Example project](/examples/rpc.guides.read-contract-state.sh-1.tar.gz)

```sh
# 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

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x0000000000000000000000000000000000000000000000000000197016bc99ff"
}
```

Matches the id you sent

Token amount: 27,969,208,490,495 base units, 27,969,208.490495 USDC

TypeScript · viem

Prerequisites

1. Install Node.js.

2. Install tsx and viem. `npm install tsx viem`

3. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

Tested with Node.js 24.22.1, tsx 4.23.15, viem 2.57.10.

[Example project](/examples/rpc.guides.read-contract-state.ts-2.tar.gz)

```typescript
import { 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 balance = await client.readContract({
  address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
  abi: erc20Abi,
  functionName: "balanceOf",
  args: ["0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"],
  blockNumber: 18_000_000n,
});

console.log(balance.toString());

```

Output

```json
27969208490495
```

Token amount: 27,969,208,490,495 base units, 27,969,208.490495 USDC

Go · ethclient

Prerequisites

1. Install Go.

2. Install github.com/ethereum/go-ethereum. `go get github.com/ethereum/go-ethereum`

3. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

Tested with Go 1.26.0, github.com/ethereum/go-ethereum v1.17.7.

[Example project](/examples/rpc.guides.read-contract-state.go-6.tar.gz)

```go
package main

import (
	"context"
	"fmt"
	"log"
	"math/big"
	"os"
	"strings"

	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/ethereum/go-ethereum/rpc"
)

const balanceOfABI = `[{
	"name": "balanceOf",
	"type": "function",
	"stateMutability": "view",
	"inputs": [{"name": "owner", "type": "address"}],
	"outputs": [{"name": "", "type": "uint256"}]
}]`

func main() {
	ctx := context.Background()
	rpcClient, err := rpc.DialOptions(ctx, "https://rpc.graphops.xyz/v1/evm/1",
		rpc.WithHeader("Authorization", "Bearer "+os.Getenv("GRAPHOPS_API_KEY")))
	if err != nil {
		log.Fatal(err)
	}
	client := ethclient.NewClient(rpcClient)
	defer client.Close()

	usdcABI, err := abi.JSON(strings.NewReader(balanceOfABI))
	if err != nil {
		log.Fatal(err)
	}
	data, err := usdcABI.Pack("balanceOf", common.HexToAddress("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"))
	if err != nil {
		log.Fatal(err)
	}
	usdc := common.HexToAddress("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
	output, err := client.CallContract(ctx, ethereum.CallMsg{To: &usdc, Data: data}, big.NewInt(18_000_000))
	if err != nil {
		log.Fatal(err)
	}

	var balance *big.Int
	if err := usdcABI.UnpackIntoInterface(&balance, "balanceOf", output); err != nil {
		log.Fatal(err)
	}
	fmt.Println(balance)
}

```

Output

```json
27969208490495
```

Token amount: 27,969,208,490,495 base units, 27,969,208.490495 USDC

Rust · alloy

Prerequisites

1. Install Rust.

2. Install alloy, reqwest, and tokio. `cargo add alloy reqwest tokio -F alloy/provider-ws -F alloy/sol-types -F alloy/contract -F reqwest/json -F tokio/macros -F tokio/rt-multi-thread`

3. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

Tested with Rust 1.98.1, alloy 2.5.0, reqwest 0.13.5, tokio 1.48.0.

[Example project](/examples/rpc.guides.read-contract-state.rust-7.tar.gz)

```rust
use alloy::primitives::address;
use alloy::providers::ProviderBuilder;
use alloy::rpc::client::RpcClient;
use alloy::sol;
use alloy::transports::http::Http;
use reqwest::header::{HeaderMap, AUTHORIZATION};
use std::{env, error::Error};

sol! {
    #[sol(rpc)]
    interface IERC20 {
        function balanceOf(address owner) external view returns (uint256);
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let mut headers = HeaderMap::new();
    headers.insert(
        AUTHORIZATION,
        format!("Bearer {}", env::var("GRAPHOPS_API_KEY")?).parse()?,
    );
    let http = reqwest::Client::builder()
        .default_headers(headers)
        .build()?;
    let provider = ProviderBuilder::new().connect_client(RpcClient::new(
        Http::with_client(http, "https://rpc.graphops.xyz/v1/evm/1".parse()?),
        false,
    ));

    let usdc = IERC20::new(
        address!("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
        &provider,
    );
    let balance = usdc
        .balanceOf(address!("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"))
        .block(18_000_000.into())
        .call()
        .await?;

    println!("{balance}");
    Ok(())
}

```

Output

```json
27969208490495
```

Token amount: 27,969,208,490,495 base units, 27,969,208.490495 USDC

Python · web3.py

Prerequisites

1. Install Python.

2. Install web3. `python -m pip install web3`

3. Set GRAPHOPS\_API\_KEY to your GraphOps API key.

Tested with Python 3.12.12, web3 8.0.0.

[Example project](/examples/rpc.guides.read-contract-state.python-4.tar.gz)

```python
import os
from web3 import Web3

w3 = Web3(
    Web3.HTTPProvider(
        "https://rpc.graphops.xyz/v1/evm/1",
        request_kwargs={
            "headers": {
                "Authorization": f"Bearer {os.environ['GRAPHOPS_API_KEY']}",
            },
        },
    )
)

usdc = w3.eth.contract(
    address="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    abi=[
        {
            "name": "balanceOf",
            "type": "function",
            "stateMutability": "view",
            "inputs": [{"name": "owner", "type": "address"}],
            "outputs": [{"name": "", "type": "uint256"}],
        }
    ],
)

balance = usdc.functions.balanceOf(
    "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"
).call(block_identifier=18_000_000)

print(balance)

```

Output

```json
27969208490495
```

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`,
`ethclient`, `alloy`, 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.*

1. `eth_getBlockByNumber("finalized")`: returns block N.
2. `eth_call` USDC.balanceOf(pair) at block N.
3. `eth_call` WETH.balanceOf(pair) at block N.

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.

Tested with Node.js 24.22.1, tsx 4.23.15, viem 2.57.10.

[Example project](/examples/rpc.guides.read-contract-state.ts-5.tar.gz)

```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

```json
{
  "block": "26285536",
  "usdc": "9800805397315",
  "weth": "4175526930203265664930"
}
```

Block in decimal: 26,285,536

USDC balance: 9,800,805,397,315 base units, 9,800,805.397315 USDC

WETH balance: 4,175,526,930,203,265,664,930 base units, 4,175.52693020326566493 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](../concepts/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](./retries-and-reorgs).
* A historical block may need archive access on your network. See
[Archive reads](../concepts/archive-reads).

## Next steps

* [Supported methods](../methods#state-and-execution): Check parameters for `eth_call`, `eth_getBalance`, and `eth_getStorageAt`.
* [Consistency and finality](../concepts/consistency-and-finality): Choose between `latest`, `safe`, `finalized`, and a stored block.
* [Fetch logs](./fetch-logs): Read the events a contract emitted instead of its current state.