Граббинг данных 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 — накопленные комиссии. Для general pool APY: pool.feeGrowthGlobal0X128 — дельта за период / liquidity.
Multicall3 (0xcA11bde05977b3631167028862bE2a173976CA11) — деплоен на всех мейджорных чейнах, позволяет пакетировать сотни eth_call в одну транзакцию. Вместо 100 отдельных RPC запросов — один batch. Для скрапинга это критично по производительности.
DeFi Llama API
https://api.llama.fi — публичный API без ключа для TVL данных по большинству протоколов. Структура данных:
GET /tvl/{protocol} → current TVL
GET /protocol/{protocol} → historical 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 позиций — нет: fees начисляются без reinvest.
Компоненты реального 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 для failed 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.







