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

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

Provides a curated API for common Ethereum RPC methods with consistent
error tuples and option handling. All functions accept `:rpc_url`,
`:timeout`, and `:block` options.

## Error Format

- Address validation: `{:error, {:invalid_address, input}}`
- Data validation: `{:error, {:invalid_data, input}}`
- Block validation: `{:error, {:invalid_block, input}}`
- Tx hash validation: `{:error, {:invalid_tx_hash, input}}` (must be 32 bytes)
- RPC/network errors: `{:error, {:rpc_error, %{code: integer, message: string}}}`

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

## Functions

| Function | Purpose |
|----------|---------|
| `eth_call/3` | Read-only contract call → raw hex response |
| `eth_call!/3` | Same, raises on error |
| `eth_send_raw_transaction/2` | Broadcast signed tx → tx hash |
| `eth_send_raw_transaction!/2` | Same, raises on error |
| `get_balance/2` | Account ETH balance in wei |
| `get_balance!/2` | Same, raises on error |
| `block_number/1` | Current block height |
| `block_number!/1` | Same, raises on error |
| `get_block_by_number/2` | Fetch block by number or tag |
| `get_block_by_number!/2` | Same, raises on error |
| `chain_id/1` | Network chain ID |
| `chain_id!/1` | Same, raises on error |
| `eth_get_logs/2` | Fetch event logs by filter |
| `eth_get_logs!/2` | Same, raises on error |
| `get_transaction_receipt/2` | Transaction receipt by hash |
| `get_transaction_receipt!/2` | Same, raises on error |
| `get_transaction_count/2` | Account nonce (tx count) |
| `get_transaction_count!/2` | Same, raises on error |
| `eth_get_code/2` | Contract bytecode (or "0x" for EOAs) |
| `eth_get_code!/2` | Same, raises on error |
| `get_transaction_by_hash/2` | Full transaction details by hash |
| `get_transaction_by_hash!/2` | Same, raises on error |

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `eth_get_logs!` | 2 | Fetch event logs matching a filter. Raises on error. | `filter: value`, `opts: value` |
| `eth_get_logs` | 2 | Fetch event logs matching a filter (eth_getLogs). | `filter: value`, `opts: value` |
| `get_transaction_by_hash!` | 2 | Get full transaction details by hash. Raises on error. | `tx_hash: value`, `opts: value` |
| `get_transaction_by_hash` | 2 | Get full transaction details by hash (eth_getTransactionByHash). | `tx_hash: value`, `opts: value` |
| `eth_get_code!` | 2 | Fetch contract bytecode at an address. Raises on error. | `address: value`, `opts: value` |
| `eth_get_code` | 2 | Fetch contract bytecode at an address (eth_getCode). | `address: value`, `opts: value` |
| `get_transaction_count!` | 2 | Get the transaction count (nonce) of an address. Raises on error. | `address: value`, `opts: value` |
| `get_transaction_count` | 2 | Get the transaction count (nonce) of an address. | `address: value`, `opts: value` |
| `get_transaction_receipt!` | 2 | Get a transaction receipt by hash. Raises on error. | `tx_hash: value`, `opts: value` |
| `get_transaction_receipt` | 2 | Get a transaction receipt by hash (eth_getTransactionReceipt). | `tx_hash: value`, `opts: value` |
| `chain_id!` | 1 | Get the network chain ID. Raises on error. | `opts: value` |
| `chain_id` | 1 | Get the network chain ID. | `opts: value` |
| `get_block_by_number!` | 2 | Fetch a block by number or tag. Raises on error. | `block_id: value`, `opts: value` |
| `get_block_by_number` | 2 | Fetch a block by number or tag (eth_getBlockByNumber). | `block_id: value`, `opts: value` |
| `block_number!` | 1 | Get the current block height. Raises on error. | `opts: value` |
| `block_number` | 1 | Get the current block height. | `opts: value` |
| `get_balance!` | 2 | Get the ETH balance of an address in wei. Raises on error. | `address: value`, `opts: value` |
| `get_balance` | 2 | Get the ETH balance of an address in wei. | `address: value`, `opts: value` |
| `eth_send_raw_transaction!` | 2 | Broadcast a signed transaction. Raises on error. | `data: value`, `opts: value` |
| `eth_send_raw_transaction` | 2 | Broadcast a signed transaction. | `data: value`, `opts: value` |
| `eth_call!` | 3 | Execute a read-only contract call. Raises on error. | `address: value`, `data: value`, `opts: value` |
| `eth_call` | 3 | Execute a read-only contract call (eth_call). | `address: value`, `data: value`, `opts: value` |

# `block_number`

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

Get the current block height.

## Parameters

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

## Returns

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

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

# `block_number!`

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

Get the current block height. Raises on error.

## Parameters

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

## Returns

Current block number (`non_neg_integer`)

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

# `chain_id`

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

Get the network chain ID.

## Parameters

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

## Returns

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

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

# `chain_id!`

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

Get the network chain ID. Raises on error.

## Parameters

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

## Returns

Chain ID integer (`non_neg_integer`)

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

# `eth_call`

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

Execute a read-only contract call (eth_call).

## Parameters

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

## Returns

Raw 0x-prefixed hex response from the contract (`{:ok, hex_string} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    data: %{
      description: "0x-prefixed hex-encoded calldata (from ABI.encode_call)",
      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: "{:ok, hex_string} | {:error, term}",
    description: "Raw 0x-prefixed hex response from the contract",
    example: "0x000000000000000000000000000000000000000000000000000000000000002a"
  }
}
```

# `eth_call!`

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

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

## Parameters

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

## Returns

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

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

# `eth_get_code`

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

Fetch contract bytecode at an address (eth_getCode).

## Parameters

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

## Returns

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

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

# `eth_get_code!`

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

Fetch contract bytecode at an address. Raises on error.

## Parameters

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

## Returns

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

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

# `eth_get_logs`

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

Fetch event logs matching a filter (eth_getLogs).

## Parameters

  * `filter` - Filter map with keys: :address (hex string or binary), :topics (list), :from_block (integer or tag), :to_block (integer or tag) (value)
  * `opts` - Options: :rpc_url, :timeout (default: `[]`, value)

## Returns

List of log maps with keys: address, topics, data, block_number, transaction_hash, log_index, transaction_index, removed (`{:ok, [log_map]} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    filter: %{
      description: "Filter map with keys: :address (hex string or binary), :topics (list), :from_block (integer or tag), :to_block (integer or tag)",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, [log_map]} | {:error, term}",
    description: "List of log maps with keys: address, topics, data, block_number, transaction_hash, log_index, transaction_index, removed"
  }
}
```

# `eth_get_logs!`

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

Fetch event logs matching a filter. Raises on error.

## Parameters

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

## Returns

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

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

# `eth_send_raw_transaction`

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

Broadcast a signed transaction.

## Parameters

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

## Returns

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

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

# `eth_send_raw_transaction!`

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

Broadcast a signed transaction. Raises on error.

## Parameters

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

## Returns

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

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

# `get_balance`

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

Get the ETH balance of an address in wei.

## Parameters

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

## Returns

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

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

# `get_balance!`

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

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

## Parameters

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

## Returns

Balance in wei (`non_neg_integer`)

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

# `get_block_by_number`

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

Fetch a block by number or tag (eth_getBlockByNumber).

## Parameters

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

## Returns

Raw block map with hex-encoded fields from the node (`{:ok, map} | {:error, term}`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    block_id: %{
      description: "Block number (integer) or tag string (\"latest\", \"finalized\", \"pending\", \"earliest\", \"safe\", or \"0x...\" hex)",
      kind: :value
    }
  },
  returns: %{
    type: "{:ok, map} | {:error, term}",
    description: "Raw block map with hex-encoded fields from the node",
    example: "%{\"number\" => \"0x1312d00\", \"timestamp\" => \"0x665ba27f\", ...}"
  }
}
```

# `get_block_by_number!`

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

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

Raw block map with hex-encoded fields (`map`)

```elixir
# descripex:contract
%{
  params: %{
    opts: %{
      default: [],
      description: "Options: :rpc_url, :timeout",
      kind: :value
    },
    block_id: %{
      description: "Block number (integer) or tag string",
      kind: :value
    }
  },
  returns: %{type: :map, description: "Raw block map with hex-encoded fields"}
}
```

# `get_transaction_by_hash`

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

Get full transaction details by hash (eth_getTransactionByHash).

## Parameters

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

## Returns

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

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

# `get_transaction_by_hash!`

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

Get full transaction details by hash. Raises on error.

## Parameters

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

## Returns

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

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

# `get_transaction_count`

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

Get the transaction count (nonce) of an address.

## Parameters

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

## Returns

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

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

# `get_transaction_count!`

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

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

## Parameters

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

## Returns

Transaction count (nonce) (`non_neg_integer`)

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

# `get_transaction_receipt`

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

Get a transaction receipt by hash (eth_getTransactionReceipt).

## Parameters

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

## Returns

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

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

# `get_transaction_receipt!`

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

Get a transaction receipt by hash. Raises on error.

## Parameters

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

## Returns

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

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

---

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