HyperEVM 公共 RPC 限流与 eth_getLogs 区块范围

HyperEVM 公共 RPC:官方端点每 IP 每分钟 100 次请求、eth_getLogs 单次 50 个区块;BlockVectra 免 key 端点每 IP 3 次/秒,带 key 的 eth_getLogs 单次最多 1,000 个区块。更长的窗口分段查询并保存游标续跑。

直接答案

HyperEVM 公共 RPC 的限制取决于端点。官方公共 RPC(rpc.hyperliquid.xyz/evm)每 IP 每分钟最多 100 次 EVM JSON-RPC 请求,eth_getLogs 单次查询最多 50 个区块(出处见 Hyperliquid 官方限流文档与 JSON-RPC 文档,访问于 2026-10-11)。BlockVectra 的免 key HyperEVM 端点每 IP 每秒 3 次请求、突发 20,且不提供 eth_getLogs;带 API key 时,eth_getLogs 单次最多覆盖 1,000 个区块(含两端;来自 GET /v1/chains 中 hyperevm_mainnet 的 max_logs_block_range 与 public.rate_limit)。核对于 。跨度超限返回 HTTP 200、JSON-RPC -32602 与 logs_range_too_large,retryable: false(见错误目录);按 [from, min(from + max − 1, end)] 分段并保存 cursor(游标)以便断点续跑,成功后从末尾加一继续。遇到 429 的处理见官方公共 RPC 限流与 429及下方服务参数。

  • 第一步:免 key 读最新区块,运行下方的 curl 命令。
  • 完成标准:回填脚本为所选窗口的每个区段输出 fromBlock、toBlock 与 result 数组;空数组表示该段没有匹配日志。

链参数与接入方式。

这篇帮你完成什么任务

  • 试连 HyperEVM RPC:先用 viem 或 ethers 发起公开读取,再选择认证方法。
  • 回填有界日志窗口:遵守 HyperEVM eth_getLogs 区间上限,并根据返回错误判断是否重试。
  • 读取地址活动:携带 key 查询已索引交易与转账,检查返回的覆盖范围与新鲜度元数据。

三步任务:回填 HyperEVM 的有界日志窗口

先免 key 读最新区块,再创建 key,最后查询指定合约在有限区块窗口内的事件日志。

选择所需的合约与区块窗口。本任务只覆盖这个有界窗口,不承诺完整的合约历史。

1. 免 key 读最新区块

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 中 HyperEVM 的 public.url。公开端点的 public.methods 不包含 eth_getLogs,第 3 步需要 key。

2. 创建 API key

为这次回填创建 key,创建 key 并保存弹层中显示的 secret,用于 hyperevm_mainnet。

AI Agent 若要通过 HTTP 免浏览器操作,可参考程序化开户指南。将指南 URL 中合法的 ref 放入 POST /auth/siwe/login 的 JSON 请求体,替换示例中的 docs-signup;没有来源时省略。不要让用户把 key 贴进对话。

3. 用 key 回填日志

完整模板仓库:blockvectra/hyperevm-backfill

将下面的脚本保存为 hyperevm-task.ts,使用 Node.js 24 或更高版本运行,无需额外安装包。将 BLOCKVECTRA_API_KEY 设为已保存的 key,将 LOG_ADDRESS 设为要查询的事件合约地址;key 仅放在服务端或本地终端使用。

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 十六进制区块号。超过单次上限的窗口会拆成连续分段,每段跨度不超过公开上限。

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 的 method_weights[].cu_weight 读取 eth_getLogs 与 eth_blockNumber 权重。脚本向标准错误输出估算值:N × weight(eth_getLogs) + weight(eth_blockNumber),其中包含脚本携带 key 查询最新区块的一次调用。估算不含额外调用及可能计费的重试,实际结算见计费规则。CU 按调用估算,不按返回日志条数计算。

事件投递: 使用下方分段 HTTP 轮询,或按 webhook 推送指南将监听地址的事件发送到 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} 请求头
WebSocket(路径携带 Key)wss://api.blockvectra.com/v1/hyperevm_mainnet/{api_key}URL 路径中传入 API key
WebSocket(请求头携带 Key)wss://api.blockvectra.com/v1/hyperevm_mainnet传入 x-api-key: {api_key} 或 Authorization: Bearer {api_key} 请求头
WebSocket 订阅类型newHeads, logs—
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 读取 chain_id 与方法策略。免 key 读取使用目录中的 public.url,只调用 public.methods 列出的方法;公开 HTTP 可用不代表 WebSocket 可用。

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。

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。

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;公共免密钥端点未列出。部署前先确认所选端点允许该发送方法。将 DEPLOY_RPC_URL 设置为其带鉴权 HTTP URL,不要假定其他服务商套用 BlockVectra 的方法或日志跨度限额。签名前核对目标 chain ID。

: "${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 部署教程部署。部署地址需持有 EVM HYPE;部署大合约前先检查下方双区块要求。

HYPE、小块与大合约部署

HyperEVM 官方网络指南说明 gas 使用 HYPE,精度为 18 位(访问日期:2026-10-07)。部署地址需要在 HyperEVM 上持有 HYPE,HyperCore 余额不能直接当成 EVM gas 余额。转入资金时按该页链接的原生资产转移说明操作。

双区块指南说明小块出块快,大块出块较慢、用于容纳较大交易(访问日期:2026-10-07)。先估算部署 gas;超过小块预算时,部署地址需先成为已有 HyperCore 用户,再签名提交 Core action {"type":"evmUserModify","usingBigBlocks":true}。只调大交易 gas limit 不会选择大块。完成后恢复 usingBigBlocks=false,返回小块模式。

在支持这些方法的服务商上,用 eth_usingBigBlocks 检查地址模式,用 eth_bigBlockGasPrice 获取大块基础费用。官方 JSON-RPC 参考说明了这两个方法(访问日期:2026-10-07)。核对所选服务商的方法;BlockVectra 能力以 /v1/chains 为准。上方最小部署面向小合约,不会更改 Core 账户模式。

HyperCore 与 HyperEVM 数据

EVM RPC 用于合约、receipt 与日志;HyperCore 交易数据和操作使用 Core API。合约可通过预编译读取 Core 状态,通过 CoreWriter 发送操作,接入时参考官方交互指南(访问日期:2026-10-07)。EVM 日志不能替代 Core 订单簿或仓位查询。

HyperEVM 的系统交易(例如 HyperCore 到 HyperEVM 的转账)不在标准 eth_getBlockByNumber 返回里,官方 RPC 通过 eth_getSystemTxsByBlockNumber 与 eth_getSystemTxsByBlockHash 单独提供(见官方 JSON-RPC 文档,访问日期:2026-10-07)。BlockVectra 当前的 HyperEVM 区块、交易与 Data API 数据不包含系统交易,需要这类数据时直接用官方 RPC 的这两个方法。

处理官方错误 10055

HyperEVM 官方指南将 10055 定义为 Core/EVM 边界错误,包括 nonce、余额不足、重复哈希与替换交易费用不足(访问日期:2026-10-07)。先读取广播 RPC 的错误消息,再决定恢复动作:

  • Nonce: 对照 eth_getTransactionCount 与待处理交易,同一部署地址串行提交,并核对下一个 nonce。
  • 余额: 检查部署地址的 EVM HYPE 是否足够支付转账金额与 gas。
  • 重复哈希: 先查询已有交易和 receipt,再决定是否提交其他交易。
  • 替换费用: 核对已有 nonce 与费用,按广播服务商的替换策略提高费用;重复发送相同字节不会提高费用。

不能仅凭 10055 盲目重试。读取接口的错误另见 BlockVectra 错误参考。

官方公共 RPC 限流与 429

Hyperliquid 官方的限流说明规定,rpc.hyperliquid.xyz/evm 每个 IP 每分钟最多 100 次 EVM JSON-RPC 请求;其 JSON-RPC 文档还规定,eth_getLogs 单次最多查询 50 个区块、最多 4 个 topics。访问日期:2026-10-11。

遇到 HTTP 429 时暂停请求,优先遵循 Retry-After(秒数或 HTTP 日期);没有该响应头时采用有次数上限、带随机抖动的指数退避,重试同一个尚未成功的区段。降低并发与轮询频率,将日志查询拆成符合端点上限的小段;分段本身不会消除速率限制,多个客户端共用一个 IP 时还需协调请求频率。

使用 BlockVectra 的带 key 端点时,从 GET /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 突发容量——默认 100 CU/s、突发 400 CU;付费充值后每个 key 至少 2,000 CU/s、突发 10,000 CU,更高的自定义限额保持不变)。方法按 CU 权重计费。
    • 限额提升:首次付费充值后,解除账户级每秒调用上限;每个 key 仍有默认 CU 速率与突发上限。关于当前费率与计费规则,请参阅定价页。

回填历史数据:分段 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 响应体结构如下:

{
  "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(可选,设为 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、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

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"

实时追踪:轮询新区块

使用 HTTP transport 时轮询新区块,在 max_logs_block_range 内连续分段读取事件日志。仅当 /v1/chains 返回 ws=true 且 subscriptions 包含所需类型时选择 WebSocket。投递到 HTTPS 接收端可使用 webhook 推送。

验证已部署的 Hello 时,将 LOG_ADDRESS 设为合约地址。经广播 RPC 发送 ping(),再用本页分段脚本回填 receipt 所在区块;后续事件从最后成功区段继续读取。

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 次,间隔五秒,将每个新区间连续分段查询;错误会停止脚本,不推进失败区段。

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 或其他可重试错误按有界退避说明处理同一个未完成区段。

相关文档

下一步

最后更新:

本页目录