Exth

Exth is an Elixir client for interacting with EVM-compatible blockchain nodes via JSON-RPC. It provides a robust, type-safe interface for making Ethereum RPC calls.

Features

Installation

Add exth to your list of dependencies in mix.exs:

def deps do
  [
    {:exth, "~> 0.1.0"},
    # Optional dependencies:
    # Mint for Tesla adapter
    {:mint, "~> 1.7"}
  ]
end

Usage

Exth offers two ways to interact with EVM nodes:

  1. Provider (High-Level): Define a provider module with convenient function names and no need to pass client references.
  2. RPC Client (Low-Level): Direct client usage with more control, requiring explicit client handling.

Provider (Recommended)

# Basic usage with inline configuration
defmodule MyProvider do
  use Exth.Provider,
    otp_app: :your_otp_app,
    transport_type: :http,
    rpc_url: "https://YOUR-RPC-URL"
end

# Dynamic configuration through application config
# In your config/config.exs or similar:
config :your_otp_app, MyProvider,
  rpc_url: "https://YOUR-RPC-URL",
  timeout: 30_000,
  max_retries: 3

# Then in your provider module:
defmodule MyProvider do
  use Exth.Provider,
    otp_app: :your_otp_app,
    transport_type: :http
end

# Configuration is merged with inline options taking precedence
defmodule MyProvider do
  use Exth.Provider,
    otp_app: :your_otp_app,
    transport_type: :http,
    rpc_url: "https://OVERRIDE-RPC-URL" # This will override the config value
end

# Use the provider
{:ok, block_number} = MyProvider.block_number()

{:ok, balance} = MyProvider.get_balance(
  "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
  "latest"
)

{:ok, block} = MyProvider.get_block_by_number("0x1", true)

{:ok, tx_hash} = MyProvider.send_raw_transaction("0x...")

The Provider approach is recommended for most use cases as it provides:

Configuration Options

Providers can be configured through both inline options and application config. Inline options take precedence over application config. Here are the available options:

# Required options
transport_type: :http | :custom  # Transport type to use
rpc_url: "https://..."          # RPC endpoint URL

# Required inline option
otp_app: :your_otp_app          # Application name for config lookup

# Custom transport options
module: MyCustomTransport       # Required when transport_type is :custom

# Optional http options
timeout: 30_000                 # Request timeout in milliseconds
headers: [{"header", "value"}]  # Custom headers for HTTP transport

RPC Client

alias Exth.Rpc

# 1. Define a client
{:ok, client} = Rpc.new_client(
  transport_type: :http,
  rpc_url: "https://YOUR-RPC-URL"
)

# 2.1. Make RPC calls with explicit client
request1 = Rpc.request(client, "eth_blockNumber", [])
{:ok, block_number} = Rpc.send(client, request1)

# 2.2. Or make RPC calls without a client
request2 = Rpc.request(
  "eth_getBalance",
  ["0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "latest"]
)
{:ok, balance} = Rpc.send(client, request2)

# 3. You can also send multiple requests in one call
requests = [request1, request2]
{:ok, responses} = Rpc.send(client, requests)

# 4. You can invert the order of the arguments and pipe
Rpc.request("eth_blockNumber", [])
|> Rpc.send(client)

# OR
[request1, request2]
|> Rpc.send(client)

Use the RPC Client approach when you need:

Transport Options

Exth uses a pluggable transport system that supports different communication protocols. Each transport type can be configured with specific options:

HTTP Transport

The default HTTP transport is built on Tesla, providing a robust HTTP client with middleware support:

# Provider configuration
defmodule MyProvider do
  use Exth.Provider,
    transport_type: :http,
    rpc_url: "https://eth-mainnet.example.com",
    # Optional HTTP-specific configuration
    adapter: Tesla.Adapter.Mint, # Default HTTP adapter
    headers: [{"authorization", "Bearer token"}],
    timeout: 30_000, # Request timeout in ms
end

# Direct client configuration
{:ok, client} = Exth.Rpc.new(
  transport_type: :http,
  rpc_url: "https://eth-mainnet.example.com",
  adapter: Tesla.Adapter.Mint,
  headers: [{"authorization", "Bearer token"}],
  timeout: 30_000
)

WebSocket Transport

The WebSocket transport provides full-duplex communication for real-time updates and subscriptions:

# Provider configuration
defmodule MyProvider do
  use Exth.Provider,
    transport_type: :websocket,
    rpc_url: "wss://eth-mainnet.example.com",
end

# Direct client configuration
{:ok, client} = Exth.Rpc.new(
  transport_type: :websocket,
  rpc_url: "wss://eth-mainnet.example.com",
)

# Example subscription
request = Rpc.request("eth_subscribe", ["newHeads"])
{:ok, response} = Rpc.send(client, request)

IPC Transport

The IPC transport provides communication with local Ethereum nodes via Unix domain sockets:

# Provider configuration
defmodule MyProvider do
  use Exth.Provider,
    transport_type: :ipc,
    path: "/tmp/ethereum.ipc",
    # Optional IPC-specific configuration
    timeout: 30_000, # Request timeout in ms
    pool_size: 10, # Number of connections in the pool
    socket_opts: [:binary, active: false, reuseaddr: true]
end

# Direct client configuration
{:ok, client} = Exth.Rpc.new(
  transport_type: :ipc,
  path: "/tmp/ethereum.ipc",
  timeout: 30_000,
  pool_size: 5
)

# Make requests
request = Rpc.request("eth_blockNumber", [])
{:ok, response} = Rpc.send(client, request)

IPC Configuration Options:

Custom Transport

Implement your own transport by creating a module and implementing the Exth.Transport.Transportable protocol:

defmodule MyCustomTransport do
  # Transport struct should be whatever you need
  defstruct [:config]
end

defimpl Exth.Transport.Transportable, for: MyCustomTransport do
  def new(transport, opts) do
    # Initialize your transport configuration
    %MyCustomTransport{config: opts}
  end

  def call(transport, request) do
    # Handle the JSON-RPC request
    # Return {:ok, response} or {:error, reason}
  end
end

# Use your custom transport
defmodule MyProvider do
  use Exth.Provider,
    transport_type: :custom,
    module: MyCustomTransport,
    rpc_url: "custom://endpoint",
    # Additional custom options
    custom_option: "value"
end

# Direct client configuration
{:ok, client} = Exth.Rpc.new_request(
  transport_type: :custom,
  rpc_url: "https://eth-mainnet.example.com",
  module: MyCustomTransport,
  custom_option: "value"
)

Examples

Check out our examples directory for practical usage examples.

Requirements

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b feature/my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin feature/my-new-feature)
  5. Create new Pull Request

License

This project is licensed under the MIT License. See LICENSE for details.