Граббінг on-chain даних (транзакції, баланси, контракти)

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Граббінг on-chain даних (транзакції, баланси, контракти)
Середній
~3-5 днів
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1286
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1122
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    859

Парсинг on-chain даних (транзакції, балансы, контракти)

Коли кажуть «потрібен парсинг блокчейна» — зазвичай мають на увазі одне з трьох: історичні дані для аналізу, моніторинг конкретних адрес в реальному часі, або побудова індексованої бази для власного продукту. Технічний підхід до кожного випадку різний. Спільне одне — потребує правильного джерела даних та розуміння того, що eth_getLogs не рівне «всі дані».

Що взагалі можна отримати з блокчейну

Транзакції рівня блоку (eth_getBlockByNumber з fullTx: true):

  • From/to/value/gas/gasPrice/nonce
  • Input data (calldata у hex)
  • Receipt: status (success/reverted), gasUsed, logs (события)

Internal transactions (виклики між контрактами) — не видні у звичайних транзакціях. Потребує debug_traceTransaction або trace_block (Erigon/OpenEthereum trace namespace). Це важливо: перевід ETH всередину DeFi-протоколу не створює звичайну транзакцію — він видний тільки в traces.

События (logs) — емітуються через emit Event(...) у Solidity. Доступні через eth_getLogs. Це найпродуктивніший спосіб парсинга — фільтрація по address + topic на рівні ноди.

Storage state — значення storage variables контракту через eth_getStorageAt(address, slot, blockNumber). Для archive node — на будь-якому історичному блоці. Потребує знати slot layout (з ABI + solc).

ERC-20 балансы — через balanceOf(address) view call або через Transfer event historya.

ENS / identity — reverse resolution через ENS registry контракт.

Вибір джерела даних

Джерело Що дає Обмеження
Публічний 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 даних Затримка, обмеження схеми

Для більшості задач парсинга: Alchemy або QuickNode на платному плані — оптимальний старт без інфраструктурної нагрузки. Для високого обсягу або специфічних даних (traces, storage) — self-hosted Erigon.

Парсинг транзакцій

Базовий блок-парсер

import { createPublicClient, http, parseAbi } 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; // hash-only mode
    
    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,
    });
  }
}

Отримання receipts

Receipt містить: статус виконання, фактичний gasUsed, logs (события). Batch-запит:

// Отримуємо receipts для всього блоку одним запитом (доступно на Alchemy/QuickNode)
const receipts = await client.request({
  method: 'eth_getBlockReceipts',
  params: [blockNumber],
});

// Або окремо для кожної TX (стандартний JSON-RPC)
const receipt = await client.getTransactionReceipt({ hash: tx.hash });

eth_getBlockReceipts — нестандартний метод, доступен на Alchemy, QuickNode, Erigon. На стандартному Geth потребує N запитів.

Парсинг событій (logs)

Найефективніший метод для парсинга специфічних даних:

// Парсинг всіх ERC-20 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(),
  });
}

Обмеження eth_getLogs: діапазон не більше 2000 блоків за запит на більшості публічних нод. Для історичних даних потребує chunked polling:

async function fetchLogsInChunks(
  fromBlock: number,
  toBlock: number,
  chunkSize = 1000,
) {
  for (let from = fromBlock; from <= toBlock; from += chunkSize) {
    const to = Math.min(from + chunkSize - 1, toBlock);
    const logs = await client.getLogs({
      address: CONTRACT,
      fromBlock: BigInt(from),
      toBlock: BigInt(to),
    });
    await processLogs(logs);
    // Rate limiting: пауза чтобы не исчерпать лімити
    await sleep(100);
  }
}

Балансы

Поточний баланс

ERC-20 баланс через view call:

const balance = await client.readContract({
  address: TOKEN_ADDRESS,
  abi: erc20Abi,
  functionName: 'balanceOf',
  args: [walletAddress],
});

Історичний баланс

Два підходи:

Обчислення з Transfer событій — накопиченням всіх Transfer в/з адреси та обчислення running balance. Точно, але потребує повної історії событій.

eth_call на історичному блоці — виклик balanceOf з blockTag: blockNumber на archive node. Прямий спосіб, але потребує archive node.

const historicalBalance = await client.readContract({
  address: TOKEN_ADDRESS,
  abi: erc20Abi,
  functionName: 'balanceOf',
  args: [walletAddress],
  blockNumber: 18_500_000n,  // історичний блок
});

Multicall для батчинга

Для отримання балансів множини адрес:

import { multicall } from 'viem';

const balances = await client.multicall({
  contracts: addresses.map(addr => ({
    address: TOKEN_ADDRESS,
    abi: erc20Abi,
    functionName: 'balanceOf',
    args: [addr],
  })),
  allowFailure: true,
});

Один HTTP запит замість N — економія rate limit квоти в 10-100x.

Парсинг контрактів

ABI отримання через Etherscan API (або його форки для інших мереж):

const abi = await fetch(
  `https://api.etherscan.io/api?module=contract&action=getabi&address=${address}&apikey=${ETHERSCAN_KEY}`
).then(r => r.json()).then(d => JSON.parse(d.result));

Для unverified контрактів — 4byte.directory для декодування сигнатур функцій.

Bytecode аналізeth_getCode повертає deployed bytecode. Можна перевірити: чи є адреса контрактом (код не пустий), чи є вона прокси (EIP-1967 slot), порівняти bytecode хеші.

Storage layout — через solc --storage-layout отримуємо mapping змінних → storage slots. Потім eth_getStorageAt для читання значень напрямку без ABI.

Многосітьовий парсинг

Одна і та сама код повинна працювати з різними мережами. Ключові відмінності:

Параметр Ethereum BNB Chain Polygon Arbitrum
Block time ~12 сек ~3 сек ~2 сек ~0.25 сек
Log chunk limit 2000 блоків 5000 3500 10000
Native decimals 18 18 18 18
Trace API Erigon/Besu Нода з debug Обмежено Стандартний
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/день для повного парсинга

Для більшості завдань не потребує full парсинг — тільки цільові контракти та события.

Зберігання: PostgreSQL для нормалізованих даних + TimescaleDB hypertables для time-series + S3 для raw JSON архіву.

Індекси критичні:

CREATE INDEX CONCURRENTLY ON transactions (from_address, block_number DESC);
CREATE INDEX CONCURRENTLY ON transactions (to_address, block_number DESC);
CREATE INDEX CONCURRENTLY ON transfers (token_address, block_number DESC);

Стек та сроки

Парсер з підтримкою транзакцій + событій + балансів для 2-3 мереж, PostgreSQL зберігання, базовий REST API: 2-4 тижні в залежності від глибини історичних даних та кількості цільових контрактів.