Запит історичного стану EVM у межах підтримуваних вікон

Розрізняйте автентифіковані вікна стану, історію без ключа та діапазони журналів. Обирайте фіксований блок для eth_call та діагностуйте помилки state_window.

Історичний виклик eth_call залежить від вікна стану мережі, а не від ліміту діапазону блоків для eth_getLogs. Перевірте вікно стану, режим автентифікації ендпоінта та цільовий блок перед читанням попереднього значення контракту.

Три різні історичні ліміти

Поле в GET /v1/chainsЩо воно контролюєЩо слід перевірити
state_window_blocksНаскільки далеко вглиб історії можуть запитувати автентифіковані читання стану, такі як eth_call, eth_getBalance, eth_getCode та eth_getStorageAtЯкщо вершина ланцюга H, а заявлене вікно W, то блок з номером, старішим за H − W, знаходиться поза межами вікна. Також перевіряйте methods.allow та methods.deny.
public.history_blocksПосилання на історичні блоки через public.url без ключаВикористовуйте лише методи з public.methods. Для читань стану застосовується менше з двох значень: публічна історія або заявлене вікно стану.
max_logs_block_rangeКількість блоків в одному автентифікованому запиті eth_getLogsРозраховується як toBlock − fromBlock + 1. Дозволений діапазон не гарантує доступності старого стану контракту чи журналів.

Ці ліміти вимірюються в блоках, а не в днях. Значення null або незаявлене вікно стану не означає наявності архівного покриття. Доступність методів без ключа відокремлена від доступності автентифікованих методів: сам лише діапазон журналів не відкриває доступ до публічного eth_getLogs.

Порівняння вікон стану для різних мереж

У таблиці наведено опубліковані вікна стану, історію без ключа, діапазони журналів та заявлені набори даних Data API з публічного знімка. Для виконання запиту зараз знову зверніться до GET /v1/chains та GET /v1/status.

Розробники та AI-агенти повинні перевіряти вікна стану та діапазони запитів логів окремо. Вікно стану зі значенням null не означає повноти архіву (archive coverage). Загальнодоступна історія застосовується лише до оголошених публічних методів.

МережаSlug мережіАвтентифіковане вікно стану: state_window_blocks (блоків)Історія без ключа: public.history_blocks (блоків)Автентифікований діапазон запитів логів: max_logs_block_range (блоків)Оголошені набори даних Data API
Arbitrum Onearb_mainnet5,0001,0001,000blocks, transactions, address_transactions, transfers, token_metadata, freshness
Basebase_mainnet10,0001,0001,000blocks, transactions, address_transactions, transfers, token_metadata, freshness
BNB Smart Chainbsc_mainnet1001001,000blocks, transactions, address_transactions, transfers, token_metadata, freshness
Ethereumeth_mainnet250,0001,0001,000blocks, transactions, address_transactions, transfers, token_metadata, freshness
Ethereum Sepoliaeth_sepoliaНе оголошено1,0001,000blocks, transactions, address_transactions, transfers, token_metadata, freshness
HyperEVMhyperevm_mainnetНе оголошено1,0001,000blocks, transactions, address_transactions, transfers, token_metadata, balances, holders, nfts, freshness
Polygonpolygon_mainnet1261261,000blocks, transactions, address_transactions, transfers, token_metadata, freshness
Robinhood Chainrobinhood_mainnet9009001,000blocks, transactions, address_transactions, transfers, token_metadata, balances, holders, nfts, dex_swaps, dex_prices, stocks, traces, freshness
Robinhood Chain Testnetrobinhood_testnet1,0231,0001,000Data API недоступний

GET /v1/chains · Вибірку зроблено (UTC):

GET /v1/status · Вибірку зроблено (UTC):

Вибір тегу блоку

Використовуйте latest для отримання поточного значення. Для історичного порівняння один раз викличте eth_blockNumber та перетворіть обраний номер блоку на шістнадцяткове число, наприклад 0x18efa2f. Зберігайте цей номер фіксованим для кожного виклику в порівнянні; повторні виклики з latest можуть використовувати різні блоки.

Для читань стану теги earliest, safe та finalized повертають помилку -32011 згідно з політикою вікна стану. Замість цього вказуйте точний номер блоку в межах заявленого вікна. Використання хешу блоку не дозволяє отримати доступ до глибшої історії: читання стану без ключа відхиляють такий формат, а автентифікований запит усе одно залежить від доступності стану.

Після реорганізації ланцюга номер блоку може посилатися на інший блок. Збережіть хеш блоку за допомогою eth_getBlockByNumber, якщо вам потрібно точно ідентифікувати блок результату. Для успішного запиту за номером у межах вікна також потрібна синхронізована мережа та наявність розгорнутого контракту на цій висоті.

Читання контракту на фіксованому блоці

В Ethereum контракт WETH за адресою 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 надає метод decimals() із селектором 0x313ce567. Виклик без ключа, зафіксований 2026-10-08 (UTC) на блоці 0x18efa2f, повернув HTTP 200 із таким результатом:

Запит до public.url мережі:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "eth_call",
  "params": [
    { "to": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "data": "0x313ce567" },
    "0x18efa2f"
  ]
}

Відповідь:

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": "0x0000000000000000000000000000000000000000000000000000000000000012"
}

Закодоване в ABI ціле число дорівнює 18. Цей результат є значенням кількості десяткових знаків (decimals), а не балансом, і не підтверджує доступність даних на інших висотах. Цей фіксований блок згодом вийде за межі обмеженого вікна; використовуйте нещодавній блок, якщо запускатимете наведений нижче приклад пізніше.

Збережіть приклад як historical-state.mjs і запустіть node historical-state.mjs за допомогою Node.js 24 або новішої версії зі встановленою змінною середовища BLOCKVECTRA_API_KEY. Скрипт використовує автентифікований ендпоінт, зберігає той самий контракт і calldata та порівнює latest, один фіксований нещодавній блок і блок за межами опублікованого автентифікованого вікна. Кожен вивід містить фактичний HTTP-статус і тіло JSON-RPC; відповідь HTTP 200 все одно може містити помилку. Скрипт зупиняється в разі неочікуваної відповіді замість того, щоб трактувати її як успішне читання.

const key = process.env.BLOCKVECTRA_API_KEY;
if (!key) throw new Error('Set BLOCKVECTRA_API_KEY');
const chainsUrl = 'https://api.blockvectra.com/v1/chains';
const catalogResponse = await fetch(chainsUrl, { signal: AbortSignal.timeout(15_000) });
if (!catalogResponse.ok) throw new Error(`Chains HTTP ${catalogResponse.status}`);
const catalog = await catalogResponse.json();
const chain = catalog.chains.find(item => item.chain === 'eth_mainnet');
const matches = (method, pattern) => pattern.endsWith('*')
  ? method.startsWith(pattern.slice(0, -1)) : method === pattern;
if (!chain?.jsonrpc || !['eth_call', 'eth_blockNumber'].every(method =>
  chain.methods?.allow?.some(pattern => matches(method, pattern)) &&
  !chain.methods?.deny?.some(pattern => matches(method, pattern)))) {
  throw new Error('Required methods are unavailable');
}
const window = chain.state_window_blocks;
if (!Number.isSafeInteger(window) || window < 10) {
  throw new Error('This example needs a declared state window of at least 10 blocks');
}
const rpcUrl = new URL('./eth_mainnet', chainsUrl).href;
let id = 0;
async function rpc(method, params) {
  const response = await fetch(rpcUrl, {
    method: 'POST', redirect: 'error', signal: AbortSignal.timeout(15_000),
    headers: { 'Content-Type': 'application/json', 'x-api-key': key },
    body: JSON.stringify({ jsonrpc: '2.0', id: ++id, method, params }),
  });
  return { http: response.status, body: await response.json() };
}
const headResponse = await rpc('eth_blockNumber', []);
if (headResponse.http !== 200 || headResponse.body.error ||
    !/^0x[0-9a-f]+$/i.test(headResponse.body.result ?? '')) {
  throw new Error(`Cannot read head: ${JSON.stringify(headResponse)}`);
}
const head = BigInt(headResponse.body.result);
if (head <= BigInt(window)) throw new Error('Head is too low for an out-of-window block');
const hex = value => `0x${value.toString(16)}`;
const fixedBlock = hex(head - 10n);
const outsideBlock = hex(head - BigInt(window) - 1n);
const call = { to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', data: '0x313ce567' };
for (const block of ['latest', fixedBlock, outsideBlock]) {
  const reply = await rpc('eth_call', [call, block]);
  console.log(JSON.stringify({ head: hex(head), block, ...reply }));
  if (block === outsideBlock) {
    if (reply.http !== 200 || reply.body.error?.code !== -32011 ||
        reply.body.error?.data?.reason !== 'state_window') {
      throw new Error('Expected state_window; inspect the actual response above');
    }
  } else if (reply.http !== 200 || reply.body.error ||
      reply.body.result !== '0x0000000000000000000000000000000000000000000000000000000000000012') {
    throw new Error('Expected the WETH decimals result; inspect the actual response above');
  }
}

Зафіксована вище відповідь використовує public.url; скрипт використовує API key. Щоб виконати читання без ключа, візьміть URL безпосередньо з public.url, опустіть ключ і оберіть блок у межах як public.history_blocks, так і вікна стану. Зміна способу автентифікації може змінювати дозволену глибину історії навіть для одного й того самого контракту та calldata.

Діагностика помилки виходу за межі вікна

На тому самому ендпоінті без ключа виклик, зафіксований 2026-10-08 (UTC), у якому було змінено лише цільовий блок на 0x18ef650 (та ID запиту), повернув HTTP 200 з error.code: -32011, error.data.reason: state_window та error.data.retryable: false. Повідомлення помилки мало вигляд block reference is outside the public history window. Це збій через обмеження публічної історії; автентифікований ендпоінт має власне вікно стану.

Використовуйте ці поля із запису помилки state_window, щоб ідентифікувати збій, замість того щоб покладатися на конкретне число вікна в повідомленні:

ПолеЗадокументоване значення або зміст
Статус HTTP200; перевіряйте JSON-RPC error, навіть коли запит HTTP успішний
error.code-32011
error.messageПомилки автентифікованого вікна стану вказують кількість останніх підтримуваних блоків; помилки публічної історії можуть містити інше повідомлення
error.data.reasonstate_window
error.data.docs_urlПосилання на пояснення state_window у каталозі помилок
error.data.retryablefalse: повторна відправка того самого запиту пізніше не відновить старіший стан

Оберіть новіший нумерований блок або використовуйте latest, якщо для завдання потрібне поточне значення. Зменшення діапазону eth_getLogs не відновлює історичний стан для eth_call. Інші причини помилки -32011 передбачають інші дії: range_not_indexed вимагає запиту в межах покритого діапазону; history_not_ready дозволяє повторити спробу після завершення індексації. Аналізуйте error.data.reason, а не лише числовий код.

Базовий стан також може бути недоступним із кодом -32000, а обрізана історія блоків — із кодом 4444; дивіться каталог помилок. Не повторюйте запит до старого блоку без змін і не припускайте, що більше заявлене вікно гарантує успішність кожної відповіді.

Вибір наступного запиту

Повний чекліст робочого навантаження та самостійне тестування дивіться в посібнику Як обрати RPC-провайдера.

Обираючи провайдера для регулярних читань контрактів, порівняйте щоденні та циклічні бюджети для читань EVM. Спочатку перевірте необхідні історичні блоки, а потім плануйте щоденний розподіл і пропускну здатність завдання; вкладання в кредитний бюджет не гарантує покриття потрібного стану.

Порівнюючи провайдерів для історичних читань, спочатку переконайтеся, що обидва можуть обслуговувати цільовий блок. Порівняння перевищення ліміту для full-запитів порівнює ціни на додаткові RU з витратами за методами, розділяє включену квоту й додаткове використання та пояснює розрахункові класи full та archive.

Для більш ранніх індексованих блоків, транзакцій, переказів або інших наборів даних перевірте заявлені в таблиці набори даних Data API та довідник Data API. Індексовані записи не забезпечують довільного виконання історичних контрактів і не означають, що кожна мережа підтримує історичні баланси.

Востаннє оновлено:

На цій сторінці