Cartouche.RPC (Cartouche v0.8.0)

Copy Markdown View Source

Excessively simple RPC client for Ethereum.

Local signing through Cartouche.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 (Cartouche.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
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
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_history1Fetch 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 computed base fee per gas for the next block.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
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
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_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.

RPC call to get the computed base fee per gas for the next block.

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

RPC call to call a transaction and preview results.

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.

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.

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.

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.

RPC to get a block by its block hash.

RPC to get a block by its block number.

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)

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

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

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.

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

Types

access_list_result()

@type access_list_result() :: %{
  :access_list => Cartouche.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()

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> Cartouche.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.t()) :: {:ok, non_neg_integer()} | {:error, term()}

RPC call to get the computed base fee per gas for the next block.

Examples

iex> Cartouche.RPC.base_fee()
{:ok, 1000000000}

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> Cartouche.RPC.blob_base_fee()
{:ok, 42}

call_trx(trx, opts \\ [])

RPC call to call a transaction and preview results.

Examples

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

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

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

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

iex> errors = ["Unauthorized()", "BadNonce()", "NotEnoughSigners()", "NotActiveWithdrawalAddress()", "NotActiveOperator()", "DuplicateSigners()"]
iex> Cartouche.Transaction.V1.new(1, {100, :gwei}, 100_000, <<10::160>>, {2, :wei}, <<1, 2, 3>>)
iex> |> Cartouche.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> Cartouche.Transaction.V1.new(1, {100, :gwei}, 100_000, <<11::160>>, {2, :wei}, <<1, 2, 3>>)
iex> |> Cartouche.RPC.call_trx(errors: errors)
{:error, %{code: 3, message: "execution reverted", error_abi: "Cool(uint256,string)", error_params: [1, "cat"], revert: ABI.encode("Cool(uint256,string)", [1, "cat"])}}

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

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

coinbase(opts \\ [])

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

RPC call to get the node's coinbase address.

Examples

iex> Cartouche.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(
  Cartouche.Transaction.V1.t()
  | Cartouche.Transaction.V2.t()
  | Cartouche.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 = Cartouche.Transaction.Call.new(<<1::160>>, <<0, 1>>)
iex> {:ok, %{access_list: [{address, [storage_key]}], gas_used: gas_used}} =
...>   Cartouche.RPC.create_access_list(call)
iex> {address, storage_key, gas_used}
{<<1::160>>, <<2::256>>, 26026}

debug_trace_call(trx, opts \\ [])

RPC to trace a transaction call speculatively via debug API.

Examples

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

estimate_gas(trx, opts \\ [])

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

Examples

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

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

iex> use Cartouche.Hex
iex> Cartouche.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<10::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], :goerli)
iex> |> Cartouche.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> Cartouche.RPC.eth_block_number()
{:ok, 0x44}

eth_capabilities(opts \\ [])

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

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

Examples

iex> {:ok, capabilities} = Cartouche.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> Cartouche.RPC.eth_chain_id()
{:ok, 0x22}

eth_config(opts \\ [])

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

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

Examples

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

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 = Cartouche.Test.Signer.start_signer()
iex> {:ok, trx_id} = Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> {:ok, trx_id} = Cartouche.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()) :: {:ok, Cartouche.FeeHistory.t()} | {:error, term()}

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

Examples

iex> Cartouche.RPC.fee_history()
{:ok, %Cartouche.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]]
}}

fill_transaction(trx, opts \\ [])

@spec fill_transaction(
  Cartouche.Transaction.V1.t()
  | Cartouche.Transaction.V2.t()
  | Cartouche.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 Cartouche.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 Cartouche.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.

Examples

iex> {:ok, trx} =
...>   Cartouche.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, :kovan)
...>   |> Cartouche.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> Cartouche.RPC.gas_price()
{:ok, 1000000000}

get_balance(address, opts \\ [])

@spec get_balance(<<_::160>>, 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> Cartouche.RPC.get_balance(~h[0x0000000000000000000000000000000000000001])
{:ok, 0x55}

get_block_by_hash(block_hash, opts \\ [])

@spec get_block_by_hash(binary(), Keyword.t()) ::
  {:ok, Cartouche.Block.t()} | {: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> Cartouche.RPC.get_block_by_hash(~h[0xdc0818cf78f21a8e70579cb46a43643f78291264dda342ae31049421c82d21ae])
{:ok, %Cartouche.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: Cartouche.Block.deserialize/1 currently returns transactions: [] regardless — see ROADMAP Task 66.

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, Cartouche.Block.t()} | {: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> Cartouche.RPC.get_block_by_number(55)
{:ok, %Cartouche.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: Cartouche.Block.deserialize/1 currently returns transactions: [] regardless — see ROADMAP Task 66.

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

get_code(address, opts \\ [])

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

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

Examples

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

get_filter_logs(filter_id, opts \\ [])

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

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

Examples

iex> {:ok, [log]} = Cartouche.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 Cartouche.Hex
iex> Cartouche.RPC.get_nonce(~h[0x407d73d8a49eeb85d32cf465507dd71d507100c1])
{:ok, 4}

get_transaction_count(address, opts \\ [])

@spec get_transaction_count(<<_::160>>, 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> Cartouche.RPC.get_transaction_count(~h[0x0000000000000000000000000000000000000001])
{:ok, 0x4}

get_trx_receipt(trx_id, opts \\ [])

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

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

Examples

iex> Cartouche.RPC.get_trx_receipt(~h[0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5])
{:ok,
  %Cartouche.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: [
      %Cartouche.Receipt.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> Cartouche.RPC.get_trx_receipt("0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5")
{:ok,
  %Cartouche.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: [
      %Cartouche.Receipt.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> Cartouche.RPC.get_trx_receipt("0xf9e69be4f1ae524854e14dc820c519d8f2b86e52c60e54448abf920d22fb6fe2")
{:ok, %Cartouche.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: [
    %Cartouche.Receipt.Log{
      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]
      ]
    },
    %Cartouche.Receipt.Log{
      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]
      ]
    },
    %Cartouche.Receipt.Log{
      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> Cartouche.RPC.get_trx_receipt(<<1::256>>)
{:error, "failed to decode `eth_getTransactionReceipt` response: %FunctionClauseError{module: Cartouche.Hex, function: :decode_hex_, arity: 1, kind: nil, args: nil, clauses: nil}"}

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

iex> Cartouche.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> Cartouche.RPC.max_priority_fee_per_gas()
{:ok, 1000000001}

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> Cartouche.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> Cartouche.RPC.new_pending_transaction_filter()
{:ok, "0xpend1ng"}

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

@spec prepare_trx(<<_::160>>, binary() | {String.t(), [term()]}, Keyword.t()) ::
  {:ok, Cartouche.Transaction.V1.t() | Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> {:ok, trx} = Cartouche.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}
%Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> {:ok, trx} = Cartouche.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}
%Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> {:ok, trx} = Cartouche.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}
%Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> {:ok, trx} = Cartouche.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}
%Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> {:ok, trx} = Cartouche.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}
%Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> {:ok, trx} = Cartouche.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}
%Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> {:ok, trx} = Cartouche.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}
%Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> {:ok, trx} = Cartouche.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}
%Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> {:ok, trx} = Cartouche.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}
%Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> {:ok, trx} = Cartouche.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}
%Cartouche.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_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> Cartouche.RPC.send_rpc("net_version", [])
{:ok, "3"}

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

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

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

Options

Common options (other RPC wrappers forward opts here):

  • :ethereum_node — node URL; falls back to Application.get_env(:cartouche, :ethereum_node)
  • :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, Cartouche.RPC, plug: ...).

send_transaction(trx, opts \\ [])

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

RPC call to eth_sendTransaction.

Examples

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

send_trx(trx, opts \\ [])

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

RPC call to send a raw transaction.

Examples

iex> signer_proc = Cartouche.Test.Signer.start_signer()
iex> {:ok, signed_trx} = Cartouche.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} = Cartouche.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 = Cartouche.Test.Signer.start_signer()
iex> {:ok, signed_trx} = Cartouche.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} = Cartouche.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} = Cartouche.RPC.sign(<<1::160>>, "hello")
iex> byte_size(signature)
65

sign_transaction(trx, opts \\ [])

@spec sign_transaction(
  Cartouche.Transaction.V1.t()
  | Cartouche.Transaction.V2.t()
  | Cartouche.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 Cartouche.Transaction.decode/1.

Examples

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

trace_call(trx, opts \\ [])

RPC to trace a transaction call speculatively.

Examples

iex> Cartouche.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>)
...> |> Cartouche.RPC.trace_call()
{:ok,
  %Cartouche.TraceCall{
    output: "",
    state_diff: nil,
    trace: [
      %Cartouche.Trace{
        action: %Cartouche.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"
      },
      %Cartouche.Trace{
        action: %Cartouche.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> Cartouche.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], :goerli)
...> |> Cartouche.RPC.trace_call()
{:ok,
  %Cartouche.TraceCall{
    output: "",
    state_diff: nil,
    trace: [
      %Cartouche.Trace{
        action: %Cartouche.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"
      },
      %Cartouche.Trace{
        action: %Cartouche.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 = Cartouche.Transaction.V1.new(1, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>)
iex> t2 = Cartouche.Transaction.V2.new(1, {1, :gwei}, {100, :gwei}, 100_000, <<1::160>>, {2, :wei}, <<1, 2, 3>>, [<<2::160>>, <<3::160>>], :goerli)
iex> Cartouche.RPC.trace_call_many([t1, t2])
{:ok, [
  %Cartouche.TraceCall{
    output: "",
    state_diff: nil,
    trace: [
      %Cartouche.Trace{
        action: %Cartouche.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"
      },
      %Cartouche.Trace{
        action: %Cartouche.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
  },
  %Cartouche.TraceCall{
    output: ~h[0x00000000000000000000000079EDBC4F3A6AA2266CD469CC544501743BE8B078],
    state_diff: nil,
    trace: [
      %Cartouche.Trace{
        action: %Cartouche.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
  },
  %Cartouche.TraceCall{
    output: <<130, 180, 41, 0>>,
    state_diff: nil,
    trace: [
      %Cartouche.Trace{
        action: %Cartouche.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"
      },
      %Cartouche.Trace{
        action: %Cartouche.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"
      },
      %Cartouche.Trace{
        action: %Cartouche.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, [Cartouche.Trace.t()]} | {:error, term()}

RPC call to get a transaction receipt

Examples

iex> Cartouche.RPC.trace_trx("0x85d995eba9763907fdf35cd2034144dd9d53ce32cbec21349d4b12823c6860c5")
{:ok,
  [
  %Cartouche.Trace{
    action: %Cartouche.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"
  },
  %Cartouche.Trace{
    action: %Cartouche.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"
  }
]}