# HyperEVM RPC 限流與日誌回填

> Source: https://docs.blockvectra.com/zh-hant/guides/hyperevm-backfill/

## 直接答案

預設官方 HyperEVM 公共 RPC 的 `eth_getLogs` 單次查詢最多 50 個區塊（出處見官方 [JSON-RPC 文件](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/json-rpc)）。在 BlockVectra 上，認證的 `eth_getLogs` 請求單次最多涵蓋 1,000 個區塊（來自 [GET /v1/chains](https://api.blockvectra.com/v1/chains) 中的 `hyperevm_mainnet.max_logs_block_range`，包含兩端）。跨度超限回傳 HTTP 200、JSON-RPC `-32602` 與 `logs_range_too_large`，`retryable: false`（見[錯誤目錄](https://docs.blockvectra.com/en/errors/#logs_range_too_large)）；按 `[from, min(from + max − 1, end)]` 分段並儲存 cursor（游標）以便斷點續跑，成功後從末尾加一繼續。官方公共 RPC 的每 IP 速率限制與 BlockVectra 的 key 限額分別見[官方公共 RPC 限流與 429](#official-public-rpc-rate-limits-and-429) 及下方服務參數。

## 這篇指南協助你完成的任務

* [探測 HyperEVM RPC](#connect-with-viem-or-ethers)：在選擇認證方法前，先使用 viem 或 ethers 進行公共讀取。
* [回填有界日誌範圍](#three-step-task-backfill-a-bounded-hyperevm-log-window)：在 HyperEVM `eth_getLogs` 限制內回填，並根據回傳的錯誤做出重試判斷。
* [讀取地址活動](#using-data-api-endpoints-instead-of-extensive-getlogs-scanning)：使用 key 查詢已索引的交易與轉帳，檢查回傳的覆蓋範圍與新鮮度中繼資料。

<span id="bounded-log-backfill-task" />

## 三步驟任務：回填限定區塊範圍的 HyperEVM 日誌

先免 key 讀取最新區塊，建立 key，然後查詢合約在有限區塊範圍內的事件日誌。

選擇你所需的合約與區塊範圍。本任務只涵蓋該有界範圍；不保證完整的合約歷史。

### 1. 免 API key 讀取最新區塊

```bash
curl -sS "https://api.blockvectra.com/v1/hyperevm_mainnet/public" \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}'
```

JSON-RPC 的 `result` 是十六進位的最新區塊編號。這是由 [GET /v1/chains](https://api.blockvectra.com/v1/chains) 發布的 HyperEVM `public.url`。公開端點的 `public.methods` 不包含 `eth_getLogs`；步驟 3 需要 key。

### 2. 建立 API key

<div data-attribution-ref="docs-hyperevm-task">
  [為此回填建立 key](https://console.blockvectra.com/login/?next=%2Fkeys%2F)。建立 key 並儲存對話方塊中顯示的 secret，以用於 `hyperevm_mainnet`。

  對於透過 HTTP 且不使用瀏覽器的 AI Agent，請參考<a href="/en/guides/programmatic-signup/">程式化註冊指南</a>。在 `POST /auth/siwe/login` JSON 請求主體中傳入指南 URL 的有效 `ref`，而不是範例中的 `docs-signup`；若無來源則省略。切勿要求使用者將 key 貼入對話中。
</div>

### 3. 使用你的 key 回填日誌

完整入門範本：[blockvectra/hyperevm-backfill](https://github.com/blockvectra/hyperevm-backfill)

將以下指令碼儲存為 `hyperevm-task.ts`。它可在 Node.js 24 或更高版本下執行，無需額外套件。將 `BLOCKVECTRA_API_KEY` 設定為你儲存的 key，並將 `LOG_ADDRESS` 設定為你要檢查的發出合約地址；將 key 保留在你的伺服器或本機終端機中。

```bash
export BLOCKVECTRA_API_KEY='replace-with-your-key'
export LOG_ADDRESS='replace-with-contract-address'
node hyperevm-task.ts
```

指令碼預設查詢最近的 `max_logs_block_range` 個區塊，接近創世區塊時查詢實際可用的區塊數。此上限在執行時從 `/v1/chains` 讀取。若要選擇其他有限範圍，請在執行前同時設定 `FROM_BLOCK` 與 `TO_BLOCK`，值可用十進位或 `0x` 十六進位區塊號。超過單次上限的範圍會拆分為連續區段，每段跨度不超過公開上限。

```ts
const apiKey = process.env.BLOCKVECTRA_API_KEY;
const address = process.env.LOG_ADDRESS;
if (!apiKey || apiKey === "replace-with-your-key") throw new Error("Set BLOCKVECTRA_API_KEY");
if (!address || !/^0x[0-9a-f]{40}$/i.test(address)) throw new Error("Set LOG_ADDRESS to a contract address");

const fromBlock = process.env.FROM_BLOCK;
const toBlock = process.env.TO_BLOCK;
if ((fromBlock !== undefined || toBlock !== undefined) && (!fromBlock || !toBlock)) {
  throw new Error("Set both FROM_BLOCK and TO_BLOCK");
}

const chainsUrl = "https://api.blockvectra.com/v1/chains";
const rpcUrl = new URL("./hyperevm_mainnet", chainsUrl).href;
async function readJson(url: string) {
  const response = await fetch(url, { signal: AbortSignal.timeout(15_000) });
  if (!response.ok) throw new Error(`Metadata HTTP ${response.status}`);
  return response.json();
}
const catalog = await readJson(chainsUrl);
const chain = catalog.chains.find((item: { chain: string }) => item.chain === "hyperevm_mainnet");
if (!chain || !Number.isSafeInteger(chain.max_logs_block_range) || chain.max_logs_block_range <= 0) {
  throw new Error("Missing or invalid max_logs_block_range");
}
if (!chain.methods.allow.includes("eth_getLogs") || chain.methods.deny.includes("eth_getLogs")) {
  throw new Error("eth_getLogs is unavailable on this chain");
}
const maxRange = BigInt(chain.max_logs_block_range);
const plans = await readJson("https://console-api.blockvectra.com/v1/plans");
function weight(method: string): bigint {
  const row = plans.method_weights.find((item: { method: string }) => item.method === method);
  if (!row || !Number.isSafeInteger(row.cu_weight) || row.cu_weight <= 0) {
    throw new Error(`Missing or invalid CU weight for ${method}`);
  }
  return BigInt(row.cu_weight);
}
const headWeight = weight("eth_blockNumber");
const logsWeight = weight("eth_getLogs");

type RpcBody = {
  result?: unknown;
  error?: { code: number; data?: { retryable?: boolean } };
};
async function rpc(method: string, params: unknown[]): Promise<unknown> {
  for (let attempt = 0; attempt < 4; attempt++) {
    const response = await fetch(rpcUrl, {
      method: "POST",
      redirect: "error",
      headers: { "Content-Type": "application/json", "x-api-key": apiKey! },
      body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
      signal: AbortSignal.timeout(15_000),
    });
    const body = await response.json() as RpcBody;
    if (response.ok && !body.error && body.result !== undefined) return body.result;
    if (body.error?.data?.retryable !== true || attempt === 3) {
      throw new Error(`RPC failed: HTTP ${response.status}, code ${body.error?.code ?? "unknown"}`);
    }
    const retryAfter = response.headers.get("Retry-After");
    const delay = retryAfter === null ? 0 : /^\d+$/.test(retryAfter)
      ? Number(retryAfter) * 1000 : Date.parse(retryAfter) - Date.now();
    if (delay > 30_000) throw new Error("Retry-After exceeds 30 seconds; rerun later");
    const backoff = 1000 * 2 ** attempt + Math.random() * 250;
    await new Promise((resolve) => setTimeout(resolve, Math.max(backoff, Number.isFinite(delay) ? delay : 0)));
  }
  throw new Error("Retry limit reached");
}
function block(value: string): bigint {
  if (!/^(0x[0-9a-f]+|[0-9]+)$/i.test(value)) throw new Error("Invalid block number");
  return BigInt(value);
}
const head = await rpc("eth_blockNumber", []);
if (typeof head !== "string" || !/^0x[0-9a-f]+$/i.test(head)) throw new Error("Invalid chain head");
const latest = block(head);
const end = toBlock ? block(toBlock) : latest;
const start = fromBlock ? block(fromBlock)
  : end >= maxRange - 1n ? end - maxRange + 1n : 0n;
if (start > end || end > latest) throw new Error("Require 0 <= FROM_BLOCK <= TO_BLOCK <= latest");
const chunks = (end - start + maxRange) / maxRange;
console.error(`Window ${start}..${end}; chunks=${chunks}; estimated CU=${headWeight + chunks * logsWeight}`);

const hex = (value: bigint) => `0x${value.toString(16)}`;
for (let from = start; from <= end; from += maxRange) {
  const to = from + maxRange - 1n < end ? from + maxRange - 1n : end;
  const result = await rpc("eth_getLogs", [{ address, fromBlock: hex(from), toBlock: hex(to) }]);
  if (!Array.isArray(result)) throw new Error("Expected eth_getLogs result array");
  console.log(JSON.stringify({ fromBlock: hex(from), toBlock: hex(to), result }));
}
```

請求按順序執行。僅當 JSON-RPC 錯誤中的 `error.data.retryable` 為 `true` 時重試，每個請求最多嘗試四次，使用指數退避與抖動，並讀取 `Retry-After` 的秒數或 HTTP 日期。等待超過 30 秒時指令碼停止，可稍後重新執行。網路失敗、逾時、回應格式異常及不可重試錯誤立即停止；指令碼以失敗狀態退出，不報告完整回填成功。

標準輸出的每一行包含一個區段的 `fromBlock`、`toBlock` 與 `result` 陣列。`result: []` 表示該段沒有相符的日誌。每條日誌可讀取以下欄位：

| 欄位                                              | 意義                                  |
| ----------------------------------------------- | ----------------------------------- |
| `address`                                       | 發出事件的合約地址。                          |
| `blockNumber`、`blockHash`                       | 日誌所在區塊，區塊編號為十六進位。                   |
| `transactionHash`、`transactionIndex`、`logIndex` | 交易與日誌位置，索引為十六進位。                    |
| `topics`、`data`                                 | 事件的索引參數與 ABI 編碼的非索引參數，需使用合約 ABI 解碼。 |
| `removed`                                       | 日誌是否因鏈重組而被移除。                       |

最新區塊不代表已最終確認。需要穩定的歷史區間時，應選用應用程式認可的已確認 `TO_BLOCK`，並處理鏈重組。

若區間包含 `B = TO_BLOCK − FROM_BLOCK + 1` 個區塊，公開單次上限為 `L`，區段數就是 `N = ceil(B / L)`。從 [GET /v1/plans](https://console-api.blockvectra.com/v1/plans) 的 `method_weights[].cu_weight` 讀取 `eth_getLogs` 與 `eth_blockNumber` 權重。指令碼向標準錯誤輸出估算值：`N × weight(eth_getLogs) + weight(eth_blockNumber)`，其中包含指令碼攜帶 key 查詢最新區塊的一次呼叫。估算不含額外呼叫及可能計費的重試，實際結算見[計費規則](https://docs.blockvectra.com/en/guides/billing-rules/)。CU 按呼叫估算，不按回傳日誌條數計算。

**事件推送：** 使用下方分段 HTTP 輪詢，或按 [Webhook 推送指南](https://docs.blockvectra.com/en/guides/webhook-push/)將監聽地址的事件發送到 HTTPS 接收端。**GET /v1/push/chains 查看支援的鏈**及確認數參數，請求攜帶 `x-api-key`。簽章校驗、去重與重放見該指南。Webhook 推送與 WebSocket 訂閱是獨立入口，後者按 `/v1/chains` 的 `ws` 與 `subscriptions` 判斷。

## 使用 viem 或 ethers 連線

| 參數 / 端點 | 取值 / 範本 | 驗證方式 |
|---|---|---|
| Chain ID（EIP-155） | `999` | — |
| JSON-RPC（路徑攜帶 Key） | `POST https://api.blockvectra.com/v1/hyperevm_mainnet/{api_key}` | URL 路徑中傳入 API key |
| JSON-RPC（請求標頭攜帶 Key） | `POST https://api.blockvectra.com/v1/hyperevm_mainnet` | 傳入 x-api-key: {api_key} 請求標頭 |
| Data API 基址 | `GET https://api.blockvectra.com/v1/data/hyperevm_mainnet/…` | 傳入 x-api-key: {api_key} 請求標頭 |
| 公開狀態端點 | `GET https://api.blockvectra.com/v1/status` | 無需驗證（公開） |

開發者與 AI Agent 可使用同一份伺服器端參數。使用 Node.js 24 或更新版本、viem 2 或 ethers 6，先使用公開讀取；呼叫帶 key 方法時，透過環境變數安全設定 `BLOCKVECTRA_API_KEY`。切勿將 key 或帶 key 的 RPC URL 放入瀏覽器程式碼、日誌或版本控制中。

儲存為 `network.mjs`。指令碼從 [GET /v1/chains](https://api.blockvectra.com/v1/chains) 讀取 `chain_id` 與方法策略。免 key 讀取使用目錄中的 `public.url`，只呼叫 `public.methods` 列出的方法；公開 HTTP 可用不代表 WebSocket 可用。

```js
const chainSlug = process.env.BLOCKVECTRA_CHAIN ?? 'hyperevm_mainnet';
const key = process.env.BLOCKVECTRA_API_KEY;
const catalogUrl = 'https://api.blockvectra.com/v1/chains';
const response = await fetch(catalogUrl, { signal: AbortSignal.timeout(15_000) });
if (!response.ok) throw new Error(`Chains HTTP ${response.status}`);
const catalog = await response.json();
export const chainInfo = catalog.chains.find(item => item.chain === chainSlug);
if (!chainInfo || !Number.isSafeInteger(chainInfo.chain_id) || chainInfo.chain_id <= 0) {
  throw new Error('Missing chain or chain_id');
}
export function allows(method) {
  const matches = pattern => pattern.endsWith('*')
    ? method.startsWith(pattern.slice(0, -1)) : pattern === method;
  if (!key) return (chainInfo.public?.methods ?? []).some(matches);
  return (chainInfo.methods?.allow ?? []).some(matches)
    && !(chainInfo.methods?.deny ?? []).some(matches);
}
if (!allows('eth_chainId')) throw new Error('eth_chainId is unavailable');
export const rpcUrl = key
  ? new URL(`./${chainSlug}/${encodeURIComponent(key)}`, catalogUrl).href
  : chainInfo.public?.url;
if (!rpcUrl) throw new Error('Public RPC is unavailable; set BLOCKVECTRA_API_KEY');
```

儲存為 `viem-client.mjs`，透過 `npm install viem@2` 安裝後執行 `node viem-client.mjs`。

```js
import { createPublicClient, defineChain, http } from 'viem';
import { chainInfo, rpcUrl } from './network.mjs';

export const chain = defineChain({
  id: chainInfo.chain_id,
  name: chainInfo.name,
  nativeCurrency: { name: 'HYPE', symbol: 'HYPE', decimals: 18 },
  rpcUrls: { default: { http: [rpcUrl] } },
});
export const client = createPublicClient({ chain, transport: http(rpcUrl) });
if (await client.getChainId() !== chain.id) throw new Error('RPC chain ID mismatch');
console.error(await client.getBlockNumber());
```

使用 ethers 時，儲存為 `ethers-client.mjs`，透過 `npm install ethers@6` 安裝後執行 `node ethers-client.mjs`。

```js
import { JsonRpcProvider } from 'ethers';
import { chainInfo, rpcUrl } from './network.mjs';

const provider = new JsonRpcProvider(rpcUrl, chainInfo.chain_id, { batchMaxCount: 1 });
const network = await provider.getNetwork();
if (network.chainId !== BigInt(chainInfo.chain_id)) throw new Error('RPC chain ID mismatch');
console.log(await provider.getBlockNumber());
provider.destroy();
```

## 使用 Foundry 或 Hardhat 部署

目前 `hyperevm_mainnet` 目錄為 `ws=false`，`methods.allow` 未列出 `eth_sendRawTransaction`。BlockVectra 用於讀取；部署需要支援廣播的 RPC。將 `DEPLOY_RPC_URL` 設定為該服務商的帶驗證 HTTP URL，不要套用 BlockVectra 的方法或日誌跨度限額。簽名前核對目標 chain ID。

```bash
: "${DEPLOY_RPC_URL:?Set a broadcasting RPC URL}"
export RPC_URL="$DEPLOY_RPC_URL"
export CHAIN_ID="$(node --input-type=module -e "import { chainInfo } from './network.mjs'; console.log(chainInfo.chain_id)")"
```

接著按共用的 [Foundry 或 Hardhat 部署教學](https://docs.blockvectra.com/en/guides/deploy-contract/)部署。部署地址需持有 EVM HYPE；部署大型合約前先檢查下方雙區塊要求。

## HYPE、小區塊與大型合約部署

[HyperEVM 官方網路指南](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm)說明 gas 使用 HYPE，精度為 18 位（存取日期：2026-10-07）。部署地址需要在 HyperEVM 上持有 HYPE，HyperCore 餘額不能直接當作 EVM gas 餘額。轉入資金時按該頁連結的原生資產轉移說明操作。

[雙區塊架構指南](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/dual-block-architecture)說明小區塊出塊快，大區塊出塊較慢、用於容納較大交易（存取日期：2026-10-07）。先估算部署 gas；超過小區塊預算時，部署地址需先成為已有 HyperCore 使用者，再簽名提交 Core action `{"type":"evmUserModify","usingBigBlocks":true}`。只調大交易 gas limit 不會選擇大區塊。完成後復原 `usingBigBlocks=false`，返回小區塊模式。

在支援這些方法的服務商上，用 `eth_usingBigBlocks` 檢查地址模式，用 `eth_bigBlockGasPrice` 取得大區塊基礎費用。[官方 JSON-RPC 參考](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/json-rpc)說明了這兩個方法（存取日期：2026-10-07）。核對所選服務商的方法；BlockVectra 能力以 `/v1/chains` 為準。上方最小部署面向小型合約，不會變更 Core 帳戶模式。

## HyperCore 與 HyperEVM 資料

EVM RPC 用於合約、收據（receipt）與日誌；HyperCore 交易資料和操作使用 Core API。合約可透過預編譯讀取 Core 狀態，透過 CoreWriter 發送操作，接入時參考[官方互動指南](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/interacting-with-hypercore)（存取日期：2026-10-07）。EVM 日誌不能替代 Core 訂單簿或倉位查詢。

HyperEVM 的系統交易（例如 HyperCore 到 HyperEVM 的轉帳）不在標準 `eth_getBlockByNumber` 回傳中，官方 RPC 透過 `eth_getSystemTxsByBlockNumber` 與 `eth_getSystemTxsByBlockHash` 單獨提供（見[官方 JSON-RPC 文件](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/json-rpc)，存取日期：2026-10-07）。BlockVectra 目前的 HyperEVM 區塊、交易與 Data API 資料不包含系統交易，需要此類資料時直接使用官方 RPC 的這兩個方法。

## 處理官方錯誤 10055

[HyperEVM 官方指南](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm)將 `10055` 定義為 Core/EVM 邊界錯誤，包括 nonce、餘額不足、重複雜湊與替換交易費用不足（存取日期：2026-10-07）。先讀取廣播 RPC 的錯誤訊息，再決定復原動作：

* **Nonce：** 對照 `eth_getTransactionCount` 與待處理交易，同一部署地址循序提交，並核對下一個 nonce。
* **餘額：** 檢查部署地址的 EVM HYPE 是否足夠支付轉帳金額與 gas。
* **重複雜湊：** 先查詢既有交易和收據，再決定是否提交其他交易。
* **替換費用：** 核對既有 nonce 與費用，按廣播服務商的替換策略提高費用；重複發送相同位元組不會提高費用。

不能僅憑 `10055` 盲目重試。錯誤與其復原指引另見 [BlockVectra 錯誤參考](https://docs.blockvectra.com/en/errors/)。

## 官方公共 RPC 限流與 429

Hyperliquid 官方的[限流說明](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/rate-limits-and-user-limits)規定，`rpc.hyperliquid.xyz/evm` 每個 IP 每分鐘最多 100 次 EVM JSON-RPC 請求；其 [JSON-RPC 文件](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/json-rpc)還規定，`eth_getLogs` 單次最多查詢 50 個區塊、最多 4 個 topics。存取日期：2026-10-07。

遇到 HTTP 429 時暫停請求，優先遵循 `Retry-After`（秒數或 HTTP 日期）；沒有該回應標頭時採用有次數上限、帶隨機抖動的指數退避，重試同一個尚未成功的區段。降低並行與輪詢頻率，將日誌查詢拆成符合端點上限的小段；分段本身不會消除速率限制，多個用戶端共用一個 IP 時還需協調請求頻率。

使用 BlockVectra 的帶 key 端點時，從 [GET /v1/chains](https://api.blockvectra.com/v1/chains) 讀取 `hyperevm_mainnet` 的 `max_logs_block_range`、`methods.allow` 與 `methods.deny`，不要套用官方公共 RPC 的區塊跨度或每分鐘請求數。請求速率另受 key 的 `cu_per_sec`、`burst_cu` 與免費方案呼叫頻率約束（見下節）；429 時按 `error.data.reason` 與 `retryable` 處理，`request_exceeds_burst` 需要拆小請求，不能原樣退避重試。

## 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`**：上限 25 次/秒，帳戶內所有 key、所有鏈和 Data API 共享。
   * **單 key 預設限制**：每個 API key 均有 CU 權杖桶（`cu_per_sec` 補充速率、`burst_cu` 突發容量——預設 400 CU/s、突發 1,600 CU）。方法按 CU 權重計費。
   * **限額提升**：首次付費儲值後，解除帳戶級每秒呼叫上限；每個 key 仍有預設 CU 速率與突發上限。關於目前費率與計費規則，請參閱[定價頁](https://blockvectra.com/en/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`，應遵循其指定的秒數或 HTTP 日期；否則採用帶抖動的指數退避重試。
* **`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
    }
  }
}
```

## 使用 Data API 端點取代大範圍 getLogs 掃描

當應用程式需要追蹤某個特定地址的交易歷史或代幣轉移時，透過 `eth_getLogs` 掃描需要按目標鏈的 `max_logs_block_range` 逐段發起請求，並解析原始 Transfer 事件日誌。

BlockVectra 的 Data API 針對 `hyperevm_mainnet` 提供了按地址索引的專用端點，單次查詢可涵蓋最寬 100,000 個區塊並支援游標分頁：

1. **地址交易清單**：`GET /v1/data/hyperevm_mainnet/addresses/{address}/transactions`
   * 查詢參數：`from_block`（必填）、`to_block`（必填）、`direction`（選填：`from`, `to`, `any`，預設 `any`）、`clamp`（選填的布林字串，預設為 `false`；設為 `true` 時，區間超出 100,000 塊或高於 `as_of_block` 會自動截斷，避免回傳 409 錯誤）、`limit`（選填，最大 500）、`cursor`（分頁游標）。
2. **地址代幣轉帳清單**：`GET /v1/data/hyperevm_mainnet/addresses/{address}/transfers`
   * 查詢參數：`standard`（必填：`erc20` 或 `erc721`；`erc1155` 不支援按地址查詢並回傳 `422 no_coverage`）、`token`（選填合約地址）、`from_block`（必填）、`to_block`（必填）、`direction`（選填：`in`, `out`, `any`）、`clamp`（選填）、`limit`、`cursor`。

### 回應結構說明

兩個端點均使用標準回應結構：

* `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`、`safe_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);
```


## 即時追蹤：輪詢新區塊

使用 HTTP transport 時輪詢新區塊，在 `max_logs_block_range` 內連續分段讀取事件日誌。僅當 `/v1/chains` 回傳 `ws=true` 且 `subscriptions` 包含所需類型時選擇 WebSocket。推送至 HTTPS 接收端可使用 [Webhook 推送](https://docs.blockvectra.com/en/guides/webhook-push/)。

驗證已部署的 `Hello` 合約時，將 `LOG_ADDRESS` 設為其地址。經廣播 RPC 發送 `ping()`，再用本頁分段指令碼回填收據所在區塊；後續事件從最後成功區段繼續讀取。

```bash
cast send "$CONTRACT_ADDRESS" "ping()" --rpc-url "$DEPLOY_RPC_URL" \
  --private-key "$DEPLOYER_PRIVATE_KEY"
```

### 輪詢實作邏輯

1. 定時呼叫輕量讀取方法 `eth_blockNumber` 取得鏈上最新高度。
2. 比較目前高度與已處理高度 `lastSeenBlock`。
3. 若 `currentBlock > lastSeenBlock`，將 `[lastSeenBlock + 1, currentBlock]` 拆成最多 `max_logs_block_range` 的區段。每段處理成功後才持久化 `lastSeenBlock`；失敗時重試未完成區段。按 `(blockHash, transactionHash, logIndex)` 去重，重新連線時回掃重疊區間以核對重組。
4. viem 的 `watchBlockNumber` 或 `watchBlocks` 在 HTTP transport 下原生使用輪詢機制，可透過 `pollingInterval` 參數自訂輪詢週期（例如 1000 毫秒）。

### 分段輪詢事件日誌

儲存為 `poll-logs.mjs`，與 `network.mjs`、`viem-client.mjs` 放在一起。設定 `BLOCKVECTRA_API_KEY`、`LOG_ADDRESS` 與 `FROM_BLOCK` 後執行 `node poll-logs.mjs`。這個有限範例輪詢鏈頭 12 次，間隔五秒，將每個新區間連續分段查詢；錯誤會停止指令碼，不推進失敗區段。

```js
import { isAddress } from 'viem';
import { client } from './viem-client.mjs';
import { chainInfo, allows } from './network.mjs';

const address = process.env.LOG_ADDRESS;
const start = process.env.FROM_BLOCK;
if (!address || !isAddress(address)) throw new Error('Set LOG_ADDRESS');
if (!/^(0x[0-9a-f]+|[0-9]+)$/i.test(start ?? '')) throw new Error('Set FROM_BLOCK');
if (!allows('eth_getLogs')) throw new Error('eth_getLogs requires an available keyed endpoint');
if (!Number.isSafeInteger(chainInfo.max_logs_block_range) || chainInfo.max_logs_block_range <= 0) {
  throw new Error('Invalid max_logs_block_range');
}
const max = BigInt(chainInfo.max_logs_block_range);
let from = BigInt(start);
for (let poll = 0; poll < 12; poll++) {
  const head = await client.getBlockNumber();
  while (from <= head) {
    const to = from + max - 1n < head ? from + max - 1n : head;
    const logs = await client.getLogs({ address, fromBlock: from, toBlock: to });
    console.log(JSON.stringify({ from: from.toString(), to: to.toString(), logs },
      (_, value) => typeof value === 'bigint' ? value.toString() : value));
    from = to + 1n;
  }
  if (poll < 11) await new Promise(resolve => setTimeout(resolve, 5_000));
}
```

每條輸出記錄一個已完成區段，繼續執行時將 `FROM_BLOCK` 設為其 `to + 1`。持久化取用端需一併儲存事件與游標、去重，並按上方步驟核對重組。429 或其他可重試錯誤按有界退避說明處理同一個未完成區段。

## 相關指南

* 在 [HyperEVM 鏈頁面](https://blockvectra.com/en/chains/hyperevm_mainnet/)尋找公開 RPC URL、支援的方法與目前限制。
* 有關 `eth_getLogs` 跨度與切塊演算法的完整規則，請參閱 [eth\_getLogs 區塊範圍限制與分段查詢](https://docs.blockvectra.com/en/guides/getlogs-block-range/)。
* 有關將 `eth_getLogs` 與 Data API 轉帳進行比較、理解 `as_of_block` 邊界以及 `safe_block` / `finalized_block` 標記，請參閱 [eth\_getLogs 與已索引轉帳：覆蓋範圍與最終性](https://docs.blockvectra.com/en/guides/logs-vs-transfers/)。
* 有關 CU 計量、不計費錯誤與重試的詳細資訊，請參閱[哪些情況不扣費：錯誤碼與計費規則](https://docs.blockvectra.com/en/guides/billing-rules/)。

## 下一步

* [瀏覽資料集目錄](https://blockvectra.com/en/data/)，查看 BlockVectra 索引的全部資料集。
* [查看免費方案與定價](https://blockvectra.com/en/pricing/#free)，確認你的帳戶包含的內容。
* [登入控制台](https://console.blockvectra.com/login/?next=%2Fkeys%2F)建立 API key。
