Парсинг on-chain даних (транзакції, баланси, контракти)
Ви намагаєтеся отримати історичні баланси гаманців Ethereum, але eth_getBalance повертає лише поточний стан? Або потрібно відстежити внутрішні транзакції контракту, які не видно у звичайних транзакціях? Для ефективного парсингу блокчейну потрібно отримувати on-chain дані: транзакції, баланси гаманців, логи контрактів. Ми стикалися з цими завданнями сотні разів і знаємо, як їх вирішити ефективно. Замість того щоб використовувати Dune або Etherscan з їхніми обмеженнями, ви можете розгорнути власний парсер, який дає повний контроль над даними, швидкістю та вартістю. Наша компанія має понад 10 років досвіду в блокчейн-розробці та реалізувала десятки успішних проєктів з парсингу. У цій статті ми розповімо про ключові аспекти парсингу on-chain даних: від вибору джерела до оптимізації зберігання. Парсинг блокчейну дозволяє отримувати дані для аналітики, моніторингу транзакцій в реальному часі, індексування блокчейну для швидкого доступу до історичних даних. Економія на інфраструктурі при такому підході може сягати 60% (наприклад, заміна Dune на власний парсер економить $2000–5000 на місяць при високих навантаженнях). Вартість базового рішення — від $5000, а типова економія на інфраструктурі складає до 60% (наприклад, $2000–5000 на місяць при високих навантаженнях порівняно з Dune).
Ethereum JSON-RPC API — офіційна документація, на якій ґрунтуються приклади нижче.
Які дані можна отримати з блокчейну?
Типи даних та джерела
Транзакції рівня блоку (eth_getBlockByNumber з fullTx: true): from/to/value/gas/gasPrice/nonce, input data (calldata в hex), receipt (status, gasUsed, logs).
Internal transactions — виклики між контрактами, не видно у звичайних транзакціях. Потрібен debug_traceTransaction або trace_block (Erigon/OpenEthereum trace namespace). Парсинг трасувань (traces) дає доступ до внутрішніх транзакцій.
Події (logs) — емітуються через emit Event(...) у Solidity, доступні через eth_getLogs. Найпродуктивніший спосіб фільтрації за address + topic на рівні ноди. Отримання логів дозволяє відстежувати Transfer, Swap та інші події контрактів.
Storage state — значення storage variables контракту через eth_getStorageAt(address, slot, blockNumber). Для archive node — на будь-якому історичному блоці.
ERC-20 баланси — через balanceOf(address) view call або через Transfer event history. Щоб отримати historical balance, використовуйте archive node.
| Джерело | Що дає | Обмеження |
|---|---|---|
| Публічний Ethereum RPC (Infura/Alchemy) | Стандартний JSON-RPC | Rate limits, немає traces |
| Self-hosted Geth | Повний JSON-RPC | Немає traces без --gcmode=archive |
| Self-hosted Erigon | JSON-RPC + trace namespace | ~2.5 TB, 3-5 днів синхронізації |
| Alchemy/QuickNode (платні плани) | Розширений API + traces | Вартість при високому RPS |
| Firehose (StreamingFast) | Бінарний стрімінг, всі дані | Складне налаштування |
| Dune Analytics / Flipside | SQL інтерфейс до indexed даних | Затримка, обмеження схеми |
Self-hosted Erigon в 10 разів швидше Geth для парсингу traces і краще підходить для високих навантажень. Це стандарт де-факто для завдань парсингу. Порівняно з Dune, наш парсер знижує витрати в 2-3 рази при великих обсягах. Важливо розуміти, що дані зберігаються в Merkle Patricia Trie, що впливає на методи доступу до історичних даних.
Як отримати історичні баланси та оптимізувати запити?
Для отримання балансу на будь-якому блоці використовуйте archive node:
const historicalBalance = await client.readContract({ address: TOKEN_ADDRESS, abi: erc20Abi, functionName: 'balanceOf', args: [walletAddress], blockNumber: 18_500_000n, }); Мультикол прискорює масові запити в 5–10 разів, об'єднуючи кілька view-викликів в один запит:
import { multicall } from 'viem'; const balances = await client.multicall({ contracts: addresses.map(addr => ({ address: TOKEN_ADDRESS, abi: erc20Abi, functionName: 'balanceOf', args: [addr], })), allowFailure: true, }); Багатомережевий парсинг та продуктивність
Конфігурація під різні мережі:
const CHAIN_CONFIGS = { ethereum: { rpc: INFURA_ETH, chunkSize: 1000, blockTime: 12 }, bsc: { rpc: BSC_RPC, chunkSize: 2000, blockTime: 3 }, polygon: { rpc: POLYGON_RPC, chunkSize: 1500, blockTime: 2 }, arbitrum: { rpc: ARB_RPC, chunkSize: 5000, blockTime: 0.25 }, }; Для Ethereum: ~6500 блоків/день × ~6000 TX/блок = ~40M транзакцій/день. Кожна з receipts: ~1-5 KB. Разом: ~40-200 GB/день для повного парсингу. Для більшості завдань достатньо парсингу лише цільових контрактів. Дані зберігаються у вигляді state trie, який оновлюється з кожним блоком.
| Тип даних | Типовий об'єм | Рекомендоване сховище |
|---|---|---|
| Транзакції + receipts | 40-200 GB/день | PostgreSQL + TimescaleDB |
| Логи подій | 5-20 GB/день | Columnar (ClickHouse) |
| Historical balances | Залежить від кількості адрес | S3 + кеш Redis |
Зберігання: PostgreSQL + TimescaleDB для time-series + S3 для raw архіву. Критичні індекси: CREATE INDEX ON transactions (from_address, block_number DESC), CREATE INDEX ON transfers (token_address, block_number DESC).
Як ми працюємо: етапи, терміни та вартість
Пропонуємо розробку парсера під ключ: від аналізу вимог до деплою та моніторингу. Оцінимо ваш проект безкоштовно.
Що входить у роботу
- Архітектурна документація
- Доступ до розгорнутої інфраструктури
- Вихідний код парсера з коментарями
- Навчання вашої команди
- Підтримка протягом 3 місяців після здачі
Як розгорнути парсер за 5 кроків
- Аналіз вимог та визначення джерел даних.
- Архітектурне проєктування та вибір технологій.
- Розробка парсера, тестування на тестовому блокчейні.
- Деплой у хмарне середовище (AWS/GCP) з моніторингом.
- Моніторинг, оптимізація та підтримка.
Терміни та вартість: базова версія для 2-3 мереж з PostgreSQL та REST API — 2-4 тижні, вартість від $5000. Складні проєкти до 8 тижнів. Типова економія на інфраструктурі — до 60% ($2000–5000 на місяць). Вартість розробки окупається за 2-3 місяці.
Наша компанія має 10+ років досвіду в блокчейн-розробці та реалізувала десятки проєктів з парсингу та індексації. Ми гарантуємо прозорість та ефективність.
Код прикладу: парсинг транзакцій блоку
import { createPublicClient, http } from 'viem';
const client = createPublicClient({
transport: http(RPC_URL),
});
async function processBlock(blockNumber: bigint) {
const block = await client.getBlock({
blockNumber,
includeTransactions: true,
});
for (const tx of block.transactions) {
if (typeof tx === 'string') continue;
const receipt = await client.getTransactionReceipt({ hash: tx.hash });
await db.insertTransaction({
hash: tx.hash,
blockNumber: Number(tx.blockNumber),
blockTimestamp: Number(block.timestamp),
from: tx.from,
to: tx.to,
value: tx.value.toString(),
gasPrice: tx.gasPrice?.toString(),
gasLimit: tx.gas.toString(),
input: tx.input,
nonce: tx.nonce,
status: receipt.status === 'success',
gasUsed: receipt.gasUsed.toString(),
});
}
}
Код прикладу: отримання подій Transfer
const logs = await client.getLogs({
address: TOKEN_ADDRESS,
event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'),
fromBlock: 19_000_000n,
toBlock: 19_100_000n,
});
for (const log of logs) {
await db.insertTransfer({
txHash: log.transactionHash,
blockNumber: Number(log.blockNumber),
from: log.args.from,
to: log.args.to,
value: log.args.value.toString(),
});
}







