> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://docs.ampdb.io/api/mcp` to find what you need.

# eth_call

`eth_call` calls a function of a smart contract on an EVM chain, like Ethereum or Base, and returns what the contract
returned. Use it to read state your datasets don't store: a token's name and decimals, a balance, a pool's reserves, or
any other `view` function. Amp sends each call to the chain's RPC endpoint while the query runs. Nothing is written to
the chain.

## Prerequisites

* A [connection](/querying/connecting) to a query server. The examples use `ampsql`.
* An [EVM RPC provider](/providers/evm-rpc) for the chain you call, registered on the Amp you query. The examples call
  Ethereum mainnet through the `mainnet_rpc` provider of the
  [example datasets](/querying/connecting#example-datasets), and the last two also read those datasets.
* To call at past blocks, an RPC endpoint that keeps the chain's old state, like an archive node. Some free public
  endpoints do, and others only keep recent state: [Retries](/querying/functions/eth-call#retries) shows what a call gets from those.

## Call a contract

Name the chain after `rpc.`, then pass the sender, the contract, the encoded call, and the block. This asks USDC's
contract for its `decimals()` at the latest block:

```sql
SELECT rpc.mainnet.eth_call(
  NULL,
  evm_encode_hex('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'),
  evm_encode_params('decimals()'),
  'latest'
) AS result
```

```text [Output]
+-------------------------------------------------------------------------------------+
| result                                                                              |
+-------------------------------------------------------------------------------------+
| {data: 0000000000000000000000000000000000000000000000000000000000000006, message: } |
+-------------------------------------------------------------------------------------+
```

The result has the bytes the contract returned, in `data`, and an error message, in `message`, which is empty here
because the call succeeded. To turn the bytes into a value, decode them with
[`evm_decode_type`](/querying/functions/evm), giving the function's return type. Name the result in a subquery to read
its fields:

```sql
SELECT
  evm_decode_type(symbol.data, 'string') AS symbol,
  evm_decode_type(decimals.data, 'uint8') AS decimals
FROM (
  SELECT
    rpc.mainnet.eth_call(NULL, usdc, evm_encode_params('symbol()'), 'latest') AS symbol,
    rpc.mainnet.eth_call(NULL, usdc, evm_encode_params('decimals()'), 'latest') AS decimals
  FROM (SELECT evm_encode_hex('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48') AS usdc)
)
```

```text [Output]
+--------+----------+
| symbol | decimals |
+--------+----------+
| USDC   | 6        |
+--------+----------+
```

`evm_encode_params` builds the encoded call from the function's signature and its arguments, and `evm_encode_hex` turns
an address into the bytes `eth_call` expects (see [EVM functions](/querying/functions/evm)).

## Call at a past block

Pass a block number instead of a tag to read the contract's state as it was after that block. This asks for USDC's
total supply after block 24,000,099, in its smallest unit (USDC has 6 decimals, so about 52.6 billion USDC):

```sql
SELECT evm_decode_type(result.data, 'uint256') AS total_supply
FROM (
  SELECT rpc.mainnet.eth_call(
    NULL,
    evm_encode_hex('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'),
    evm_encode_params('totalSupply()'),
    24000099
  ) AS result
)
```

```text [Output]
+-------------------+
| total_supply      |
+-------------------+
| 52596774795447175 |
+-------------------+
```

To read each row's state at its own block, pass the row's block number. This follows the supply over the first five
example blocks:

```sql
SELECT block_num, evm_decode_type(supply.data, 'uint256') AS total_supply
FROM (
  SELECT
    block_num,
    rpc.mainnet.eth_call(
      NULL,
      evm_encode_hex('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'),
      evm_encode_params('totalSupply()'),
      block_num
    ) AS supply
  FROM "acme/eth_mainnet".blocks
  WHERE block_num < 24000005
)
ORDER BY block_num
```

```text [Output]
+-----------+-------------------+
| block_num | total_supply      |
+-----------+-------------------+
| 24000000  | 52595449157032994 |
| 24000001  | 52595449157032994 |
| 24000002  | 52595449157032994 |
| 24000003  | 52595572667725848 |
| 24000004  | 52595572667725848 |
+-----------+-------------------+
```

Both need an endpoint that keeps old state. From one that doesn't, the calls fail (see [Retries](/querying/functions/eth-call#retries)), and
decoding their `NULL` `data` fails the query (see [Handle failed calls](/querying/functions/eth-call#handle-failed-calls)).

## Reference

```sql
rpc.<network>.eth_call(from, to, input, block)
```

### Network

The part between `rpc.` and `.eth_call` is the chain's network, as its provider names it, with `_` instead of `-`: a
provider for `mainnet` is `rpc.mainnet.eth_call`, and one for `base-sepolia` is `rpc.base_sepolia.eth_call`. Calls
always go through an [`evm-rpc` provider](/providers/evm-rpc) for that network:

* With several `evm-rpc` providers for the network, Amp uses the first by name, in alphabetical order.
* The provider's `rate_limit_per_minute` and `timeout_secs` (30 seconds by default) apply to the calls.
* Amp picks the provider the first time a query calls the network, and keeps it until `ampd` restarts. Restart it after
  you register, change, or remove that network's providers.

On a hosted Amp, the networks you can call are the ones it has providers for.

### Arguments

| Argument | Type                          | Description                                                                                                                                                                                                 |
| -------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `from`   | `FixedSizeBinary(20)`, `NULL` | The address the call comes from, for functions that check their caller. `NULL` sends it from no address.                                                                                                    |
| `to`     | `FixedSizeBinary(20)`         | The contract to call. It can't be `NULL`.                                                                                                                                                                   |
| `input`  | `Binary`, `NULL`              | The encoded call: the function's selector, then its arguments, as [`evm_encode_params`](/querying/functions/evm) builds them. `NULL` sends no data, like a plain transfer would.                            |
| `block`  | `Utf8`, `UInt64`, `Int64`     | The block whose state the call reads: a number, like `24000099`, `'24000099'`, or `'0x16e3663'`, or one of the tags `'latest'`, `'safe'`, `'finalized'`, `'pending'`, and `'earliest'`. It can't be `NULL`. |

Addresses must be 20 bytes, not text: pass an address column, like a log's `address` or a transaction's `"to"`, or
convert a `0x` string with [`evm_encode_hex`](/querying/functions/evm#hex).

### Return value

A struct of two fields, `Struct("data": Binary, "message": Utf8)`:

| Field     | When the call succeeds                                        | When the contract reverts                                      | When the RPC request fails |
| --------- | ------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------- |
| `data`    | The bytes the function returned. Empty when `to` has no code. | The revert data, when the endpoint returns it, or else `NULL`. | `NULL`                     |
| `message` | `NULL`                                                        | The endpoint's error, like `execution reverted`.               | `unexpected rpc error`     |

A call that fails doesn't fail the query: its row gets the message, and the query goes on.

To read a field, name the result in a subquery, then use `result.data` and `result.message`, as in the examples.
`get_field(rpc.mainnet.eth_call(…), 'data')` works too. Writing `(rpc.mainnet.eth_call(…)).data` fails with
`Dot access not supported for non-string expr`.

## Handle failed calls

A contract reverts when the function doesn't exist or refuses the call. Check `message` before decoding `data`:

```sql
SELECT
  evm_decode_hex(contract) AS contract,
  result.message AS message,
  CASE WHEN result.message IS NULL AND result.data <> X'' THEN evm_decode_type(result.data, 'uint8') END AS decimals
FROM (
  SELECT contract, rpc.mainnet.eth_call(NULL, contract, evm_encode_params('decimals()'), 'latest') AS result
  FROM (VALUES
    (evm_encode_hex('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48')),
    (evm_encode_hex('0x00000000219ab540356cbb839cbe05303d7705fa')),
    (evm_encode_hex('0x0000000000000000000000000000000000000000'))
  ) AS contracts(contract)
)
```

```text [Output]
+--------------------------------------------+--------------------+----------+
| contract                                   | message            | decimals |
+--------------------------------------------+--------------------+----------+
| 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 |                    | 6        |
| 0x00000000219ab540356cbb839cbe05303d7705fa | execution reverted |          |
| 0x0000000000000000000000000000000000000000 |                    |          |
+--------------------------------------------+--------------------+----------+
```

USDC has a `decimals()` function. The Ethereum deposit contract doesn't, so it reverts. The zero address has no code,
so the call succeeds with empty data.

:::warning
Only decode the `data` of calls that succeeded and returned something. `evm_decode_type` can't decode `NULL`, empty
data, or anything else that isn't a valid encoding of the type, and when it meets one, the whole query fails with
`Column 'decimals' is declared as non-nullable but contains null values`. The `CASE` above skips the failed and empty
calls, and returns NULL for any other data that doesn't decode (see
[decode data that may not match](/querying/functions/evm#decode-data-that-may-not-match)).
:::

When a contract reverts with a reason, `message` includes it:

```sql
SELECT result.message AS message
FROM (
  SELECT rpc.mainnet.eth_call(
    NULL,
    evm_encode_hex('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'),
    evm_encode_params(
      evm_encode_hex('0x0000000000000000000000000000000000000001'),
      evm_encode_hex('0x0000000000000000000000000000000000000002'),
      arrow_cast(1, 'Decimal256(76, 0)'),
      'transferFrom(address from, address to, uint256 value)'
    ),
    'latest'
  ) AS result
)
```

```text [Output]
+--------------------------------------------------------------+
| message                                                      |
+--------------------------------------------------------------+
| execution reverted: ERC20: transfer amount exceeds allowance |
+--------------------------------------------------------------+
```

### Retries

Amp tries each call up to 3 times when the request fails, like on a timeout, a rate limit, or a connection error. It
doesn't retry an error with the code `3` or `-32000`, which endpoints return for reverts and many other failures: the
call's `message` is then the endpoint's own. After the third failure of any other kind, the call's `message` is
`unexpected rpc error`, whatever the endpoint said. So a call at a past block, to an endpoint that doesn't keep old
state, fails with the endpoint's message when it answers `-32000`, like `historical state … is not available`, and
otherwise this way:

```sql
SELECT result.message AS message
FROM (
  SELECT rpc.mainnet.eth_call(
    NULL,
    evm_encode_hex('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'),
    evm_encode_params('totalSupply()'),
    24000099
  ) AS result
)
```

```text [Output]
+----------------------+
| message              |
+----------------------+
| unexpected rpc error |
+----------------------+
```

## Call for many rows

Amp makes one call per row, one after another, even when rows repeat the same arguments. A query that calls
`eth_call` for thousands of rows takes as many round trips to the endpoint, and can hit its rate limit. Call it on as
few rows as you can: filter, aggregate, or deduplicate the rows first, in a subquery.

This names the tokens transferred most often in the example blocks, with one call per token rather than one per
transfer:

```sql
SELECT evm_decode_hex(token) AS token, transfers, evm_decode_type(symbol.data, 'string') AS symbol
FROM (
  SELECT token, transfers, rpc.mainnet.eth_call(NULL, token, evm_encode_params('symbol()'), 'latest') AS symbol
  FROM (
    SELECT address AS token, COUNT(*) AS transfers
    FROM "acme/eth_mainnet".logs
    WHERE topic0 = evm_topic('Transfer(address indexed from, address indexed to, uint256 value)')
    GROUP BY address
    ORDER BY transfers DESC
    LIMIT 4
  )
)
ORDER BY transfers DESC
```

```text [Output]
+--------------------------------------------+-----------+------------------+
| token                                      | transfers | symbol           |
+--------------------------------------------+-----------+------------------+
| 0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852 | 43168     | UNI-V2           |
| 0x609e0f0cb16e53878ba5e959a22fc7fcd81b124a | 42151     | sUNI_V2-ETH-USDT |
| 0xdac17f958d2ee523a2206206994597c13d831ec7 | 5459      | USDT             |
| 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 | 4158      | USDC             |
+--------------------------------------------+-----------+------------------+
```

To read each row's state at its own block instead of the latest, pass `block_num` as `block` (see
[Call at a past block](/querying/functions/eth-call#call-at-a-past-block)).

:::warning
A call whose four arguments are all constants fails in a query over several rows, with
`all columns in a record batch must have the same length`. Make the call in a subquery without `FROM`, which has one
row, and join its result to your rows:

```sql
WITH usdc AS (
  SELECT evm_decode_type(result.data, 'uint8') AS decimals
  FROM (
    SELECT rpc.mainnet.eth_call(
      NULL,
      evm_encode_hex('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'),
      evm_encode_params('decimals()'),
      'latest'
    ) AS result
  )
)
SELECT b.block_num, usdc.decimals
FROM "acme/eth_mainnet".blocks b CROSS JOIN usdc
WHERE b.block_num < 24000002
```

```text [Output]
+-----------+----------+
| block_num | decimals |
+-----------+----------+
| 24000000  | 6        |
| 24000001  | 6        |
+-----------+----------+
```
:::

## In streaming queries and derived datasets

`eth_call` works in [streaming queries](/querying/streaming-queries) and in the SQL of
[derived datasets](/datasets/derived-datasets). Their calls run as Amp processes each range of blocks, so `'latest'`
reads the chain's state at that moment, not at the row's block. A derived dataset materialized again later gets
different results. To record the state at each row's block, pass `block_num` as `block`, with an endpoint that keeps
old state.

This derived table records USDC's total supply after each block, with one call per block:

```sql
SELECT
  block_num,
  rpc.mainnet.eth_call(
    NULL,
    evm_encode_hex('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'),
    evm_encode_params('totalSupply()'),
    block_num
  ) AS total_supply
FROM eth.blocks
```

Its [schema](/datasets/derived-datasets#get-the-tables-schema) keeps the call's result as it is, a struct of `data` and
`message`, so decode it when you query the table. To deploy it as `acme/usdc_supply`, over the example dataset, get the
schema from the Admin API, write the manifest with it, then register and deploy the dataset:

```bash
SQL="SELECT block_num, rpc.mainnet.eth_call(NULL, evm_encode_hex('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'), evm_encode_params('totalSupply()'), block_num) AS total_supply FROM eth.blocks"

jq -n --arg sql "$SQL" '{dependencies: {eth: "acme/eth_mainnet@1.0.0"}, tables: {usdc_supply: $sql}}' |
  curl -s -X POST http://localhost:1610/schema -H 'content-type: application/json' -d @- |
  jq --arg sql "$SQL" '{kind: "manifest", dependencies: {eth: "acme/eth_mainnet@1.0.0"},
    tables: {usdc_supply: {input: {sql: $sql}, schema: .schemas.usdc_supply}}}' >usdc_supply.json

ampctl dataset register acme/usdc_supply ./usdc_supply.json --tag 1.0.0
ampctl dataset deploy acme/usdc_supply@1.0.0
```

Once it has extracted, query it:

```sql
SELECT block_num, evm_decode_type(total_supply.data, 'uint256') AS total_supply
FROM "acme/usdc_supply".usdc_supply
ORDER BY block_num
LIMIT 4
```

```text [Output]
+-----------+-------------------+
| block_num | total_supply      |
+-----------+-------------------+
| 24000000  | 52595449157032994 |
| 24000001  | 52595449157032994 |
| 24000002  | 52595449157032994 |
| 24000003  | 52595572667725848 |
+-----------+-------------------+
```

A call that fails doesn't stop the job: its row stores the call's `message`, as in a query.

## Errors

These fail the query:

| Message contains                                                  | Cause                                                                                         | Fix                                                                                               |
| ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `No EVM RPC provider found for network 'goerli'`                  | No `evm-rpc` provider serves that network.                                                    | Register one, or fix the [network](/querying/functions/eth-call#network) name.                                                |
| `Invalid function 'eth_call'`                                     | The call doesn't name a network.                                                              | Write `rpc.<network>.eth_call`.                                                                   |
| `expected 4 arguments but received 3`                             | An argument is missing.                                                                       | Pass all four. Use `NULL` for `from` and `input` when you don't need them.                        |
| `'to' address is not a valid address`                             | `to` isn't 20 bytes, like a `'0x…'` string or a literal `NULL`.                               | Convert the address with `evm_encode_hex`.                                                        |
| `to address is NULL`                                              | A row's `to` is `NULL`.                                                                       | Filter out those rows.                                                                            |
| `'from' address is not a valid address`                           | `from` isn't 20 bytes or `NULL`.                                                              | Convert the address with `evm_encode_hex`.                                                        |
| `input data is not a valid data`                                  | `input` isn't bytes or `NULL`, like a `'0x…'` string.                                         | Build it with `evm_encode_params`.                                                                |
| `'block' is not a valid block number or tag`                      | `block` is a literal `NULL`, a decimal number, or another type.                               | Pass an integer or a string.                                                                      |
| `'block' is NULL`                                                 | A row's `block` is `NULL`.                                                                    | Filter out those rows, or use a tag.                                                              |
| `block is not a valid integer, "0x" prefixed hex integer, or tag` | `block` is a string Amp can't read, like `'yesterday'`.                                       | Use a block number or a tag.                                                                      |
| `block number cannot be negative`                                 | `block` is a negative integer.                                                                | Use a block number from `0` up.                                                                   |
| `all columns in a record batch must have the same length`         | All four arguments are constants, in a query over several rows.                               | Make the call in a one-row subquery, and join it (see [Call for many rows](/querying/functions/eth-call#call-for-many-rows)). |
| `Column '…' is declared as non-nullable but contains null values` | `evm_decode_type` met data it can't decode, like the `NULL` or empty `data` of a failed call. | Decode inside a `CASE` that skips failed calls (see [Handle failed calls](/querying/functions/eth-call#handle-failed-calls)). |
| `Dot access not supported for non-string expr`                    | The query reads a field with `(rpc.….eth_call(…)).data`.                                      | Name the result in a subquery, then use `result.data`.                                            |
