Read a contract like a table

Query live contract state as typed SQL, then compose it with balances, logs, calldata, simulations, and policy checks.

Goal

Call any read-only Solidity function from SQL and get typed values back. If a contract exposes a view or pure function, read_contract can call it through RPC and return the decoded output as a SQL value.

read_contract performs an eth_call: it ABI-encodes the function call, sends it through $transport, decodes the response, and returns a value you can use in SQL. It does not send a transaction or require a wallet signature.

Use it for token metadata, balances, oracle values, vault parameters, pool state, or risk inputs. Start with a direct call when you only need a few values.

Usage

The basic call shape matches the contract function:

read_contract($transport, contract_address, abi_json, 'functionName', ...function_args)

For a no-argument function, pass the transport, contract address, ABI, and function name. This query reads USDC metadata directly from Ethereum mainnet.

1
-- Read USDC metadata directly from Ethereum mainnet.
2
-- The inline ABI tells read_contract how to encode and decode the call.
3
SELECT
4
read_contract(
5
$transport,
6
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'::ADDRESS,
7
'[{
8
"type": "function",
9
"name": "name",
10
"stateMutability": "view",
11
"inputs": [],
12
"outputs": [{"type": "string"}]
13
}]'::JSON,
14
'name'
15
) AS token_name,
16
 
17
read_contract(
18
$transport,
19
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'::ADDRESS,
20
'[{
21
"type": "function",
22
"name": "decimals",
23
"stateMutability": "view",
24
"inputs": [],
25
"outputs": [{"type": "uint8"}]
26
}]'::JSON,
27
'decimals'
28
) AS decimals;
Notebook ready in readonly mode.

For single-output functions like name() and decimals(), read_contract returns the value itself.

token_name | decimals
-----------|---------
USD Coin   | 6

Passing arguments

If the Solidity function takes inputs, pass one SQL value per ABI input after the function name. The ABI determines how each value is encoded.

This example calls balanceOf(address) on USDC for Vitalik’s address, then formats the raw uint256 with USDC’s 6 decimals.

1
-- Read USDC balanceOf(address) for Vitalik's address.
2
SELECT
3
format_units(
4
read_contract(
5
$transport,
6
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'::ADDRESS,
7
'[{
8
"type": "function",
9
"name": "balanceOf",
10
"stateMutability": "view",
11
"inputs": [{"name": "account", "type": "address"}],
12
"outputs": [{"type": "uint256"}]
13
}]'::JSON,
14
'balanceOf',
15
'0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'::ADDRESS
16
),
17
6
18
) AS vitalik_usdc;
Notebook ready in readonly mode.
vitalik_usdc
------------
...

The address argument is cast to ADDRESS so the SQL value matches the ABI input type. For functions with more inputs, keep adding arguments in ABI order.

If you need a block override, pass an options JSON before the function arguments:

read_contract(
  $transport,
  contract_address,
  abi_json,
  'balanceOf',
  json_object('block_number', 21000000),
  account_address
)

Return value

read_contract returns a typed SQL value, not raw RPC hex.

  • One ABI output returns the value directly.
  • Multiple ABI outputs return a STRUCT.
  • Named ABI outputs become struct fields you can project with dot notation.

Query: Read pool state and compute a price

The same pattern works for protocol-specific state. Uniswap v4 pools are read through the StateView contract with a poolId; v4 pools do not have individual pool contract addresses.

This query calls getSlot0(bytes32), receives a struct, projects its fields, and computes the WETH/USDC spot price in SQL.

1
-- Read WETH/USDC spot state from a Uniswap v4 pool on Ethereum mainnet.
2
SELECT
3
(
4
-- Uniswap stores price as a Q64.96 fixed-point square root.
5
-- Squaring sqrtPriceX96 gives the raw pool ratio; 2^192 removes the fixed-point scale.
6
-- This pool's raw ratio is WETH per USDC, so invert it to report USDC per WETH.
7
-- Keep the math in UINT256 helpers so the square and inversion stay exact.
8
format_units(
9
divmul(
10
1::UINT256 << 192,
11
muldiv(
12
(pool_state.sqrtPriceX96)::UINT256,
13
(pool_state.sqrtPriceX96)::UINT256,
14
1::UINT256
15
),
16
-- 10^12 adjusts WETH's 18 decimals against USDC's 6.
17
-- The extra 8 decimals keep cents and sub-cent precision in the displayed price.
18
parse_units('1', 20)
19
),
20
8
21
)
22
)::DECIMAL(38, 8) AS weth_price_usdc,
23
pool_state.tick AS tick,
24
pool_state.lpFee AS lp_fee,
25
pool_state.protocolFee AS protocol_fee
26
FROM (
27
SELECT read_contract(
28
$transport,
29
'0x7ffe42c4a5deea5b0fec41c94c136cf115597227'::ADDRESS,
30
'[{
31
"type": "function",
32
"name": "getSlot0",
33
"stateMutability": "view",
34
"inputs": [{"name": "poolId", "type": "bytes32"}],
35
"outputs": [
36
{"name": "sqrtPriceX96", "type": "uint160"},
37
{"name": "tick", "type": "int24"},
38
{"name": "protocolFee", "type": "uint24"},
39
{"name": "lpFee", "type": "uint24"}
40
]
41
}]'::JSON,
42
'getSlot0',
43
'0x5a5c7cab5f55c7ea020e97d4fa6dd5d99270e56ce76afa61d8cbddec0af92060'::BYTES32
44
) AS pool_state
45
) AS slot0
46
WHERE pool_state.sqrtPriceX96 != 0;
Notebook ready in readonly mode.

Read the output

Multi-output calls, like getSlot0, return a struct. Project fields with pool_state.field_name, then compute with normal SQL expressions.

weth_price_usdc | tick   | lp_fee | protocol_fee
----------------|--------|--------|-------------
...             | ...    | ...    | ...

Once a contract read is decoded, it behaves like any other SQL value: project it, format it, join it, feed it into policy checks, or combine it with simulations.

Functions used in this guide