> ## 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.

# EVM SDK

> OrbitFlare EVM 链的 Rust SDK：orbitflare-evm-sdk crate。

一个 Rust 客户端，服务于 OrbitFlare 的各条 EVM 链——Polygon、BNB Smart Chain 与 Robinhood Chain——以及你自己定义的任意 EVM 链。全程构建于 [alloy](https://github.com/alloy-rs/alloy) 类型之上（`Address`、`U256`、`B256`、`Filter`、`TransactionRequest`，以及类型化的区块、交易、收据与日志），并配备 OrbitFlare 自有的传输层：端点故障转移、带退避的重试与自愈的 WebSocket 订阅。

客户端对链是泛型的（`RpcClient<C>`、`WsClient<C>`）；下文的别名是便捷简写。gRPC 目前仅支持 Polygon（Bor）。

## 支持的链

| 链               | Chain ID | 别名                                       |
| --------------- | -------- | ---------------------------------------- |
| Polygon         | 137      | `PolygonRpcClient`、`PolygonWsClient`     |
| BNB Smart Chain | 56       | `BnbRpcClient`、`BnbWsClient`             |
| Robinhood Chain | 4663     | `RobinhoodRpcClient`、`RobinhoodWsClient` |
| 你自己的链           | 任意       | `RpcClient<C>`，其中 `impl Chain for C`     |

<Note>
  正从 `orbitflare-robinhood-sdk` 迁移？Robinhood Chain 现已在此通过 `RobinhoodRpcClient` / `RobinhoodWsClient` 覆盖。其 RPC 与 WebSocket 接口完全一致；独立 crate 已弃用。
</Note>

## 安装

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

默认仅启用 RPC 客户端。按需启用其他特性：

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

## RPC

<CodeGroup>
  ```rust 示例 theme={null}
  let client = PolygonRpcClient::builder()
      .url("https://ams.poly.rpc.orbitflare.com")
      .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN")
      .build()?;

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

  ```rust 完整示例 theme={null}
  use orbitflare_evm_sdk::{primitives::address, PolygonRpcClient, Result};

  #[tokio::main]
  async fn main() -> Result<()> {
      let client = PolygonRpcClient::builder()
          .url("https://ams.poly.rpc.orbitflare.com")
          .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN")
          .build()?;

      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?;

      println!("block {block}, gas {gas_price} wei, balance {balance} wei");
      Ok(())
  }
  ```
</CodeGroup>

没有内置的默认端点：用 `.url()` 或环境变量设置 URL（参见 [端点](#endpoints)），用 `.api_key()` 或 `ORBITFLARE_LICENSE_KEY` 设置 API 密钥。

### 构建器方法

**`.url(url)`** — 主端点。解析顺序：构建器上的 `.url()`，然后是环境变量 `ORBITFLARE_RPC_URL`。

**`.urls(&[...])`** — 一次设置主端点与所有备用端点。第一个元素为主端点，其余为备用。

**`.fallback_url(url)` / `.fallback_urls(&[...])`** — 添加故障转移端点。失败的端点会被隔离并进入指数冷却（10s、20s、40s，最大 60s），冷却结束后自动重试；健康端点始终优先。

**`.api_key(key)`** — 你的 OrbitFlare 许可证密钥。若未设置，SDK 会检查 `ORBITFLARE_LICENSE_KEY`。密钥在请求时以 `?api_key=<key>` 附加到端点 URL。

**`.block_tag(tag)`** — 状态查询（`get_balance`、`call`、`get_code` 等）使用的默认区块标签。默认为 `Latest`。

**`.retry(policy)`** — 控制瞬时错误（5xx、429、连接重置、JSON-RPC 错误码 -32005）的重试，采用指数退避，之后故障转移到下一个端点。

**`.timeout(duration)`** — 单次 HTTP 请求超时。

### 可用方法

| 方法                                                           | 返回值                                 |
| ------------------------------------------------------------ | ----------------------------------- |
| `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_block_by_hash(B256, 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`                             |
| `get_storage_at(Address, B256)`                              | `B256`                              |
| `call(&TransactionRequest)`                                  | `Bytes`                             |
| `estimate_gas(&TransactionRequest)`                          | `u64`                               |
| `send_raw_transaction(&[u8])`                                | `B256` 交易哈希                         |
| `fee_history(blocks, newest, percentiles)`                   | `FeeHistory`                        |
| `request(method, params)`                                    | 按名称调用任意 RPC 方法（`serde_json::Value`） |
| `request_raw(body)`                                          | 原始 JSON-RPC 请求体字符串                  |

### 日志过滤器

`get_logs` 直接接受 alloy 的 `Filter`：

<CodeGroup>
  ```rust 示例 theme={null}
  let filter = Filter::new()
      .from_block(60_000_000u64)
      .to_block(BlockNumberOrTag::Latest)
      .address(address!("0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270"))
      .event_signature(transfer_topic);

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

  ```rust 完整示例 theme={null}
  use orbitflare_evm_sdk::{primitives::{address, b256}, BlockNumberOrTag, Filter, PolygonRpcClient, Result};

  #[tokio::main]
  async fn main() -> Result<()> {
      let client = PolygonRpcClient::builder()
          .url("https://ams.poly.rpc.orbitflare.com")
          .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN")
          .build()?;

      let transfer_topic =
          b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef");

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

      let logs = client.get_logs(&filter).await?;
      println!("{} logs", logs.len());
      Ok(())
  }
  ```
</CodeGroup>

### 合约调用与交易

`call` 与 `estimate_gas` 接受 alloy 的 `TransactionRequest`。要发送交易，先用 alloy（`alloy-signer`、`alloy-network`）构建并签名，再通过 SDK 广播：

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

### 任意方法

`request` 按名称调用任意 RPC 方法——SDK 构建 JSON-RPC 信封，处理重试与故障转移，并返回 `result` 字段。`request_raw` 发送原始 JSON-RPC 请求体字符串。

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

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

## WebSocket

启用 `ws` 特性。`.build()` 为异步——在返回前会建立连接。

<CodeGroup>
  ```rust 示例 theme={null}
  let client = PolygonWsClient::builder()
      .url("wss://ams.poly.rpc.orbitflare.com")
      .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN")
      .build()
      .await?;

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

  ```rust 完整示例 theme={null}
  use orbitflare_evm_sdk::{PolygonWsClient, Result};

  #[tokio::main]
  async fn main() -> Result<()> {
      let client = PolygonWsClient::builder()
          .url("wss://ams.poly.rpc.orbitflare.com")
          .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN")
          .build()
          .await?;

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

      while let Some(head) = heads.next().await {
          println!("block {} (gas used {})", head.number, head.gas_used);
      }
      Ok(())
  }
  ```
</CodeGroup>

构建器与 RPC 构建器共享 `.urls()`、`.fallback_url(s)`、`.api_key()` 与 `.retry()`；WebSocket 特有的选项为 `.ping_interval_secs(n)`（默认 10）与 `.max_missed_pongs(n)`（默认 3）。

### 订阅

订阅是类型化的——每个订阅产出对应的 alloy 类型，而非原始 JSON：

| 方法                                     | 产出               |
| -------------------------------------- | ---------------- |
| `new_heads_subscribe()`                | 每个新区块一条 `Header` |
| `logs_subscribe(&Filter)`              | 匹配过滤器的 `Log`     |
| `new_pending_transactions_subscribe()` | `B256` 交易哈希      |

所有订阅返回 `WsSubscription<T>`。调用 `.next()` 获取下一条类型化事件（`None` 表示订阅已关闭），或调用 `.next_raw()` 获取未类型化的 `serde_json::Value` 负载。`sub.unsubscribe().await` 显式移除某个订阅；直接丢弃订阅也可以。

### 重连

若连接断开，后台任务会以指数退避重连，并自动重新订阅所有活跃订阅。你的 `.next()` 调用会持续工作。失效连接通过主动 ping/pong 检测。

## Polygon gRPC

Polygon 暴露了一个 Bor gRPC 接口，用于低开销地访问区块、区块头与收据。启用 `grpc` 特性。gRPC 运行于明文 HTTP/2；通过 `.api_key()` 用令牌认证（作为 `x-token` 发送），或使用 IP 白名单。

<CodeGroup>
  ```rust 示例 theme={null}
  let client = PolygonGrpcClient::builder()
      .url("http://your-bor-grpc-endpoint:3131")
      .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN")
      .build()?;

  let header = client.header_by_number(BlockNumber::Latest).await?;
  ```

  ```rust 完整示例 theme={null}
  use orbitflare_evm_sdk::grpc::{BlockNumber, PolygonGrpcClient};
  use orbitflare_evm_sdk::Result;

  #[tokio::main]
  async fn main() -> Result<()> {
      let client = PolygonGrpcClient::builder()
          .url("http://your-bor-grpc-endpoint:3131")
          .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN")
          .build()?;

      let header = client.header_by_number(BlockNumber::Latest).await?;
      let author = client.author(header.number).await?;

      println!("block {} authored by {author}", header.number);
      Ok(())
  }
  ```
</CodeGroup>

| 方法                                         | 返回值              |
| ------------------------------------------ | ---------------- |
| `header_by_number(impl Into<BlockNumber>)` | `Header`         |
| `block_by_number(impl Into<BlockNumber>)`  | `Block`          |
| `transaction_receipt(B256)`                | `Receipt`        |
| `bor_block_receipt(B256)`                  | `Receipt`        |
| `author(impl Into<BlockNumber>)`           | `Address`        |
| `td_by_hash(B256)` / `td_by_number(...)`   | `u64` 总难度        |
| `root_hash(start, end)`                    | `String`         |
| `block_info_in_batch(start, end)`          | `Vec<BlockInfo>` |

H160/H256 值可通过 `ToAlloy` trait 转换为 alloy 的 `Address`/`B256`。

## 自定义链

任意 EVM 链都可使用：为一个标记类型实现 `Chain`，并让泛型 `RpcClient<C>` 指向其 URL。`CHAIN_ID` 是元数据（可通过 `RpcClient::<C>::chain_id_const()` 获取）；它不会限制请求。

<CodeGroup>
  ```rust 示例 theme={null}
  struct Base;

  impl Chain for Base {
      const CHAIN_ID: u64 = 8453;
  }

  let client = RpcClient::<Base>::builder()
      .url("https://mainnet.base.org")
      .build()?;
  ```

  ```rust 完整示例 theme={null}
  use orbitflare_evm_sdk::{Chain, RpcClient, Result};

  struct Base;

  impl Chain for Base {
      const CHAIN_ID: u64 = 8453;
  }

  #[tokio::main]
  async fn main() -> Result<()> {
      let client = RpcClient::<Base>::builder()
          .url("https://mainnet.base.org")
          .build()?;

      println!("chain id: {}", client.get_chain_id().await?);
      println!("block:    {}", client.get_block_number().await?);
      Ok(())
  }
  ```
</CodeGroup>

## Arbitrum 与 Nitro 区块字段

Robinhood Chain 是 Arbitrum Nitro，因此其区块携带额外字段（`l1BlockNumber`、`sendRoot`、`sendCount`），而标准 EVM 区块类型会丢弃这些字段。SDK 会保留它们，并通过 `NitroBlockExt` 暴露类型化的访问器。其他任何非标准字段仍可在 `block.other` 上获取。

<CodeGroup>
  ```rust 示例 theme={null}
  let block = client
      .get_block_by_number(client.block_tag(), false)
      .await?
      .expect("block");

  let l1 = block.l1_block_number();
  let send_root = block.send_root();
  ```

  ```rust 完整示例 theme={null}
  use orbitflare_evm_sdk::{NitroBlockExt, RobinhoodRpcClient, Result};

  #[tokio::main]
  async fn main() -> Result<()> {
      let client = RobinhoodRpcClient::builder()
          .url("https://robinhood.rpc.orbitflare.com")
          .api_key("ORBIT-XXXXXX-NNNNNN-NNNNNN")
          .build()?;

      let block = client
          .get_block_by_number(client.block_tag(), false)
          .await?
          .expect("block");

      println!("l1 block:   {:?}", block.l1_block_number());
      println!("send root:  {:?}", block.send_root());
      println!("send count: {:?}", block.send_count());
      Ok(())
  }
  ```
</CodeGroup>

Arbitrum 预编译合约（ArbSys、ArbGasInfo 等）可像任何合约一样通过 `call()` 访问。

## 端点

没有默认端点。用 `.url()` 为每个客户端设置 URL，或通过环境变量设置。解析顺序：构建器上的 `.url()`，然后是环境变量。

| 链               | RPC                                    | WebSocket                            |
| --------------- | -------------------------------------- | ------------------------------------ |
| Polygon         | `https://ams.poly.rpc.orbitflare.com`  | `wss://ams.poly.rpc.orbitflare.com`  |
| BNB Smart Chain | `https://bsc.rpc.orbitflare.com`       | `wss://bsc.rpc.orbitflare.com`       |
| Robinhood Chain | `https://robinhood.rpc.orbitflare.com` | `wss://robinhood.rpc.orbitflare.com` |

请使用你 OrbitFlare 控制台中的确切端点。

## 环境变量

| 变量                       | 使用者                | 用途                                   |
| ------------------------ | ------------------ | ------------------------------------ |
| `ORBITFLARE_LICENSE_KEY` | RPC、WebSocket、gRPC | 附加到端点 URL 的 API 密钥（gRPC 用 `x-token`） |
| `ORBITFLARE_RPC_URL`     | RPC                | 未调用 `.url()` 时使用的端点                  |
| `ORBITFLARE_WS_URL`      | WebSocket          | 未调用 `.url()` 时使用的端点                  |
| `ORBITFLARE_GRPC_URL`    | gRPC               | 未调用 `.url()` 时使用的端点                  |

## 源码

SDK 为开源：[github.com/orbitflare/orbitflare-evm-sdk-rs](https://github.com/orbitflare/orbitflare-evm-sdk-rs)
