Onchain.RPC (onchain v0.16.0)

Copy Markdown View Source

Shared JSON-RPC transport and typed Ethereum client.

Single calls and batches use config :cartouche, :ethereum_node for the URL, config :cartouche, Onchain.RPC, [...] for transport defaults, and config :cartouche, :req_options, [...] for global Req options. Per-call :rpc_url / :ethereum_node and :req_options override these defaults. A missing URL returns {:error, {:missing_option, :ethereum_node}}. The former :onchain batch transport configuration is no longer used.

Both paths emit [:onchain, :rpc, :request] telemetry spans. Refusals now uniformly return :method_not_found, :namespace_unavailable, or :unavailable tags; other single-call error shapes remain unchanged. See Onchain.RPC's node-capability refusal documentation for the live Alchemy provenance and message-sensitive meaning of -32600 and -32001.

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 in Onchain.RPC on the shared single-call and batch paths (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 Onchain.RPC.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.

Input adapters and bang functions

eth_call/3, eth_estimate_gas/2, eth_get_code/2, and eth_send_raw_transaction/2 accept the former Onchain inputs and delegate to the typed methods. Hex-returning adapters preserve hex results and wrap unclassified RPC errors as {:rpc_error, map}. The typed methods keep their native error shapes. Both surfaces preserve classified node refusals. get_transaction_receipt/2 returns Onchain.Receipt, and get_block_by_number/2 returns Onchain.Block; logs use Onchain.Filter.Log. Bang variants unwrap {:ok, value} and raise RuntimeError on {:error, reason}.

Error Format

Hex-input adapters (eth_call/3, eth_estimate_gas/2, eth_get_code/2, eth_send_raw_transaction/2, get_transaction_receipt/2, fee_history/2, and call/3) return {:error, {:rpc_error, map}} for an unclassified RPC error. A code: 3 revert map includes :revert (raw bytes) and :data (0x hex). When :errors names a matching custom-error signature, the map also includes :error_abi and :error_params. Classified node refusals stay :method_not_found, :namespace_unavailable, or :unavailable. Typed methods such as get_balance/2, get_block_by_number/2, and call_trx/2 return the transport error directly.

Local signing through Onchain.Signer is the normal route for submitting transactions (eth_sendRawTransaction). The node-custody methods (accounts/1, coinbase/1, fill_transaction/2, sign/3, sign_transaction/2, send_transaction/2) exist for nodes that hold keys. The distinction is not key custody as such (Onchain.Signer.CloudKMS already signs with a key cartouche does not hold) but which side constructs the transaction: those methods move envelope construction, nonce and fee policy, EIP-155 v derivation, and low-s normalization into the node.

sign/3 (eth_sign) takes a message, not a pre-computed digest: the node returns an EIP-191 signature over the data it is handed (execution-apis, src/eth/sign.yaml). The method is historically dangerous because early implementations signed the bytes verbatim, which let a caller pass a transaction hash and get it signed; most nodes ship it disabled today.

API Functions

FunctionArityDescriptionParam Kinds
chain_id1Get the network chain ID.opts: value
block_number1Get the current block height.opts: value
blob_base_fee!1Get the current base fee per blob gas. Raises on error.opts: value
fee_history!2Fetch fee history. Raises on error.block_count: value, opts: value
fee_history2Fetch base-fee history and per-block priority-fee percentiles (eth_feeHistory).block_count: value, opts: value
batch2Generic JSON-RPC array batch — invoke many methods in one HTTP request.requests: value, opts: value
call!3Generic JSON-RPC passthrough. Raises on error.method: value, params: value, opts: value
call3Generic JSON-RPC passthrough — invoke any method not covered by a named wrapper.method: value, params: value, opts: value
eth_get_code!2Fetch contract bytecode at an address. Raises on error.address: value, opts: value
eth_get_code2Fetch contract bytecode at an address (eth_getCode).address: value, opts: value
get_transaction_count!2Get the transaction count (nonce) of an address. Raises on error.address: value, opts: value
get_transaction_receipt!2Get a transaction receipt by hash. Raises on error.tx_hash: value, opts: value
get_transaction_receipt2Get a transaction receipt by hash (eth_getTransactionReceipt).tx_hash: value, opts: value
chain_id!1Get the network chain ID. Raises on error.opts: value
get_block_access_list!2Fetch an EIP-7928 block access list. Raises on error.block: value, opts: value
get_block_access_list2Fetch an EIP-7928 block access list (eth_getBlockAccessList).block: value, opts: value
get_block_by_number!2Fetch a block by number or tag. Raises on error.block_id: value, opts: value
block_number!1Get the current block height. Raises on error.opts: value
get_balance!2Get the ETH balance of an address in wei. Raises on error.address: value, opts: value
eth_send_raw_transaction!2Broadcast a signed transaction. Raises on error.data: value, opts: value
eth_send_raw_transaction2Broadcast a signed transaction.data: value, opts: value
eth_estimate_gas!2Estimate the gas a transaction would consume. Raises on error.tx_params: value, opts: value
eth_estimate_gas2Estimate the gas a transaction would consume.tx_params: value, opts: value
eth_call!3Execute a read-only contract call. Raises on error.address: value, data: value, opts: value
eth_call3Execute a read-only contract call (eth_call).address: value, data: value, opts: value
send_transaction2Ask the node to sign and broadcast a transaction with a managed account.trx: value, opts: value
sign_transaction2Ask the node to sign a transaction with a managed account.trx: value, opts: value
sign3Ask the node to sign a message under EIP-191 with a managed account (eth_sign).account: value, message: value, opts: value
fill_transaction2Ask the node to populate missing nonce, gas, and fee fields without signing.trx: value, opts: value
coinbase1Return the node's coinbase (fee recipient) address.opts: value
accounts1List addresses the node manages.opts: value
get_filter_logs2Return every log matching a log filter, not only changes since the last poll.filter_id: value, opts: value
eth_get_logs2Fetch event logs matching a stateless filter (eth_getLogs).filter: value, opts: value
new_pending_transaction_filter1Create a node-side filter that records new pending transaction hashes.opts: value
new_block_filter1Create a node-side filter that records new block hashes.opts: value
execute_trx3Prepare, sign, and submit a transaction to the Ethereum network.contract: value, call_data: value, opts: value
prepare_trx3Prepare and sign a transaction for later submission.contract: value, call_data: value, opts: value
fee_history2Fetch and decode EIP-1559 fee history data.opts: value
max_priority_fee_per_gas1Fetch the current max priority fee per gas.opts: value
blob_base_fee1Fetch the current base fee per blob gas.opts: value
base_fee1Fetch the EIP-1559 base fee per gas for the next block.opts: value
eth_get_proof3Fetch an EIP-1186 account and storage proof.address: value, storage_keys: value, opts: value
eth_get_storage_at3Read a 32-byte contract storage word.address: value, slot: value, opts: value
gas_price1Fetch the current legacy gas price.opts: value
debug_trace_call2Trace a transaction call speculatively with the debug trace API.trx: value, opts: value
trace_call_many2Trace multiple transaction calls speculatively with the parity trace API.trxs: value, opts: value
trace_call2Trace a transaction call speculatively with the parity trace API.trx: value, opts: value
trace_trx2Fetch parity-style traces for a transaction by transaction hash.trx_id: value, opts: value
eth_get_block_receipts2Fetch every receipt in a block (eth_getBlockReceipts).block: value, opts: value
eth_get_transaction_by_block_number_and_index3Fetch one transaction by block number or tag and position (eth_getTransactionByBlockNumberAndIndex).block: value, transaction_index: value, opts: value
eth_get_transaction_by_block_hash_and_index3Fetch one transaction by block hash and position (eth_getTransactionByBlockHashAndIndex).block_hash: value, transaction_index: value, opts: value
eth_get_transaction_by_hash2Fetch one transaction by hash (eth_getTransactionByHash).tx_hash: value, opts: value
get_trx_receipt2Fetch and decode a transaction receipt by transaction hash.trx_id: value, opts: value
get_block_by_hash2Fetch a block by its 32-byte block hash.block_hash: value, opts: value
get_block_by_number2Fetch a block by block number or block tag.block_number: value, opts: value
web3_client_version1Fetch the node client version string.opts: value
net_peer_count1Fetch the number of peers currently connected to the node.opts: value
net_listening1Ask whether the node is listening for network connections.opts: value
eth_get_block_transaction_count_by_number2Count transactions in the block with the given number or tag.block: value, opts: value
eth_get_block_transaction_count_by_hash2Count transactions in the block with the given hash.block_hash: value, opts: value
eth_syncing1Fetch the node's sync status.opts: value
eth_block_number1Fetch the current Ethereum block number.opts: value
get_transaction_count2Fetch an account transaction count at a block selector.address: value, opts: value
get_balance2Fetch an account ETH balance at a block selector.address: value, opts: value
get_code2Fetch contract bytecode at an address and block selector.address: value, opts: value
eth_capabilities1Fetch the node's effective historical-data capabilities.opts: value
eth_config1Fetch the node's EIP-7910 chain and fork configuration.opts: value
eth_chain_id1Fetch the current Ethereum chain id.opts: value
estimate_gas2Estimate gas for a transaction or call object.trx: value, opts: value
create_access_list2Generate an EIP-2930 access list for a transaction or call object.trx: value, opts: value
call_trx2Run eth_call against a transaction or call object without submitting it.trx: value, opts: value
send_trx2Submit a signed Ethereum transaction to the network.trx: exchange_data, opts: value
get_nonce2Fetch an account nonce at a block selector.account: value, opts: value
send_batch2Send several Ethereum JSON-RPC requests as one array batch and return raw results in request order.requests: value, opts: value
send_rpc3Send one Ethereum JSON-RPC request and optionally decode the result.method: value, params: value, opts: value

Summary

Types

Decoded eth_createAccessList result, retaining an optional execution error.

Error returned when JSON encoding rejects the outbound request body.

Structured JSON-RPC error envelope returned by an Ethereum node or by local response validation.

All values that can appear inside an {:error, reason} tuple returned by send_rpc/3.

Functions

RPC call to list accounts the node holds keys for.

Returns the next block's base fee in wei via eth_feeHistory(1, "latest", []).

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

RPC call to get the current base fee per blob gas.

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

Get the current block height.

Get the current block height. Raises on error.

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

Generic JSON-RPC passthrough. Raises on error.

RPC call to call a transaction and preview results.

Get the network chain ID.

Get the network chain ID. Raises on error.

RPC call to get the node's coinbase address.

Generates the access list and gas used by a call at a block selector.

RPC to trace a transaction call speculatively via debug API.

RPC call to call to estimate gas used by a given call.

RPC to get the current block number.

Execute a read-only contract call (eth_call).

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

Returns the node's head and resource-retention capabilities.

RPC to get the current chain id.

Returns the current, next, and last configured Ethereum forks reported by the node.

Estimate the gas a transaction would consume.

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

Returns every receipt in the block identified by number, tag, or hash.

Returns how many transactions are in the block identified by block_hash.

Returns how many transactions are in the block identified by number or tag.

Fetch contract bytecode at an address (eth_getCode).

Fetch contract bytecode at an address. Raises on error.

Stateless eth_getLogs query.

Fetches an EIP-1186 account and storage proof as Onchain.RPC.Proof.

Reads a 32-byte storage word via eth_getStorageAt (execution-apis v1.0.0-beta.7).

Returns the transaction at transaction_index in the block identified by block_hash.

Returns the transaction at transaction_index in the block identified by number or tag.

Returns the transaction identified by tx_hash.

Broadcast a signed transaction.

Broadcast a signed transaction. Raises on error.

Returns the node's sync status from eth_syncing.

Helper function to work with other Cartouche modules to get a nonce, sign a transction, and transmit it to the network.

RPC call to call to get the Eip-1559 fee history data.

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

Fetch fee history. Raises on error.

RPC call to fill missing transaction fields via eth_fillTransaction.

RPC call to call to get the current gas price.

RPC to get an account's eth balance.

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

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

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

RPC to get a block by its block hash.

RPC to get a block by its block number.

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

RPC call to get code for a contract at an address.

RPC call to fetch the full log backlog for a filter.

RPC call to get account nonce.

RPC to get an account's transaction count (i.e. nonce)

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

Get a transaction receipt by hash (eth_getTransactionReceipt).

Get a transaction receipt by hash. Raises on error.

RPC call to get a transaction receipt. Note, this will return {:ok, %Onchain.Receipt{}} or {:ok, nil} if the receipt is not yet available.

RPC call to call to get the current max priority fee per gas.

Returns whether the node is listening for peer connections (net_listening).

Returns the node's connected peer count (net_peerCount).

RPC call to create a new block filter.

RPC call to create a new pending-transaction filter.

Helper function to work with other Cartouche modules to get a nonce, sign a transction, and prepare it to be submitted on-chain.

Sends a JSON-RPC array batch and returns raw results in request order. Uses the same options as send_rpc/3.

Simple RPC client for a JSON-RPC Ethereum node.

RPC call to eth_sendTransaction.

RPC call to send a raw transaction.

RPC call to eth_sign.

RPC call to eth_signTransaction.

RPC to trace a transaction call speculatively.

RPC to trace many transaction calls speculatively.

RPC call to get a transaction receipt

Returns the node client's version string (web3_clientVersion).

Types

access_list_result()

@type access_list_result() :: %{
  :access_list => Onchain.Transaction.V_2930.access_list(),
  :gas_used => non_neg_integer(),
  optional(:error) => String.t()
}

Decoded eth_createAccessList result, retaining an optional execution error.

invalid_params_error()

@type invalid_params_error() :: {:invalid_params, Exception.t()}

Error returned when JSON encoding rejects the outbound request body.

rpc_error()

@type rpc_error() :: %{
  :code => integer(),
  :message => String.t(),
  optional(:revert) => binary(),
  optional(:error_abi) => String.t(),
  optional(:error_params) => [term()],
  optional(:trace) => term()
}

Structured JSON-RPC error envelope returned by an Ethereum node or by local response validation.

send_rpc_error()

@type send_rpc_error() ::
  rpc_error()
  | invalid_params_error()
  | Req.Response.t()
  | String.t()
  | {atom(), term()}

All values that can appear inside an {:error, reason} tuple returned by send_rpc/3.

Functions

accounts(opts \\ [])

@spec accounts(Keyword.t()) :: {:ok, [<<_::160>>]} | {:error, term()}

RPC call to list accounts the node holds keys for.

Examples

iex> Onchain.RPC.accounts()
{:ok, [<<64, 125, 115, 216, 164, 158, 235, 133, 211, 44, 244, 101, 80, 125, 215, 29, 80, 113, 0, 193>>]}

base_fee(opts \\ [])

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

Returns the next block's base fee in wei via eth_feeHistory(1, "latest", []).

The final baseFeePerGas entry is the next block's fee, as specified in execution-apis v1.0.0-beta.7. This avoids hosted endpoints' differing handling of the pending block tag. Uses the shared fee-history decoder and transport; node errors propagate unchanged. An incomplete fee window returns {:error, :invalid_base_fee_history}.

eth_baseFee landed on execution-apis main on 2026-06-15, but is in no tagged release (latest verified: v1.0.0-beta.7). Alchemy and Infura mainnet refused it in live probes on 2026-10-01; see docs/base-fee-portability.md.

batch(requests, opts \\ [])

@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(opts \\ [])

@spec blob_base_fee(Keyword.t()) :: {:ok, non_neg_integer()} | {:error, term()}

RPC call to get the current base fee per blob gas.

Examples

iex> Onchain.RPC.blob_base_fee()
{:ok, 42}

blob_base_fee!(opts \\ [])

@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)

# 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(opts \\ [])

@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})

# 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!(opts \\ [])

@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)

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

call(method, params, opts \\ [])

@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})

# 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!(method, params, opts \\ [])

@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)

# 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"}
}

call_trx(trx, opts \\ [])

@spec call_trx(
  Onchain.Transaction.V1.t()
  | Onchain.Transaction.V2.t()
  | Onchain.Transaction.Call.t()
  | map(),
  Keyword.t()
) :: {:ok, binary()} | {:error, term()}

RPC call to call a transaction and preview results.

Examples

iex> Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>)
iex> |> Onchain.RPC.call_trx()
{:ok, <<0x0c>>}

iex> Onchain.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], :goerli)
iex> |> Onchain.RPC.call_trx()
{:ok, <<0x0d>>}

iex> Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>)
iex> |> Onchain.RPC.call_trx(decode: :hex_unsigned)
{:ok, 0x0c}

iex> Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<10::160>>, {2, :wei}, <<1, 2, 3>>)
iex> |> Onchain.RPC.call_trx()
{:error, %{code: 3, message: "execution reverted", revert: <<61, 115, 139, 46>>}}

iex> errors = ["Unauthorized()", "BadNonce()", "NotEnoughSigners()", "NotActiveWithdrawalAddress()", "NotActiveOperator()", "DuplicateSigners()"]
iex> Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<10::160>>, {2, :wei}, <<1, 2, 3>>)
iex> |> Onchain.RPC.call_trx(errors: errors)
{:error, %{code: 3, message: "execution reverted", error_abi: "NotActiveOperator()", error_params: [], revert: <<61, 115, 139, 46>>}}

iex> errors = ["Cool(uint256,string)"]
iex> Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<11::160>>, {2, :wei}, <<1, 2, 3>>)
iex> |> Onchain.RPC.call_trx(errors: errors)
{:error, %{code: 3, message: "execution reverted", error_abi: "Cool(uint256,string)", error_params: [1, "cat"], revert: Onchain.ABI.encode("Cool(uint256,string)", [1, "cat"])}}

iex> errors = ["Cool(uint256,string)"]
iex> Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<12::160>>, {2, :wei}, <<1, 2, 3>>)
iex> |> Onchain.RPC.call_trx(errors: errors)
{:error, %{code: 3, message: "execution reverted", revert: <<>>}}

iex> Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<13::160>>, {2, :wei}, <<1, 2, 3>>)
iex> |> Onchain.RPC.call_trx()
{:error, %{code: -32602, message: "Failed to decode transaction"}}

chain_id(opts \\ [])

@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})

# 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!(opts \\ [])

@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)

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

coinbase(opts \\ [])

@spec coinbase(Keyword.t()) :: {:ok, <<_::160>>} | {:error, term()}

RPC call to get the node's coinbase address.

Examples

iex> Onchain.RPC.coinbase()
{:ok, <<254, 59, 85, 126, 143, 182, 43, 137, 244, 145, 107, 114, 27, 229, 92, 235, 130, 141, 189, 115>>}

create_access_list(trx, opts \\ [])

@spec create_access_list(
  Onchain.Transaction.V1.t()
  | Onchain.Transaction.V2.t()
  | Onchain.Transaction.Call.t(),
  Keyword.t()
) :: {:ok, access_list_result()} | {:error, term()}

Generates the access list and gas used by a call at a block selector.

A node may return an :error string alongside a usable access list when execution reverts; the field is retained in the successful result map.

Examples

iex> call = Onchain.Transaction.Call.new(<<1::160>>, <<0, 1>>)
iex> {:ok, %{access_list: [{address, [storage_key]}], gas_used: gas_used}} =
...>   Onchain.RPC.create_access_list(call)
iex> {address, storage_key, gas_used}
{<<1::160>>, <<2::256>>, 26026}

debug_trace_call(trx, opts \\ [])

@spec debug_trace_call(
  Onchain.Transaction.V1.t()
  | Onchain.Transaction.V2.t()
  | Onchain.Transaction.Call.t(),
  Keyword.t()
) :: {:ok, Onchain.DebugTrace.t()} | {:error, term()}

RPC to trace a transaction call speculatively via debug API.

Examples

iex> Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>)
...> |> Onchain.RPC.debug_trace_call()
{:ok,
  %Onchain.DebugTrace{
  failed: false,
  gas: 24034,
  return_value: ~h[0x0000000000000000000000000000000000000000000000000858898f93629000],
  struct_logs: [
    %Onchain.DebugTrace.StructLog{
      depth: 1,
      gas: 599978568,
      gas_cost: 3,
      op: :PUSH1,
      pc: 0,
      stack: []
    },
    %Onchain.DebugTrace.StructLog{
      depth: 1,
      gas: 599978565,
      gas_cost: 3,
      op: :PUSH1,
      pc: 2,
      stack: [~h[0x80]]
    },
    %Onchain.DebugTrace.StructLog{
      depth: 1,
      gas: 599978562,
      gas_cost: 12,
      op: :MSTORE,
      pc: 4,
      stack: [~h[0x80], ~h[0x40]]
    }
  ]
}}

estimate_gas(trx, opts \\ [])

@spec estimate_gas(
  Onchain.Transaction.V1.t()
  | Onchain.Transaction.V2.t()
  | Onchain.Transaction.Call.t()
  | map(),
  Keyword.t()
) :: {:ok, non_neg_integer()} | {:error, term()}

RPC call to call to estimate gas used by a given call.

Examples

iex> Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>)
iex> |> Onchain.RPC.estimate_gas()
{:ok, 0x0d}

iex> Onchain.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], :goerli)
iex> |> Onchain.RPC.estimate_gas()
{:ok, 0xdd}

iex> use Onchain.Hex
iex> Onchain.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<10::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], :goerli)
iex> |> Onchain.RPC.estimate_gas()
{:error, %{code: 3, message: "execution reverted: Dai/insufficient-balance", revert: ~h[0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000184461692f696e73756666696369656e742d62616c616e63650000000000000000]}}

eth_block_number(opts \\ [])

@spec eth_block_number(Keyword.t()) :: {:ok, non_neg_integer()} | {:error, term()}

RPC to get the current block number.

Docs: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_blocknumber

Examples

iex> Onchain.RPC.eth_block_number()
{:ok, 0x44}

eth_call(address, data, opts \\ [])

@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_hex_error/2` expects 0x hex, which is exactly :data.
    Onchain.ABI.decode_hex_error(hex_data, ["MyError(uint256)"])

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

eth_call!(address, data, opts \\ [])

@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)

# 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_capabilities(opts \\ [])

@spec eth_capabilities(Keyword.t()) ::
  {:ok, Onchain.RPC.Capabilities.t()} | {:error, term()}

Returns the node's head and resource-retention capabilities.

Examples

iex> {:ok, capabilities} = Onchain.RPC.eth_capabilities()
iex> {capabilities.head.number, capabilities.blocks.oldest_block}
{42, 0}

eth_chain_id(opts \\ [])

@spec eth_chain_id(Keyword.t()) :: {:ok, non_neg_integer()} | {:error, term()}

RPC to get the current chain id.

Docs: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_chainid

Examples

iex> Onchain.RPC.eth_chain_id()
{:ok, 0x22}

eth_config(opts \\ [])

@spec eth_config(Keyword.t()) ::
  {:ok, Onchain.RPC.Configuration.t()} | {:error, term()}

Returns the current, next, and last configured Ethereum forks reported by the node.

Examples

iex> {:ok, config} = Onchain.RPC.eth_config()
iex> {config.current.chain_id, config.current.fork_id}
{1, <<7, 201, 70, 46>>}

eth_estimate_gas(tx_params, opts \\ [])

@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()})

# 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!(tx_params, opts \\ [])

@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)

# 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_block_receipts(block, opts \\ [])

@spec eth_get_block_receipts(integer() | String.t(), Keyword.t()) ::
  {:ok, [Onchain.Receipt.t()] | nil} | {:error, term()}

Returns every receipt in the block identified by number, tag, or hash.

Defined by execution-apis v1.0.0-beta.7 (src/eth/block.yaml, eth_getBlockReceipts). Each receipt is decoded by Onchain.Receipt.deserialize/1, the same decoder as get_trx_receipt/2. A null result is {:ok, nil}.

eth_get_block_transaction_count_by_hash(block_hash, opts \\ [])

@spec eth_get_block_transaction_count_by_hash(String.t(), Keyword.t()) ::
  {:ok, non_neg_integer() | nil} | {:error, term()}

Returns how many transactions are in the block identified by block_hash.

Defined by execution-apis v1.0.0-beta.7 (src/eth/block.yaml, eth_getBlockTransactionCountByHash). The result is a hex quantity, or null when the node does not know the block.

Examples

iex> Onchain.RPC.eth_get_block_transaction_count_by_hash("0x" <> String.duplicate("cd", 32))
{:ok, 2}

eth_get_block_transaction_count_by_number(block, opts \\ [])

@spec eth_get_block_transaction_count_by_number(integer() | String.t(), Keyword.t()) ::
  {:ok, non_neg_integer() | nil} | {:error, term()}

Returns how many transactions are in the block identified by number or tag.

Defined by execution-apis v1.0.0-beta.7 (src/eth/block.yaml, eth_getBlockTransactionCountByNumber). The result is a hex quantity, or null when the node does not know the block.

Examples

iex> Onchain.RPC.eth_get_block_transaction_count_by_number("latest")
{:ok, 2}

eth_get_code(address, opts \\ [])

@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})

# 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!(address, opts \\ [])

@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)

# 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(filter, opts \\ [])

@spec eth_get_logs(map(), Keyword.t()) ::
  {:ok, [Onchain.Filter.Log.t()]} | {:error, term()}

Stateless eth_getLogs query.

Accepts the execution-apis filter object: a block range (fromBlock / toBlock) or a mutually exclusive blockHash, plus optional address and topics. Logs are decoded with Onchain.Filter.Log, the same decoder Onchain.Filter and get_filter_logs/2 use. That decoder expects the mined-log fields (block hash, block number, and transaction hash present).

Examples

iex> {:ok, [log]} = Onchain.RPC.eth_get_logs(%{from_block: 16, to_block: 16})
iex> log.block_number
16

eth_get_proof(address, storage_keys, opts \\ [])

@spec eth_get_proof(String.t() | binary(), [String.t()], keyword()) ::
  {:ok, Onchain.RPC.Proof.t()} | {:error, term()}

Fetches an EIP-1186 account and storage proof as Onchain.RPC.Proof.

Defined by execution-apis v1.0.0-beta.7. Keys are hex strings of 1 to 64 digits, left-padded to 32 bytes on the wire; :block selects a block number or tag (default "latest"). Quantities decode to integers and addresses, hashes and proof nodes to bytes. No local Merkle verification is performed.

Observed 2026-10-01: Alchemy mainnet served DAI proofs at latest, block 18,000,000 and block 1 (before deployment, with empty storage proof nodes). The archive node served latest but refused both historical blocks with -32602, "distance to target block exceeds maximum proof window". Availability is endpoint-specific; node refusals propagate unchanged. See docs/state-read-portability.md for verbatim evidence.

eth_get_storage_at(address, slot, opts \\ [])

@spec eth_get_storage_at(String.t() | binary(), String.t(), keyword()) ::
  {:ok, <<_::256>>} | {:error, term()}

Reads a 32-byte storage word via eth_getStorageAt (execution-apis v1.0.0-beta.7).

Accepts a hex slot of up to 32 bytes, including leading zeroes. :block selects a block number or tag (default "latest"). Returns raw bytes.

Observed 2026-10-01: Alchemy mainnet and the archive node returned DAI slot 1 at block 18,000,000; Alchemy also returned zero at block 1 (before deployment). This does not establish an unlimited history guarantee. Verbatim responses and refusals are recorded in docs/state-read-portability.md.

eth_get_transaction_by_block_hash_and_index(block_hash, transaction_index, opts \\ [])

@spec eth_get_transaction_by_block_hash_and_index(
  String.t(),
  non_neg_integer() | String.t(),
  Keyword.t()
) :: {:ok, Onchain.Transaction.Info.t()} | {:error, term()}

Returns the transaction at transaction_index in the block identified by block_hash.

Defined by execution-apis v1.0.0-beta.7 (src/eth/transaction.yaml, eth_getTransactionByBlockHashAndIndex). Same envelope as eth_get_transaction_by_hash/2. A null result is {:error, :not_found}.

eth_get_transaction_by_block_number_and_index(block, transaction_index, opts \\ [])

@spec eth_get_transaction_by_block_number_and_index(
  integer() | String.t(),
  non_neg_integer() | String.t(),
  Keyword.t()
) :: {:ok, Onchain.Transaction.Info.t()} | {:error, term()}

Returns the transaction at transaction_index in the block identified by number or tag.

Defined by execution-apis v1.0.0-beta.7 (src/eth/transaction.yaml, eth_getTransactionByBlockNumberAndIndex). Same envelope as eth_get_transaction_by_hash/2. A null result is {:error, :not_found}.

eth_get_transaction_by_hash(tx_hash, opts \\ [])

@spec eth_get_transaction_by_hash(String.t(), Keyword.t()) ::
  {:ok, Onchain.Transaction.Info.t()} | {:error, term()}

Returns the transaction identified by tx_hash.

Defined by execution-apis v1.0.0-beta.7 (src/eth/transaction.yaml, eth_getTransactionByHash). The result is a Onchain.Transaction.Info envelope: inclusion metadata plus the typed transaction from Vn.from_json/1. Block fields are nil when the transaction is pending. block_timestamp is also nil when the node omits blockTimestamp (Alchemy mainnet sometimes does, observed 2026-10-01). A null result is {:error, :not_found}.

eth_send_raw_transaction(data, opts \\ [])

@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})

# 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!(data, opts \\ [])

@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)

# 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"}
}

eth_syncing(opts \\ [])

@spec eth_syncing(Keyword.t()) ::
  {:ok, false | Onchain.RPC.SyncStatus.t()} | {:error, term()}

Returns the node's sync status from eth_syncing.

execution-apis v1.0.0-beta.7 (src/eth/client.yaml, schema SyncingStatus) defines one union: false when the node is not syncing, or a progress object whose quantity fields are startingBlock, currentBlock, and highestBlock. This decodes that union to false or %Onchain.RPC.SyncStatus{}.

Examples

iex> Onchain.RPC.eth_syncing()
{:ok, false}

execute_trx(contract, call_data, opts \\ [])

@spec execute_trx(<<_::160>>, binary() | {String.t(), [term()]}, Keyword.t()) ::
  {:ok, binary()} | {:error, term()}

Helper function to work with other Cartouche modules to get a nonce, sign a transction, and transmit it to the network.

If you need higher-level functionality, like manual nonce tracking, you may want to use the more granular function calls.

Options:

  • gas_price - Set the base gas for the transaction, overrides all other gas prices listed below (default nil) [note: only compatible with V1 transaction]
  • base_fee - Set the base price for the transaction, if nil, will use base gas price from eth_gasPrice call (default nil) [note: only compatible with V2 transactions]
  • base_fee_buffer - Buffer for the gas price when estimating gas (default: 1.2 = 120%) [note: only compatible with V2 transactions]
  • priority_fee - Additional gas to send as a priority fee. (default: {0, :gwei}) [note: only compatible with V2 transactions]
  • gas_limit - Set the gas limit for the transaction (default: calls eth_estimateGas)
  • gas_buffer - Buffer if estimating gas limit (default: 1.5 = 150%)
  • value - Value to provide with transaction in wei (default: 0)
  • nonce - Nonce to send with transaction. (default: lookup via eth_transactionCount)
  • verify - Verify the function is likely to succeed (default: true)
  • trx_type - :v1 for V1 (pre-EIP-1559 transactions), :v2 for V2 (EIP-1559) transactions, and nil for auto-detect.

Note: if we don't verify, then estimateGas will likely fail if the transaction were to fail.

    To prevent this, `gas_limit` should always be supplied when `verify` is set to false.

Examples

iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, trx_id} = Onchain.RPC.execute_trx(<<1::160>>, {"baz(uint,address)", [50, :binary.decode_unsigned(<<1::160>>)]}, gas_price: {50, :gwei}, value: 0, signer: signer_proc)
iex> <<nonce::integer-size(8), gas_price::integer-size(64), gas_limit::integer-size(24), to::binary>> = trx_id
iex> {nonce, gas_price, gas_limit, to}
{4, 50000000000, 20, <<1::160>>}

iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> Onchain.RPC.execute_trx(<<10::160>>, {"baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned]}, gas_price: {50, :gwei}, gas_limit: 100_000, value: 0, nonce: 10, signer: signer_proc)
{:error, %{code: 3, message: "execution reverted", revert: <<61, 115, 139, 46>>}}

iex> # Set base fee and priority fee (v2)
iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, trx_id} = Onchain.RPC.execute_trx(<<10::160>>, {"baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned]}, base_fee: {1, :gwei}, priority_fee: {3, :gwei}, gas_limit: 100_000, value: 0, nonce: 10, verify: false, signer: signer_proc)
iex> <<nonce::integer-size(8), max_priority_fee_per_gas::integer-size(64), max_fee_per_gas::integer-size(64), gas_limit::integer-size(24), to::binary>> = trx_id
iex> {nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to}
{10, 3000000000, 4000000000, 100000, <<10::160>>}

fee_history(opts \\ [])

@spec fee_history(Keyword.t() | pos_integer()) ::
  {:ok, Onchain.FeeHistory.t()} | {:error, term()}

RPC call to call to get the Eip-1559 fee history data.

Examples

iex> Onchain.RPC.fee_history()
{:ok, %Onchain.FeeHistory{
  base_fee_per_gas: [20566340803, 20460504186, 19629790325, 19239635811, 19090900440, 19048391846],
  gas_used_ratio: [0.4794155666666667, 0.3375966, 0.42049746666666665, 0.4690773, 0.49109343333333333],
  oldest_block: 16607861,
  reward: [[1000000000, 1000000000, 1500000000], [1000000000, 1000000000, 2000000000], [1000000000, 1000000000, 1000000000], [780000000, 1000000000, 2000000000], [1000000000, 1000000000, 1500000000]]
}}

fee_history(block_count, opts)

@spec fee_history(pos_integer(), keyword()) ::
  {:ok, Onchain.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, Onchain.FeeHistory.t()} | {:error, term})

# 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, Onchain.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!(block_count, opts \\ [])

@spec fee_history!(pos_integer(), keyword()) :: Onchain.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 (Onchain.FeeHistory.t())

# 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: "Onchain.FeeHistory.t()",
    description: "Deserialized fee history struct"
  }
}

fill_transaction(trx, opts \\ [])

@spec fill_transaction(
  Onchain.Transaction.V1.t()
  | Onchain.Transaction.V2.t()
  | Onchain.Transaction.Call.t(),
  Keyword.t()
) :: {:ok, struct()} | {:error, term()}

RPC call to fill missing transaction fields via eth_fillTransaction.

The node populates nonce, gas, and fee fields and returns the completed unsigned transaction. Spec-conforming nodes return a FillTransactionResult carrying only tx; geth returns a {raw, tx} pair. Both shapes decode into a Onchain.Transaction struct.

Decoding prefers the tx object execution-apis requires over geth's additional raw field, because raw serializes an unsigned transaction in the signed wire format and so cannot be told apart from a signed one.

A typed envelope's unsigned form is nil signature fields; a legacy one keeps the chain id in v with r/s zero.

Legacy fills need a chain id, which Onchain.Transaction.V1 holds in v until a signature replaces it. geth omits chainId from an unsigned legacy result; pass chain_id: to supply it. Without it — from either side — the call errors rather than return a transaction that would sign to the wrong address, and so does a chain_id: that disagrees with the one the response names: Onchain.Signer takes the chain id from its caller rather than from the struct, so a disagreement signs a digest the encoded payload does not match.

Examples

iex> {:ok, trx} =
...>   Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, :kovan)
...>   |> Onchain.RPC.fill_transaction()
iex> trx.nonce
1

gas_price(opts \\ [])

@spec gas_price(Keyword.t()) :: {:ok, non_neg_integer()} | {:error, term()}

RPC call to call to get the current gas price.

Examples

iex> Onchain.RPC.gas_price()
{:ok, 1000000000}

get_balance(address, opts \\ [])

@spec get_balance(binary(), Keyword.t()) ::
  {:ok, non_neg_integer()} | {:error, term()}

RPC to get an account's eth balance.

Docs: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getbalance

Examples

iex> Onchain.RPC.get_balance(~h[0x0000000000000000000000000000000000000001])
{:ok, 0x55}

get_balance!(address, opts \\ [])

@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)

# 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(block, opts \\ [])

@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})

# 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!(block, opts \\ [])

@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)

# 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_hash(block_hash, opts \\ [])

@spec get_block_by_hash(binary(), Keyword.t()) ::
  {:ok, Onchain.Block.t() | nil} | {:error, term()}

RPC to get a block by its block hash.

Docs: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbyhash

Examples

iex> Onchain.RPC.get_block_by_hash(~h[0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae])
{:ok, %Onchain.Block{
  difficulty: 0x4ea3f27bc,
  extra_data: ~h[0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32],
  gas_limit: 0x1388,
  gas_used: 0x0,
  hash: ~h[0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae],
  logs_bloom: ~h[0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000],
  miner: ~h[0xbb7b8287f3f0a933474a79eae42cbca977791171],
  mix_hash: ~h[0x4fffe9ae21f1c9e15207b1f472d5bbdd68c9595d461666602f2be20daf5e7843],
  nonce: 0x689056015818adbe,
  number: 0x1b4,
  parent_hash: ~h[0xe99e022112df268087ea7eafaf4790497fd21dbeeb6bd7a1721df161a6657a54],
  receipts_root: ~h[0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421],
  sha3_uncles: ~h[0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347],
  size: 0x220,
  state_root: ~h[0xddc8b0234c2e0cad087c8b389aa7ef01f7d79b2570bccb77ce48648aa61c904d],
  timestamp: 0x55ba467c,
  total_difficulty: 0x78ed983323d,
  transactions: [],
  transactions_root: ~h[0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421],
  uncles: [],
  base_fee_per_gas: nil,
  withdrawals_root: nil,
  withdrawals: nil,
  parent_beacon_block_root: nil,
  blob_gas_used: nil,
  excess_blob_gas: nil
}}

Options

  • :include_transaction_details — when true, the node returns full transaction objects in transactions; when false (default), just hashes. Forwarded to eth_getBlockByHash as the second wire param (real nodes reject single-param calls with -32602 Invalid params). Note: Onchain.Block.deserialize/1 decodes full transaction objects when requested and retains hashes otherwise.

Plus any option accepted by send_rpc/3 (e.g. :ethereum_node, :timeout, :req_options).

get_block_by_number(block_number, opts \\ [])

@spec get_block_by_number(non_neg_integer() | String.t(), Keyword.t()) ::
  {:ok, Onchain.Block.t() | nil} | {:error, term()}

RPC to get a block by its block number.

Docs: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber

Examples

iex> Onchain.RPC.get_block_by_number(55)
{:ok, %Onchain.Block{
  difficulty: 0x4ea3f27bc,
  extra_data: ~h[0x476574682f4c5649562f76312e302e302f6c696e75782f676f312e342e32],
  gas_limit: 0x1388,
  gas_used: 0x0,
  hash: ~h[0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae],
  logs_bloom: ~h[0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000],
  miner: ~h[0xbb7b8287f3f0a933474a79eae42cbca977791171],
  mix_hash: ~h[0x4fffe9ae21f1c9e15207b1f472d5bbdd68c9595d461666602f2be20daf5e7843],
  nonce: 0x689056015818adbe,
  number: 0x1b4,
  parent_hash: ~h[0xe99e022112df268087ea7eafaf4790497fd21dbeeb6bd7a1721df161a6657a54],
  receipts_root: ~h[0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421],
  sha3_uncles: ~h[0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347],
  size: 0x220,
  state_root: ~h[0xddc8b0234c2e0cad087c8b389aa7ef01f7d79b2570bccb77ce48648aa61c904d],
  timestamp: 0x55ba467c,
  total_difficulty: 0x78ed983323d,
  transactions: [],
  transactions_root: ~h[0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421],
  uncles: [],
  base_fee_per_gas: nil,
  withdrawals_root: nil,
  withdrawals: nil,
  parent_beacon_block_root: nil,
  blob_gas_used: nil,
  excess_blob_gas: nil
}}

Options

  • :include_transaction_details — when true, the node returns full transaction objects in transactions; when false (default), just hashes. Forwarded to eth_getBlockByNumber as the second wire param. Note: Onchain.Block.deserialize/1 decodes full transaction objects when requested and retains hashes otherwise.

Plus any option accepted by send_rpc/3 (e.g. :ethereum_node, :timeout, :req_options).

get_block_by_number!(block_id, opts \\ [])

@spec get_block_by_number!(integer() | String.t(), keyword()) ::
  Onchain.Block.t() | 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 Onchain.Block struct (map | nil)

# 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 Onchain.Block struct"}
}

get_code(address, opts \\ [])

@spec get_code(binary(), Keyword.t()) :: {:ok, binary()} | {:error, term()}

RPC call to get code for a contract at an address.

Examples

iex> Onchain.RPC.get_code(<<1::160>>)
{:ok, <<0x11, 0x22, 0x33>>}

get_filter_logs(filter_id, opts \\ [])

@spec get_filter_logs(String.t(), Keyword.t()) ::
  {:ok, [Onchain.Filter.Log.t()]} | {:error, term()}

RPC call to fetch the full log backlog for a filter.

Examples

iex> {:ok, [log]} = Onchain.RPC.get_filter_logs("0xf11735")
iex> log.address
<<181, 165, 242, 38, 148, 53, 44, 21, 176, 3, 35, 132, 74, 213, 69, 171, 178, 177, 16, 40>>

get_nonce(account, opts \\ [])

@spec get_nonce(<<_::160>>, Keyword.t()) ::
  {:ok, non_neg_integer()} | {:error, term()}

RPC call to get account nonce.

Examples

iex> use Onchain.Hex
iex> Onchain.RPC.get_nonce(~h[0x407d73d8a49eeb85d32cf465507dd71d507100c1])
{:ok, 4}

get_transaction_count(address, opts \\ [])

@spec get_transaction_count(binary(), Keyword.t()) ::
  {:ok, non_neg_integer()} | {:error, term()}

RPC to get an account's transaction count (i.e. nonce)

Docs: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactioncount

Examples

iex> Onchain.RPC.get_transaction_count(~h[0x0000000000000000000000000000000000000001])
{:ok, 0x4}

get_transaction_count!(address, opts \\ [])

@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)

# 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(tx_hash, opts \\ [])

@spec get_transaction_receipt(String.t(), keyword()) ::
  {:ok, Onchain.Receipt.t() | 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

Onchain.Receipt struct, or nil if the transaction is pending/unknown ({:ok, map | nil} | {:error, term})

# 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: "Onchain.Receipt struct, or nil if the transaction is pending/unknown"
  }
}

get_transaction_receipt!(tx_hash, opts \\ [])

@spec get_transaction_receipt!(String.t(), keyword()) :: Onchain.Receipt.t() | 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

Onchain.Receipt struct or nil (map | nil)

# 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: "Onchain.Receipt struct or nil"}
}

get_trx_receipt(trx_id, opts \\ [])

@spec get_trx_receipt(binary() | String.t(), Keyword.t()) ::
  {:ok, Onchain.Receipt.t() | nil} | {:error, term()}

RPC call to get a transaction receipt. Note, this will return {:ok, %Onchain.Receipt{}} or {:ok, nil} if the receipt is not yet available.

Examples

iex> Onchain.RPC.get_trx_receipt(~h[0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5])
{:ok,
  %Onchain.Receipt{
    transaction_hash: ~h[0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5],
    transaction_index: 0x66,
    block_hash: ~h[0xa957d47df264a31badc3ae823e10ac1d444b098d9b73d204c40426e57f47e8c3],
    block_number: 0xeff35f,
    from: ~h[0x6221a9c005f6e47eb398fd867784cacfdcfff4e7],
    to: ~h[0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2],
    cumulative_gas_used: 0xa12515,
    effective_gas_price: 0x5a9c688d4,
    gas_used: 0xb4c8,
    contract_address: nil,
    logs: [
      %Onchain.Filter.Log{
        log_index: 1,
        block_number: 0x01b4,
        block_hash: ~h[0xaa8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcfdf829c5a142f1fccd7d],
        transaction_hash: ~h[0xaadf829c5a142f1fccd7d8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcf],
        transaction_index: 0,
        address: ~h[0x16c5785ac562ff41e2dcfdf829c5a142f1fccd7d],
        data: ~h[0x0000000000000000000000000000000000000000000000000000000000000000],
        topics: [
          ~h[0x59ebeb90bc63057b6515673c3ecf9438e5058bca0f92585014eced636878c9a5]
        ]
      }
    ],
    logs_bloom: ~h[0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001],
    type: 0x02,
    status: 0x01,
  }
}

iex> Onchain.RPC.get_trx_receipt("0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5")
{:ok,
  %Onchain.Receipt{
    transaction_hash: ~h[0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5],
    transaction_index: 0x66,
    block_hash: ~h[0xa957d47df264a31badc3ae823e10ac1d444b098d9b73d204c40426e57f47e8c3],
    block_number: 0xeff35f,
    from: ~h[0x6221a9c005f6e47eb398fd867784cacfdcfff4e7],
    to: ~h[0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2],
    cumulative_gas_used: 0xa12515,
    effective_gas_price: 0x5a9c688d4,
    gas_used: 0xb4c8,
    contract_address: nil,
    logs: [
      %Onchain.Filter.Log{
        log_index: 1,
        block_number: 0x01b4,
        block_hash: ~h[0xaa8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcfdf829c5a142f1fccd7d],
        transaction_hash: ~h[0xaadf829c5a142f1fccd7d8216c5785ac562ff41e2dcfdf5785ac562ff41e2dcf],
        transaction_index: 0,
        address: ~h[0x16c5785ac562ff41e2dcfdf829c5a142f1fccd7d],
        data: ~h[0x0000000000000000000000000000000000000000000000000000000000000000],
        topics: [
          ~h[0x59ebeb90bc63057b6515673c3ecf9438e5058bca0f92585014eced636878c9a5]
        ]
      }
    ],
    logs_bloom: ~h[0x00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001],
    type: 0x02,
    status: 0x01,
  }
}

iex> Onchain.RPC.get_trx_receipt("0xf9e69be4f1ae524854e14dc820c519d8f2b86e52c60e54448abf920d22fb6fe2")
{:ok, %Onchain.Receipt{
  transaction_hash: ~h[0xf9e69be4f1ae524854e14dc820c519d8f2b86e52c60e54448abf920d22fb6fe2],
  transaction_index: 0,
  block_hash: ~h[0x4bc3c26b1a599ced9876d9bf9a17c9bd58ec8b71a68e75335de7f2820e9336ca],
  block_number: 10493428,
  from: ~h[0xb03d1100c68e58aa1895f8c1f230c0851ff41851],
  to: ~h[0x9d8ec03e9ddb71f04da9db1e38837aaac1782a97],
  cumulative_gas_used: 222642,
  effective_gas_price: 1200000010,
  gas_used: 222642,
  contract_address: nil,
  logs: [
    %Onchain.Filter.Log{
      removed: false,
      log_index: 0,
      block_number: 10493428,
      block_hash: ~h[0x4bc3c26b1a599ced9876d9bf9a17c9bd58ec8b71a68e75335de7f2820e9336ca],
      transaction_hash: ~h[0xf9e69be4f1ae524854e14dc820c519d8f2b86e52c60e54448abf920d22fb6fe2],
      transaction_index: 0,
      address: ~h[0x9d8ec03e9ddb71f04da9db1e38837aaac1782a97],
      data: ~h[0x000000000000000000000000cb372382aa9a9e6f926714f4305afac4566f75380000000000000000000000000000000000000000000000000000000000000000],
      topics: [
        ~h[0x3ffe5de331422c5ec98e2d9ced07156f640bb51e235ef956e50263d4b28d3ae4],
        ~h[0x0000000000000000000000002326aba712500ae3114b664aeb51dba2c2fb416d],
        ~h[0x0000000000000000000000002326aba712500ae3114b664aeb51dba2c2fb416d]
      ]
    },
    %Onchain.Filter.Log{
      removed: false,
      log_index: 1,
      block_number: 10493428,
      block_hash: ~h[0x4bc3c26b1a599ced9876d9bf9a17c9bd58ec8b71a68e75335de7f2820e9336ca],
      transaction_hash: ~h[0xf9e69be4f1ae524854e14dc820c519d8f2b86e52c60e54448abf920d22fb6fe2],
      transaction_index: 0,
      address: ~h[0xcb372382aa9a9e6f926714f4305afac4566f7538],
      data: ~h[0x0000000000000000000000000000000000000000000000000000000000000000],
      topics: [
        ~h[0xe0d20d95fbbe7375f6edead77b5ce5c5b096e7dac85848c45c37a95eaf17fe62],
        ~h[0x0000000000000000000000009d8ec03e9ddb71f04da9db1e38837aaac1782a97],
        ~h[0x00000000000000000000000054f0a87eb5c8c8ba70243de1ac19e735b41b10a2],
        ~h[0x0000000000000000000000000000000000000000000000000000000000000000]
      ]
    },
    %Onchain.Filter.Log{
      removed: false,
      log_index: 2,
      block_number: 10493428,
      block_hash: ~h[0x4bc3c26b1a599ced9876d9bf9a17c9bd58ec8b71a68e75335de7f2820e9336ca],
      transaction_hash: ~h[0xf9e69be4f1ae524854e14dc820c519d8f2b86e52c60e54448abf920d22fb6fe2],
      transaction_index: 0,
      address: ~h[0xcb372382aa9a9e6f926714f4305afac4566f7538],
      data: <<>>,
      topics: [
        ~h[0x0000000000000000000000000000000000000000000000000000000000000055]
      ]
    }
  ],
  logs_bloom: ~h[0x00800000000000000000000400000000000000000000000000000000000000000000000000000000000000000000002000200040000000000000000200001000000000000000000000000000000000000000000000000000000000000010000000008000020000004000000200000800000000000000000000220000000000000000000000000800000000000400000000000000000000000000000000000000000000040000000000008000008000000000000000000000000000000004000000800000000000004000000000000000000000000000000004080000000020000000000000000080000000000000000000000000000000000000000000000000],
  type: 0,
  status: 1
}}

iex> Onchain.RPC.get_trx_receipt(<<1::256>>)
{:error, "failed to decode `eth_getTransactionReceipt` response: %FunctionClauseError{module: Onchain.Hex, function: :decode_hex_, arity: 1, kind: nil, args: nil, clauses: nil}"}

iex> Onchain.RPC.get_trx_receipt(<<1::256>>, verbose: true)
{:error, "failed to decode `eth_getTransactionReceipt` response: %FunctionClauseError{module: Onchain.Hex, function: :decode_hex_, arity: 1, kind: nil, args: nil, clauses: nil}"}

iex> Onchain.RPC.get_trx_receipt(<<2::256>>)
{:ok, nil}

max_priority_fee_per_gas(opts \\ [])

@spec max_priority_fee_per_gas(Keyword.t()) ::
  {:ok, non_neg_integer()} | {:error, term()}

RPC call to call to get the current max priority fee per gas.

Examples

iex> Onchain.RPC.max_priority_fee_per_gas()
{:ok, 1000000001}

net_listening(opts \\ [])

@spec net_listening(Keyword.t()) :: {:ok, boolean()} | {:error, term()}

Returns whether the node is listening for peer connections (net_listening).

net_listening is not in any tagged execution-apis release. It landed on main after v1.0.0-beta.7 in commit 7c58b32 (#843, 2026-08-24). This wrapper does not make the method standard.

Observed 2026-10-01 on the hosted lanes from the multi-endpoint seam: Alchemy (ETHEREUM_ALCHEMY_URL) and Infura (ETHEREUM_INFURA_URL) both answered HTTP 200 with JSON true. Neither lane refused the method.

A refusing endpoint's JSON-RPC error is classified by send_rpc/3, not by a per-method probe. -32601, and -32600 whose message says the method is unsupported or not available, become {:error, {:method_not_found, map}}. A plan-disabled namespace becomes {:error, {:namespace_unavailable, map}}. -32001 whose message says the node is unable to complete the request becomes {:error, {:unavailable, map}}.

Examples

iex> Onchain.RPC.net_listening()
{:ok, true}

net_peer_count(opts \\ [])

@spec net_peer_count(Keyword.t()) :: {:ok, non_neg_integer()} | {:error, term()}

Returns the node's connected peer count (net_peerCount).

net_peerCount is not in any tagged execution-apis release. It landed on main after v1.0.0-beta.7 in commit 7c58b32 (#843, 2026-08-24). This wrapper does not make the method standard.

Observed 2026-10-01 on Alchemy mainnet (ETHEREUM_ALCHEMY_URL), HTTP 400:

{"code":-32600,"message":"net_peerCount is not available on the ETH_MAINNET. For more information see our docs: https://docs.alchemy.com/alchemy/documentation/apis/ethereum"}

That refusal surfaces as {:error, {:method_not_found, %{code: -32600, message: "net_peerCount is not available on the ETH_MAINNET. For more information see our docs: https://docs.alchemy.com/alchemy/documentation/apis/ethereum"}}}. The same classification is send_rpc/3's shared refusal tag. There is no separate capability probe. Infura answered a peer-count quantity on the same day; a hosted success is not evidence the method is standard.

Examples

iex> Onchain.RPC.net_peer_count()
{:ok, 2}

new_block_filter(opts \\ [])

@spec new_block_filter(Keyword.t()) :: {:ok, String.t()} | {:error, term()}

RPC call to create a new block filter.

Examples

iex> Onchain.RPC.new_block_filter()
{:ok, "0xb10cf11e"}

new_pending_transaction_filter(opts \\ [])

@spec new_pending_transaction_filter(Keyword.t()) ::
  {:ok, String.t()} | {:error, term()}

RPC call to create a new pending-transaction filter.

Examples

iex> Onchain.RPC.new_pending_transaction_filter()
{:ok, "0xpend1ng"}

prepare_trx(contract, call_data, opts \\ [])

@spec prepare_trx(<<_::160>>, binary() | {String.t(), [term()]}, Keyword.t()) ::
  {:ok, Onchain.Transaction.V1.t() | Onchain.Transaction.V2.t()}
  | {:error, term()}

Helper function to work with other Cartouche modules to get a nonce, sign a transction, and prepare it to be submitted on-chain.

If you need higher-level functionality, like manual nonce tracking, you may want to use the more granular function calls.

Options:

  • gas_price - Set the gas price for a v1 (non-Eip1559) transaction, if nil, comes from eth_gasPrice (default nil) [note: only compatible with V1 transaction]
  • base_fee - Set the base price for the transaction, if nil, will use base gas price from eth_feeHistory (default nil) [note: only compatible with V2 transactions]
  • base_fee_buffer - Buffer for the gas price or base fee when estimating gas price. Ingored if gas_price (for v1) or base_fee (for v2) is specified directly (default: 1.2 = 120%)
  • priority_fee - Additional gas to send as a priority fee. (default: {0, :gwei}) [note: only compatible with V2 transactions]
  • gas_limit - Set the gas limit for the transaction (default: calls eth_estimateGas)
  • gas_buffer - Buffer if estimating gas limit (default: 1.5 = 150%)
  • value - Value to provide with transaction in wei (default: 0)
  • nonce - Nonce to send with transaction. (default: lookup via eth_transactionCount)
  • verify - Verify the function is likely to succeed (default: true)
  • trx_type - :v1 for V1 (pre-EIP-1559 transactions), :v2 for V2 (EIP-1559) transactions, and nil for auto-detect.

Note: if we don't verify, then estimateGas will likely fail if the transaction were to fail.

    To prevent this, `gas_limit` should always be supplied when `verify` is set to false.

Examples

iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, trx} = Onchain.RPC.prepare_trx(<<1::160>>, {"baz(uint,address)", [50, :binary.decode_unsigned(<<1::160>>)]}, gas_price: {50, :gwei}, nonce: 10, value: 0, signer: signer_proc)
iex> %{trx|v: nil, r: nil, s: nil}
%Onchain.Transaction.V1{
  nonce: 10,
  gas_price: 50000000000,
  gas_limit: 20,
  to: <<1::160>>,
  value: 0,
  data: <<162, 145, 173, 214, 0::248, 50, 0::248, 1>>
}

iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, trx} = Onchain.RPC.prepare_trx(<<1::160>>, {"baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned]}, gas_price: {50, :gwei}, gas_limit: 100_000, value: 0, signer: signer_proc)
iex> %{trx|v: nil, r: nil, s: nil}
%Onchain.Transaction.V1{
  nonce: 4,
  gas_price: 50000000000,
  gas_limit: 100000,
  to: <<1::160>>,
  value: 0,
  data: <<162, 145, 173, 214, 0::248, 50, 0::248, 1>>
}

iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, trx} = Onchain.RPC.prepare_trx(<<1::160>>, {"baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned]}, gas_price: {50, :gwei}, gas_limit: 100_000, value: 0, nonce: 10, signer: signer_proc)
iex> %{trx|v: nil, r: nil, s: nil}
%Onchain.Transaction.V1{
  nonce: 10,
  gas_price: 50000000000,
  gas_limit: 100000,
  to: <<1::160>>,
  value: 0,
  data: <<162, 145, 173, 214, 0::248, 50, 0::248, 1>>
}

iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> Onchain.RPC.prepare_trx(<<10::160>>, {"baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned]}, gas_price: {50, :gwei}, gas_limit: 100_000, value: 0, nonce: 10, signer: signer_proc)
{:error, %{code: 3, message: "execution reverted", revert: <<61, 115, 139, 46>>}}

iex> # Set gas price directly
iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, trx} = Onchain.RPC.prepare_trx(<<10::160>>, {"baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned]}, gas_price: {50, :gwei}, gas_limit: 100_000, value: 0, nonce: 10, verify: false, signer: signer_proc)
iex> %{trx|v: nil, r: nil, s: nil}
%Onchain.Transaction.V1{
  nonce: 10,
  gas_price: 50000000000,
  gas_limit: 100000,
  to: <<10::160>>,
  value: 0,
  data: <<162, 145, 173, 214, 0::248, 50, 0::248, 1>>
}

iex> # Default gas price v1
iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, trx} = Onchain.RPC.prepare_trx(<<10::160>>, {"baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned]}, gas_limit: 100_000, trx_type: :v1, value: 0, nonce: 10, verify: false, signer: signer_proc)
iex> %{trx|v: nil, r: nil, s: nil}
%Onchain.Transaction.V1{
  nonce: 10,
  gas_price: 1200000000,
  gas_limit: 100000,
  to: <<10::160>>,
  value: 0,
  data: <<162, 145, 173, 214, 0::248, 50, 0::248, 1>>
}

iex> # Default gas price v2
iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, trx} = Onchain.RPC.prepare_trx(<<10::160>>, {"baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned]}, gas_limit: 100_000, trx_type: :v2, value: 0, nonce: 10, verify: false, signer: signer_proc)
iex> %{trx|signature_y_parity: nil, signature_r: nil, signature_s: nil}
%Onchain.Transaction.V2{
  chain_id: 5,
  nonce: 10,
  gas_limit: 100000,
  destination: <<10::160>>,
  amount: 0,
  max_fee_per_gas: 25679608965,
  max_priority_fee_per_gas: 1000000001,
  data: <<162, 145, 173, 214, 0::248, 50, 0::248, 1>>,
  access_list: []
}

iex> # Default gas price (trx_type: nil)
iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, trx} = Onchain.RPC.prepare_trx(<<10::160>>, {"baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned]}, gas_limit: 100_000, value: 0, nonce: 10, verify: false, signer: signer_proc)
iex> %{trx|signature_y_parity: nil, signature_r: nil, signature_s: nil}
%Onchain.Transaction.V2{
  chain_id: 5,
  nonce: 10,
  gas_limit: 100000,
  destination: <<10::160>>,
  amount: 0,
  max_fee_per_gas: 25679608965,
  max_priority_fee_per_gas: 1000000001,
  data: <<162, 145, 173, 214, 0::248, 50, 0::248, 1>>,
  access_list: []
}

iex> # Set priority fee (v2)
iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, trx} = Onchain.RPC.prepare_trx(<<10::160>>, {"baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned]}, priority_fee: {3, :gwei}, gas_limit: 100_000, value: 0, nonce: 10, verify: false, signer: signer_proc)
iex> %{trx|signature_y_parity: nil, signature_r: nil, signature_s: nil}
%Onchain.Transaction.V2{
  chain_id: 5,
  nonce: 10,
  gas_limit: 100000,
  destination: <<10::160>>,
  amount: 0,
  max_fee_per_gas: 27679608964,
  max_priority_fee_per_gas: 3000000000,
  data: <<162, 145, 173, 214, 0::248, 50, 0::248, 1>>,
  access_list: []
}

iex> # Set base fee and priority fee (v2)
iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, trx} = Onchain.RPC.prepare_trx(<<10::160>>, {"baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned]}, base_fee: {1, :gwei}, priority_fee: {3, :gwei}, gas_limit: 100_000, value: 0, nonce: 10, verify: false, signer: signer_proc)
iex> %{trx|signature_y_parity: nil, signature_r: nil, signature_s: nil}
%Onchain.Transaction.V2{
  chain_id: 5,
  nonce: 10,
  gas_limit: 100000,
  destination: <<10::160>>,
  amount: 0,
  max_fee_per_gas: 4000000000,
  max_priority_fee_per_gas: 3000000000,
  data: <<162, 145, 173, 214, 0::248, 50, 0::248, 1>>,
  access_list: []
}

iex> # Sets chain id
iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, trx} = Onchain.RPC.prepare_trx(<<10::160>>, {"baz(uint,address)", [50, <<1::160>> |> :binary.decode_unsigned]}, base_fee: {1, :gwei}, priority_fee: {3, :gwei}, gas_limit: 100_000, value: 0, nonce: 10, verify: false, signer: signer_proc, chain_id: 99)
iex> %{trx|signature_y_parity: nil, signature_r: nil, signature_s: nil}
%Onchain.Transaction.V2{
  chain_id: 99,
  nonce: 10,
  gas_limit: 100000,
  destination: <<10::160>>,
  amount: 0,
  max_fee_per_gas: 4000000000,
  max_priority_fee_per_gas: 3000000000,
  data: <<162, 145, 173, 214, 0::248, 50, 0::248, 1>>,
  access_list: []
}

send_batch(requests, opts \\ [])

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

Sends a JSON-RPC array batch and returns raw results in request order. Uses the same options as send_rpc/3.

send_rpc(method, params, opts \\ [])

@spec send_rpc(binary(), [term()], Keyword.t()) ::
  {:ok, term()} | {:error, send_rpc_error()} | :invalid_hex

Simple RPC client for a JSON-RPC Ethereum node.

Examples

iex> Onchain.RPC.send_rpc("net_version", [])
{:ok, "3"}

iex> use Onchain.Hex
iex> Onchain.RPC.send_rpc("get_balance", ["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"], ethereum_node: "http://example.com")
{:ok, "0x0234c8a3397aab58"}

iex> match?({:error, {:invalid_params, %Jason.EncodeError{}}}, Onchain.RPC.send_rpc(<<255>>, []))
true

iex> match?({:error, {:invalid_params, %Protocol.UndefinedError{}}}, Onchain.RPC.send_rpc("net_version", [self()]))
true

Options

Common options (other RPC wrappers forward opts here):

  • :rpc_url / :ethereum_node — node URL (:rpc_url takes precedence); falls back to Application.get_env(:cartouche, :ethereum_node)
  • :retry — opt-in [max_retries: 2, backoff_ms: 100]; only transport failures retry, before typed decoding. JSON-RPC application errors are final.
  • :timeout — Req receive_timeout in ms
  • :headers — extra request headers
  • :verbose — when true, decode failures log at :error instead of :info
  • :req_options — a keyword list merged into the Req.request/1 options (highest precedence), exposing Req's whole pipeline (retries, redirects, a custom finch: pool, telemetry, proxies, plugs). A global default can be set with config :cartouche, :req_options, [...]. Tests stub the transport by passing req_options: [plug: ...] (or configuring config :cartouche, Onchain.RPC, plug: ...).

send_transaction(trx, opts \\ [])

@spec send_transaction(
  Onchain.Transaction.V1.t()
  | Onchain.Transaction.V2.t()
  | Onchain.Transaction.Call.t(),
  Keyword.t()
) :: {:ok, binary()} | {:error, term()}

RPC call to eth_sendTransaction.

Examples

iex> {:ok, hash} =
...>   Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>)
...>   |> Onchain.RPC.send_transaction()
iex> Base.encode16(hash, case: :lower)
"abababababababababababababababababababababababababababababababab"

send_trx(trx, opts \\ [])

@spec send_trx(
  Onchain.Transaction.V1.t() | Onchain.Transaction.V2.t() | String.t(),
  Keyword.t()
) ::
  {:ok, binary()} | {:error, term()}

RPC call to send a raw transaction.

Examples

iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, signed_trx} = Onchain.Transaction.build_signed_trx(<<1::160>>, 5, {"baz(uint,address)", [50, :binary.decode_unsigned(<<1::160>>)]}, {50, :gwei}, 100_000, 0, chain_id: :goerli, signer: signer_proc)
iex> {:ok, trx_id} = Onchain.RPC.send_trx(signed_trx)
iex> <<nonce::integer-size(8), gas_price::integer-size(64), gas_limit::integer-size(24), to::binary>> = trx_id
iex> {nonce, gas_price, gas_limit, to}
{5, 50000000000, 100000, <<1::160>>}

iex> signer_proc = Onchain.Test.Signer.start_signer()
iex> {:ok, signed_trx} = Onchain.Transaction.build_signed_trx_v2(<<1::160>>, 5, {"baz(uint,address)", [50, :binary.decode_unsigned(<<1::160>>)]}, {50, :gwei}, {10, :gwei}, 100_000, 0, [], chain_id: :goerli, signer: signer_proc)
iex> {:ok, trx_id} = Onchain.RPC.send_trx(signed_trx)
iex> <<nonce::integer-size(8), max_priority_fee_per_gas::integer-size(64), max_fee_per_gas::integer-size(64), gas_limit::integer-size(24), to::binary>> = trx_id
iex> {nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to}
{5, 50000000000, 10000000000, 100000, <<1::160>>}

sign(account, message, opts \\ [])

@spec sign(<<_::160>>, binary(), Keyword.t()) :: {:ok, binary()} | {:error, term()}

RPC call to eth_sign.

The node signs message with a managed account and returns an EIP-191 signature over it — message is the data to be signed, not a digest the node signs verbatim. Most nodes disable this method; when they do, the node's error is returned unchanged.

Examples

iex> {:ok, signature} = Onchain.RPC.sign(<<1::160>>, "hello")
iex> byte_size(signature)
65

sign_transaction(trx, opts \\ [])

@spec sign_transaction(
  Onchain.Transaction.V1.t()
  | Onchain.Transaction.V2.t()
  | Onchain.Transaction.Call.t(),
  Keyword.t()
) :: {:ok, struct()} | {:error, term()}

RPC call to eth_signTransaction.

The node signs with its keystore and returns a raw transaction, which is decoded through Onchain.Transaction.decode/1.

Examples

iex> {:ok, trx} =
...>   Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, :kovan)
...>   |> Onchain.RPC.sign_transaction()
iex> trx.nonce
1

trace_call(trx, opts \\ [])

RPC to trace a transaction call speculatively.

Examples

iex> Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>)
...> |> Onchain.RPC.trace_call()
{:ok,
  %Onchain.TraceCall{
    output: "",
    state_diff: nil,
    trace: [
      %Onchain.RPC.Trace{
        action: %Onchain.RPC.Trace.Action{
          call_type: "call",
          init: nil,
          from: <<0::160>>,
          gas: 499_978_072,
          input: ~h[0xd1692f56000000000000000000000000142da9114e5a98e015aa95afca0585e84832a612000000000000000000000000142da9114e5a98e015aa95afca0585e84832a6120000000000000000000000000000000000000000000000000000000000000000],
          to: ~h[0x13172EE393713FBA9925A9A752341EBD31E8D9A7],
          value: 0
        },
        block_hash: nil,
        block_number: nil,
        gas_used: 492_166_471,
        error: "Reverted",
        output: "",
        result_code: nil,
        result_address: nil,
        subtraces: 1,
        trace_address: [],
        transaction_hash: nil,
        transaction_position: nil,
        type: "call"
      },
      %Onchain.RPC.Trace{
        action: %Onchain.RPC.Trace.Action{
          call_type: nil,
          init: ~h[0x60e03461009157601f6101ec38819003918201601f19168301916001600160401b038311848410176100965780849260609460405283398101031261009157610047816100ac565b906100606040610059602084016100ac565b92016100ac565b9060805260a05260c05260405161012b90816100c18239608051816088015260a051816045015260c0518160c60152f35b600080fd5b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036100915756fe608060405260043610156013575b3660ba57005b6000803560e01c8063238ac9331460775763c34c08e51460325750600d565b34607457806003193601126074576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b80fd5b5034607457806003193601126074577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166080908152602090f35b600036818037808036817f00000000000000000000000000000000000000000000000000000000000000005af4903d918282803e60f357fd5bf3fea264697066735822122032b5603d6937ceb7a252e16379744d8545670ff4978c8d76c985d051dfcfe46c64736f6c6343000817003300000000000000000000000049e5d261e95f6a02505078bb339fecb210a0b634000000000000000000000000142da9114e5a98e015aa95afca0585e84832a612000000000000000000000000142da9114e5a98e015aa95afca0585e84832a612],
          from: ~h[0x13172EE393713FBA9925A9A752341EBD31E8D9A7],
          gas: 492_133_529,
          input: nil,
          to: nil,
          value: 0
        },
        block_hash: nil,
        block_number: nil,
        gas_used: nil,
        error: "contract address collision",
        output: nil,
        result_code: nil,
        result_address: nil,
        subtraces: 0,
        trace_address: [0],
        transaction_hash: nil,
        transaction_position: nil,
        type: "create"
      }
    ],
    vm_trace: nil
  }
}

iex> Onchain.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], :goerli)
...> |> Onchain.RPC.trace_call()
{:ok,
  %Onchain.TraceCall{
    output: "",
    state_diff: nil,
    trace: [
      %Onchain.RPC.Trace{
        action: %Onchain.RPC.Trace.Action{
          call_type: "call",
          init: nil,
          from: <<0::160>>,
          gas: 499_978_072,
          input: ~h[0xd1692f56000000000000000000000000142da9114e5a98e015aa95afca0585e84832a612000000000000000000000000142da9114e5a98e015aa95afca0585e84832a6120000000000000000000000000000000000000000000000000000000000000000],
          to: ~h[0x13172EE393713FBA9925A9A752341EBD31E8D9A7],
          value: 0
        },
        block_hash: nil,
        block_number: nil,
        gas_used: 492_166_471,
        error: "Reverted",
        output: "",
        result_code: nil,
        result_address: nil,
        subtraces: 1,
        trace_address: [],
        transaction_hash: nil,
        transaction_position: nil,
        type: "call"
      },
      %Onchain.RPC.Trace{
        action: %Onchain.RPC.Trace.Action{
          call_type: nil,
          init: ~h[0x60e03461009157601f6101ec38819003918201601f19168301916001600160401b038311848410176100965780849260609460405283398101031261009157610047816100ac565b906100606040610059602084016100ac565b92016100ac565b9060805260a05260c05260405161012b90816100c18239608051816088015260a051816045015260c0518160c60152f35b600080fd5b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036100915756fe608060405260043610156013575b3660ba57005b6000803560e01c8063238ac9331460775763c34c08e51460325750600d565b34607457806003193601126074576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b80fd5b5034607457806003193601126074577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166080908152602090f35b600036818037808036817f00000000000000000000000000000000000000000000000000000000000000005af4903d918282803e60f357fd5bf3fea264697066735822122032b5603d6937ceb7a252e16379744d8545670ff4978c8d76c985d051dfcfe46c64736f6c6343000817003300000000000000000000000049e5d261e95f6a02505078bb339fecb210a0b634000000000000000000000000142da9114e5a98e015aa95afca0585e84832a612000000000000000000000000142da9114e5a98e015aa95afca0585e84832a612],
          from: ~h[0x13172EE393713FBA9925A9A752341EBD31E8D9A7],
          gas: 492_133_529,
          input: nil,
          to: nil,
          value: 0
        },
        block_hash: nil,
        block_number: nil,
        gas_used: nil,
        error: "contract address collision",
        output: nil,
        result_code: nil,
        result_address: nil,
        subtraces: 0,
        trace_address: [0],
        transaction_hash: nil,
        transaction_position: nil,
        type: "create"
      }
    ],
    vm_trace: nil
  }
}

trace_call_many(trxs, opts \\ [])

RPC to trace many transaction calls speculatively.

Examples

iex> t1 = Onchain.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>)
iex> t2 = Onchain.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], :goerli)
iex> Onchain.RPC.trace_call_many([t1, t2])
{:ok, [
  %Onchain.TraceCall{
    output: "",
    state_diff: nil,
    trace: [
      %Onchain.RPC.Trace{
        action: %Onchain.RPC.Trace.Action{
          call_type: "call",
          init: nil,
          from: <<0::160>>,
          gas: 499_978_072,
          input: ~h[0xd1692f56000000000000000000000000142da9114e5a98e015aa95afca0585e84832a612000000000000000000000000142da9114e5a98e015aa95afca0585e84832a6120000000000000000000000000000000000000000000000000000000000000000],
          to: ~h[0x13172EE393713FBA9925A9A752341EBD31E8D9A7],
          value: 0
        },
        block_hash: nil,
        block_number: nil,
        gas_used: 492_166_471,
        error: "Reverted",
        output: "",
        result_code: nil,
        result_address: nil,
        subtraces: 1,
        trace_address: [],
        transaction_hash: nil,
        transaction_position: nil,
        type: "call"
      },
      %Onchain.RPC.Trace{
        action: %Onchain.RPC.Trace.Action{
          call_type: nil,
          init: ~h[0x60e03461009157601f6101ec38819003918201601f19168301916001600160401b038311848410176100965780849260609460405283398101031261009157610047816100ac565b906100606040610059602084016100ac565b92016100ac565b9060805260a05260c05260405161012b90816100c18239608051816088015260a051816045015260c0518160c60152f35b600080fd5b634e487b7160e01b600052604160045260246000fd5b51906001600160a01b03821682036100915756fe608060405260043610156013575b3660ba57005b6000803560e01c8063238ac9331460775763c34c08e51460325750600d565b34607457806003193601126074576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b80fd5b5034607457806003193601126074577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166080908152602090f35b600036818037808036817f00000000000000000000000000000000000000000000000000000000000000005af4903d918282803e60f357fd5bf3fea264697066735822122032b5603d6937ceb7a252e16379744d8545670ff4978c8d76c985d051dfcfe46c64736f6c6343000817003300000000000000000000000049e5d261e95f6a02505078bb339fecb210a0b634000000000000000000000000142da9114e5a98e015aa95afca0585e84832a612000000000000000000000000142da9114e5a98e015aa95afca0585e84832a612],
          from: ~h[0x13172EE393713FBA9925A9A752341EBD31E8D9A7],
          gas: 492_133_529,
          input: nil,
          to: nil,
          value: 0
        },
        block_hash: nil,
        block_number: nil,
        gas_used: nil,
        error: "contract address collision",
        output: nil,
        result_code: nil,
        result_address: nil,
        subtraces: 0,
        trace_address: [0],
        transaction_hash: nil,
        transaction_position: nil,
        type: "create"
      }
    ],
    vm_trace: nil
  },
  %Onchain.TraceCall{
    output: ~h[0x00000000000000000000000079EDBC4F3A6AA2266CD469CC544501743BE8B078],
    state_diff: nil,
    trace: [
      %Onchain.RPC.Trace{
        action: %Onchain.RPC.Trace.Action{
          call_type: "call",
          init: nil,
          from: <<0::160>>,
          gas: 499_945_916,
          input: ~h[0xd6d38d3f0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000081a60808060405234610016576107fe908161001c8239f35b600080fdfe6040608081526004908136101561001557600080fd5b600091823560e01c80630c0a769b146102eb57806350a4548914610255578063c3da3590146100fc5763f1afb11f1461004d57600080fd5b8291346100f85760803660031901126100f857610068610389565b61007061039f565b6100786103b5565b6001600160a01b03908116929091606435918390610097848288610482565b1693843b156100f457879460649386928851998a978896634232cd6360e01b88521690860152602485015260448401525af19081156100eb57506100d85750f35b6100e1906103fc565b6100e85780f35b80fd5b513d84823e3d90fd5b8780fd5b5050fd5b503461025157606036600319011261025157610116610389565b67ffffffffffffffff929060243584811161024d5761013890369085016103cb565b9190946044359081116102495761015290369086016103cb565b9590928681036102395791958793926001600160a01b0380891693909290865b83811061017d578780f35b6101a88561019461018f848887610448565b61046e565b168c6101a184878c610448565b3591610482565b6101b661018f828685610448565b6101c182858a610448565b3590873b15610235578a51631e573fb760e31b81526001600160a01b03909116818d019081526020810192909252908990829081906040010381838b5af1801561022b57908991610217575b5050600101610172565b610220906103fc565b6100f457873861020d565b8a513d8b823e3d90fd5b8980fd5b845163b4fa3fb360e01b81528690fd5b8680fd5b8580fd5b8280fd5b50346102515760a03660031901126102515761026f610389565b9161027861039f565b6102806103b5565b906001600160a01b039060643582811691908290036102e6578288971693843b156100f457879460849385879389519a8b988997639032317760e01b895216908701521660248501526044840152833560648401525af19081156100eb57506100d85750f35b600080fd5b5090346102515760603660031901126102515782610307610389565b61030f61039f565b604435916001600160a01b03906103298482858516610482565b1690813b15610385578451631e573fb760e31b81526001600160a01b039091169581019586526020860192909252909384919082908490829060400103925af19081156100eb5750610379575080f35b610382906103fc565b80f35b8380fd5b600435906001600160a01b03821682036102e657565b602435906001600160a01b03821682036102e657565b604435906001600160a01b03821682036102e657565b9181601f840112156102e65782359167ffffffffffffffff83116102e6576020808501948460051b0101116102e657565b67ffffffffffffffff811161041057604052565b634e487b7160e01b600052604160045260246000fd5b90601f8019910116810190811067ffffffffffffffff82111761041057604052565b91908110156104585760051b0190565b634e487b7160e01b600052603260045260246000fd5b356001600160a01b03811681036102e65790565b60405163095ea7b360e01b602082018181526001600160a01b0385166024840152604480840196909652948252949390926104be606485610426565b83516000926001600160a01b039291858416918591829182855af1906104e26105a4565b82610572575b5081610567575b50156104ff575b50505050509050565b60405196602088015216602486015280604486015260448552608085019085821067ffffffffffffffff8311176105535750610548939461054391604052826105fc565b6105fc565b8038808080806104f6565b634e487b7160e01b81526041600452602490fd5b90503b1515386104ef565b8051919250811591821561058a575b505090386104e8565b61059d92506020809183010191016105e4565b3880610581565b3d156105df573d9067ffffffffffffffff821161041057604051916105d3601f8201601f191660200184610426565b82523d6000602084013e565b606090565b908160209103126102e6575180151581036102e65790565b60408051908101916001600160a01b031667ffffffffffffffff8311828410176104105761066c926040526000806020958685527f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c656487860152868151910182855af16106666105a4565b916106f4565b8051908282159283156106dc575b505050156106855750565b6084906040519062461bcd60e51b82526004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152fd5b6106ec93508201810191016105e4565b38828161067a565b919290156107565750815115610708575090565b3b156107115790565b60405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606490fd5b8251909150156107695750805190602001fd5b6040519062461bcd60e51b82528160208060048301528251908160248401526000935b8285106107af575050604492506000838284010152601f80199101168101030190fd5b848101820151868601604401529381019385935061078c56fea264697066735822122065151e6cccce6828ff0901f46ab142cb8aa214fc37379817e3635a556dd638a564736f6c63430008170033000000000000],
          to: ~h[0x2926631647877E9A84BB7E3A0821D643BF8D63C0],
          value: 0
        },
        block_hash: nil,
        block_number: nil,
        gas_used: 4298,
        error: nil,
        output: ~h[0x00000000000000000000000079EDBC4F3A6AA2266CD469CC544501743BE8B078],
        result_code: nil,
        result_address: nil,
        subtraces: 0,
        trace_address: [],
        transaction_hash: nil,
        transaction_position: nil,
        type: "call"
      }
    ],
    vm_trace: nil
  },
  %Onchain.TraceCall{
    output: <<130, 180, 41, 0>>,
    state_diff: nil,
    trace: [
      %Onchain.RPC.Trace{
        action: %Onchain.RPC.Trace.Action{
          call_type: "call",
          init: nil,
          from: <<0::160>>,
          gas: 499_977_072,
          input: ~h[0xdd560874000000000000000000000000000000000000000000000000000000000000000400000000000000000000000079edbc4f3a6aa2266cd469cc544501743be8b078000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000640c0a769b000000000000000000000000aec1f48e02cfb822be958b68c7957156eb3f0b6e0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000000000000000000000000000000000000000f429000000000000000000000000000000000000000000000000000000000],
          to: ~h[0x6E995746B61C48C5BDF58FC788B1AEA08DFB7E43],
          value: 0
        },
        block_hash: nil,
        block_number: nil,
        gas_used: 4202,
        error: "Reverted",
        output: ~h[0x82B42900],
        result_code: nil,
        result_address: nil,
        subtraces: 1,
        trace_address: [],
        transaction_hash: nil,
        transaction_position: nil,
        type: "call"
      },
      %Onchain.RPC.Trace{
        action: %Onchain.RPC.Trace.Action{
          call_type: "delegatecall",
          init: nil,
          from: ~h[0x6E995746B61C48C5BDF58FC788B1AEA08DFB7E43],
          gas: 492_162_171,
          input: ~h[0xdd560874000000000000000000000000000000000000000000000000000000000000000400000000000000000000000079edbc4f3a6aa2266cd469cc544501743be8b078000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000640c0a769b000000000000000000000000aec1f48e02cfb822be958b68c7957156eb3f0b6e0000000000000000000000001c7d4b196cb0c7b01d743fbc6116a902379c723800000000000000000000000000000000000000000000000000000000000f429000000000000000000000000000000000000000000000000000000000],
          to: ~h[0x49E5D261E95F6A02505078BB339FECB210A0B634],
          value: 0
        },
        block_hash: nil,
        block_number: nil,
        gas_used: 1362,
        error: "Reverted",
        output: <<130, 180, 41, 0>>,
        result_code: nil,
        result_address: nil,
        subtraces: 1,
        trace_address: [0],
        transaction_hash: nil,
        transaction_position: nil,
        type: "call"
      },
      %Onchain.RPC.Trace{
        action: %Onchain.RPC.Trace.Action{
          call_type: "staticcall",
          init: nil,
          from: ~h[0x6E995746B61C48C5BDF58FC788B1AEA08DFB7E43],
          gas: 484_471_386,
          input: ~h[0xC34C08E5],
          to: ~h[0x6E995746B61C48C5BDF58FC788B1AEA08DFB7E43],
          value: 0
        },
        block_hash: nil,
        block_number: nil,
        gas_used: 190,
        error: nil,
        output: ~h[0x000000000000000000000000142DA9114E5A98E015AA95AFCA0585E84832A612],
        result_code: nil,
        result_address: nil,
        subtraces: 0,
        trace_address: [0, 0],
        transaction_hash: nil,
        transaction_position: nil,
        type: "call"
      }
    ],
    vm_trace: nil
  }
]}

trace_trx(trx_id, opts \\ [])

@spec trace_trx(binary() | String.t(), Keyword.t()) ::
  {:ok, [Onchain.RPC.Trace.t()]} | {:error, term()}

RPC call to get a transaction receipt

Examples

iex> Onchain.RPC.trace_trx("0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5")
{:ok,
  [
  %Onchain.RPC.Trace{
    action: %Onchain.RPC.Trace.Action{
      call_type: "call",
      from: ~h[0x83806d539d4ea1c140489a06660319c9a303f874],
      gas: 0x01a1f8,
      input: <<>>,
      to: ~h[0x1c39ba39e4735cb65978d4db400ddd70a72dc750],
      value: 0x7a16c911b4d00000,
    },
    block_hash: ~h[0x7eb25504e4c202cf3d62fd585d3e238f592c780cca82dacb2ed3cb5b38883add],
    block_number: 3068185,
    gas_used: 0x2982,
    output: <<>>,
    subtraces: 2,
    trace_address: [~h[0x1c39ba39e4735cb65978d4db400ddd70a72dc750]],
    transaction_hash: ~h[0x17104ac9d3312d8c136b7f44d4b8b47852618065ebfa534bd2d3b5ef218ca1f3],
    transaction_position: 2,
    type: "call"
  },
  %Onchain.RPC.Trace{
    action: %Onchain.RPC.Trace.Action{
      call_type: "call",
      from: ~h[0x83806d539d4ea1c140489a06660319c9a303f874],
      gas: 0x01a1f8,
      input: <<>>,
      to: ~h[0x1c39ba39e4735cb65978d4db400ddd70a72dc750],
      value: 0x7a16c911b4d00000,
    },
    block_hash: ~h[0x7eb25504e4c202cf3d62fd585d3e238f592c780cca82dacb2ed3cb5b38883add],
    block_number: 3068186,
    gas_used: 0x2982,
    output: <<>>,
    subtraces: 2,
    trace_address: [~h[0x1c39ba39e4735cb65978d4db400ddd70a72dc750]],
    transaction_hash: ~h[0x17104ac9d3312d8c136b7f44d4b8b47852618065ebfa534bd2d3b5ef218ca1f3],
    transaction_position: 2,
    type: "call"
  }
]}

web3_client_version(opts \\ [])

@spec web3_client_version(Keyword.t()) :: {:ok, String.t()} | {:error, term()}

Returns the node client's version string (web3_clientVersion).

web3_clientVersion is not in execution-apis at any tag or on main. EIP-1474 is the only definition.

Observed 2026-10-01, HTTP 200, no JSON-RPC refusal. Alchemy (ETHEREUM_ALCHEMY_URL) returned "reth/v2.5.2-5a6940e/x86_64-unknown-linux-gnu". Infura (ETHEREUM_INFURA_URL) returned "Geth/v1.17.6-stable-3d84c6b2/linux-arm64/go1.27.1".

A refusing endpoint's JSON-RPC error is classified by send_rpc/3, not by a per-method probe: {:error, {:method_not_found, map}}, {:error, {:namespace_unavailable, map}}, or {:error, {:unavailable, map}}.

Examples

iex> Onchain.RPC.web3_client_version()
{:ok, "TestClient/v1"}