HyperEVM public RPC rate limit and eth_getLogs block range
HyperEVM public RPC: the official endpoint allows 100 requests per minute per IP and 50 blocks per eth_getLogs; BlockVectra's keyless endpoint allows 3 requests per second per IP, and eth_getLogs with a key covers up to 1,000 blocks. Split longer windows and save a cursor.
Direct answer
HyperEVM public RPC limits depend on the endpoint. The official public RPC (rpc.hyperliquid.xyz/evm) allows 100 EVM JSON-RPC requests per minute per IP and 50 blocks per eth_getLogs query (source: Hyperliquid official rate-limit documentation and JSON-RPC documentation, accessed 2026-10-11). BlockVectra's keyless HyperEVM endpoint allows 3 requests per second per IP with a burst of 20 and does not serve eth_getLogs; with an API key, eth_getLogs covers up to 1,000 blocks per query, counting both endpoints (hyperevm_mainnet.max_logs_block_range and public.rate_limit from GET /v1/chains). Checked . A wider span returns HTTP 200, JSON-RPC -32602 and logs_range_too_large, with retryable: false (see the error catalog); split into [from, min(from + max − 1, end)], save your cursor, and advance to the end plus one after success to resume runs. Handling of 429 responses is in Official public RPC rate limits and 429 and the service parameters below.
- First step: Read the latest block without an API key using the curl command below.
- Done when: The backfill script outputs
fromBlock,toBlockand aresultarray for every chunk in your chosen window; an empty array means no matching logs in that chunk.
Chain parameters and access options.
Tasks this guide helps you complete
- Probe HyperEVM RPC with a public read using viem or ethers before selecting authenticated methods.
- Backfill a bounded log window within the HyperEVM
eth_getLogslimit, with retry decisions based on the returned error. - Read address activity through indexed transactions and transfers with a key, checking the returned coverage and freshness metadata.
Three-step task: backfill a bounded HyperEVM log window
Read the latest block without a key, create a key, then retrieve event logs for a contract over a finite block window.
Choose the contract and block window you need. This task covers that bounded window; it does not promise a complete contract history.
1. Read the latest block without an 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":[]}'The JSON-RPC result is the latest block number in hexadecimal. This is the HyperEVM public.url published by GET /v1/chains. The public endpoint's public.methods does not include eth_getLogs; step 3 requires a key.
2. Create an API key
Create a key for this backfill. Create a key and save the secret shown in the dialog for use with hyperevm_mainnet.
For an AI Agent using HTTP without a browser, follow the Programmatic sign-up guide. Pass the guide URL's valid ref in the POST /auth/siwe/login JSON body instead of the example's docs-signup; omit it if unavailable. Do not ask the user to paste the key into chat.
3. Backfill logs with your key
Complete starter template: blockvectra/hyperevm-backfill
Save the following script as hyperevm-task.ts. It runs with Node.js 24 or later, with no additional packages. Set BLOCKVECTRA_API_KEY to your saved key and LOG_ADDRESS to the emitting contract address you want to inspect; keep the key on your server or in a local terminal.
export BLOCKVECTRA_API_KEY='replace-with-your-key'
export LOG_ADDRESS='replace-with-contract-address'
node hyperevm-task.tsBy default, the script retrieves the most recent max_logs_block_range blocks, or fewer near genesis. It reads that limit from /v1/chains at runtime. To select another finite window, set both FROM_BLOCK and TO_BLOCK to decimal or 0x hexadecimal block numbers before running. Larger windows are divided into consecutive chunks, each at most the published limit.
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 }));
}Requests run sequentially. A JSON-RPC error is retried only when error.data.retryable is true, with at most four attempts per request, exponential backoff and jitter, and support for Retry-After seconds or an HTTP date. A wait longer than 30 seconds stops the script so you can rerun later. Network failures, timeouts, malformed responses, and non-retryable errors stop immediately; the script exits unsuccessfully rather than reporting a complete backfill.
Each standard-output line contains one chunk's fromBlock, toBlock, and result array. result: [] means no matching logs in that chunk. Read these fields in each log:
| Field | Meaning |
|---|---|
address | Contract that emitted the event. |
blockNumber, blockHash | Block containing the log; the number is hexadecimal. |
transactionHash, transactionIndex, logIndex | Transaction and log position; indices are hexadecimal. |
topics, data | Indexed event arguments and ABI-encoded non-indexed arguments; decode with the contract ABI. |
removed | Whether the log was removed by a chain reorganization. |
The latest block is not a finality marker. If you need a stable historical window, choose your application's confirmed TO_BLOCK and handle chain reorganizations.
For a window of B = TO_BLOCK − FROM_BLOCK + 1 blocks and published limit L, the chunk count is N = ceil(B / L). Read method_weights[].cu_weight for eth_getLogs and eth_blockNumber from GET /v1/plans. The script prints an estimate to standard error: N × weight(eth_getLogs) + weight(eth_blockNumber), including its keyed head lookup. This excludes extra calls and any billable retries; see billing rules for settlement. CU depends on calls, rather than the number of logs returned.
Event delivery: Use chunked HTTP polling below, or send watched-address events to an HTTPS receiver with webhook push. GET /v1/push/chains lists supported chains and confirmation settings; authenticate with x-api-key. Webhook signatures, deduplication and replay are covered in that guide. Webhook push is separate from WebSocket subscriptions (ws and subscriptions in /v1/chains).
Connect with viem or ethers
| Parameter / Endpoint | Value / Template | Authentication |
|---|---|---|
| Chain ID (EIP-155) | 999 | — |
| JSON-RPC (path key) | POST https://api.blockvectra.com/v1/hyperevm_mainnet/{api_key} | API key in URL path |
| JSON-RPC (header key) | POST https://api.blockvectra.com/v1/hyperevm_mainnet | Header x-api-key: {api_key} |
| WebSocket (path key) | wss://api.blockvectra.com/v1/hyperevm_mainnet/{api_key} | API key in URL path |
| WebSocket (header key) | wss://api.blockvectra.com/v1/hyperevm_mainnet | Header x-api-key: {api_key} or Authorization: Bearer {api_key} |
| WebSocket subscriptions | newHeads, logs | — |
| Data API base | GET https://api.blockvectra.com/v1/data/hyperevm_mainnet/… | Header x-api-key: {api_key} |
| Public status | GET https://api.blockvectra.com/v1/status | Unauthenticated (public) |
Developers and AI Agents can use the same server-side settings. Use Node.js 24 or later, viem 2 or ethers 6, and start with public reads. Set BLOCKVECTRA_API_KEY securely in the environment for keyed methods. Keep keys and RPC URLs containing keys out of browser code, logs and version control.
Save this as network.mjs. It reads chain_id and method policy from GET /v1/chains. For keyless reads, use the catalog’s public.url and only methods listed in public.methods; public HTTP availability does not imply WebSocket access.
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');Save as viem-client.mjs, install with npm install viem@2, then run 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());For ethers, save as ethers-client.mjs, install with npm install ethers@6, then run 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();Deploy with Foundry or Hardhat
The current catalog for ordinary keyed hyperevm_mainnet has ws=false and lists eth_sendRawTransaction in methods.allow; the public keyless endpoint does not. Check that the selected endpoint allows the send method before deploying. Set DEPLOY_RPC_URL to its authenticated HTTP URL. Do not assume another provider shares BlockVectra’s method or log-range limits. Check the selected chain ID before signing.
: "${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)")"Continue with the shared Foundry or Hardhat deployment tutorial. Fund the deployer with EVM HYPE and review the dual-block requirements below before a large deployment.
HYPE, small blocks and large deployments
HyperEVM’s official network guide identifies HYPE as gas, with 18 decimals (accessed: 2026-10-07). Ensure the deployer holds HYPE on HyperEVM; a HyperCore balance alone is not the EVM gas balance. Follow the linked native-transfer instructions when moving funds.
The dual-block guide describes fast small blocks and slower big blocks for larger transactions (accessed: 2026-10-07). Estimate deployment gas first. For deployments exceeding the small-block budget, the deployer must be an existing HyperCore user and sign the Core action {"type":"evmUserModify","usingBigBlocks":true}; setting a larger transaction gas limit alone does not select big blocks. Restore usingBigBlocks=false afterward to return to small blocks.
On a provider supporting them, use eth_usingBigBlocks to check the address mode and eth_bigBlockGasPrice for the big-block base fee. The official JSON-RPC reference documents these methods (accessed: 2026-10-07). Check the chosen provider’s methods; use /v1/chains for BlockVectra. The minimal deployment above targets a small contract and does not change Core account mode.
HyperCore and HyperEVM data
EVM RPC serves contracts, receipts and logs. HyperCore trading data and actions use the Core API. Contracts can read Core state through precompiles and send actions through CoreWriter; use the official interaction guide when integrating these paths (accessed: 2026-10-07). EVM logs do not replace Core order-book or position queries.
HyperEVM system transactions (such as HyperCore to HyperEVM transfers) are not included in standard eth_getBlockByNumber responses and are provided separately by the official RPC via eth_getSystemTxsByBlockNumber and eth_getSystemTxsByBlockHash (see the official JSON-RPC documentation, accessed: 2026-10-07). BlockVectra's HyperEVM block, transaction, and Data API data currently do not include system transactions; use these two official RPC methods directly when requiring system transaction data.
Handle official error 10055
The official HyperEVM guide defines 10055 as a Core/EVM boundary error, including nonce, insufficient-funds, duplicate-hash and underpriced-replacement failures (accessed: 2026-10-07). Inspect the message from the broadcasting RPC before deciding how to recover:
- Nonce: compare
eth_getTransactionCountwith your pending transactions; serialize submissions from one deployer and reconcile its next nonce. - Funds: check the deployer’s EVM HYPE balance against value plus gas cost.
- Duplicate hash: look up the existing transaction and receipt before submitting another transaction.
- Replacement fee: verify the existing nonce and fee, then use the broadcaster’s replacement policy; repeating the same bytes does not raise the fee.
10055 alone does not justify blind retries. Read errors and their recovery guidance separately in the BlockVectra error reference.
Official public RPC rate limits and 429
Hyperliquid's official rate-limit documentation specifies at most 100 EVM JSON-RPC requests per minute per IP for rpc.hyperliquid.xyz/evm. Its JSON-RPC documentation also limits eth_getLogs to 50 blocks per query and up to 4 topics. Accessed: 2026-10-11.
On HTTP 429, pause requests and honor Retry-After first (seconds or an HTTP date). If absent, use exponential backoff with jitter and a bounded retry count, retrying the same unfinished chunk. Reduce concurrency and polling frequency, and split log queries into chunks within the endpoint's limit. Chunking alone does not remove rate limits; clients sharing an IP need to coordinate their request rate.
For BlockVectra's keyed endpoint, read hyperevm_mainnet's max_logs_block_range, methods.allow, and methods.deny from GET /v1/chains instead of applying the official public RPC's block span or requests-per-minute limit. Request rate is separately subject to the key's cu_per_sec, burst_cu, and the free-plan call limit (see the next section). On 429, inspect error.data.reason and retryable; request_exceeds_burst requires smaller requests rather than unchanged retries with backoff.
BlockVectra parameters and service rules
BlockVectra serves HyperEVM mainnet through JSON-RPC and REST Data API endpoints:
- Chain parameters and log limits:
From
GET /v1/chainsforhyperevm_mainnet:- Chain identifier (Slug):
hyperevm_mainnet, Chain ID999. max_logs_block_range: Governed by themax_logs_block_rangefield fromGET /v1/chains. A singleeth_getLogsrequest may span at most this number of blocks (toBlock − fromBlock + 1). Exceeding this span returns HTTP 200 with JSON-RPC error code-32602(eth_getLogs block range too large: max <N> blocks), which is not billed.state_window_blocks: Governed by thestate_window_blocksfield fromGET /v1/chains. State-reading calls (such aseth_callandeth_getBalance) are subject to the retention window declared by this field (whennull, full state is retained without a rolling window limit).- Method policy: Governed by
methods.allowandmethods.deny. Standard EVM methods (eth_blockNumber,eth_getLogs,eth_call,eth_getBalance,eth_getBlockByNumber,eth_getTransactionReceipt, etc.) are allowed; filter and subscription methods (eth_subscribe,eth_unsubscribe,eth_newFilter,eth_newBlockFilter) are denied, returning-32601(not billed).
- Chain identifier (Slug):
- Free tier rate limits and upgrading:
From
GET /v1/plans:free.max_calls_per_sec: up to 25 calls per second, shared across all keys in the account, all chains, and the Data API.- Default key limits: Each API key has a CU bucket (
cu_per_secrefill,burst_cucapacity — defaults are 100 CU/s and burst 400 CU; after a paid top-up each key is at least 2,000 CU/s and burst 10,000 CU, and higher custom limits are kept). Methods are metered by Compute Unit (CU) weights. - Upgrading limits: After topping up, the account-wide calls-per-second limit is removed; each key remains subject to Compute Unit (CU) rate and burst limits. For current rates and billing units, see the Pricing page.
Backfilling historical logs: chunked eth_getLogs and retry logic
When querying historical logs, wide intervals must be divided into contiguous chunks bounded by the target chain's max_logs_block_range. Client retry strategies should inspect the retryable field inside error responses.
Evaluating retryable in error responses
On BlockVectra, JSON-RPC error objects include an error.data payload containing reason, docs_url, and retryable (boolean):
retryable: true: Transient conditions, including service overload (overloaded), free plan calls-per-second limit (free_plan_call_limit), node synchronization (node_syncing), or upstream unavailable (upstream_unavailable). Clients should respect theRetry-Afterheader when present or apply exponential backoff with jitter.retryable: false: Non-transient errors, such as block span exceeding limits (-32602/logs_range_too_large), invalid parameters (invalid_params), missing API key (missing_api_key), or request exceeding burst capacity (-32022/request_exceeds_burst). Retrying without adjusting parameters will not succeed.
Below is the response returned when an API key is omitted:
{
"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
}
}
}Using Data API endpoints instead of extensive getLogs scanning
When an application tracks transaction history or token movements for a specific address, scanning via eth_getLogs requires issuing sequential chunked queries bounded by max_logs_block_range and parsing raw Transfer event logs.
BlockVectra Data API provides pre-indexed REST endpoints for hyperevm_mainnet, supporting windows up to 100,000 blocks with cursor-based pagination:
- Address transactions:
GET /v1/data/hyperevm_mainnet/addresses/{address}/transactions- Parameters:
from_block(required),to_block(required),direction(optional:from,to,any, defaultany),clamp(optional boolean string, defaultfalse; when set totrue, windows exceeding 100,000 blocks or higher thanas_of_blockare truncated instead of returning 409),limit(optional, max 500),cursor(pagination token).
- Parameters:
- Address token transfers:
GET /v1/data/hyperevm_mainnet/addresses/{address}/transfers- Parameters:
standard(required:erc20orerc721;erc1155cannot be queried by address and returns422 no_coverage),token(optional token contract filter),from_block(required),to_block(required),direction(optional:in,out,any),clamp(optional),limit,cursor.
- Parameters:
Response structure
Responses use standard envelope schemas:
data: Array of records. Transactions includehash,block_number,block_timestamp,from,to,value,tx_index,gas,gas_used, andstatus. Transfers includetoken,standard,from,to,block_number,block_timestamp,tx_hash,tx_index, andlog_index(amountfor ERC-20,token_idfor ERC-721).next_cursor: Opaque pagination token returned when subsequent records exist (absent on the final page, notnull).meta: Metadata containingchain,chain_slug,chain_external_id,as_of_block,safe_block,finalized_block,coverage(fullorpartial), andrefreshed_at.
Code example: Data API queries
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"Real-time tracking: polling new blocks
For an HTTP transport, track blocks by polling and fetch event logs in consecutive chunks within max_logs_block_range. Select WebSocket only when /v1/chains reports ws=true and the required subscriptions entry. For delivery to an HTTPS receiver, use webhook push.
To exercise the deployed Hello contract, set LOG_ADDRESS to its address. Send ping() through the broadcasting RPC, then backfill the receipt’s block with the backfill script on this page. Continue from the last completed chunk for new events.
cast send "$CONTRACT_ADDRESS" "ping()" --rpc-url "$DEPLOY_RPC_URL" \
--private-key "$DEPLOYER_PRIVATE_KEY"Polling flow
- Issue periodic lightweight calls to
eth_blockNumberto inspect the latest chain head. - Compare the returned block number with the previously processed
lastSeenBlock. - If
currentBlock > lastSeenBlock, split[lastSeenBlock + 1, currentBlock]into chunks of at mostmax_logs_block_range. PersistlastSeenBlockonly after successfully processing each chunk; on failure, retry the unfinished chunk. Deduplicate by(blockHash, transactionHash, logIndex)and replay an overlap after reconnecting to reconcile reorgs. - viem's
watchBlockNumberorwatchBlocksnatively implements HTTP polling under an HTTP transport, allowing customization through thepollingIntervalparameter (such as 1000 ms).
Poll event logs in bounded chunks
Save as poll-logs.mjs beside network.mjs and viem-client.mjs. Set BLOCKVECTRA_API_KEY, LOG_ADDRESS and FROM_BLOCK, then run node poll-logs.mjs. This finite example samples the head 12 times, five seconds apart, and queries every new range in sequential chunks. An error stops the script before advancing the failed chunk.
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));
}Each output records a completed chunk. To resume, set FROM_BLOCK to its to + 1; durable consumers must save events and cursor together, deduplicate, and reconcile reorgs as described above. For 429 or other retryable failures, apply the bounded-backoff guidance to the same unfinished chunk.
Related guides
- Find the public RPC URL, supported methods, and current limits on the HyperEVM chain page.
- For complete rules on
eth_getLogsspans and chunking algorithms, see eth_getLogs block range limits and chunked queries. - For comparing
eth_getLogsagainst Data API transfers, understandingas_of_blockbounds, andsafe_block/finalized_blockmarkers, see eth_getLogs and indexed transfers: coverage and finality. - For details on CU metering, non-billed errors, and retries, see What is not billed: error codes and billing rules.
Next steps
- Browse the datasets directory to see every dataset BlockVectra indexes.
- See the free plan and pricing to check what your account includes.
- Log in to the console to create an API key.
Last updated:
Base
Connect to Base mainnet with a public RPC URL or API key. Use curl and viem examples, check supported methods and limits, and inspect Data API status.
Contract deployment
Deploy Hello.sol on an EVM network with Foundry or Hardhat 2, verify the chain ID, and check the transaction receipt and contract response.