HyperEVM RPC 속도 제한 및 로그 백필

HyperEVM RPC 속도 제한 및 429 응답 처리, 유한 범위의 인증된 eth_getLogs 쿼리, 커서 저장 및 누락된 활동 복구.

핵심 요약

기본 공식 HyperEVM 퍼블릭 RPC는 eth_getLogs 쿼리당 최대 50개의 블록을 허용합니다(출처: Hyperliquid 공식 JSON-RPC 문서). BlockVectra에서는 인증된 eth_getLogs 요청 시 쿼리당 최대 1,000개의 블록(GET /v1/chains의 hyperevm_mainnet.max_logs_block_range, 양 끝점 포함)을 지원합니다. 이 범위를 초과하면 HTTP 200, JSON-RPC -32602 및 logs_range_too_large가 retryable: false와 함께 반환됩니다(오류 카탈로그 참조). 범위를 [from, min(from + max − 1, end)]로 분할하고 커서를 저장한 뒤 성공 후 끝 블록에 1을 더한 위치로 진행하여 실행을 재개하세요. 공식 퍼블릭 RPC의 IP별 속도 제한과 BlockVectra의 키 제한은 공식 퍼블릭 RPC 속도 제한 및 429 및 아래의 서비스 파라미터에 별도로 설명되어 있습니다.

이 가이드를 통해 수행할 수 있는 작업

  • viem 또는 ethers로 연결: 인증된 메서드를 선택하기 전에 viem 또는 ethers로 퍼블릭 읽기를 테스트합니다.
  • 유한한 로그 윈도우 백필: HyperEVM eth_getLogs 한도 내에서 로그를 백필하고 반환된 오류에 따라 재시도를 결정합니다.
  • 주소 활동 읽기: API key를 사용하여 인덱싱된 트랜잭션 및 전송 내역을 조회하고 반환된 적용 범위 및 최신성 메타데이터를 확인합니다.

3단계 작업: 유한한 HyperEVM 로그 윈도우 백필

API key 없이 최신 블록을 조회하고, 키를 생성한 후, 유한한 블록 윈도우에서 컨트랙트의 이벤트 로그를 가져옵니다.

필요한 컨트랙트와 블록 윈도우를 선택하세요. 이 작업은 지정된 유한한 윈도우만 다루며 전체 컨트랙트 이력을 보장하지는 않습니다.

1. API 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는 16진수 형태의 최신 블록 번호입니다. 이는 GET /v1/chains에 고시된 HyperEVM의 public.url입니다. 퍼블릭 엔드포인트의 public.methods에는 eth_getLogs가 포함되지 않으므로 3단계를 수행하려면 키가 필요합니다.

2. API key 생성

이번 백필을 위한 키 생성. 키를 생성하고 다이얼로그에 표시된 시크릿을 저장하여 hyperevm_mainnet에 사용하세요.

브라우저 없이 HTTP를 사용하는 AI Agent의 경우 프로그래밍 방식 회원가입 가이드를 따르세요. 예시의 docs-signup 대신 가이드 URL의 유효한 ref를 POST /auth/siwe/login JSON 본문에 전달하세요(제공되지 않는 경우 생략). 사용자에게 키를 채팅에 붙여넣도록 요청하지 마세요.

3. API key로 로그 백필

전체 스타터 템플릿: blockvectra/hyperevm-backfill

다음 스크립트를 hyperevm-task.ts로 저장하세요. Node.js 24 이상에서 추가 패키지 없이 실행됩니다. BLOCKVECTRA_API_KEY에 저장된 키를, LOG_ADDRESS에 검사할 이벤트 발생 컨트랙트 주소를 설정하세요. 키는 서버나 로컬 터미널에 안전하게 보관하세요.

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 모두 10진수 또는 0x 16진수 블록 번호로 설정하세요. 더 큰 윈도우는 고시된 한도를 초과하지 않는 연속적인 청크로 분할됩니다.

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일 때만 재시도되며, 요청당 최대 4회 시도, 지수 백오프 및 지터 적용, 그리고 초 단위 또는 HTTP 날짜 형식의 Retry-After 헤더를 지원합니다. 30초 이상의 대기 시간이 발생하면 나중에 다시 실행할 수 있도록 스크립트가 중단됩니다. 네트워크 장애, 시간 초과, 잘못된 응답, 재시도 불가능한 오류는 즉시 중단되며, 스크립트는 백필 완료를 보고하지 않고 실패로 종료됩니다.

표준 출력의 각 줄에는 한 청크의 fromBlock, toBlock 및 result 배열이 포함됩니다. result: []는 해당 청크에 일치하는 로그가 없음을 의미합니다. 각 로그에서 다음 필드를 확인하세요:

필드의미
address이벤트를 발생시킨 컨트랙트 주소입니다.
blockNumber, blockHash로그가 포함된 블록 정보이며, 블록 번호는 16진수입니다.
transactionHash, transactionIndex, logIndex트랜잭션 및 로그의 위치 정보이며 인덱스는 16진수입니다.
topics, data인덱싱된 이벤트 인자 및 ABI 인코딩된 비인덱싱 인자이며 컨트랙트 ABI로 디코딩합니다.
removed체인 리오그로 인해 로그가 제거되었는지 여부입니다.

최신 블록이 완결성(finality) 마커는 아닙니다. 안정적인 이력 윈도우가 필요한 경우 애플리케이션에서 확정된 TO_BLOCK을 지정하고 체인 리오그를 처리하세요.

B = TO_BLOCK − FROM_BLOCK + 1개의 블록으로 구성된 윈도우와 고시된 한도 L에 대해 청크 수는 N = ceil(B / L)입니다. eth_getLogs 및 eth_blockNumber에 대한 method_weights[].cu_weight는 GET /v1/plans에서 확인하세요. 스크립트는 키를 통한 최신 블록 조회를 포함하여 N × weight(eth_getLogs) + weight(eth_blockNumber) 추정치를 표준 에러로 출력합니다. 여기에는 추가 호출 및 과금 대상 재시도가 제외되어 있습니다. 정산 세부사항은 과금 규칙을 참조하세요. CU는 반환된 로그 수가 아닌 호출 횟수에 따라 부과됩니다.

이벤트 전달: 아래의 청크 분할 HTTP 폴링을 사용하거나 Webhook 푸시를 사용하여 감시 대상 주소 이벤트를 HTTPS 수신 엔드포인트로 전송하세요. GET /v1/push/chains는 지원 체인 목록과 확인 설정을 반환하며, x-api-key로 인증합니다. Webhook 서명, 중복 제거 및 리플레이는 해당 가이드에서 다룹니다. Webhook 푸시는 WebSocket 구독(/v1/chains의 ws 및 subscriptions)과 분리되어 있습니다.

viem 또는 ethers로 연결

파라미터 / 엔드포인트값 / 템플릿인증 방식
Chain ID (EIP-155)999—
JSON-RPC (경로 키)POST https://api.blockvectra.com/v1/hyperevm_mainnet/{api_key}URL 경로에 API key 전달
JSON-RPC (헤더 키)POST https://api.blockvectra.com/v1/hyperevm_mainnetx-api-key: {api_key} 헤더 전달
Data API 기본 URLGET 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을 사용하고 퍼블릭 읽기부터 시작하세요. 인증된 메서드를 사용할 때는 환경 변수에 BLOCKVECTRA_API_KEY를 안전하게 설정하세요. 키와 키가 포함된 RPC URL이 브라우저 코드, 로그, 버전 관리 시스템에 노출되지 않도록 주의하세요.

이를 network.mjs로 저장하세요. GET /v1/chains에서 chain_id와 메서드 정책을 읽어옵니다. 키 없는 읽기의 경우 카탈로그의 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이 나열되어 있지 않습니다. 읽기 작업에는 BlockVectra를 사용하고, 배포에는 브로드캐스팅을 지원하는 RPC를 사용하세요. DEPLOY_RPC_URL을 해당 공급자의 인증된 HTTP URL로 설정하세요. 해당 공급자가 BlockVectra와 동일한 메서드 또는 로그 범위 제한을 가질 것으로 가정하지 마세요. 서명하기 전에 선택한 체인 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의 공식 네트워크 가이드에 따르면 HYPE이 가스로 사용되며 소수점 18자리를 갖습니다(접속일: 2026-10-07). 배포자가 HyperEVM 상에 HYPE을 보유하고 있는지 확인하세요. HyperCore 잔액만으로는 EVM 가스 잔액이 되지 않습니다. 자금을 이동할 때는 연결된 네이티브 전송 안내를 따르세요.

듀얼 블록 가이드에서는 빠른 소형 블록(small block)과 더 큰 트랜잭션을 위한 느린 대형 블록(big block)을 설명합니다(접속일: 2026-10-07). 배포 가스를 먼저 추정하세요. 소형 블록 예산을 초과하는 배포의 경우 배포자가 기존 HyperCore 사용자여야 하며 Core 액션 {"type":"evmUserModify","usingBigBlocks":true}에 서명해야 합니다. 트랜잭션 가스 한도만 크게 설정한다고 해서 대형 블록이 선택되지는 않습니다. 소형 블록으로 돌아가려면 배포 후 usingBigBlocks=false로 복원하세요.

이를 지원하는 공급자에서는 eth_usingBigBlocks를 사용하여 주소 모드를 확인하고 eth_bigBlockGasPrice로 대형 블록 기본 수수료를 확인하세요. 공식 JSON-RPC 레퍼런스에 이러한 메서드가 문서화되어 있습니다(접속일: 2026-10-07). 선택한 공급자의 메서드를 확인하세요. BlockVectra의 경우 /v1/chains를 확인하세요. 위의 최소 배포 예제는 소형 컨트랙트를 대상으로 하며 Core 계정 모드를 변경하지 않습니다.

HyperCore 및 HyperEVM 데이터

EVM RPC는 컨트랙트, 영수증, 로그를 제공합니다. 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를 nonce, 잔액 부족, 중복 해시, 수수료 부족 교체 실패를 포함하는 Core/EVM 경계 오류로 정의합니다(접속일: 2026-10-07). 복구 방법을 결정하기 전에 브로드캐스팅 RPC의 메시지를 확인하세요:

  • Nonce: eth_getTransactionCount를 대기 중인 트랜잭션과 비교하세요. 단일 배포자로부터의 제출을 직렬화하고 다음 nonce를 조정하세요.
  • 잔액(Funds): 배포자의 EVM HYPE 잔액을 전송 가치와 가스 비용의 합과 대조 확인하세요.
  • 중복 해시(Duplicate hash): 다른 트랜잭션을 제출하기 전에 기존 트랜잭션 및 영수증을 조회하세요.
  • 교체 수수료(Replacement fee): 기존 nonce와 수수료를 확인한 후 브로드캐스터의 교체 정책을 따르세요. 동일한 바이트를 반복해도 수수료가 인상되지 않습니다.

10055 오류 하나만으로 무작정 재시도해서는 안 됩니다. 오류 및 복구 안내는 BlockVectra 오류 레퍼런스를 별도로 참조하세요.

공식 퍼블릭 RPC 속도 제한 및 429

Hyperliquid의 공식 속도 제한 문서에 따르면 rpc.hyperliquid.xyz/evm은 IP당 분당 최대 100개의 EVM JSON-RPC 요청으로 제한됩니다. JSON-RPC 문서에서도 eth_getLogs를 쿼리당 50개 블록 및 최대 4개 topic으로 제한합니다(접속일: 2026-10-07).

HTTP 429가 발생하면 요청을 일시 중단하고 Retry-After(초 단위 또는 HTTP 날짜)를 먼저 준수하세요. 이 헤더가 없는 경우 지터가 포함된 지수 백오프와 제한된 재시도 횟수를 사용하여 미완료된 동일 청크를 재시도하세요. 동시성 및 폴링 빈도를 줄이고 로그 쿼리를 엔드포인트 한도 내의 청크로 분할하세요. 청크 분할만으로는 속도 제한이 해제되지 않으므로 동일한 IP를 공유하는 클라이언트 간에 요청 속도를 조율해야 합니다.

BlockVectra의 키 엔드포인트의 경우 공식 퍼블릭 RPC의 블록 범위나 분당 요청 한도를 적용하는 대신 GET /v1/chains에서 hyperevm_mainnet의 max_logs_block_range, methods.allow, methods.deny를 확인하세요. 요청 속도는 키의 cu_per_sec, burst_cu 및 무료 플랜 호출 한도의 적용을 별도로 받습니다(다음 섹션 참조). 429가 발생하면 error.data.reason과 retryable을 검사하세요. request_exceeds_burst의 경우 백오프를 통한 동일 재시도가 아닌 더 작은 크기의 요청이 필요합니다.

BlockVectra 파라미터 및 서비스 규칙

BlockVectra는 JSON-RPC 및 REST Data API 엔드포인트를 통해 HyperEVM 메인넷을 제공합니다:

  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에 의해 관리됩니다. 표준 EVM 메서드(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회 호출, 계정 내 모든 키, 모든 체인 및 Data API에서 공유됩니다.
    • 기본 키 한도: 각 API key에는 CU 버킷(cu_per_sec 충전 속도, burst_cu 용량 — 기본 속도 400 CU/s, 버스트 용량 1,600 CU)이 할당됩니다. 메서드는 컴퓨팅 유닛(CU) 가중치에 따라 측정됩니다.
    • 한도 업그레이드: 충전 후에는 계정 전반의 초당 호출 수 제한이 제거되며, 각 키는 여전히 컴퓨팅 유닛(CU) 속도 및 버스트 한도의 적용을 받습니다. 현재 요율 및 과금 단위는 요금 페이지를 참조하세요.

과거 로그 백필: 청크 분할 eth_getLogs 및 재시도 로직

과거 로그를 쿼리할 때 넓은 구간은 대상 체인의 max_logs_block_range로 제한되는 연속적인 청크로 나누어야 합니다. 클라이언트 재시도 전략은 오류 응답 내부의 retryable 필드를 검사해야 합니다.

오류 응답의 retryable 평가

BlockVectra에서 JSON-RPC 오류 객체에는 reason, docs_url, retryable(불리언)을 포함하는 error.data 페이로드가 포함됩니다:

  • retryable: true: 서비스 과부하(overloaded), 무료 플랜 초당 호출 수 한도(free_plan_call_limit), 노드 동기화 중(node_syncing), 업스트림 사용 불가(upstream_unavailable)를 포함한 일시적인 상황입니다. 클라이언트는 Retry-After 헤더가 있는 경우 이를 준수하거나 지터가 포함된 지수 백오프를 적용해야 합니다.
  • retryable: false: 블록 범위 한도 초과(-32602 / logs_range_too_large), 잘못된 파라미터(invalid_params), API key 누락(missing_api_key), 버스트 용량 초과 요청(-32022 / request_exceeds_burst)과 같은 비일시적 오류입니다. 파라미터를 수정하지 않고 재시도하면 성공할 수 없습니다.

다음은 API key가 생략되었을 때 반환되는 응답 예시입니다:

{
  "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
    }
  }
}

대규모 getLogs 스캔 대신 Data API 엔드포인트 활용

애플리케이션이 특정 주소의 트랜잭션 내역이나 토큰 이동을 추적할 때 eth_getLogs를 통한 스캔은 max_logs_block_range로 제한된 순차적 청크 쿼리를 발행하고 원시 Transfer 이벤트 로그를 파싱해야 합니다.

BlockVectra Data API는 hyperevm_mainnet을 위한 사전 인덱싱된 REST 엔드포인트를 제공하여 커서 기반 페이지네이션을 통해 최대 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 쿼리

export BLOCKVECTRA_API_KEY="rgw_your_api_key"

# 1. Query address transaction history (clamp=true prevents 409 errors)
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. Query address ERC-20 token transfers
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 전송의 경우 폴링을 통해 블록을 추적하고 max_logs_block_range 내의 연속적인 청크로 이벤트 로그를 가져옵니다. /v1/chains에서 ws=true 및 필요한 subscriptions 항목을 보고할 때만 WebSocket을 선택하세요. HTTPS 수신 엔드포인트로의 전달에는 Webhook 푸시를 사용하세요.

배포된 Hello 컨트랙트를 테스트하려면 LOG_ADDRESS를 해당 주소로 설정하세요. 브로드캐스팅 RPC를 통해 ping()을 전송한 다음 이 페이지의 백필 스크립트로 영수증의 블록을 백필하세요. 새 이벤트의 경우 마지막으로 완료된 청크부터 계속 진행하세요.

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 전송 환경에서 HTTP 폴링을 기본적으로 구현하여 pollingInterval 파라미터(예: 1000ms)를 통한 맞춤 설정을 지원합니다.

유한한 청크로 이벤트 로그 폴링

network.mjs 및 viem-client.mjs 옆에 poll-logs.mjs로 저장하세요. BLOCKVECTRA_API_KEY, LOG_ADDRESS, FROM_BLOCK을 설정한 다음 node poll-logs.mjs를 실행하세요. 이 유한한 예제는 5초 간격으로 헤드를 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 또는 기타 재시도 가능한 실패의 경우 동일한 미완료 청크에 대해 유한한 백오프 안내를 적용하세요.

관련 가이드

다음 단계

최종 수정일:

이 페이지의 내용