# HyperEVM：公共 RPC 限速下，历史回填和轮询怎么做

> 原文地址: https://docs.blockvectra.com/zh/guides/hyperevm-backfill/

在 HyperEVM 上构建应用或进行链上数据同步时，开发者通常需要处理两项基础任务：一是拉取历史事件日志与交易（历史回填，Backfill），二是持续监听新产生的区块与事件（实时轮询，Polling）。

官方公共 RPC 与第三方节点服务在调用速率、支持方法与历史状态保留机制上各有明确定义。本文基于官方公布的文档与接口规范，说明两者的事实参数，并提供分段回填、基于错误体判定重试、使用 Data API 替代海量扫描以及轮询新区块的具体实现。

## 现状与官方公共 RPC 限制

根据 Hyperliquid 官方开发文档，公共 RPC 的限制与运行特性如下：

1. **调用速率限制**：
   官方文档[限速与用户限额说明](https://hyperliquid.gitbook.io/Hyperliquid-docs/for-developers/api/rate-limits-and-user-limits)中明确规定，针对公共端点 `rpc.hyperliquid.xyz/evm`，每个 IP 地址每分钟最多允许 100 次 EVM JSON-RPC 请求（*Maximum of 100 EVM JSON-RPC requests per minute for rpc.hyperliquid.xyz/evm*）。
2. **WebSocket 支持情况**：
   官方文档 [HyperEVM 概述](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm)中明确指出，公共 RPC 端点 `rpc.hyperliquid.xyz/evm` 当前不支持 WebSocket JSON-RPC（*There is currently no websocket JSON-RPC support for the RPC at rpc.hyperliquid.xyz/evm but other RPC implementations may support it*）。
3. **网络标识与端点**：
   * 主网（Mainnet）：Chain ID 为 `999`，官方公共 JSON-RPC 端点为 `https://rpc.hyperliquid.xyz/evm`。
   * 测试网（Testnet）：Chain ID 为 `998`，官方公共 JSON-RPC 端点为 `https://rpc.hyperliquid-testnet.xyz/evm`。
4. **硬分叉与费用机制**：
   HyperEVM 基于 Cancun 硬分叉（支持 `MCOPY`、`TSTORE` 与 `TLOAD` 操作码），但不支持 blob 交易（blob transactions）。支持 EIP-1559 规范，但没有优先费用（no priority fees，优先费用同样被销毁并转入零地址，因此交易的 `maxFeePerGas` 与 `maxPriorityFeePerGas` 必须相等）。

在公共端点每分钟 100 次调用且不支持 WebSocket 的约束下，单 IP 若直接全量扫描历史日志或高频拉取最新区块，容易触发限流。

## BlockVectra 参数与服务规则

BlockVectra 为 HyperEVM 主网提供 JSON-RPC 与 REST 风格的 Data API。接口规则与速率限制均来自公开端点与套餐规范：

1. **链参数与日志范围**：
   根据公开接口 `GET /v1/chains` 中 `hyperevm_mainnet` 的记录：
   * **链标识（Slug）**：`hyperevm_mainnet`，Chain ID 为 `999`。
   * **`max_logs_block_range`**：以 `GET /v1/chains` 的 `max_logs_block_range` 字段为准。单次 `eth_getLogs` 请求的区块跨度（`toBlock − fromBlock + 1`）不能超过该上限。若跨度超出上限，平台返回 HTTP 200 与 JSON-RPC 错误码 `-32602`（`eth_getLogs block range too large: max <N> blocks`），该错误不计费。
   * **`state_window_blocks`**：以 `GET /v1/chains` 的 `state_window_blocks` 字段为准。状态读取方法（如 `eth_call`、`eth_getBalance`）受该字段声明的保留窗口约束（为 `null` 时表示全量保留，不设短期滚动窗口限制）。
   * **方法策略**：以 `methods.allow` 与 `methods.deny` 字段为准。常用读取方法（`eth_blockNumber`、`eth_getLogs`、`eth_call`、`eth_getBalance`、`eth_getBlockByNumber`、`eth_getTransactionReceipt` 等）均开放；过滤和订阅方法（`eth_subscribe`、`eth_unsubscribe`、`eth_newFilter`、`eth_newBlockFilter`）已禁用，调用禁用方法返回 `-32601`（不计费）。
2. **免费套餐速率限制与升级**：
   根据 `GET /v1/plans` 的数据：
   * **`free.max_calls_per_sec`**：以接口返回的免费套餐每秒调用次数上限为准（注明：该数值是账户内所有 API key 合计的平均值）。
   * **单 key 默认限制**：每个 API key 均有 CU 令牌桶（`cu_per_sec` 补充速率、`burst_cu` 突发容量——默认 400 CU/s、突发 1,600 CU）。方法按 CU 权重计费（例如 `eth_getLogs` 为 30 CU，`eth_blockNumber` 为 1 CU，地址交易与转账为 25 CU）。
   * **限额提升**：首次付费充值后，解除账户级每秒调用上限；每个 key 仍有默认 CU 速率与突发上限。关于当前费率与计费规则，请参阅[定价页](https://blockvectra.com/zh/pricing/)。

## 回填历史数据：分段 eth\_getLogs 与重试判断

回填历史事件日志时，需要将大跨度区间按目标链的 `max_logs_block_range` 上限分块请求。同时，当网络抖动或触发限流时，应依据错误响应中的 `retryable` 字段判断是否重试。

### 错误体中的 retryable 判定

在 BlockVectra 平台上，JSON-RPC 错误响应的 `error.data` 包含 `reason`、`docs_url` 以及 `retryable`（布尔值）：

* **`retryable: true`（可重试）**：例如瞬时过载（`overloaded`）、免费档每秒频次超限（`free_plan_call_limit`）、节点同步中（`node_syncing`）或底层暂时不可用（`upstream_unavailable`）。若 HTTP 响应头带有 `Retry-After`，应按指定秒数休眠；或采用带抖动的指数退避重试。
* **`retryable: false`（不可重试）**：例如区块跨度超限（`-32602` / `logs_range_too_large`）、请求参数错误（`invalid_params`）、缺少 API key（`missing_api_key`）或突发容量超限（`-32022` / `request_exceeds_burst`）。此类错误盲目重试无法成功，必须修正参数后再发起请求。

未带 API key 时的真实 401 响应体结构如下：

```json
{
  "jsonrpc": "2.0",
  "id": null,
  "error": {
    "code": -32024,
    "message": "missing API key: send it in the request path (/v1/{chain}/<api_key>) or in the x-api-key header",
    "data": {
      "reason": "missing_api_key",
      "docs_url": "https://docs.blockvectra.com/en/errors/#missing_api_key",
      "retryable": false
    }
  }
}
```

### 代码示例：分段拉取与重试

**cURL**

```bash
export BLOCKVECTRA_API_KEY="rgw_your_api_key"

# 1. 验证目标链 max_logs_block_range（公开接口，免认证）
curl -s "https://api.blockvectra.com/v1/chains"

# 2. 单次分段请求（区间不超过 max_logs_block_range，如 0x1 到 0x3e8）
curl -s "https://api.blockvectra.com/v1/hyperevm_mainnet" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "eth_getLogs",
    "params": [{
      "address": "0x2222222222222222222222222222222222222222",
      "fromBlock": "0x1",
      "toBlock": "0x3e8"
    }]
  }'
```


  **TypeScript (viem)**

```ts
import { createPublicClient, http, defineChain } from "viem";

const hyperevm = defineChain({
  id: 999,
  name: "HyperEVM",
  nativeCurrency: {
    decimals: 18,
    name: "Hyperliquid",
    symbol: "HYPE",
  },
  rpcUrls: {
    default: {
      http: ["https://api.blockvectra.com/v1/hyperevm_mainnet"],
    },
  },
});

const client = createPublicClient({
  chain: hyperevm,
  transport: http("https://api.blockvectra.com/v1/hyperevm_mainnet", {
    fetchOptions: {
      headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
    },
  }),
});

// 从公开端点动态获取区块跨度上限
const chainsRes = await fetch("https://api.blockvectra.com/v1/chains");
const { chains } = (await chainsRes.json()) as {
  chains: Array<{ chain: string; max_logs_block_range: number }>;
};
const chainInfo = chains.find((c) => c.chain === "hyperevm_mainnet");
if (!chainInfo || !chainInfo.max_logs_block_range) {
  throw new Error("Target chain or max_logs_block_range not found");
}
const maxChunk = BigInt(chainInfo.max_logs_block_range);

// 单块重试辅助函数：仅当 retryable 为 true 时退避重试
async function fetchLogsWithRetry(fromBlock: bigint, toBlock: bigint) {
  const maxRetries = 3;
  let attempt = 0;

  while (true) {
    try {
      return await client.getLogs({
        address: "0x2222222222222222222222222222222222222222",
        fromBlock,
        toBlock,
      });
    } catch (err: unknown) {
      attempt++;
      const errorObj = err as { data?: { retryable?: boolean } };
      const isRetryable = errorObj?.data?.retryable ?? false;

      if (isRetryable && attempt <= maxRetries) {
        await new Promise((resolve) => setTimeout(resolve, attempt * 1000));
        continue;
      }
      throw err;
    }
  }
}

// 分段回填主循环
const startBlock = 100000n;
const endBlock = 103000n;
const allLogs = [];

for (let cur = startBlock; cur <= endBlock; cur += maxChunk) {
  const chunkEnd = cur + maxChunk - 1n < endBlock ? cur + maxChunk - 1n : endBlock;
  const logs = await fetchLogsWithRetry(cur, chunkEnd);
  allLogs.push(...logs);
}

console.log(`回填完成，累计获取日志 ${allLogs.length} 条`);
```


## 使用 Data API 替代大量 getLogs 扫描

当业务需求是追踪某个具体地址的交互历史，或者特定地址的代币划转时，通过 `eth_getLogs` 扫描需要按目标链的 `max_logs_block_range` 逐段发起请求，并手动过滤 Transfer 事件与解码数据。

BlockVectra 的 Data API 针对 `hyperevm_mainnet` 提供了按地址索引的专用端点（路径见 `openapi/data.yaml`），单次查询可覆盖最宽 100,000 个区块并支持游标分页：

1. **地址交易列表**：`GET /v1/data/hyperevm_mainnet/addresses/{address}/transactions`
   * 查询参数：`from_block`（必填）、`to_block`（必填）、`direction`（可选：`from`, `to`, `any`，默认 `any`）、`clamp`（可选，设为 `true` 可在区间超出 100,000 块或超出最新最终确认区块时自动截断，避免返回 409 错误）、`limit`（可选，最大 500）、`cursor`（分页游标）。
2. **地址代币转账列表**：`GET /v1/data/hyperevm_mainnet/addresses/{address}/transfers`
   * 查询参数：`standard`（必填：`erc20` 或 `erc721`；OpenAPI 明确声明 `erc1155` 不支持按地址查询并返回 `422 no_coverage`）、`token`（可选合约地址）、`from_block`（必填）、`to_block`（必填）、`direction`（可选：`in`, `out`, `any`）、`clamp`（可选）、`limit`、`cursor`。

### 响应结构说明（依据 OpenAPI 规范）

两个端点均返回标准信封：

* `data`：数据对象数组。地址交易包含 `hash`、`block_number`、`block_timestamp`、`from`、`to`、`value`、`tx_index`、`gas_limit`、`gas_used`、`status` 等；代币转账包含 `token`、`standard`、`from`、`to`、`block_number`、`block_timestamp`、`tx_hash`、`tx_index`、`log_index`（ERC-20 包含 `amount`，ERC-721 包含 `token_id`）。
* `next_cursor`：存在下一页数据时返回的不透明游标字符串；若为最后一页则该字段不存在（非 `null`）。
* `meta`：包含 `chain`、`chain_slug`、`chain_external_id`、`as_of_block`、`finalized_block`、`coverage`（`full` 或 `partial`）以及 `refreshed_at`。

### 代码示例：调用 Data API

**cURL**

```bash
export BLOCKVECTRA_API_KEY="rgw_your_api_key"

# 1. 查询地址历史交易（支持 clamp=true 避免 409）
curl -s "https://api.blockvectra.com/v1/data/hyperevm_mainnet/addresses/0x2222222222222222222222222222222222222222/transactions?from_block=0&to_block=50000&clamp=true" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"

# 2. 查询地址 ERC-20 转账记录
curl -s "https://api.blockvectra.com/v1/data/hyperevm_mainnet/addresses/0x2222222222222222222222222222222222222222/transfers?standard=erc20&from_block=0&to_block=50000&clamp=true" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY"
```


  **TypeScript**

```ts
const apiKey = process.env.BLOCKVECTRA_API_KEY!;
const targetAddress = "0x2222222222222222222222222222222222222222";

let cursor: string | undefined;

do {
  const url = new URL(
    `https://api.blockvectra.com/v1/data/hyperevm_mainnet/addresses/${targetAddress}/transfers`,
  );
  url.searchParams.set("standard", "erc20");
  url.searchParams.set("from_block", "0");
  url.searchParams.set("to_block", "50000");
  url.searchParams.set("clamp", "true");
  if (cursor) {
    url.searchParams.set("cursor", cursor);
  }

  const res = await fetch(url, {
    headers: { "x-api-key": apiKey },
  });

  if (!res.ok) {
    throw new Error(`Data API HTTP ${res.status}`);
  }

  const body = (await res.json()) as {
    data: unknown[];
    next_cursor?: string;
  };

  console.log(`拉取到 ${body.data.length} 条代币转账`);
  cursor = body.next_cursor; // 尾页时字段缺失，自动退出循环
} while (cursor);
```


## 实时追踪：轮询新区块

由于 HyperEVM 官方公共 RPC 不提供 WebSocket JSON-RPC 支持，且 BlockVectra 的方法策略中亦禁用了 `eth_subscribe`（`methods.deny` 中声明），实时监听新区块与新事件需要采用轮询机制。

### 轮询实现逻辑

1. 定时调用轻量读取方法 `eth_blockNumber`（权重为 1 CU）获取链上最新高度。
2. 比较当前高度与已处理高度 `lastSeenBlock`。
3. 若 `currentBlock > lastSeenBlock`，则按区间 `[lastSeenBlock + 1, currentBlock]` 读取新产生的日志或区块数据，并更新 `lastSeenBlock`。
4. viem 的 `watchBlockNumber` 或 `watchBlocks` 在 HTTP transport 下原生使用轮询机制，可通过 `pollingInterval` 参数自定义轮询周期（例如 1000 毫秒）。

### 代码示例：轮询最新区块

**cURL**

```bash
export BLOCKVECTRA_API_KEY="rgw_your_api_key"

# 单次轮询最新区块高度（消耗 1 CU）
curl -s "https://api.blockvectra.com/v1/hyperevm_mainnet" \
  -H "Content-Type: application/json" \
  -H "x-api-key: $BLOCKVECTRA_API_KEY" \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
```


  **TypeScript (viem)**

```ts
import { createPublicClient, http, defineChain } from "viem";

const hyperevm = defineChain({
  id: 999,
  name: "HyperEVM",
  nativeCurrency: {
    decimals: 18,
    name: "Hyperliquid",
    symbol: "HYPE",
  },
  rpcUrls: {
    default: {
      http: ["https://api.blockvectra.com/v1/hyperevm_mainnet"],
    },
  },
});

const client = createPublicClient({
  chain: hyperevm,
  transport: http("https://api.blockvectra.com/v1/hyperevm_mainnet", {
    fetchOptions: {
      headers: { "x-api-key": process.env.BLOCKVECTRA_API_KEY! },
    },
  }),
  pollingInterval: 1000, // 轮询周期：1000 毫秒
});

// 使用 watchBlockNumber 持续监听最新区块
const unwatch = client.watchBlockNumber({
  onBlockNumber: (blockNumber) => {
    console.log("收到最新区块高度:", blockNumber);
  },
  onError: (error) => {
    console.error("轮询异常:", error);
  },
});
```


## 相关文档与规则参考

* 有关 `eth_getLogs` 区块跨度与切块规范的完整说明，请参阅 [eth\_getLogs 区块范围限制与分段查询](https://docs.blockvectra.com/zh/guides/getlogs-block-range/)。
* 有关 `eth_getLogs` 与 Data API 转账接口的覆盖范围、最终确认水位对比，请参阅 [节点近期数据与已索引全量历史：何时使用 eth\_getLogs，何时使用转账接口](https://docs.blockvectra.com/zh/guides/logs-vs-transfers/)。
* 有关 CU 计费单元、不计费错误码与重试规则，请参阅 [哪些情况不扣费：错误码与计费规则](https://docs.blockvectra.com/zh/guides/billing-rules/)。

## 下一步

* [浏览数据集目录](https://blockvectra.com/zh/data/)，查看 BlockVectra 索引的全部数据集。
* [查看免费额度与定价](https://blockvectra.com/zh/pricing/#free)，确认账户可用的方案。
* [登录控制台](https://console.blockvectra.com/zh/login/?next=%2Fzh%2Fkeys%2F)创建 API key。
