> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbitflare.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rust SDK

> Rust SDK for Robinhood Chain, the orbitflare-robinhood-sdk crate.

Built on [alloy](https://github.com/alloy-rs/alloy) types throughout - `Address`, `U256`, `B256`, `Filter`, `TransactionRequest`, typed blocks, transactions, receipts, and logs - with OrbitFlare's own transport: endpoint failover, retry with backoff, and self-healing WebSocket subscriptions. It targets [Robinhood Chain](/robinhood-chain), the EVM-compatible L2, and defaults to OrbitFlare's production endpoints.

## Install

```bash theme={null}
cargo add orbitflare-robinhood-sdk
```

Only the RPC client is enabled by default. Enable what you need:

```bash theme={null}
cargo add orbitflare-robinhood-sdk --features ws
cargo add orbitflare-robinhood-sdk --features all
```

## Alloy types

All addresses, hashes, and quantities are alloy types. The common ones are re-exported at the crate root (`Address`, `U256`, `B256`, `Bytes`, `Filter`, `TransactionRequest`, `BlockNumberOrTag`, ...), and the full crates are available as `orbitflare_robinhood_sdk::primitives` (alloy-primitives) and `orbitflare_robinhood_sdk::rpc_types` (alloy-rpc-types-eth).

```rust theme={null}
use orbitflare_robinhood_sdk::primitives::{address, b256, utils::format_ether};
```

## RPC client

Here's a client with every option set:

```rust theme={null}
use orbitflare_robinhood_sdk::{BlockNumberOrTag, Result, RetryPolicy, RpcClientBuilder};
use std::time::Duration;

let client = RpcClientBuilder::new()
    .url("https://robinhood.rpc.orbitflare.com")
    .fallback_urls(&["https://robinhood-backup.rpc.orbitflare.com"])
    .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN")
    .block_tag(BlockNumberOrTag::Finalized)
    .retry(RetryPolicy {
        initial_delay: Duration::from_millis(100),
        max_delay: Duration::from_secs(30),
        multiplier: 2.0,
        max_attempts: 5,
    })
    .timeout(Duration::from_secs(30))
    .build()?;
```

Everything has a sensible default - the builder defaults to the production OrbitFlare endpoint, so the minimal setup is:

```rust theme={null}
let client = RpcClientBuilder::new().build()?;
```

### Builder methods

**`.url(url)`** - The primary endpoint. Resolution order: `.url()` on the builder, then the `ORBITFLARE_ROBINHOOD_RPC_URL` environment variable, then the default `https://robinhood.rpc.orbitflare.com` (`rpc::DEFAULT_RPC_URL`).

**`.urls(&[...])`** - Set the primary and all fallbacks in one call. The first element is the primary, the rest are fallbacks.

**`.fallback_url(url)` / `.fallback_urls(&[...])`** - Add failover endpoints. When the primary fails, the SDK tries fallbacks in order. Failing endpoints are quarantined with exponential cooldown (10s, 20s, 40s, max 60s) and automatically retried once the cooldown expires; healthy endpoints are always preferred.

**`.api_key(key)`** - Your OrbitFlare license key. If not set, the SDK checks `ORBITFLARE_LICENSE_KEY` from the environment. The key is appended to the endpoint URL at request time.

**`.block_tag(tag)`** - Default block tag used by state queries (`get_balance`, `call`, `get_code`, ...). Takes anything that converts into `BlockNumberOrTag`. Defaults to `Latest`.

**`.retry(policy)`** - Controls retry on transient errors (5xx, 429, connection resets, JSON-RPC error code -32005) with exponential backoff before failing over to the next endpoint. 429 responses with a `Retry-After` header are respected.

**`.timeout(duration)`** - HTTP timeout for each individual request.

### Available RPC methods

| Method                                                       | Returns                                      |
| ------------------------------------------------------------ | -------------------------------------------- |
| `get_block_number()`                                         | `u64`                                        |
| `get_chain_id()`                                             | `u64`                                        |
| `get_balance(Address)`                                       | `U256` wei                                   |
| `get_transaction_count(Address)`                             | `u64` nonce                                  |
| `get_gas_price()`                                            | `u128` wei                                   |
| `max_priority_fee_per_gas()`                                 | `u128` wei                                   |
| `get_block_by_number(impl Into<BlockNumberOrTag>, full_txs)` | `Option<Block>`                              |
| `get_transaction_by_hash(B256)`                              | `Option<Transaction>`                        |
| `get_transaction_receipt(B256)`                              | `Option<TransactionReceipt>`                 |
| `get_logs(&Filter)`                                          | `Vec<Log>`                                   |
| `get_code(Address)`                                          | `Bytes`                                      |
| `call(&TransactionRequest)`                                  | `Bytes`                                      |
| `estimate_gas(&TransactionRequest)`                          | `u64`                                        |
| `send_raw_transaction(&[u8])`                                | `B256` tx hash                               |
| `fee_history(blocks, newest, percentiles)`                   | `FeeHistory`                                 |
| `request(method, params)`                                    | Any RPC method by name (`serde_json::Value`) |
| `request_raw(body)`                                          | Raw JSON-RPC body string                     |

### Reading chain state

```rust theme={null}
use orbitflare_robinhood_sdk::primitives::{address, utils::format_ether};

let block = client.get_block_number().await?;
let gas_price = client.get_gas_price().await?;

let wallet = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
let balance = client.get_balance(wallet).await?;
let nonce = client.get_transaction_count(wallet).await?;

println!("ETH: {}", format_ether(balance));
```

### Log filters

`get_logs` takes alloy's `Filter` directly:

```rust theme={null}
use orbitflare_robinhood_sdk::primitives::{address, b256};
use orbitflare_robinhood_sdk::{BlockNumberOrTag, Filter};

let transfer_topic =
    b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef");

let filter = Filter::new()
    .from_block(1_000_000u64)
    .to_block(BlockNumberOrTag::Latest)
    .address(address!("d0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC"))
    .event_signature(transfer_topic);

let logs = client.get_logs(&filter).await?;
```

### Contract calls and transactions

`call` and `estimate_gas` take alloy's `TransactionRequest`. To send a transaction, build and sign it with alloy (`alloy-signer`, `alloy-network`), then broadcast through the SDK:

```rust theme={null}
let hash = client.send_raw_transaction(&signed_tx_rlp).await?;
let receipt = client.get_transaction_receipt(hash).await?;
```

### Arbitrary methods

`request` calls any RPC method by name - the SDK builds the JSON-RPC envelope, handles retry and failover, and returns the `result` field. This also covers the `arb_*` extension methods Robinhood Chain serves as an Arbitrum Nitro chain. `request_raw` sends a raw JSON-RPC body string.

```rust theme={null}
use serde_json::json;

let syncing = client.request("eth_syncing", json!([])).await?;

let version = client
    .request_raw(r#"{"jsonrpc":"2.0","id":1,"method":"web3_clientVersion","params":[]}"#)
    .await?;
```

## WebSocket client

Enable the `ws` feature. Robinhood Chain produces blocks roughly every 100 milliseconds, so `newHeads` fires far faster than on Ethereum mainnet or most L2s.

```rust theme={null}
use orbitflare_robinhood_sdk::{Result, RetryPolicy, WsClientBuilder};
use std::time::Duration;

let client = WsClientBuilder::new()
    .url("wss://robinhood.rpc.orbitflare.com")
    .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN")
    .retry(RetryPolicy {
        initial_delay: Duration::from_millis(100),
        max_delay: Duration::from_secs(30),
        multiplier: 2.0,
        max_attempts: 0,
    })
    .ping_interval_secs(10)
    .max_missed_pongs(3)
    .build()
    .await?;
```

Minimal:

```rust theme={null}
let client = WsClientBuilder::new().build().await?;
```

Note that `.build()` is async - it establishes the WebSocket connection before returning. The builder shares `.urls()`, `.fallback_url(s)()`, `.api_key()`, and `.retry()` with the RPC builder; the WebSocket-specific options are:

**`.url(url)`** - Primary WebSocket endpoint. Resolution order: `.url()`, then `ORBITFLARE_ROBINHOOD_WS_URL`, then the default `wss://robinhood.rpc.orbitflare.com` (`ws::DEFAULT_WS_URL`).

**`.ping_interval_secs(n)`** - How often the SDK sends WebSocket `Ping` frames to detect dead connections. Default: 10.

**`.max_missed_pongs(n)`** - Pings without a response before the connection is considered dead and reconnected. Default: 3.

### Subscriptions

Subscriptions are typed - each yields the corresponding alloy type instead of raw JSON:

| Method                                 | Yields                    |
| -------------------------------------- | ------------------------- |
| `new_heads_subscribe()`                | `Header` per new block    |
| `logs_subscribe(&Filter)`              | `Log` matching the filter |
| `new_pending_transactions_subscribe()` | `B256` transaction hash   |

```rust theme={null}
use orbitflare_robinhood_sdk::primitives::b256;
use orbitflare_robinhood_sdk::Filter;

let mut heads = client.new_heads_subscribe().await?;

let transfer_topic =
    b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef");
let mut transfers = client
    .logs_subscribe(&Filter::new().event_signature(transfer_topic))
    .await?;
```

All subscriptions return a `WsSubscription<T>`. Call `.next()` for the next typed event (`None` means the subscription was closed), or `.next_raw()` for the untyped `serde_json::Value` payload.

```rust theme={null}
while let Some(head) = heads.next().await {
    println!("block {} (gas used {})", head.number, head.gas_used);
}
```

All subscriptions run on a single WebSocket connection, and you can add new ones at any time. `sub.unsubscribe().await` removes one explicitly; dropping a subscription without calling it also works - the SDK detects the orphan and sends the unsubscribe automatically.

### Reconnection

If the connection drops, the background task reconnects with exponential backoff and re-subscribes every active subscription automatically. Your `.next()` calls just keep working - events resume once the connection is back. Dead connections are detected via active ping/pong, configurable with `.ping_interval_secs()` and `.max_missed_pongs()`.

## Environment variables

| Variable                       | Used by        | Purpose                                                  |
| ------------------------------ | -------------- | -------------------------------------------------------- |
| `ORBITFLARE_LICENSE_KEY`       | RPC, WebSocket | API key appended to endpoint URLs                        |
| `ORBITFLARE_ROBINHOOD_RPC_URL` | RPC            | Overrides the default endpoint if `.url()` is not called |
| `ORBITFLARE_ROBINHOOD_WS_URL`  | WebSocket      | Overrides the default endpoint if `.url()` is not called |

## Full example

A monitoring script that reads a wallet, then follows new blocks and ERC-20 transfers in real time:

```rust theme={null}
use orbitflare_robinhood_sdk::primitives::{address, b256, utils::format_ether};
use orbitflare_robinhood_sdk::{Filter, Result, RpcClientBuilder, WsClientBuilder};

#[tokio::main]
async fn main() -> Result<()> {
    let rpc = RpcClientBuilder::new().build()?;

    let wallet = address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
    let balance = rpc.get_balance(wallet).await?;
    let block = rpc.get_block_number().await?;
    println!("block {block}, wallet holds {} ETH", format_ether(balance));

    let ws = WsClientBuilder::new().build().await?;

    let mut heads = ws.new_heads_subscribe().await?;

    let transfer_topic = b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef");
    let mut transfers = ws
        .logs_subscribe(&Filter::new().event_signature(transfer_topic))
        .await?;

    println!("watching new heads and ERC-20 transfers...");

    loop {
        tokio::select! {
            Some(head) = heads.next() => {
                println!("block {} (gas used {})", head.number, head.gas_used);
            }
            Some(log) = transfers.next() => {
                let tx = log.transaction_hash.unwrap_or_default();
                println!("transfer on {} in {tx}", log.address());
            }
        }
    }
}
```

## Source

The SDK is open source: [github.com/orbitflare/orbitflare-robinhood-sdk-rs](https://github.com/orbitflare/orbitflare-robinhood-sdk-rs)
