# HyperEVM RPC 속도 제한 및 로그 백필

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

## 핵심 요약

기본 공식 HyperEVM 퍼블릭 RPC는 `eth_getLogs` 쿼리당 최대 50개의 블록을 허용합니다(출처: Hyperliquid 공식 [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)]`로 분할하고 커서를 저장한 뒤 성공 후 끝 블록에 1을 더한 위치로 진행하여 실행을 재개하세요. 공식 퍼블릭 RPC의 IP별 속도 제한과 BlockVectra의 키 제한은 [공식 퍼블릭 RPC 속도 제한 및 429](#official-public-rpc-rate-limits-and-429) 및 아래의 서비스 파라미터에 별도로 설명되어 있습니다.

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

* [viem 또는 ethers로 연결](#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): API key를 사용하여 인덱싱된 트랜잭션 및 전송 내역을 조회하고 반환된 적용 범위 및 최신성 메타데이터를 확인합니다.

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

<span id="three-step-task-backfill-a-bounded-hyperevm-log-window" />

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

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

### 2. API key 생성

<div data-attribution-ref="docs-hyperevm-task">
  [이번 백필을 위한 키 생성](https://console.blockvectra.com/login/?next=%2Fkeys%2F). 키를 생성하고 다이얼로그에 표시된 시크릿을 저장하여 `hyperevm_mainnet`에 사용하세요.

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

### 3. API key로 로그 백필

전체 스타터 템플릿: [blockvectra/hyperevm-backfill](https://github.com/blockvectra/hyperevm-backfill)

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

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

```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`일 때만 재시도되며, 요청당 최대 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](https://console-api.blockvectra.com/v1/plans)에서 확인하세요. 스크립트는 키를 통한 최신 블록 조회를 포함하여 `N × weight(eth_getLogs) + weight(eth_blockNumber)` 추정치를 표준 에러로 출력합니다. 여기에는 추가 호출 및 과금 대상 재시도가 제외되어 있습니다. 정산 세부사항은 [과금 규칙](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 서명, 중복 제거 및 리플레이는 해당 가이드에서 다룹니다. Webhook 푸시는 WebSocket 구독(`/v1/chains`의 `ws` 및 `subscriptions`)과 분리되어 있습니다.

<span id="connect-with-viem-or-ethers" />

## 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_mainnet` | x-api-key: {api_key} 헤더 전달 |
| Data API 기본 URL | `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을 사용하고 퍼블릭 읽기부터 시작하세요. 인증된 메서드를 사용할 때는 환경 변수에 `BLOCKVECTRA_API_KEY`를 안전하게 설정하세요. 키와 키가 포함된 RPC URL이 브라우저 코드, 로그, 버전 관리 시스템에 노출되지 않도록 주의하세요.

이를 `network.mjs`로 저장하세요. [GET /v1/chains](https://api.blockvectra.com/v1/chains)에서 `chain_id`와 메서드 정책을 읽어옵니다. 키 없는 읽기의 경우 카탈로그의 `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와 동일한 메서드 또는 로그 범위 제한을 가질 것으로 가정하지 마세요. 서명하기 전에 선택한 체인 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)에 따르면 HYPE이 가스로 사용되며 소수점 18자리를 갖습니다(접속일: 2026-10-07). 배포자가 HyperEVM 상에 HYPE을 보유하고 있는지 확인하세요. HyperCore 잔액만으로는 EVM 가스 잔액이 되지 않습니다. 자금을 이동할 때는 연결된 네이티브 전송 안내를 따르세요.

[듀얼 블록 가이드](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/dual-block-architecture)에서는 빠른 소형 블록(small block)과 더 큰 트랜잭션을 위한 느린 대형 블록(big block)을 설명합니다(접속일: 2026-10-07). 배포 가스를 먼저 추정하세요. 소형 블록 예산을 초과하는 배포의 경우 배포자가 기존 HyperCore 사용자여야 하며 Core 액션 `{"type":"evmUserModify","usingBigBlocks":true}`에 서명해야 합니다. 트랜잭션 가스 한도만 크게 설정한다고 해서 대형 블록이 선택되지는 않습니다. 소형 블록으로 돌아가려면 배포 후 `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는 컨트랙트, 영수증, 로그를 제공합니다. 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`를 nonce, 잔액 부족, 중복 해시, 수수료 부족 교체 실패를 포함하는 Core/EVM 경계 오류로 정의합니다(접속일: 2026-10-07). 복구 방법을 결정하기 전에 브로드캐스팅 RPC의 메시지를 확인하세요:

* **Nonce:** `eth_getTransactionCount`를 대기 중인 트랜잭션과 비교하세요. 단일 배포자로부터의 제출을 직렬화하고 다음 nonce를 조정하세요.
* **잔액(Funds):** 배포자의 EVM HYPE 잔액을 전송 가치와 가스 비용의 합과 대조 확인하세요.
* **중복 해시(Duplicate hash):** 다른 트랜잭션을 제출하기 전에 기존 트랜잭션 및 영수증을 조회하세요.
* **교체 수수료(Replacement fee):** 기존 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개 topic으로 제한합니다(접속일: 2026-10-07).

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

BlockVectra의 키 엔드포인트의 경우 공식 퍼블릭 RPC의 블록 범위나 분당 요청 한도를 적용하는 대신 [GET /v1/chains](https://api.blockvectra.com/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) 속도 및 버스트 한도의 적용을 받습니다. 현재 요율 및 과금 단위는 [요금 페이지](https://blockvectra.com/en/pricing/)를 참조하세요.

## 과거 로그 백필: 청크 분할 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가 생략되었을 때 반환되는 응답 예시입니다:

```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
    }
  }
}
```

<span id="using-data-api-endpoints-instead-of-extensive-getlogs-scanning" />

## 대규모 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 쿼리

**cURL**

```bash
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"
```


  **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(`Fetched ${body.data.length} transfers`);
  cursor = body.next_cursor; // Loop terminates when cursor is absent
} while (cursor);
```


## 실시간 추적: 새 블록 폴링

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

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

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

## 관련 가이드

* 퍼블릭 RPC URL, 지원 메서드 및 현재 한도는 [HyperEVM 체인 페이지](https://blockvectra.com/en/chains/hyperevm_mainnet/)에서 확인하세요.
* `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 생성.
