Інтеграція The Graph (індексація даних)
Типова проблема, з якою приходять: фронтенд робить десятки eth_call та getLogs при кожній загрузці сторінки. На mainnet це повільно, на публічних RPC-нодах—ненадійно, а коли потрібна агрегація чи історичні дані—просто неможливо через прямі виклики. The Graph вирішує це правильно: один раз пишеш subgraph, який індексує події контракту, і отримуєш GraphQL API з довільними запитами.
Архітектура subgraph
Subgraph складається з трьох компонентів:
subgraph.yaml — маніфест. Описує джерела даних: які контракти слухати, з якого блока (startBlock), які eventi та функції обробляти. Критично важливо: 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—важлива оптимізація. Незмінні сутності не зберігаються в буфері скасування, індексація швидша на 30–40%.
Обробники: тонкості
AssemblyScript не повний TypeScript—немає null через ?., немає Array.from(), немає стандартних JS методів. Це частий источник помилок для розробників, які прийшли з фронтенду.
// Коректне завантаження або створення сутності
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()
// Незмінна—створи один раз, не завантажуй
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()
}
Обробники викликів та обробники блоків
Крім подій, The Graph може обробляти виклики функцій (callHandlers) та кожен блок (blockHandlers). Обробники викликів потрібні, коли контракт не видає события для потрібних операцій—старі контракти мають цей недолік. Обробники блоків—для періодичних знімків (напр., щоденна статистика). Обидва типи значно сповільнюють індексацію, особливо обробники блоків—використовуйте тільки якщо без них не обійтися.
Розгортання та експлуатація
Hosted Service vs Децентралізована мережа
Hosted Service (The Graph Network hosted)—централізована, безплатна, підходить для розробки та MVP. Без гарантій SLA, може бути недоступна.
Децентралізована мережа—індексери отримують GRT за запити, сигнал курування впливає на пріоритет. Для production з вимогами до uptime—правильний вибір, але потрібно розуміти економіку: вартість запитів залежить від обсягу.
Self-hosted Graph Node—повний контроль, немає витрат на GRT, але інфраструктурне навантаження. Вимагає: PostgreSQL 14+, IPFS ноду, архівну ноду Ethereum (або Firehose). Мінімальні вимоги для production: 16 CPU, 64GB RAM, NVMe SSD.
Типові проблеми індексації
Subgraph не вдається з "store error"—найчастіше спроба зберегти сутність з полем null, яке оголошено як non-nullable у схемі. Перевіряйте всі поля перед save().
Індексація зависла на конкретному блоці—зазвичай це обробник виклику на скасовану транзакцію. Потрібно додати обробку через receipt.status.
Розходження даних з on-chain—subgraph не враховує reorgs. Для критичних даних потрібен minEthereumBlockConfirmations у маніфесті або додаткова логіка на клієнті.
Інтеграція на фронтенді
Стандартний стек: Apollo Client або urql для React додатків. The Graph підтримує підписки через WebSocket—для оновлень в реальному часі без поллінгу.
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
}
}
`
Пагінація через first/skip працює до skip: 5000—далі The Graph повертає помилку. Для великих наборів даних потрібна пагінація keyset через id_gt.
Терміни та що входить
За 2–3 дні: проектування схеми під задачі клієнта, написання mappings для всіх подій та викликів, тестування на fork, розгортання на Hosted Service або настройка self-hosted ноди, базова інтеграція в існуючий фронтенд або надання GraphQL endpoint.







