# Query contract state with SQL

Call an EVM contract function from SQL, pin the read to one block, and use a column as the function argument.

Canonical HTML: <https://determica.com/docs/guides/query-contract-state-with-sql>

## Goal

Query USDC `balanceOf(address)` from Ethereum mainnet. Keep exact [`UINT256`](/docs/types/uint256.md) values separate from formatted display values.

The notebook starts with a live read, repeats it at a pinned block, then uses an account column as the `balanceOf` argument.

## Parameters

- **Ethereum client** (`client`)

## 1. Read `balanceOf` as `UINT256`

Call USDC `balanceOf(address)` for one account. The ABI defines
the argument and output types, so&#x20;
[`read_contract`](/docs/functions/read_contract.md)&#x20;
returns a `UINT256`.

#### Read and format a live USDC balance

Call balanceOf(address) against live state, then format the exact UINT256 result for display.

```sql
WITH balance AS (
SELECT read_contract(
  $client,
  '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'::ADDRESS,
  '[{
    "type": "function",
    "name": "balanceOf",
    "stateMutability": "view",
    "inputs": [
      {
        "name": "account",
        "type": "address"
      }
    ],
    "outputs": [
      {
        "name": "balance",
        "type": "uint256"
      }
    ]
  }]'::JSON,
  'balanceOf',
  '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'::ADDRESS
) AS raw_balance
)
SELECT format_units(raw_balance, 6) AS vitalik_usdc
FROM balance;
```

[`format_units(raw_balance, 6)`](/docs/functions/format_units.md) returns display text. The unpinned client reads live state, so the value can
change.

## 2. Pin a block and keep the exact value

Use [`pin`](/docs/functions/pin.md) to resolve Ethereum
block `25,601,821` and return a client fixed to that block.

#### Read the balance at one pinned block

Resolve the block number to its hash, then return the exact UINT256 balance beside its formatted value.

```sql
WITH pinned AS (
SELECT pin($client, 25601821::UBIGINT) AS client
),
balance AS (
SELECT read_contract(
  client,
  '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'::ADDRESS,
  '[{
    "type": "function",
    "name": "balanceOf",
    "stateMutability": "view",
    "inputs": [
      {
        "name": "account",
        "type": "address"
      }
    ],
    "outputs": [
      {
        "name": "balance",
        "type": "uint256"
      }
    ]
  }]'::JSON,
  'balanceOf',
  '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'::ADDRESS
) AS raw_balance
FROM pinned
)
SELECT
raw_balance,
format_units(raw_balance, 6) AS usdc_balance
FROM balance;
```

`raw_balance` remains exact, while `usdc_balance` is
display text. The RPC must retain state for the pinned block. For one
historical read, [`read_contract_at`](/docs/functions/read_contract_at.md) accepts a block number directly.

## 3. Call `balanceOf` for each input row

Pass the `account` column as the function argument.&#x20;
`read_contract` evaluates `balanceOf` once per row.

#### Call balanceOf once per input row

Pass each account to balanceOf. Return the exact UINT256 balance beside its formatted value.

```sql
WITH holders(account) AS (
VALUES
  ('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'::ADDRESS),
  ('0x000000000000000000000000000000000000dEaD'::ADDRESS)
),
balances AS (
SELECT
  account,
  read_contract(
    $client,
    '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'::ADDRESS,
    '[{
      "type": "function",
      "name": "balanceOf",
      "stateMutability": "view",
      "inputs": [
        {
          "name": "account",
          "type": "address"
        }
      ],
      "outputs": [
        {
          "name": "balance",
          "type": "uint256"
        }
      ]
    }]'::JSON,
    'balanceOf',
    account
  ) AS raw_usdc_balance
FROM holders
)
SELECT
account,
raw_usdc_balance,
format_units(raw_usdc_balance, 6) AS usdc_balance
FROM balances;
```

Each result row contains the account as&#x20;
[`ADDRESS`](/docs/types/address.md), the raw balance as&#x20;
[`UINT256`](/docs/types/uint256.md), and the formatted
balance as `VARCHAR`.
The query makes one RPC-backed call per row. Use [`read_contract_multicall`](/docs/functions/read_contract_multicall.md) to batch calls and inspect failures per input.

## Functions used in this guide

- [read_contract](/docs/functions/read_contract.md)
- [format_units](/docs/functions/format_units.md)
- [pin](/docs/functions/pin.md)

## Continue with

- [read_contract_at](/docs/functions/read_contract_at.md)
- [read_contract_multicall](/docs/functions/read_contract_multicall.md)
