# `Onchain.RPC`
[🔗](https://github.com/ZenHive/onchain-stack/blob/onchain-v0.14.0/packages/onchain/lib/onchain/rpc.ex#L1)

Ethereum JSON-RPC wrapper using cartouche's RPC client.

Provides a curated API for common Ethereum RPC methods with consistent
error tuples and option handling. All functions accept `:rpc_url`,
`:timeout`, and `:block` options. Single RPC calls also accept an opt-in
`:retry` policy. Omit `:retry` to preserve the underlying
`Cartouche.RPC.send_rpc/3` single-attempt behavior. Pass
`retry: [max_retries: 2, backoff_ms: 100]` to retry RPC/network errors before
returning the final normalized error.

## Telemetry

Every single-call RPC (`do_rpc/3`, all named wrappers, and `call/3`) plus
`batch/2` is wrapped in `:telemetry.span/3` under the event prefix
`[:onchain, :rpc, :request]` — emitting `:start`, `:stop`, and `:exception`
events. Start metadata is `%{method: method}`; stop metadata is
`%{method: method, status: :ok}` on success or
`%{method: method, status: :error, error: reason}` on a returned error tuple
(`method` is `"batch"` for `batch/2`). Attach a handler to measure latency or
count failures per method.

## Error Format

- Address validation: `{:error, {:invalid_address, input}}`
- Data validation: `{:error, {:invalid_data, input}}`
- Block validation: `{:error, {:invalid_block, input}}`
- Block hash validation: `{:error, {:invalid_block_hash, input}}`
- Tx hash validation: `{:error, {:invalid_tx_hash, input}}` (must be 32 bytes)
- Transaction index validation: `{:error, {:invalid_transaction_index, input}}`
- RPC/network errors: `{:error, {:rpc_error, map}}`
- Method the node does not implement: `{:error, {:method_not_found, map}}`
- Namespace disabled on the provider plan: `{:error, {:namespace_unavailable, map}}`
- Node could not complete the request: `{:error, {:unavailable, map}}`

For RPC errors, the map always has at least a `:message` key. JSON-RPC error
responses from the node include `:code`; network/transport errors are wrapped
with `inspect/1` as the message.

### Node-capability refusals

A weaker node than this repo's archive endpoint routinely refuses a call for
one of three reasons: the method is not implemented, the provider has disabled
that namespace on the current plan, or the node cannot complete the request
(historical state pruned, method the gateway does not route, transient
overload). Classification runs once on the shared `do_rpc/3` result path and
again on `batch/2`'s decode path (both item-level and top-level batch errors),
so a codegen'd wrapper, a hand-written wrapper, `call/3` and a batched call
apply the same rules to the same wire response.

The classifier is uniform; the provider's wire response is not. Verified live
on Alchemy mainnet 2026-08-25: historical `eth_feeHistory` at block 20_000_000
answers `-32001 "Unable to complete request at this time."` as a single call
(classified `{:unavailable, map}`), but the byte-identical request inside a
JSON-RPC array batch answers `-32000 "Internal error"` — alone or alongside a
healthy call — which stays `{:rpc_error, map}`. That is not a gap in the
classifier: `-32000 "Internal error"` is indistinguishable from a genuine
internal failure, and tagging it would invent a distinction the node does not
make. Batching can therefore downgrade a classifiable refusal to an
unclassifiable one; when a caller needs the capability signal, issue that
probe as a single call. Unrecognized codes keep `{:error, {:rpc_error, map}}`
unchanged — the classifier names distinguishable cases, it does not guess.

Each classified map retains the observed `:code` and `:message` (and `:data`
when the node sent it). Branch on the tag; inspect the map when you need the
wire detail.

- `{:error, {:method_not_found, map}}` — this node does not implement the
  method. The reliable standard signal is JSON-RPC `-32601` ("Method not
  found"). Hosted providers often misuse `-32600` ("Invalid Request") instead:
  Alchemy mainnet answers `-32600` `"Unsupported method: <method> on ETH_MAINNET"`
  and `-32600` `"eth_baseFee is not available on the ETH_MAINNET..."`. Those
  two message shapes are pinned from live responses; a bare `-32600` without
  them still passes through as `{:rpc_error, map}`, because other nodes use
  that code for genuinely malformed requests. Callers should pick a portable
  construction (see `base_fee/1`) or a different method.

- `{:error, {:namespace_unavailable, map}}` — the method exists but this
  provider plan has disabled the namespace. Observed on Alchemy mainnet as
  `-32600` `"<method> is not available on the Free tier - upgrade to Pay As
  You Go, or Enterprise for access."` for `trace_*` and `debug_*`. Callers
  should use a plan that serves the namespace, or avoid it.

- `{:error, {:unavailable, map}}` — the node refused to complete a request it
  otherwise accepts. Observed on Alchemy mainnet as HTTP 503 / `-32001`
  `"Unable to complete request at this time."` for `eth_feeHistory` at block
  20_000_000, while `eth_feeHistory` at `"latest"` succeeds on the same URL.
  The identical wire error is also returned for some unimplemented methods
  (`erigon_getHeaderByNumber`), so this is **not** a unique pruned-history
  signal — treat it as "this node cannot serve this request". Callers that
  need historical state should retry against an archive endpoint.

Codes the classifier does not name, including `-32602` ("Invalid params" —
also what reth answers for `eth_getStorageValues` with empty params, which is
indistinguishable from a genuine bad-params error), reach the caller as
`{:error, {:rpc_error, map}}` exactly as before.

### Revert errors (`code: 3`)

When `eth_call` reverts, the node returns a JSON-RPC error with `code: 3`. The
inner map is widened with extra fields populated by cartouche
(see `t:Cartouche.RPC.rpc_error/0`) plus `:data` mirrored by Onchain
(enriched internally by the Onchain.RPC.Helpers module):

- `:revert` — the raw revert-data binary (present on `code: 3` when the node
  returned a `data` field; absent if the node omitted it). Use this for
  selector inspection or pass to ABI libraries that accept binaries.
- `:data` — the same payload as a lowercase `0x`-prefixed hex string. Mirrored
  from `:revert` whenever the latter is set so callers can pipe it straight
  into `Onchain.ABI.decode_error/2` (which expects 0x hex, not raw bytes).
- `:error_abi` — the matching custom-error signature `String.t()` from the
  `:errors` opt (e.g. `"InsufficientBalance(uint256,uint256)"`). Only present
  when the caller passed `errors:` AND the revert payload's selector matches
  one of those signatures (or a Solidity 0.8.x `Panic` variant — those decode
  to a human-readable string like `"arithmetic error: overflow or underflow"`).
- `:error_params` — the decoded argument list for `:error_abi` (e.g.
  `[1_000, 500]` for `InsufficientBalance(1000, 500)`). Empty list `[]` for
  nullary errors. Same population rule as `:error_abi`.

Pattern matches:

    # Revert without :errors opt — decode out-of-band via the hex :data mirror
    {:error, {:rpc_error, %{code: 3, data: hex_data}}} = result
    {:ok, %{error: signature, args: args}} =
      Onchain.ABI.decode_error(hex_data, ["InsufficientBalance(uint256,uint256)"])

    # Revert with matching :errors opt — already decoded inline
    {:error,
     {:rpc_error,
      %{code: 3, error_abi: "InsufficientBalance(uint256,uint256)", error_params: [1000, 500]}}} =
      result

Existing `{:error, {:rpc_error, %{code:, message:}}}` matches still work — the
revert fields are additive on the inner map.

## Functions

| Function | Purpose |
|----------|---------|
| `eth_call/3` | Read-only contract call → raw hex response |
| `eth_call!/3` | Same, raises on error |
| `eth_send_raw_transaction/2` | Broadcast signed tx → tx hash |
| `eth_send_raw_transaction!/2` | Same, raises on error |
| `get_balance/2` | Account ETH balance in wei |
| `get_balance!/2` | Same, raises on error |
| `block_number/1` | Current block height |
| `block_number!/1` | Same, raises on error |
| `syncing/1` | Node sync status (`false` or sync-status map) |
| `syncing!/1` | Same, raises on error |
| `get_block_by_number/2` | Fetch block by number or tag → atom-keyed decoded map (same conventions as `get_transaction_by_hash`) |
| `get_block_by_number!/2` | Same, raises on error |
| `get_block_receipts/2` | Fetch every receipt in a block → parsed receipt maps |
| `get_block_receipts!/2` | Same, raises on error |
| `get_block_transaction_count_by_hash/2` | Transaction count for a block hash |
| `get_block_transaction_count_by_hash!/2` | Same, raises on error |
| `get_block_transaction_count_by_number/2` | Transaction count for a block number or tag |
| `get_block_transaction_count_by_number!/2` | Same, raises on error |
| `get_transaction_by_block_hash_and_index/3` | Fetch one transaction by block hash and position |
| `get_transaction_by_block_hash_and_index!/3` | Same, raises on error |
| `get_transaction_by_block_number_and_index/3` | Fetch one transaction by block number/tag and position |
| `get_transaction_by_block_number_and_index!/3` | Same, raises on error |
| `get_block_access_list/2` | Fetch an EIP-7928 block access list |
| `get_block_access_list!/2` | Same, raises on error |
| `chain_id/1` | Network chain ID |
| `chain_id!/1` | Same, raises on error |
| `eth_get_logs/2` | Fetch event logs by filter |
| `eth_get_logs!/2` | Same, raises on error |
| `get_transaction_receipt/2` | Transaction receipt by hash |
| `get_transaction_receipt!/2` | Same, raises on error |
| `get_transaction_count/2` | Account nonce (tx count) |
| `get_transaction_count!/2` | Same, raises on error |
| `eth_get_code/2` | Contract bytecode (or "0x" for EOAs) |
| `eth_get_code!/2` | Same, raises on error |
| `get_transaction_by_hash/2` | Full transaction details by hash |
| `get_transaction_by_hash!/2` | Same, raises on error |
| `call/3` | Generic JSON-RPC passthrough — any method, raw result |
| `call!/3` | Same, raises on error |
| `batch/2` | Generic JSON-RPC array batch — one HTTP round-trip for many raw calls |
| `fee_history/2` | EIP-1559 fee history (`eth_feeHistory`) → `Cartouche.FeeHistory.t()` |
| `fee_history!/2` | Same, raises on error |
| `get_proof/3` | Account + storage Merkle proofs (`eth_getProof`) |
| `get_proof!/3` | Same, raises on error |

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `blob_base_fee!` | 1 | Get the current base fee per blob gas. Raises on error. | `opts: value` |
| `blob_base_fee` | 1 | Get the current base fee per blob gas (eth_blobBaseFee). | `opts: value` |
| `base_fee!` | 1 | Get the base fee per gas for the next block. Raises on error. | `opts: value` |
| `base_fee` | 1 | Get the EIP-1559 base fee per gas for the next block. | `opts: value` |
| `get_proof!` | 3 | Fetch Merkle proof for an account and storage slots. Raises on error. | `address: value`, `storage_keys: value`, `opts: value` |
| `get_proof` | 3 | Fetch Merkle proof for an account and storage slots (eth_getProof). | `address: value`, `storage_keys: value`, `opts: value` |
| `fee_history!` | 2 | Fetch fee history. Raises on error. | `block_count: value`, `opts: value` |
| `fee_history` | 2 | Fetch base-fee history and per-block priority-fee percentiles (eth_feeHistory). | `block_count: value`, `opts: value` |
| `batch` | 2 | Generic JSON-RPC array batch — invoke many methods in one HTTP request. | `requests: value`, `opts: value` |
| `call!` | 3 | Generic JSON-RPC passthrough. Raises on error. | `method: value`, `params: value`, `opts: value` |
| `call` | 3 | Generic JSON-RPC passthrough — invoke any method not covered by a named wrapper. | `method: value`, `params: value`, `opts: value` |
| `eth_get_logs!` | 2 | Fetch event logs matching a filter. Raises on error. | `filter: value`, `opts: value` |
| `eth_get_logs` | 2 | Fetch event logs matching a filter (eth_getLogs). | `filter: value`, `opts: value` |
| `get_transaction_by_hash!` | 2 | Get full transaction details by hash. Raises on error. | `tx_hash: value`, `opts: value` |
| `get_transaction_by_hash` | 2 | Get full transaction details by hash (eth_getTransactionByHash). | `tx_hash: value`, `opts: value` |
| `eth_get_code!` | 2 | Fetch contract bytecode at an address. Raises on error. | `address: value`, `opts: value` |
| `eth_get_code` | 2 | Fetch contract bytecode at an address (eth_getCode). | `address: value`, `opts: value` |
| `get_transaction_count!` | 2 | Get the transaction count (nonce) of an address. Raises on error. | `address: value`, `opts: value` |
| `get_transaction_count` | 2 | Get the transaction count (nonce) of an address. | `address: value`, `opts: value` |
| `get_transaction_receipt!` | 2 | Get a transaction receipt by hash. Raises on error. | `tx_hash: value`, `opts: value` |
| `get_transaction_receipt` | 2 | Get a transaction receipt by hash (eth_getTransactionReceipt). | `tx_hash: value`, `opts: value` |
| `chain_id!` | 1 | Get the network chain ID. Raises on error. | `opts: value` |
| `chain_id` | 1 | Get the network chain ID. | `opts: value` |
| `get_block_access_list!` | 2 | Fetch an EIP-7928 block access list. Raises on error. | `block: value`, `opts: value` |
| `get_block_access_list` | 2 | Fetch an EIP-7928 block access list (eth_getBlockAccessList). | `block: value`, `opts: value` |
| `get_transaction_by_block_number_and_index!` | 3 | Fetch a transaction by block number/tag and position. Raises on error. | `block: value`, `transaction_index: value`, `opts: value` |
| `get_transaction_by_block_number_and_index` | 3 | Fetch a transaction by block number/tag and position (eth_getTransactionByBlockNumberAndIndex). | `block: value`, `transaction_index: value`, `opts: value` |
| `get_transaction_by_block_hash_and_index!` | 3 | Fetch a transaction by block hash and position. Raises on error. | `block_hash: value`, `transaction_index: value`, `opts: value` |
| `get_transaction_by_block_hash_and_index` | 3 | Fetch a transaction by block hash and position (eth_getTransactionByBlockHashAndIndex). | `block_hash: value`, `transaction_index: value`, `opts: value` |
| `get_block_transaction_count_by_number!` | 2 | Get a block's transaction count by number or tag. Raises on error. | `block: value`, `opts: value` |
| `get_block_transaction_count_by_number` | 2 | Get a block's transaction count by number or tag (eth_getBlockTransactionCountByNumber). | `block: value`, `opts: value` |
| `get_block_transaction_count_by_hash!` | 2 | Get a block's transaction count by hash. Raises on error. | `block_hash: value`, `opts: value` |
| `get_block_transaction_count_by_hash` | 2 | Get a block's transaction count by hash (eth_getBlockTransactionCountByHash). | `block_hash: value`, `opts: value` |
| `get_block_receipts!` | 2 | Fetch every receipt in a block. Raises on error. | `block: value`, `opts: value` |
| `get_block_receipts` | 2 | Fetch every receipt in a block (eth_getBlockReceipts). | `block: value`, `opts: value` |
| `get_block_by_number!` | 2 | Fetch a block by number or tag. Raises on error. | `block_id: value`, `opts: value` |
| `get_block_by_number` | 2 | Fetch a block by number or tag (eth_getBlockByNumber). | `block_id: value`, `opts: value` |
| `syncing!` | 1 | Get the node's sync status. Raises on error. | `opts: value` |
| `syncing` | 1 | Get the node's sync status (eth_syncing). | `opts: value` |
| `block_number!` | 1 | Get the current block height. Raises on error. | `opts: value` |
| `block_number` | 1 | Get the current block height. | `opts: value` |
| `get_balance!` | 2 | Get the ETH balance of an address in wei. Raises on error. | `address: value`, `opts: value` |
| `get_balance` | 2 | Get the ETH balance of an address in wei. | `address: value`, `opts: value` |
| `eth_send_raw_transaction!` | 2 | Broadcast a signed transaction. Raises on error. | `data: value`, `opts: value` |
| `eth_send_raw_transaction` | 2 | Broadcast a signed transaction. | `data: value`, `opts: value` |
| `eth_estimate_gas!` | 2 | Estimate the gas a transaction would consume. Raises on error. | `tx_params: value`, `opts: value` |
| `eth_estimate_gas` | 2 | Estimate the gas a transaction would consume. | `tx_params: value`, `opts: value` |
| `eth_call!` | 3 | Execute a read-only contract call. Raises on error. | `address: value`, `data: value`, `opts: value` |
| `eth_call` | 3 | Execute a read-only contract call (eth_call). | `address: value`, `data: value`, `opts: value` |

# `base_fee`

```elixir
@spec base_fee(keyword()) :: {:ok, non_neg_integer() | nil} | {:error, term()}
```

Get the EIP-1559 base fee per gas for the next block.

## Parameters

  * `opts` - Options: :block (default "pending" — the next block's base fee; pass "latest" for the most recent mined block), :rpc_url, :timeout (default: `[]`, value)

## Returns

Base fee per gas in wei, or nil for a pre-EIP-1559 block. Read from the block header, so it works on any EIP-1559 node rather than only those implementing Erigon's eth_baseFee. (`{:ok, non_neg_integer | nil} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :block (default \"pending\" — the next block's base fee; pass \"latest\" for the most recent mined block), :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, non_neg_integer | nil} | {:error, term}",
    description: "Base fee per gas in wei, or nil for a pre-EIP-1559 block. Read from the block header, so it works on any EIP-1559 node rather than only those implementing Erigon's eth_baseFee.",
    example: "71_739_926"
  }
}
```

# `base_fee!`

```elixir
@spec base_fee!(keyword()) :: non_neg_integer() | nil
```

Get the base fee per gas for the next block. Raises on error.

## Parameters

  * `opts` - Options: :block, :rpc_url, :timeout (default: `[]`, value)

## Returns

Base fee per gas in wei (`non_neg_integer | nil`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :block, :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "non_neg_integer | nil",
    description: "Base fee per gas in wei"
  }
}
```

# `batch`

```elixir
@spec batch(
  [{String.t(), [term()]}],
  keyword()
) :: {:ok, [term()]} | {:error, term()}
```

Invoke many raw JSON-RPC calls in one HTTP request.

Each request is a `{method, params}` tuple. Results are returned in request
order even when the node returns the JSON-RPC response array out of order.

# `blob_base_fee`

```elixir
@spec blob_base_fee(keyword()) :: {:ok, non_neg_integer()} | {:error, term()}
```

Get the current base fee per blob gas (eth_blobBaseFee).

## Parameters

  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

EIP-4844 base fee per blob gas in wei (`{:ok, non_neg_integer} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, non_neg_integer} | {:error, term}",
    description: "EIP-4844 base fee per blob gas in wei",
    example: "3_936_408"
  }
}
```

# `blob_base_fee!`

```elixir
@spec blob_base_fee!(keyword()) :: non_neg_integer()
```

Get the current base fee per blob gas. Raises on error.

## Parameters

  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Base fee per blob gas in wei (`non_neg_integer`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: :non_neg_integer,
    description: "Base fee per blob gas in wei"
  }
}
```

# `block_number`

```elixir
@spec block_number(keyword()) :: {:ok, non_neg_integer()} | {:error, term()}
```

Get the current block height.

## Parameters

  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Current block number (`{:ok, non_neg_integer} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, non_neg_integer} | {:error, term}",
    description: "Current block number"
  }
}
```

# `block_number!`

```elixir
@spec block_number!(keyword()) :: non_neg_integer()
```

Get the current block height. Raises on error.

## Parameters

  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Current block number (`non_neg_integer`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{type: :non_neg_integer, description: "Current block number"}
}
```

# `call`

```elixir
@spec call(String.t(), [term()], keyword()) :: {:ok, term()} | {:error, term()}
```

Generic JSON-RPC passthrough — invoke any method not covered by a named wrapper.

## Parameters

  * `method` - JSON-RPC method name, e.g. "eth_getStorageAt", "debug_traceTransaction", "trace_call", "eth_feeHistory" (value)
  * `params` - List of params for the method, in the order the JSON-RPC spec requires (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Raw decoded JSON result (no further decoding — caller knows what they asked for) or wrapped error tuple (`{:ok, term} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    params: %{
      description: "List of params for the method, in the order the JSON-RPC spec requires",
      kind: :value
    },
    method: %{
      description: "JSON-RPC method name, e.g. \"eth_getStorageAt\", \"debug_traceTransaction\", \"trace_call\", \"eth_feeHistory\"",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, term} | {:error, term}",
    description: "Raw decoded JSON result (no further decoding — caller knows what they asked for) or wrapped error tuple"
  }
}
```

# `call!`

```elixir
@spec call!(String.t(), [term()], keyword()) :: term()
```

Generic JSON-RPC passthrough. Raises on error.

## Parameters

  * `method` - JSON-RPC method name (value)
  * `params` - List of params for the method (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Raw decoded JSON result (`term`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    params: %{description: "List of params for the method", kind: :value},
    method: %{description: "JSON-RPC method name", kind: :value}
  },
  returns: %{type: :term, description: "Raw decoded JSON result"}
}
```

# `chain_id`

```elixir
@spec chain_id(keyword()) :: {:ok, non_neg_integer()} | {:error, term()}
```

Get the network chain ID.

## Parameters

  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Chain ID (1 = mainnet, 11155111 = sepolia, etc.) (`{:ok, non_neg_integer} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, non_neg_integer} | {:error, term}",
    description: "Chain ID (1 = mainnet, 11155111 = sepolia, etc.)"
  }
}
```

# `chain_id!`

```elixir
@spec chain_id!(keyword()) :: non_neg_integer()
```

Get the network chain ID. Raises on error.

## Parameters

  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Chain ID integer (`non_neg_integer`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{type: :non_neg_integer, description: "Chain ID integer"}
}
```

# `eth_call`

```elixir
@spec eth_call(String.t() | binary(), String.t(), keyword()) ::
  {:ok, String.t()} | {:error, term()}
```

Execute a read-only contract call (`eth_call`).

## Options

- `:rpc_url` — node URL (overrides `Application.get_env(:cartouche, :ethereum_node)`)
- `:timeout` — request timeout in ms (default 30_000)
- `:block` — block number / tag / 0x hex (default `"latest"`)
- `:errors` — list of Solidity custom-error signatures, e.g.
  `["InsufficientBalance(uint256,uint256)", "Unauthorized()"]`. When the call
  reverts with matching revert data, the error map carries decoded
  `:error_abi` + `:error_params` alongside the raw `:revert` binary and its
  hex mirror `:data`.

## Revert handling

On `code: 3` reverts the inner map widens — see the module's "Error Format"
section. Quick pattern-match shape:

    case Onchain.RPC.eth_call(token, calldata, errors: ["InsufficientBalance(uint256,uint256)"]) do
      {:ok, hex_result} ->
        # Decode hex_result with Onchain.ABI.decode_response/2
        :ok

      {:error, {:rpc_error, %{code: 3, error_abi: "InsufficientBalance(uint256,uint256)", error_params: [requested, available]}}} ->
        {:insufficient, requested, available}

      {:error, {:rpc_error, %{code: 3, data: hex_data}}} ->
        # Custom error not in :errors list (or :errors omitted) — fall back
        # to the hex-mirrored revert payload and decode out-of-band.
        # `Onchain.ABI.decode_error/2` expects 0x hex, which is exactly :data.
        Onchain.ABI.decode_error(hex_data, ["MyError(uint256)"])

      {:error, {:rpc_error, %{message: msg}}} ->
        {:rpc, msg}
    end

# `eth_call!`

```elixir
@spec eth_call!(String.t() | binary(), String.t(), keyword()) :: String.t()
```

Execute a read-only contract call. Raises on error.

## Parameters

  * `address` - Contract address as 0x hex string or 20-byte binary (value)
  * `data` - 0x-prefixed hex-encoded calldata (value)
  * `opts` - Options: :rpc_url, :timeout, :block (default: `[]`, value)

## Returns

Raw 0x-prefixed hex response (`string`)

```elixir
# descripex:contract
%{
  params: %{
    data: %{description: "0x-prefixed hex-encoded calldata", kind: :value},
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :block",
      kind: :value
    },
    address: %{
      description: "Contract address as 0x hex string or 20-byte binary",
      kind: :value
    }
  },
  returns: %{type: :string, description: "Raw 0x-prefixed hex response"}
}
```

# `eth_estimate_gas`

```elixir
@spec eth_estimate_gas(
  map(),
  keyword()
) :: {:ok, non_neg_integer()} | {:error, term()}
```

Estimate the gas a transaction would consume.

## Parameters

  * `tx_params` - Transaction-params map with atom keys. Recognized: :from, :to, :data, :value, :gas, :gas_price, :max_fee_per_gas, :max_priority_fee_per_gas, :access_list. Absent keys are omitted from the call object. (value)
  * `opts` - Options: :rpc_url, :timeout, :block (default: `[]`, value)

## Returns

Estimated gas units as an integer (`{:ok, non_neg_integer()} | {:error, term()}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :block",
      kind: :value
    },
    tx_params: %{
      description: "Transaction-params map with atom keys. Recognized: :from, :to, :data, :value, :gas, :gas_price, :max_fee_per_gas, :max_priority_fee_per_gas, :access_list. Absent keys are omitted from the call object.",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, non_neg_integer()} | {:error, term()}",
    description: "Estimated gas units as an integer"
  }
}
```

# `eth_estimate_gas!`

```elixir
@spec eth_estimate_gas!(
  map(),
  keyword()
) :: non_neg_integer()
```

Estimate the gas a transaction would consume. Raises on error.

## Parameters

  * `tx_params` - Transaction-params map with atom keys (see eth_estimate_gas/2) (value)
  * `opts` - Options: :rpc_url, :timeout, :block (default: `[]`, value)

## Returns

Estimated gas units (`integer`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :block",
      kind: :value
    },
    tx_params: %{
      description: "Transaction-params map with atom keys (see eth_estimate_gas/2)",
      kind: :value
    }
  },
  returns: %{type: :integer, description: "Estimated gas units"}
}
```

# `eth_get_code`

```elixir
@spec eth_get_code(
  String.t() | binary(),
  keyword()
) :: {:ok, String.t()} | {:error, term()}
```

Fetch contract bytecode at an address (eth_getCode).

## Parameters

  * `address` - Account address as 0x hex string or 20-byte binary (value)
  * `opts` - Options: :rpc_url, :timeout, :block (default: `[]`, value)

## Returns

0x-prefixed bytecode hex string, or "0x" for EOA addresses (`{:ok, hex_string} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :block",
      kind: :value
    },
    address: %{
      description: "Account address as 0x hex string or 20-byte binary",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, hex_string} | {:error, term}",
    description: "0x-prefixed bytecode hex string, or \"0x\" for EOA addresses",
    example: "\"0x\" for EOAs, \"0x6080604052...\" for contracts"
  }
}
```

# `eth_get_code!`

```elixir
@spec eth_get_code!(
  String.t() | binary(),
  keyword()
) :: String.t()
```

Fetch contract bytecode at an address. Raises on error.

## Parameters

  * `address` - Account address as 0x hex string or 20-byte binary (value)
  * `opts` - Options: :rpc_url, :timeout, :block (default: `[]`, value)

## Returns

0x-prefixed bytecode hex string (`string`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :block",
      kind: :value
    },
    address: %{
      description: "Account address as 0x hex string or 20-byte binary",
      kind: :value
    }
  },
  returns: %{type: :string, description: "0x-prefixed bytecode hex string"}
}
```

# `eth_get_logs`

```elixir
@spec eth_get_logs(
  map(),
  keyword()
) :: {:ok, [map()]} | {:error, term()}
```

Fetch event logs matching a filter (eth_getLogs).

## Parameters

  * `filter` - Filter map. Atom keys: :address (hex string), :topics (list), :from_block (integer or tag), :to_block (integer or tag), :block_hash (32-byte hex). Canonical JSON-RPC camelCase string keys ("fromBlock", "toBlock", "address", "topics", "blockHash") are accepted as aliases. If both an atom key and its camelCase alias are present, the atom key wins (the alias value is silently dropped). :block_hash is mutually exclusive with :from_block / :to_block per EIP-1474. Unknown keys return \{:error, \{:invalid_filter_key, key\}\}. (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

List of log maps with keys: address, topics, data, block_number, transaction_hash, log_index, transaction_index, removed. Errors: \{:invalid_filter_key, key\} for unknown filter keys; \{:invalid_filter, \{field, value\}\} for bad values; \{:invalid_filter, \{:block_hash_mutually_exclusive, present\}\} when :block_hash is combined with :from_block / :to_block. (`{:ok, [log_map]} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    filter: %{
      description: "Filter map. Atom keys: :address (hex string), :topics (list), :from_block (integer or tag), :to_block (integer or tag), :block_hash (32-byte hex). Canonical JSON-RPC camelCase string keys (\"fromBlock\", \"toBlock\", \"address\", \"topics\", \"blockHash\") are accepted as aliases. If both an atom key and its camelCase alias are present, the atom key wins (the alias value is silently dropped). :block_hash is mutually exclusive with :from_block / :to_block per EIP-1474. Unknown keys return {:error, {:invalid_filter_key, key}}.",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, [log_map]} | {:error, term}",
    description: "List of log maps with keys: address, topics, data, block_number, transaction_hash, log_index, transaction_index, removed. Errors: {:invalid_filter_key, key} for unknown filter keys; {:invalid_filter, {field, value}} for bad values; {:invalid_filter, {:block_hash_mutually_exclusive, present}} when :block_hash is combined with :from_block / :to_block."
  }
}
```

# `eth_get_logs!`

```elixir
@spec eth_get_logs!(
  map(),
  keyword()
) :: [map()]
```

Fetch event logs matching a filter. Raises on error.

## Parameters

  * `filter` - Filter map (see eth_get_logs/2) (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

List of parsed log maps (`[log_map]`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    filter: %{description: "Filter map (see eth_get_logs/2)", kind: :value}
  },
  returns: %{type: "[log_map]", description: "List of parsed log maps"}
}
```

# `eth_send_raw_transaction`

```elixir
@spec eth_send_raw_transaction(
  String.t(),
  keyword()
) :: {:ok, String.t()} | {:error, term()}
```

Broadcast a signed transaction.

## Parameters

  * `data` - 0x-prefixed hex-encoded signed transaction (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Transaction hash as 0x hex string (`{:ok, tx_hash} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    data: %{
      description: "0x-prefixed hex-encoded signed transaction",
      kind: :value
    },
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, tx_hash} | {:error, term}",
    description: "Transaction hash as 0x hex string",
    example: "0xabc123..."
  }
}
```

# `eth_send_raw_transaction!`

```elixir
@spec eth_send_raw_transaction!(
  String.t(),
  keyword()
) :: String.t()
```

Broadcast a signed transaction. Raises on error.

## Parameters

  * `data` - 0x-prefixed hex-encoded signed transaction (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Transaction hash as 0x hex string (`string`)

```elixir
# descripex:contract
%{
  params: %{
    data: %{
      description: "0x-prefixed hex-encoded signed transaction",
      kind: :value
    },
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{type: :string, description: "Transaction hash as 0x hex string"}
}
```

# `fee_history`

```elixir
@spec fee_history(
  pos_integer(),
  keyword()
) :: {:ok, Cartouche.FeeHistory.t()} | {:error, term()}
```

Fetch base-fee history and per-block priority-fee percentiles (eth_feeHistory).

## Parameters

  * `block_count` - Number of recent blocks to query, 1..1024 (EIP-1474 cap) (value)
  * `opts` - Options: :newest_block (default "latest"), :reward_percentiles (default [50] — list of ints 0..100, monotonically non-decreasing), :rpc_url, :timeout (default: `[]`, value)

## Returns

Deserialized fee history struct: oldest_block, base_fee_per_gas (block_count + 1 entries), gas_used_ratio, reward (block_count rows × length(reward_percentiles) cols) (`{:ok, Cartouche.FeeHistory.t()} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :newest_block (default \"latest\"), :reward_percentiles (default [50] — list of ints 0..100, monotonically non-decreasing), :rpc_url, :timeout",
      kind: :value
    },
    block_count: %{
      description: "Number of recent blocks to query, 1..1024 (EIP-1474 cap)",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, Cartouche.FeeHistory.t()} | {:error, term}",
    description: "Deserialized fee history struct: oldest_block, base_fee_per_gas (block_count + 1 entries), gas_used_ratio, reward (block_count rows × length(reward_percentiles) cols)"
  }
}
```

# `fee_history!`

```elixir
@spec fee_history!(
  pos_integer(),
  keyword()
) :: Cartouche.FeeHistory.t()
```

Fetch fee history. Raises on error.

## Parameters

  * `block_count` - Number of recent blocks to query, 1..1024 (value)
  * `opts` - Options: :newest_block, :reward_percentiles, :rpc_url, :timeout (default: `[]`, value)

## Returns

Deserialized fee history struct (`Cartouche.FeeHistory.t()`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :newest_block, :reward_percentiles, :rpc_url, :timeout",
      kind: :value
    },
    block_count: %{
      description: "Number of recent blocks to query, 1..1024",
      kind: :value
    }
  },
  returns: %{
    type: "Cartouche.FeeHistory.t()",
    description: "Deserialized fee history struct"
  }
}
```

# `get_balance`

```elixir
@spec get_balance(
  String.t() | binary(),
  keyword()
) :: {:ok, non_neg_integer()} | {:error, term()}
```

Get the ETH balance of an address in wei.

## Parameters

  * `address` - Account address as 0x hex string or 20-byte binary (value)
  * `opts` - Options: :rpc_url, :timeout, :block (default: `[]`, value)

## Returns

Balance in wei (`{:ok, non_neg_integer} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :block",
      kind: :value
    },
    address: %{
      description: "Account address as 0x hex string or 20-byte binary",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, non_neg_integer} | {:error, term}",
    description: "Balance in wei"
  }
}
```

# `get_balance!`

```elixir
@spec get_balance!(
  String.t() | binary(),
  keyword()
) :: non_neg_integer()
```

Get the ETH balance of an address in wei. Raises on error.

## Parameters

  * `address` - Account address as 0x hex string or 20-byte binary (value)
  * `opts` - Options: :rpc_url, :timeout, :block (default: `[]`, value)

## Returns

Balance in wei (`non_neg_integer`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :block",
      kind: :value
    },
    address: %{
      description: "Account address as 0x hex string or 20-byte binary",
      kind: :value
    }
  },
  returns: %{type: :non_neg_integer, description: "Balance in wei"}
}
```

# `get_block_access_list`

```elixir
@spec get_block_access_list(
  integer() | String.t(),
  keyword()
) :: {:ok, [map()] | nil} | {:error, term()}
```

Fetch an EIP-7928 block access list (eth_getBlockAccessList).

## Parameters

  * `block` - Block number, tag, or 0x-prefixed 32-byte block hash (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Raw camelCase EIP-7928 account-access entries, or nil when unavailable (`{:ok, [map] | nil} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    block: %{
      description: "Block number, tag, or 0x-prefixed 32-byte block hash",
      kind: :value
    },
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, [map] | nil} | {:error, term}",
    description: "Raw camelCase EIP-7928 account-access entries, or nil when unavailable"
  }
}
```

# `get_block_access_list!`

```elixir
@spec get_block_access_list!(
  integer() | String.t(),
  keyword()
) :: [map()] | nil
```

Fetch an EIP-7928 block access list. Raises on error.

## Parameters

  * `block` - Block number, tag, or 0x-prefixed 32-byte block hash (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Raw camelCase account-access entries or nil (`[map] | nil`)

```elixir
# descripex:contract
%{
  params: %{
    block: %{
      description: "Block number, tag, or 0x-prefixed 32-byte block hash",
      kind: :value
    },
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "[map] | nil",
    description: "Raw camelCase account-access entries or nil"
  }
}
```

# `get_block_by_number`

```elixir
@spec get_block_by_number(
  integer() | String.t(),
  keyword()
) :: {:ok, map() | nil} | {:error, term()}
```

Fetch a block by number or tag (eth_getBlockByNumber).

## Parameters

  * `block_id` - Block number (integer) or tag string ("latest", "finalized", "pending", "earliest", "safe", or "0x..." hex) (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Decoded block map (atom keys): quantities as integers, miner checksummed, blooms/hashes/roots/extra_data as 0x hex; transactions are tx hashes or decoded maps if full txs requested (`{:ok, map | nil} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    block_id: %{
      description: "Block number (integer) or tag string (\"latest\", \"finalized\", \"pending\", \"earliest\", \"safe\", or \"0x...\" hex)",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, map | nil} | {:error, term}",
    description: "Decoded block map (atom keys): quantities as integers, miner checksummed, blooms/hashes/roots/extra_data as 0x hex; transactions are tx hashes or decoded maps if full txs requested",
    example: "%{number: 20_000_000, timestamp: 1_717_281_407, hash: \"0x...\", transactions: [\"0x...\", ...]}"
  }
}
```

# `get_block_by_number!`

```elixir
@spec get_block_by_number!(
  integer() | String.t(),
  keyword()
) :: map() | nil
```

Fetch a block by number or tag. Raises on error.

## Parameters

  * `block_id` - Block number (integer) or tag string (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Decoded atom-keyed block map (`map | nil`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    block_id: %{
      description: "Block number (integer) or tag string",
      kind: :value
    }
  },
  returns: %{type: "map | nil", description: "Decoded atom-keyed block map"}
}
```

# `get_block_receipts`

```elixir
@spec get_block_receipts(
  integer() | String.t(),
  keyword()
) :: {:ok, [map()] | nil} | {:error, term()}
```

Fetch every receipt in a block (eth_getBlockReceipts).

## Parameters

  * `block` - Block number, tag, or 0x-prefixed 32-byte block hash (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Parsed receipt maps matching get_transaction_receipt/2, or nil when the block is unknown (`{:ok, [map] | nil} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    block: %{
      description: "Block number, tag, or 0x-prefixed 32-byte block hash",
      kind: :value
    },
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, [map] | nil} | {:error, term}",
    description: "Parsed receipt maps matching get_transaction_receipt/2, or nil when the block is unknown"
  }
}
```

# `get_block_receipts!`

```elixir
@spec get_block_receipts!(
  integer() | String.t(),
  keyword()
) :: [map()] | nil
```

Fetch every receipt in a block. Raises on error.

## Parameters

  * `block` - Block number, tag, or 0x-prefixed 32-byte block hash (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Parsed receipt maps or nil (`[map] | nil`)

```elixir
# descripex:contract
%{
  params: %{
    block: %{
      description: "Block number, tag, or 0x-prefixed 32-byte block hash",
      kind: :value
    },
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{type: "[map] | nil", description: "Parsed receipt maps or nil"}
}
```

# `get_block_transaction_count_by_hash`

```elixir
@spec get_block_transaction_count_by_hash(
  String.t(),
  keyword()
) :: {:ok, non_neg_integer() | nil} | {:error, term()}
```

Get a block's transaction count by hash (eth_getBlockTransactionCountByHash).

## Parameters

  * `block_hash` - 0x-prefixed 32-byte block hash (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Transaction count, or nil when the block is unknown (`{:ok, non_neg_integer | nil} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    block_hash: %{description: "0x-prefixed 32-byte block hash", kind: :value}
  },
  returns: %{
    type: "{:ok, non_neg_integer | nil} | {:error, term}",
    description: "Transaction count, or nil when the block is unknown"
  }
}
```

# `get_block_transaction_count_by_hash!`

```elixir
@spec get_block_transaction_count_by_hash!(
  String.t(),
  keyword()
) :: non_neg_integer() | nil
```

Get a block's transaction count by hash. Raises on error.

## Parameters

  * `block_hash` - 0x-prefixed 32-byte block hash (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Transaction count or nil (`non_neg_integer | nil`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    block_hash: %{description: "0x-prefixed 32-byte block hash", kind: :value}
  },
  returns: %{
    type: "non_neg_integer | nil",
    description: "Transaction count or nil"
  }
}
```

# `get_block_transaction_count_by_number`

```elixir
@spec get_block_transaction_count_by_number(
  integer() | String.t(),
  keyword()
) :: {:ok, non_neg_integer() | nil} | {:error, term()}
```

Get a block's transaction count by number or tag (eth_getBlockTransactionCountByNumber).

## Parameters

  * `block` - Block number, tag, or 0x-prefixed quantity (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Transaction count, or nil when the block is unknown (`{:ok, non_neg_integer | nil} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    block: %{
      description: "Block number, tag, or 0x-prefixed quantity",
      kind: :value
    },
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, non_neg_integer | nil} | {:error, term}",
    description: "Transaction count, or nil when the block is unknown"
  }
}
```

# `get_block_transaction_count_by_number!`

```elixir
@spec get_block_transaction_count_by_number!(
  integer() | String.t(),
  keyword()
) :: non_neg_integer() | nil
```

Get a block's transaction count by number or tag. Raises on error.

## Parameters

  * `block` - Block number, tag, or 0x-prefixed quantity (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Transaction count or nil (`non_neg_integer | nil`)

```elixir
# descripex:contract
%{
  params: %{
    block: %{
      description: "Block number, tag, or 0x-prefixed quantity",
      kind: :value
    },
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "non_neg_integer | nil",
    description: "Transaction count or nil"
  }
}
```

# `get_proof`

```elixir
@spec get_proof(String.t() | binary(), [String.t()], keyword()) ::
  {:ok, map()} | {:error, term()}
```

Fetch Merkle proof for an account and storage slots (eth_getProof).

## Parameters

  * `address` - Account address as 0x hex string or 20-byte binary (value)
  * `storage_keys` - List of 0x-prefixed 32-byte hex storage slot keys (may be empty for account-only proofs) (value)
  * `opts` - Options: :rpc_url, :timeout, :block (default: `[]`, value)

## Returns

Atom-keyed proof map: address (checksummed), balance (integer wei), nonce (integer), code_hash (0x hex), storage_hash (0x hex), account_proof ([0x hex]), storage_proof ([%\{key, value, proof\}]) (`{:ok, map} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :block",
      kind: :value
    },
    address: %{
      description: "Account address as 0x hex string or 20-byte binary",
      kind: :value
    },
    storage_keys: %{
      description: "List of 0x-prefixed 32-byte hex storage slot keys (may be empty for account-only proofs)",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, map} | {:error, term}",
    description: "Atom-keyed proof map: address (checksummed), balance (integer wei), nonce (integer), code_hash (0x hex), storage_hash (0x hex), account_proof ([0x hex]), storage_proof ([%{key, value, proof}])"
  }
}
```

# `get_proof!`

```elixir
@spec get_proof!(String.t() | binary(), [String.t()], keyword()) :: map()
```

Fetch Merkle proof for an account and storage slots. Raises on error.

## Parameters

  * `address` - Account address as 0x hex string or 20-byte binary (value)
  * `storage_keys` - List of 0x-prefixed 32-byte hex storage slot keys (may be empty for account-only proofs) (value)
  * `opts` - Options: :rpc_url, :timeout, :block (default: `[]`, value)

## Returns

Atom-keyed proof map (see get_proof/3) (`map`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :block",
      kind: :value
    },
    address: %{
      description: "Account address as 0x hex string or 20-byte binary",
      kind: :value
    },
    storage_keys: %{
      description: "List of 0x-prefixed 32-byte hex storage slot keys (may be empty for account-only proofs)",
      kind: :value
    }
  },
  returns: %{type: "map", description: "Atom-keyed proof map (see get_proof/3)"}
}
```

# `get_transaction_by_block_hash_and_index`

```elixir
@spec get_transaction_by_block_hash_and_index(
  String.t(),
  non_neg_integer() | String.t(),
  keyword()
) ::
  {:ok, map() | nil} | {:error, term()}
```

Fetch a transaction by block hash and position (eth_getTransactionByBlockHashAndIndex).

## Parameters

  * `block_hash` - 0x-prefixed 32-byte block hash (value)
  * `transaction_index` - Zero-based non-negative integer or 0x quantity (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Parsed transaction map, or nil when the block or index is unknown (`{:ok, map | nil} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    block_hash: %{description: "0x-prefixed 32-byte block hash", kind: :value},
    transaction_index: %{
      description: "Zero-based non-negative integer or 0x quantity",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, map | nil} | {:error, term}",
    description: "Parsed transaction map, or nil when the block or index is unknown"
  }
}
```

# `get_transaction_by_block_hash_and_index!`

```elixir
@spec get_transaction_by_block_hash_and_index!(
  String.t(),
  non_neg_integer() | String.t(),
  keyword()
) :: map() | nil
```

Fetch a transaction by block hash and position. Raises on error.

## Parameters

  * `block_hash` - 0x-prefixed 32-byte block hash (value)
  * `transaction_index` - Zero-based non-negative integer or 0x quantity (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Parsed transaction map or nil (`map | nil`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    block_hash: %{description: "0x-prefixed 32-byte block hash", kind: :value},
    transaction_index: %{
      description: "Zero-based non-negative integer or 0x quantity",
      kind: :value
    }
  },
  returns: %{type: "map | nil", description: "Parsed transaction map or nil"}
}
```

# `get_transaction_by_block_number_and_index`

```elixir
@spec get_transaction_by_block_number_and_index(
  integer() | String.t(),
  non_neg_integer() | String.t(),
  keyword()
) :: {:ok, map() | nil} | {:error, term()}
```

Fetch a transaction by block number/tag and position (eth_getTransactionByBlockNumberAndIndex).

## Parameters

  * `block` - Block number, tag, or 0x-prefixed quantity (value)
  * `transaction_index` - Zero-based non-negative integer or 0x quantity (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Parsed transaction map, or nil when the block or index is unknown (`{:ok, map | nil} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    block: %{
      description: "Block number, tag, or 0x-prefixed quantity",
      kind: :value
    },
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    transaction_index: %{
      description: "Zero-based non-negative integer or 0x quantity",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, map | nil} | {:error, term}",
    description: "Parsed transaction map, or nil when the block or index is unknown"
  }
}
```

# `get_transaction_by_block_number_and_index!`

```elixir
@spec get_transaction_by_block_number_and_index!(
  integer() | String.t(),
  non_neg_integer() | String.t(),
  keyword()
) :: map() | nil
```

Fetch a transaction by block number/tag and position. Raises on error.

## Parameters

  * `block` - Block number, tag, or 0x-prefixed quantity (value)
  * `transaction_index` - Zero-based non-negative integer or 0x quantity (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Parsed transaction map or nil (`map | nil`)

```elixir
# descripex:contract
%{
  params: %{
    block: %{
      description: "Block number, tag, or 0x-prefixed quantity",
      kind: :value
    },
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    transaction_index: %{
      description: "Zero-based non-negative integer or 0x quantity",
      kind: :value
    }
  },
  returns: %{type: "map | nil", description: "Parsed transaction map or nil"}
}
```

# `get_transaction_by_hash`

```elixir
@spec get_transaction_by_hash(
  String.t(),
  keyword()
) :: {:ok, map() | nil} | {:error, term()}
```

Get full transaction details by hash (eth_getTransactionByHash).

## Parameters

  * `tx_hash` - 0x-prefixed hex transaction hash (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Parsed transaction map, or nil if the transaction is unknown (`{:ok, map | nil} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    tx_hash: %{description: "0x-prefixed hex transaction hash", kind: :value}
  },
  returns: %{
    type: "{:ok, map | nil} | {:error, term}",
    description: "Parsed transaction map, or nil if the transaction is unknown"
  }
}
```

# `get_transaction_by_hash!`

```elixir
@spec get_transaction_by_hash!(
  String.t(),
  keyword()
) :: map() | nil
```

Get full transaction details by hash. Raises on error.

## Parameters

  * `tx_hash` - 0x-prefixed hex transaction hash (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Parsed transaction map or nil (`map | nil`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    tx_hash: %{description: "0x-prefixed hex transaction hash", kind: :value}
  },
  returns: %{type: "map | nil", description: "Parsed transaction map or nil"}
}
```

# `get_transaction_count`

```elixir
@spec get_transaction_count(
  String.t() | binary(),
  keyword()
) :: {:ok, non_neg_integer()} | {:error, term()}
```

Get the transaction count (nonce) of an address.

## Parameters

  * `address` - Account address as 0x hex string or 20-byte binary (value)
  * `opts` - Options: :rpc_url, :timeout, :block (default: `[]`, value)

## Returns

Transaction count (nonce) (`{:ok, non_neg_integer} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :block",
      kind: :value
    },
    address: %{
      description: "Account address as 0x hex string or 20-byte binary",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, non_neg_integer} | {:error, term}",
    description: "Transaction count (nonce)"
  }
}
```

# `get_transaction_count!`

```elixir
@spec get_transaction_count!(
  String.t() | binary(),
  keyword()
) :: non_neg_integer()
```

Get the transaction count (nonce) of an address. Raises on error.

## Parameters

  * `address` - Account address as 0x hex string or 20-byte binary (value)
  * `opts` - Options: :rpc_url, :timeout, :block (default: `[]`, value)

## Returns

Transaction count (nonce) (`non_neg_integer`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout, :block",
      kind: :value
    },
    address: %{
      description: "Account address as 0x hex string or 20-byte binary",
      kind: :value
    }
  },
  returns: %{type: :non_neg_integer, description: "Transaction count (nonce)"}
}
```

# `get_transaction_receipt`

```elixir
@spec get_transaction_receipt(
  String.t(),
  keyword()
) :: {:ok, map() | nil} | {:error, term()}
```

Get a transaction receipt by hash (eth_getTransactionReceipt).

## Parameters

  * `tx_hash` - 0x-prefixed hex transaction hash (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Parsed receipt map, or nil if the transaction is pending/unknown (`{:ok, map | nil} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    tx_hash: %{description: "0x-prefixed hex transaction hash", kind: :value}
  },
  returns: %{
    type: "{:ok, map | nil} | {:error, term}",
    description: "Parsed receipt map, or nil if the transaction is pending/unknown"
  }
}
```

# `get_transaction_receipt!`

```elixir
@spec get_transaction_receipt!(
  String.t(),
  keyword()
) :: map() | nil
```

Get a transaction receipt by hash. Raises on error.

## Parameters

  * `tx_hash` - 0x-prefixed hex transaction hash (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

Parsed receipt map or nil (`map | nil`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    tx_hash: %{description: "0x-prefixed hex transaction hash", kind: :value}
  },
  returns: %{type: "map | nil", description: "Parsed receipt map or nil"}
}
```

# `syncing`

```elixir
@spec syncing(keyword()) :: {:ok, false | map()} | {:error, term()}
```

Get the node's sync status (eth_syncing).

## Parameters

  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

`false` when the node is fully synced; otherwise a raw sync-status map with hex-encoded fields (`startingBlock`, `currentBlock`, `highestBlock`, sometimes snap-sync fields). Field shape varies by client — caller decodes. (`{:ok, false | map} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, false | map} | {:error, term}",
    description: "`false` when the node is fully synced; otherwise a raw sync-status map with hex-encoded fields (`startingBlock`, `currentBlock`, `highestBlock`, sometimes snap-sync fields). Field shape varies by client — caller decodes."
  }
}
```

# `syncing!`

```elixir
@spec syncing!(keyword()) :: false | map()
```

Get the node's sync status. Raises on error.

## Parameters

  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

`false` when synced, sync-status map otherwise (`false | map`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    }
  },
  returns: %{
    type: "false | map",
    description: "`false` when synced, sync-status map otherwise"
  }
}
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
