Граббинг даних DeFi-протоколів (TVL, APY, пулы)
Завдання на першого погляду просте: зібрати TVL та APY по протоколам, кинути в базу, видати через API. На практиці — в кожному протоколі своя логіка розрахунку, частина даних тільки on-chain, частина — через субграфи з затримкою, а APY змінюється кожен блок. До того ж багато протоколів деплоять різні версії на різних чейнах з несумісними ABI.
Источники даних та їх особливості
The Graph: основний источник агрегованих даних
Більшість крупних протоколів мають офіційні субграфи: Uniswap, Curve, Aave, Compound, Balancer, Yearn. The Graph Studio дозволяє запитувати історичні та поточні дані через GraphQL.
Проблеми, з якими зустрічаємся:
Затримка. Субграф оновлюється з затримкою 1-10 хвилин після on-chain подій. Для моніторингу реального часу — не підходить. Для історичних даних та дашбордів — норма.
Застарілі субграфи. Субграф Uniswap V2 давно не підтримується командою, дані можуть бути неповними. Для Uniswap V3 офіційний субграф періодично відстає при високому обсягу.
Пагінація. The Graph повертає максимум 1000 записів за запрос. Для отримання всіх пулів Uniswap V3 (їх >50,000) потрібна пагінація через skip або id_gt паттерн.
query GetPools($lastId: String) {
pools(first: 1000, where: { id_gt: $lastId }, orderBy: id) {
id
token0 { symbol, decimals }
token1 { symbol, decimals }
totalValueLockedUSD
volumeUSD
feeTier
}
}
Нюанс з TVL у The Graph: Субграф Uniswap V3 рахує TVL як суму значень токенів у USD через внутрішній price feed. Цей price feed іноді дає неверні значення для малоліквідних токенів — пул з $500k реального TVL може виглядати як $50M через маніпульовану ціну одного токена. Потрібна перевірка через зовнішнє джерело.
On-chain запросити для точних даних
Для даних, що важливо отримати точно та актуально — прямі eth_call до контрактів:
Aave v3 TVL: Pool.getReserveData(asset) повертає aToken.totalSupply() * liquidityIndex. Для кожного активу в кожному ринку.
Curve APY: Minter.minted(gauge, user) для CRV емісії, gauge.inflation_rate() для поточної ставки. Реальний APY = (crv_per_year * crv_price) / gauge_tvl_usd.
Uniswap V3 fee APY: positions.tokensOwed0/1 — накопилені комісії. Для загального pool APY: pool.feeGrowthGlobal0X128 — дельта за період / ліквідність.
Multicall3 (0xcA11bde05977b3631167028862bE2a173976CA11) — деплоєн на всіх майджорних чейнах, дозволяє пакетирувати сотні eth_call в одну транзакцію. Замість 100 окремих RPC запросів — один batch. Для граббінга критично за продуктивністю.
DeFi Llama API
https://api.llama.fi — публічний API без ключа для TVL даних по більшості протоколів. Структура даних:
GET /tvl/{protocol} → поточний TVL
GET /protocol/{protocol} → історичний TVL + breakdown
GET /pools → APY по всім пулам (~10k записів)
/pools — золота жила: там уже посчитан APY для тисяч пулів на всіх чейнах. Але DeFi Llama оновляє дані раз у кілька хвилин — для realtime задач потрібен власний розрахунок.
Архітектура системи граббінга
Шари збирання даних
Scheduler (cron / event-driven)
├── GraphQL Fetcher (The Graph subgraphs)
├── On-chain Fetcher (Multicall3 + ethers.js)
├── HTTP Fetcher (DeFi Llama, CoinGecko)
└── WebSocket Listener (real-time events)
↓
Normalizer (єдиний формат)
↓
TimescaleDB / PostgreSQL
↓
API (REST/GraphQL)
Normalizer — ключовий компонент. Кожен протокол повертає дані в своєму форматі. Нормалізація: { protocolId, chainId, poolAddress, tvlUsd, apy, timestamp }. Єдина схема дозволяє будувати cross-protocol порівняння.
Розрахунок APY
APY = Annual Percentage Yield з урахуванням compound. Для більшості DeFi-протоколів дані — це APR (без compound), який потрібно пересчитати:
APY = (1 + APR/n)^n - 1, де n — кількість compound періодів на рік.
Для lending протоколів APR зазвичай вже з compound (Aave v3 рахує через liquidityRate). Для LP позицій — ні: комісії накопичуються без реінвесту.
Компоненти реального APY для Uniswap V3 LP позиції:
- Trading fees APR (залежить від volume та position range)
- Liquidity mining rewards (якщо є incentives)
- Мінус IL (історична оцінка)
Чесний APY без вирахування IL — вводить користувачів в заблуд. Показуємо обидва числа.
Обробка помилок та rate limiting
RPC провайдери мають rate limits. Alchemy free tier: 300 CUPS (compute units per second). Один eth_call = 10-40 CU, Multicall3 = 20 CU незалежно від кількості запросів всередину. Пакетируємо максимально.
The Graph: 1000 запросів на день на бесплатному плані. Використовуємо кэш з TTL — більшість даних не потребує оновлення частіше ніж раз у 5 хвилин.
Retry з exponential backoff на всі HTTP запросити. Dead letter queue для невдалих fetches — не втрачаємо дані при тимчасових RPC збоях.
Стек розробки
TypeScript + Node.js для скреперів. PostgreSQL + TimescaleDB для зберігання time-series. Redis для кэширування проміжних даних. Docker Compose для локальної розробки.
ethers.js v6 для on-chain взаємодій. graphql-request для The Graph запросів. p-limit для контролю паралелізму (не давимо RPC провайдера).
Орієнтири за часом
Граббер для 2-3 протоколів на одному чейні з базовим API — 2-3 дні. Multi-protocol, multi-chain система з історичною базою та нормалізацією — 1-2 тижні залежно від кількості источників та вимог до точності APY.







