Ми часто стикаємося з ситуацією, коли фронтенд робить десятки 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 та десятки успішних інтеграцій гарантують результат. Замовте консультацію — ми підготуємо індивідуальну пропозицію.







