Ми часто стикаємося з ситуацією, коли фронтенд робить десятки eth_call та getLogs при кожному завантаженні сторінки. На mainnet це займає 2–3 секунди, на публічних RPC — ненадійно, а коли потрібна агрегація або історичні дані — просто неможливо через прямі виклики. The Graph вирішує цю проблему правильно: один раз пишеться subgraph, який індексує події контракту, і отримується GraphQL API з довільними запитами за мілісекунди. Економія на інфраструктурі може досягати 90% (до $2000 на місяць для типового DeFi-проекту), а швидкість запитів зростає в десятки разів. Нижче розберемо, як спроектувати та написати subgraph, щоб уникнути типових помилок і отримати максимум продуктивності.
Як спроектувати схему subgraph?
Subgraph складається з трьох компонентів, і критично важливо спроектувати схему під запити фронтенду, а не під структуру подій.
-
subgraph.yaml— маніфест. Описує джерела даних: які контракти слухати, з якого блоку (startBlock), які події та функції обробляти. Критично важливо:startBlockмає бути блоком деплою контракту, а не нулем — інакше індексація займе дні. -
schema.graphql— типи сутностей. Це те, що буде доступне через GraphQL. Проектується виходячи з потреб фронтенду, а не зі структури подій контракту — це різні речі. -
mappings.ts— AssemblyScript обробники. Трансформують сирі події в сутності схеми.
Проектування схеми
Найчастіша помилка — робити схему дзеркалом подій контракту. Якщо подія Transfer(address from, address to, uint256 amount) — не потрібна сутність TransferEvent. Потрібно думати про запити: «який поточний баланс користувача», «топ холдерів», «обсяг за 24 години».
type Token @entity { id: ID! totalSupply: BigInt! holderCount: Int! } type Account @entity { id: Bytes! balance: BigInt! transfersIn: [Transfer!]! @derivedFrom(field: "to") transfersOut: [Transfer!]! @derivedFrom(field: "from") } type Transfer @entity(immutable: true) { id: Bytes! from: Account! to: Account! amount: BigInt! blockNumber: BigInt! timestamp: BigInt! } @entity(immutable: true) для Transfer — важлива оптимізація. Immutable сутності не зберігаються в undo-буфері, індексація швидша на 30–40%.
Обробники: кроки створення
В AssemblyScript немає повноцінного TypeScript — немає null через ?., немає Array.from(), немає стандартних JS методів. Це часте джерело помилок для розробників, що прийшли з фронтенду.
- Визначте подію, яку обробляєте (наприклад,
Transfer). - Завантажте або створіть сутність за допомогою
Account.load(address)абоnew Account(address). - Оновіть поля (баланс, посилання).
- Збережіть зміни через
save().
// Правильне завантаження або створення сутності function getOrCreateAccount(address: Address): Account { let account = Account.load(address) if (account == null) { account = new Account(address) account.balance = BigInt.fromI32(0) } return account as Account } export function handleTransfer(event: TransferEvent): void { let from = getOrCreateAccount(event.params.from) let to = getOrCreateAccount(event.params.to) from.balance = from.balance.minus(event.params.value) to.balance = to.balance.plus(event.params.value) from.save() to.save() // Immutable — створюємо один раз, не завантажуємо let transfer = new Transfer( event.transaction.hash.concatI32(event.logIndex.toI32()) ) transfer.from = from.id transfer.to = to.id transfer.amount = event.params.value transfer.blockNumber = event.block.number transfer.timestamp = event.block.timestamp transfer.save() } Call handlers та block handlers
Окрім подій, The Graph вміє обробляти виклики функцій (callHandlers) і кожен блок (blockHandlers). Call handlers потрібні коли контракт не емітить події для потрібних операцій — legacy контракти грішать цим. Block handlers — для періодичних снапшотів (наприклад, денна статистика). Обидва типи значно сповільнюють індексацію, особливо block handlers — використовуйте лише якщо без них не обійтися.
| Тип обробника | Призначення | Вплив на швидкість індексації |
|---|---|---|
| Event handler | Обробка подій контракту | Мінімальний (основний тип) |
| Call handler | Відстеження викликів функцій | Середній (потрібен архівний вузол) |
| Block handler | Періодична обробка блоків | Високий (виконується на кожен блок) |
Чому The Graph швидше за прямі виклики RPC?
Прямі виклики RPC виконуються послідовно і завантажують ноду. The Graph індексує дані один раз і зберігає їх в оптимізованій базі, доступній через GraphQL. Запити виконуються за мілісекунди (типовий час від 50 до 200 мс), а пагінація через first/skip працює до skip: 5000 — для великих датасетів використовуйте keyset pagination через id_gt. За даними документації The Graph, пропускна здатність індексації може досягати 1000 подій на секунду на стандартному обладнанні. Економія на інфраструктурі може досягати 90%, що підтверджується досвідом великих DeFi-проектів — наприклад, Uniswap скоротив витрати на RPC на $3000+ на місяць після переходу на subgraph.
Який хостинг обрати: Hosted Service, Decentralized чи self-hosted?
Вибір варіанту розгортання subgraph залежить від вимог до доступності, контролю та бюджету.
| Варіант | Доступність | Контроль | Вартість |
|---|---|---|---|
| Hosted Service | Базова (без SLA) | Обмежений | Безкоштовно для невеликих проектів |
| Decentralized Network | Висока (децентралізовані індексери) | Середній | Потребує GRT (токен) |
| Self-hosted Graph Node | Повна (власне обладнання) | Повний | Витрати на інфраструктуру ($1000+ на місяць) |
Наша команда з десятирічним досвідом у блокчейні реалізувала понад 20 інтеграцій The Graph. Ми допомагаємо обрати оптимальний варіант під ваш проект і уникнути типових проблем.
Типові проблеми індексації
- Subgraph fails з помилкою "store error" — перевірте non-nullable поля.
- Індексація зависає на блоці — додайте обробку реверсованих транзакцій через
receipt.status. - Розбіжність даних через reorgs — встановіть
minEthereumBlockConfirmations. - Надмірне споживання газу при записі — використовуйте
@entity(immutable: true)для незмінних даних.
Що потрібно знати про пагінацію та фільтрацію?
| Тип запиту | Приклад | Обмеження |
|---|---|---|
| Pagination | first: 100, skip: 0 |
skip до 5000 |
| Keyset pagination | where: { id_gt: "..." } |
Без обмежень |
| Фільтрація | where: { balance_gt: "0" } |
Підтримуються всі оператори |
| Сортування | orderBy: timestamp, orderDirection: desc |
За будь-яким полем сутності |
Keyset pagination є кращим для великих наборів даних — вона швидша і не має обмеження по skip.
Інтеграція на фронтенді
Стандартний стек: Apollo Client або urql для React додатків. The Graph підтримує subscriptions через WebSocket — для realtime оновлень без поллінгу.
const POSITIONS_QUERY = gql` query UserPositions($account: Bytes!, $skip: Int!) { positions( where: { owner: $account, liquidity_gt: "0" } orderBy: createdAt orderDirection: desc first: 100 skip: $skip ) { id pool { token0 { symbol } token1 { symbol } feeTier } liquidity depositedToken0 depositedToken1 } } ` Терміни та що входить в роботу
За 2–5 днів: проектування схеми під завдання клієнта, написання mappings для всіх подій та calls, тестування на форку, деплой на Hosted Service або налаштування self-hosted ноди, базова інтеграція в існуючий фронтенд або надання GraphQL endpoint. Вартість розраховується індивідуально, але економія на RPC-запитах виправдовує інвестиції — типова окупність настає протягом 1-3 місяців.
Зв'яжіться з нами, щоб обговорити інтеграцію The Graph у ваш проект. Досвід роботи з The Graph та десятки успішних інтеграцій гарантують результат. Замовте консультацію — ми підготуємо індивідуальну пропозицію.







