Граббинг 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 history.

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,  // исторический блок
});

Мультиколл для батчинга

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

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 недели в зависимости от глубины исторических данных и количества целевых контрактов.