# read_contract

Calls a contract function against live, pinned, or retained execution state and returns a typed SQL value. Without a CLIENT, it builds a reusable READ and does not execute the call.

Canonical HTML: <https://determica.com/docs/functions/read_contract>

- Kind: scalar function
- Category: [Chain reads](/docs/category/chain-reads.md)
- Tags: RPC, Program Compiler, Simulate

Calls a contract function against live, pinned, or retained execution state and returns a typed SQL value. Without a CLIENT, it builds a reusable READ and does not execute the call.

## Overload 1: read_contract(CLIENT, ADDRESS, JSON, VARCHAR, ...args)

```sql
read_contract(CLIENT, ADDRESS, JSON, VARCHAR, ...args)
```

Derives the SQL return type and field names from a literal or foldable ABI. function_name must also be literal or foldable. The block selector is latest.

- Kind: scalar
- Execution context: Live RPC
- Behavior: Immediate Read
- Execution modes: Live Client, Pinned Client, Execution Client
- Input shape: Scalar Arguments
- Planning contract: Bind Time Schema
- Cardinality: One Per Input
- Determinism: External State
- Return shape: Scalar
- Schema stability: Abi Dependent
- Stability: Stable
- Side effects: network_io

### Requirements

- client_state_context
- rpc

### Risks

- provider_limits
- chain_reorganization

### Inputs

- `client` (CLIENT; required; positional) — Live, pinned, or retained execution CLIENT. Pinned and execution state require the resolved block selector to be latest and reject other selectors.
- `to` (ADDRESS; required; positional) — Contract address to call.
- `abi` (JSON; required; positional) — Literal or foldable JSON ABI used for argument encoding and output types.
- `function_name` (VARCHAR; required; positional) — Literal or foldable ABI function name.
- `function_args` (ANY; positional; variadic) — Pass one SQL value per ABI input. The ABI determines how many values are accepted and how each value is cast. The ABI and function name must be literal or foldable so the return type can be inferred during planning.

### Returns

ABI-derived scalar or STRUCT

A function with one output returns that value directly. A function with multiple outputs returns a STRUCT with fields named from ABI outputs, or output0, output1, and so on for unnamed outputs.

- `result` (ABI-derived scalar or STRUCT; dynamic) — A function with one output returns that value directly. A function with multiple outputs returns a STRUCT with fields named from ABI outputs, or output0, output1, and so on for unnamed outputs.

ABI-derived scalar or STRUCT. A function with one output returns that value directly. A function with multiple outputs returns a STRUCT with fields named from ABI outputs, or output0, output1, and so on for unnamed outputs.

### Examples

_Named parameters · Needs RPC · RPC required_

```sql
-- WETH balance of vitalik.eth in raw units
SELECT read_contract(
  $client, '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'::ADDRESS,
  '[{"type":"function","name":"balanceOf","inputs":[{"name":"account","type":"address"}],"outputs":[{"name":"balance","type":"uint256"}]}]'::JSON,
  'balanceOf', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'::ADDRESS
) AS raw_balance;
```

## Overload 2: read_contract(CLIENT, ADDRESS, JSON, VARCHAR, JSON, ...args)

```sql
read_contract(CLIENT, ADDRESS, JSON, VARCHAR, JSON, ...args)
```

Allows per-row options while deriving the return type from a literal or foldable ABI. function_name must also be literal or foldable.

- Kind: scalar
- Execution context: Live RPC
- Behavior: Immediate Read
- Execution modes: Live Client, Pinned Client, Execution Client
- Input shape: Scalar Arguments
- Planning contract: Bind Time Schema
- Cardinality: One Per Input
- Determinism: External State
- Return shape: Scalar
- Schema stability: Abi Dependent
- Stability: Stable
- Side effects: network_io

### Requirements

- client_state_context
- rpc

### Risks

- provider_limits
- chain_reorganization

### Inputs

- `client` (CLIENT; required; positional) — Live, pinned, or retained execution CLIENT. Pinned and execution state require the resolved block selector to be latest and reject other selectors.
- `to` (ADDRESS; required; positional) — Contract address to call.
- `abi` (JSON; required; positional) — Literal or foldable JSON ABI used for argument encoding and output types.
- `function_name` (VARCHAR; required; positional) — Literal or foldable ABI function name.
- `options` (JSON; required; positional) — JSON object. The block defaults to latest. block_number is preferred over blockNumber, and both take precedence over block_tag; a null block_number gives NULL without a call. on_error is preferred over onError and accepts raise, null (reverts and decode failures, not transport failures) or null_on_failure (transport failures too, for a caller that records them as unread). rpc_batch_size defaults to 100 and must be at least 1. Any other key raises.
- `function_args` (ANY; positional; variadic) — Pass one SQL value per ABI input. The ABI determines how many values are accepted and how each value is cast. The ABI and function name must be literal or foldable so the return type can be inferred during planning.

### Returns

ABI-derived scalar or STRUCT

A function with one output returns that value directly. A function with multiple outputs returns a STRUCT with fields named from ABI outputs, or output0, output1, and so on for unnamed outputs.

- `result` (ABI-derived scalar or STRUCT; dynamic) — A function with one output returns that value directly. A function with multiple outputs returns a STRUCT with fields named from ABI outputs, or output0, output1, and so on for unnamed outputs.

ABI-derived scalar or STRUCT. A function with one output returns that value directly. A function with multiple outputs returns a STRUCT with fields named from ABI outputs, or output0, output1, and so on for unnamed outputs.

### Examples

_Named parameters · Needs RPC · RPC required_

```sql
-- Read a historical price across per-row block options
WITH blocks AS (
  SELECT block_number, json_object('blockNumber', concat('0x', to_hex(block_number))) AS opts
  FROM generate_series(21000000, 21000010) AS t(block_number)
)
SELECT
  block_number,
  read_contract(
    $client,
    '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419'::ADDRESS, -- Ethereum mainnet Chainlink ETH/USD feed
    '[{"inputs":[],"name":"latestAnswer","outputs":[{"type":"int256"}],"stateMutability":"view","type":"function"}]'::JSON,
    'latestAnswer',
    opts -- per-row block options from the CTE
  ) AS price
FROM blocks;
```

## Overload 3: read_contract(CLIENT, ADDRESS, JSON, VARCHAR, JSON, VARCHAR, ...args)

```sql
read_contract(CLIENT, ADDRESS, JSON, VARCHAR, JSON, VARCHAR, ...args)
```

Uses a literal or foldable return_schema to fix the SQL return type. The ABI may vary by row in raw or explicit-schema mode; function_name and return_schema must remain foldable. raw returns BYTES, one declared type returns a scalar, and multiple declared fields return a STRUCT named by return_schema.

- Kind: scalar
- Execution context: Live RPC
- Behavior: Immediate Read
- Execution modes: Live Client, Pinned Client, Execution Client
- Input shape: Scalar Arguments
- Planning contract: Bind Time Schema
- Cardinality: One Per Input
- Determinism: External State
- Return shape: Scalar
- Schema stability: Abi Dependent
- Stability: Stable
- Side effects: network_io

### Requirements

- client_state_context
- rpc

### Risks

- provider_limits
- chain_reorganization

### Inputs

- `client` (CLIENT; required; positional) — Live, pinned, or retained execution CLIENT. Pinned and execution state require the resolved block selector to be latest and reject other selectors.
- `to` (ADDRESS; required; positional) — Contract address to call.
- `abi` (JSON; required; positional) — JSON ABI used for argument encoding. It may vary by row in raw or explicit-schema mode; otherwise it must be literal or foldable.
- `function_name` (VARCHAR; required; positional) — Literal or foldable ABI function name.
- `options` (JSON; required; positional) — JSON object. The block defaults to latest. block_number is preferred over blockNumber, and both take precedence over block_tag; a null block_number gives NULL without a call. on_error is preferred over onError and accepts raise, null (reverts and decode failures, not transport failures) or null_on_failure (transport failures too, for a caller that records them as unread). rpc_batch_size defaults to 100 and must be at least 1. Any other key raises.
- `return_schema` (VARCHAR; required; positional) — Literal or foldable output schema: raw, one type, or comma-separated fields such as amount:uint256. Unnamed fields use field_0, field_1, and so on.
- `function_args` (ANY; positional; variadic) — Pass one SQL value per ABI input. The ABI determines argument count and casts, and may be dynamic in this overload. return_schema supplies the planning-time output contract.

### Returns

return_schema-declared scalar, STRUCT, or raw BYTES

return_schema='raw' returns raw BYTES data. A single declared Solidity type such as 'uint256' returns that value directly. Multiple outputs should use named entries such as 'amountOut:uint256,sqrtPriceX96After:uint256', which returns a STRUCT with those field names. Unnamed entries such as 'uint256,uint256' are accepted but fall back to field_0, field_1, and so on.

- `result` (ANY)

return_schema-declared scalar, STRUCT, or raw BYTES. return_schema='raw' returns raw BYTES data. A single declared Solidity type such as 'uint256' returns that value directly. Multiple outputs should use named entries such as 'amountOut:uint256,sqrtPriceX96After:uint256', which returns a STRUCT with those field names. Unnamed entries such as 'uint256,uint256' are accepted but fall back to field_0, field_1, and so on.

### Examples

_Named parameters · Needs RPC · RPC required_

```sql
-- Dynamic ABI from a CTE needs an explicit return_schema
WITH abis AS (
  SELECT '[
    {
      "type": "function",
      "name": "balanceOf",
      "stateMutability": "view",
      "inputs":  [{ "name": "account", "type": "address" }],
      "outputs": [{ "name": "", "type": "uint256" }]
    }
  ]'::JSON AS abi
)
SELECT read_contract(
  $client,
  '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'::ADDRESS, -- Ethereum mainnet WETH
  abi,
  'balanceOf',
  '{}'::JSON, -- options; use NULL or {} when no override is needed
  'uint256', -- return_schema fixes the SQL output type
  '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'::ADDRESS -- function argument
) AS balance FROM abis;
```

## Overload 4: read_contract(ADDRESS, JSON, VARCHAR, ...args)

```sql
read_contract(ADDRESS, JSON, VARCHAR, ...args)
```

Selects an ABI function and encodes its arguments for a later isolated observation call.

- Kind: scalar
- Execution context: Offline
- Behavior: Read Builder
- Execution modes: Read Constructor
- Input shape: Scalar Arguments
- Planning contract: Bind Time Schema
- Cardinality: One Per Input
- Determinism: Deterministic
- Return shape: Scalar
- Schema stability: Abi Dependent
- Stability: Stable
- Side effects: None documented

### Inputs

- `to` (ADDRESS; required; positional) — Contract address that the later observation should call.
- `abi` (JSON; required; positional) — Non-NULL JSON ABI used for function selection, input encoding, and output decoding.
- `function_name` (VARCHAR; required; positional) — Function name, canonical signature, or supported selector literal resolved against the ABI and arguments.
- `function_args` (ANY; positional; variadic) — One non-NULL SQL value per ABI input, in order. Incompatible values and ambiguous overloads fail.

### Returns

Contract READ

Returns an ABI-aware READ with encoded calldata, output schema, zero caller, zero Wei value, and a 16,777,216 gas limit.

- `read` (READ)

Contract READ. Returns an ABI-aware READ with encoded calldata, output schema, zero caller, zero Wei value, and a 16,777,216 gas limit.

### Examples

_Local SQL · Local_

```sql
SELECT read_contract('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'::ADDRESS, -- Ethereum mainnet WETH
  '[{"type":"function","name":"balanceOf","inputs":[{"name":"account","type":"address"}],"outputs":[{"name":"balance","type":"uint256"}]}]'::JSON,
  'balanceOf', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'::ADDRESS);
```

## Guidance

### Options

The default block selector is latest. block_number is preferred over blockNumber, and either takes precedence over block_tag.

on_error is preferred over onError. The default is raise; null converts reverts, ABI result-decode errors (including empty return data from an address without code) and decoded output-count mismatches to NULL. Transport, rate-limit and archive failures still raise, as do invalid options, arguments, ABI encoding, client-state errors and return_schema cast failures.

null_on_failure also gives NULL for a read the node could not answer, for a caller that records it as unread instead of reading NULL as a value.

A null block_number, or a NULL options value on a row, gives NULL without a call. An unknown option key raises.

rpc_batch_size defaults to 100 and must be an integer of at least 1.

- Pinned and retained execution CLIENT values require the resolved block selector to be latest. read_contract_at and options that select another block are rejected for those states.

```sql
WITH tokens(symbol, token_address, holder, decimals) AS (
  VALUES
    ('WETH', '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'::ADDRESS,
             '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'::ADDRESS, 18)
),
raw_balances AS (
  SELECT
    symbol,
    decimals,
    read_contract(
      $client,
      token_address,
      '[{"type":"function","name":"balanceOf","inputs":[{"name":"account","type":"address"}],"outputs":[{"name":"balance","type":"uint256"}]}]'::JSON,
      'balanceOf',
      '{"block_number":21000000}'::JSON,
      holder
    ) AS raw_balance
  FROM tokens
)
SELECT symbol, raw_balance, format_units(raw_balance, decimals) AS balance
FROM raw_balances
ORDER BY sort_key(raw_balance) DESC;
```

### Pinned account snapshots

Use the same block number for every read in an account snapshot, and retain raw UINT256 values beside formatted display values.

Compute code hashes with code_at and keccak256 at that block when bytecode identity is part of the evidence.

- Read decimals, balanceOf, allowance, and code hash in one table-shaped query.
- Older block heights may require an archive-capable provider.

```sql
-- Pinned ERC-20 account snapshot at Ethereum block 21000000
WITH params AS (
  SELECT
    '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'::ADDRESS AS account,
    '0x111111125421cA6dc452d289314280a0f8842A65'::ADDRESS AS spender,
    21000000::BIGINT AS block_number
),
tokens(symbol, token_address, decimals) AS (
  VALUES
    ('WETH', '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'::ADDRESS, 18),
    ('USDC', '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'::ADDRESS, 6)
),
snapshot AS (
  SELECT
    t.symbol,
    t.token_address,
    keccak256(code_at($client, t.token_address, p.block_number)) AS code_hash,
    read_contract_at(
      $client, t.token_address,
      '[{"type":"function","name":"decimals","inputs":[],"outputs":[{"name":"decimals","type":"uint8"}]}]'::JSON,
      'decimals', p.block_number
    ) AS onchain_decimals,
    read_contract_at(
      $client, t.token_address,
      '[{"type":"function","name":"balanceOf","inputs":[{"name":"account","type":"address"}],"outputs":[{"name":"balance","type":"uint256"}]}]'::JSON,
      'balanceOf', p.block_number, p.account
    ) AS raw_balance,
    read_contract_at(
      $client, t.token_address,
      '[{"type":"function","name":"allowance","inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"outputs":[{"name":"allowance","type":"uint256"}]}]'::JSON,
      'allowance', p.block_number, p.account, p.spender
    ) AS raw_allowance,
    t.decimals
  FROM tokens t, params p
)
SELECT
  symbol,
  token_address,
  code_hash,
  onchain_decimals,
  raw_balance,
  format_units(raw_balance, decimals) AS balance,
  raw_allowance,
  format_units(raw_allowance, decimals) AS allowance
FROM snapshot
ORDER BY symbol;
```

### Caller-sensitive quote checks

V4Quoter-style eth_call results can depend on caller, block state, hooks, and revert behavior.

Materialize the returned STRUCT before ranking or formatting so NULL and error handling are explicit.

- Use raw_call/call_decode or read_contract_multicall when you need ok, reverted, or error diagnostics.
- A quote is read evidence, not a promise of execution, MEV protection, settlement, or future fill.
- Keep raw units for ranking and format only presentation columns.

```sql
-- V4Quoter-style sanity rows; materialize the returned STRUCT first
WITH quote_inputs(amount_in, zero_for_one) AS (
  VALUES
    (5::UINT256 * 1000000000000000000::UINT256, true),
    (20::UINT256 * 1000000000000000000::UINT256, true),
    (100::UINT256 * 1000000000000000000::UINT256, true)
),
quotes AS (
  SELECT
    amount_in,
    zero_for_one,
    read_contract(
      $client,
      '0x52f0e24d1c21c8a0cb1e5a5dd6198556bd9e1203'::ADDRESS,
      '[{"type":"function","name":"quoteExactInputSingle","inputs":[{"name":"params","type":"tuple","components":[{"name":"poolKey","type":"tuple","components":[{"name":"currency0","type":"address"},{"name":"currency1","type":"address"},{"name":"fee","type":"uint24"},{"name":"tickSpacing","type":"int24"},{"name":"hooks","type":"address"}]},{"name":"zeroForOne","type":"bool"},{"name":"exactAmount","type":"uint128"},{"name":"hookData","type":"bytes"}]}],"outputs":[{"name":"amountOut","type":"uint256"},{"name":"gasEstimate","type":"uint256"}]}]'::JSON,
      'quoteExactInputSingle',
      '{"block_number":21000000,"on_error":"null"}'::JSON,
      {
        'poolKey': {
          'currency0': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'::ADDRESS,
          'currency1': '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'::ADDRESS,
          'fee': 200::UINT24,
          'tickSpacing': 4::INT24,
          'hooks': '0x0000000000000000000000000000000000000000'::ADDRESS
        },
        'zeroForOne': zero_for_one,
        'exactAmount': amount_in,
        'hookData': ''::BYTES
      }
    ) AS quote
  FROM quote_inputs
)
SELECT
  amount_in,
  zero_for_one,
  quote IS NOT NULL AS quote_ok,
  (quote).amountOut AS amount_out,
  (quote).gasEstimate AS gas_estimate
FROM quotes
ORDER BY amount_in;
```

### ABI validation

Construction fails for malformed ABI, NULL arguments, missing or incompatible functions, ambiguous overloads, argument-count mismatches, or values that cannot be ABI-encoded.

### Call defaults

The observation call uses the zero address as caller, zero Wei value, and a 16,777,216 gas limit; all observation calls are capped at 30,000,000 gas.

### Which ABI helper should I use?

- Use read_contract when you want an ABI-aware eth_call with a typed SQL result.
- Use encode_function_data when you need calldata for simulation, transaction preparation, or raw eth_call.
- Use raw_call when you want raw eth_call return bytes.
- Use call_decode when you already have raw return bytes and want ABI-derived output columns.
- Use event_signature when you need topic0 for a known event signature.
- Use event_decode_json when you already have raw log topics and data.

### Constant ABI vs dynamic ABI

read_contract must know its SQL result type while the query is planned.

Use a constant ABI literal when the ABI is fixed in the query; use the options + return_schema overload when abi comes from a column, CTE, or join.

```sql
SELECT read_contract(
  $client,
  '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'::ADDRESS,
  '[{"type":"function","name":"balanceOf","inputs":[{"type":"address"}],"outputs":[{"type":"uint256"}]}]'::JSON,
  'balanceOf',
  '{}'::JSON,
  'uint256',
  '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'::ADDRESS
) AS balance;
```

## Additional examples

```sql
-- Table-driven ERC-20 balances with raw and display values
WITH tokens(symbol, token_address, holder, decimals) AS (
  VALUES
    ('WETH', '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'::ADDRESS,
             '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'::ADDRESS, 18),
    ('USDC', '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'::ADDRESS,
             '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'::ADDRESS, 6)
),
balances AS (
  SELECT
    symbol,
    decimals,
    read_contract(
      $client,
      token_address,
      '[{
        "type": "function",
        "name": "balanceOf",
        "stateMutability": "view",
        "inputs":  [{ "name": "account", "type": "address" }],
        "outputs": [{ "name": "balance", "type": "uint256" }]
      }]'::JSON,
      'balanceOf',
      '{"block_tag":"finalized"}'::JSON,
      holder
    ) AS raw_balance
  FROM tokens
)
SELECT
  symbol,
  raw_balance,
  CASE symbol
    WHEN 'USDC' THEN format_units(raw_balance, 6)
    ELSE format_units(raw_balance, 18)
  END AS balance
FROM balances;
```

## Related functions

- [encode_function_data](/docs/functions/encode_function_data.md) — Alternative
- [raw_call](/docs/functions/raw_call.md) — Alternative
- [call_decode](/docs/functions/call_decode.md) — Alternative
- [read_contract_at](/docs/functions/read_contract_at.md) — See Also
- [read_contract_multicall](/docs/functions/read_contract_multicall.md) — See Also
