Парсинг даних NFT-колекцій (floor price, volume, holders)
OpenSea API повертає floor price із затримкою 5-15 хвилин та агрегує дані за своєю методологією. Для торгових ботів, аналітичних платформ та minting dApps, яким потрібен реальний floor — це неприйнятно. Ми розробляємо парсери, які читають події прямо з блокчейну, забезпечуючи точність до секунди. Це єдиний спосіб отримати актуальний floor без затримок.
Наш досвід — 5+ років у блокчейн-розробці та десятки проєктів з парсингу NFT-даних. Ми знаємо всі тонкощі: реорганізації ланцюжків, rate limits валідаторів, wash trading та як їх обробляти. Гарантуємо стабільну роботу парсера навіть на високонавантажених колекціях.
У цій статті розберемо повну архітектуру парсера NFT-даних: вибір стеку, індексування подій, обчислення floor price, зберігання в ClickHouse та типові проблеми. Також покажемо, що входить у наше рішення під ключ.
Джерела даних: що звідки брати
On-chain події
Для ERC-721/ERC-1155 колекцій всі продажі видно через події маркетплейсів. Кожен маркетплейс емітує власну подію:
- OpenSea Seaport:
OrderFulfilled(...)— контракт0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC - Blur:
TakerAsk/TakerBidна0x000000000000Ad05Ccc4F10045630fb830B95127 - LooksRare v2:
TakerAsk/TakerBid - X2Y2:
EvInventory
Floor price не можна отримати з подій напряму — події показують виконані ордери, а не активні лістинги. Для актуального floor потрібно або індексувати active listings через маркетплейс API, або використовувати агрегатори.
Holders та transfers
Transfer(address indexed from, address indexed to, uint256 indexed tokenId) — стандарт ERC-721. Повний граф володіння будується через replay всіх Transfer подій від блоку деплою. Unique holders = унікальні адреси to за вирахуванням адрес, які потім перевели токени на іншу адресу.
Для ERC-1155: TransferSingle та TransferBatch. Тут володіння — це баланс, не бінарний стан: balanceOf(address, tokenId).
Як ми обчислюємо floor price?
Два підходи:
1. Маркетплейс API агрегація — запитуємо floor у OpenSea, Blur, LooksRare, беремо мінімум. Проблема: rate limits та кешування на стороні API. Ми використовуємо кеш на 60 секунд і fallback при перевищенні лімітів.
2. Orderbook індексування — підписуємося на події створення/скасування ордерів. Seaport: OrderValidated (створення), OrderCancelled, OrderFulfilled (виконання). Будуємо локальний orderbook, обчислюємо floor самостійно. Точніше, але складніше в підтримці при оновленнях контрактів маркетплейса. Ми рекомендуємо перший підхід для більшості проєктів, другий — для торгових ботів із субсекундним відгуком.
| Метод | Точність | Складність | Затримка |
|---|---|---|---|
| API агрегація | Середня | Низька | ~60 сек |
| Orderbook | Висока | Середня | <5 сек |
Архітектура парсера
Стек
ethereum-node (Alchemy/Infura/Quicknode) → ethers.js / viem (event filtering) → message queue (Redis Streams / BullMQ) → PostgreSQL / ClickHouse (storage) → REST/WebSocket API (видача даних) Для історичних даних — getLogs з фільтром по address та topics[0]. Блоки батчимо по 2000 (обмеження більшості RPC провайдерів на eth_getLogs):
async function fetchTransferEvents( contract: string, fromBlock: number, toBlock: number, provider: JsonRpcProvider ) { const iface = new Interface(['event Transfer(address indexed from, address indexed to, uint256 indexed tokenId)']); const filter = { address: contract, topics: [iface.getEventTopic('Transfer')], fromBlock, toBlock, }; const logs = await provider.getLogs(filter); return logs.map(log => iface.parseLog(log)); } Для real-time: WebSocket підписка через provider.on(filter, callback) або Alchemy eth_subscribe newLogs.
Зберігання та запити
ClickHouse ефективніший за PostgreSQL для time-series NFT даних — аналітичні запити на мільйонах рядків у 10-50x швидші. Схема:
| Колонка | Тип | Опис |
|---|---|---|
block_number |
UInt64 | Блок події |
tx_hash |
FixedString(66) | Хеш транзакції |
contract |
FixedString(42) | Адреса колекції |
token_id |
UInt256 | ID токена |
from |
FixedString(42) | Продавець/відправник |
to |
FixedString(42) | Покупець/отримувач |
price_wei |
UInt256 | Ціна в wei |
marketplace |
LowCardinality(String) | Маркетплейс |
timestamp |
DateTime | Час блоку |
Партиціонування по місяцях (toYYYYMM(timestamp)), сортувальний ключ (contract, timestamp).
Чому on-chain дані точніші за OpenSea API?
OpenSea API використовує власний пул ордерів і кешує floor price із затримкою до 15 хвилин. Це критично для арбітражних ботів та аналітики в реальному часі. On-chain дані — єдине джерело істини. Ми гарантуємо точність до останнього підтвердженого блоку (фіналізація за 2 епохи — 64 блоки на Ethereum PoS).
Вирішення типових проблем
Rate limits
Alchemy Free — 330 CUPS, Growth — 660 CUPS. При історичному парсингу великої колекції (BAYC: 500k+ Transfer подій) без throttling отримаємо 429. Реалізуємо exponential backoff + queue з concurrency control.
Як уникнути rate limits при історичному парсингу?
Використовуйте exponential backoff та кілька RPC ендпоінтів. Ми налаштовуємо чергу з максимум 5 паралельними запитами та таймаутом 30 секунд.Реорганізації блокчейну
Події з останніх 12 блоків потрібно позначати як «pending» і підтверджувати тільки після finality. Для Ethereum PoS — 2 епохи (64 блоки) для економічного finality.
Wash trading
Обсяг за адресами з циклічними переказами спотворює статистику. Базова евристика: угоди де from і to — пов'язані адреси (отримували ETH з одного джерела) позначаються прапорцем.
Що входить у роботу
- Архітектура парсера під ваше завдання
- Код на TypeScript з використанням ethers.js/viem
- Налаштування ClickHouse для зберігання та аналітики
- Дашборд у Grafana з ключовими метриками (floor price, volume, holders)
- REST/WebSocket API для інтеграції з вашим додатком
- Повна документація та навчання команди
- Підтримка після запуску
Ми надаємо рішення під ключ. Оцінимо ваш проєкт за 1 день.
Орієнтири за термінами
Парсер Transfer подій + holders tracker — 1 день. Додавання floor price через маркетплейс API + кеш — ще півдня. Історичний бекфіл для великої колекції + дашборд — 2-3 дні сумарно.
Зв'яжіться з нами, щоб отримати консультацію та точну оцінку для вашого проєкту.







