Розробка subgraph для The Graph

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка subgraph для The Graph
Середній
~3-5 днів
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1306
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1218
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    920
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1147
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    610
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    885

Розробка Subgraph для The Graph

Проблема, з якою стикаються всі розробники dApp: смарт-контракти не зберігають історію стану в зручному для запитів форматі. eth_getLogs з фільтром за подіями — це грубий інструмент: немає сортування, немає агрегації, немає зв'язків між подіями різних контрактів. В результаті фронтенд або тягне тони даних і обробляє їх на клієнті, або команда піднімає власний індексуючий бекенд. The Graph вирішує це завдання стандартним способом — ви описуєте, що індексувати, а мережа робить це за вас.

Subgraph — це по суті декларація: які контракти слухати, які подій обробляти, як трансформувати дані в entities. Написати його правильно з першої спроби складніше, ніж здається.

Архітектура Subgraph і типові помилки

Структура проекту

Subgraph складається з трьох частин: subgraph.yaml (маніфест), schema.graphql (модель даних), AssemblyScript handlers (логіка трансформації). Маніфест — найважливіше місце:

dataSources:
  - kind: ethereum
    name: UniswapV3Pool
    network: mainnet
    source:
      address: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"
      abi: UniswapV3Pool
      startBlock: 12369621  # блок розгортання контракту — обов'язково
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.7
      language: wasm/assemblyscript
      entities:
        - Pool
        - Swap
      abis:
        - name: UniswapV3Pool
          file: ./abis/UniswapV3Pool.json
      eventHandlers:
        - event: Swap(indexed address,indexed address,int256,int256,uint160,uint128,int24)
          handler: handleSwap

startBlock — критичний параметр. Якщо вказати 0, індексування пройде з genesis блока, синхронізація займе дні. Завжди вказуємо блок розгортання контракту. Знайти його можна через Etherscan, поле "Contract Creation".

Дизайн schema: думати GraphQL-запитами

Схема має проектуватися виходячи з того, які запити потрібні фронтенду — не з структури подій контракту. Типова помилка: робити entities один-до-одного з подіями. Це ведає до того, що фронтенд робить N+1 запитів.

Правильний підхід — денормалізовані entities з передагрегованими даними:

type Pool @entity {
  id: ID!                        # адреса пулу
  token0: Token!
  token1: Token!
  feeTier: BigInt!
  totalVolumeUSD: BigDecimal!    # накопичений об'єм — оновлюємо на кожному Swap
  totalValueLockedUSD: BigDecimal!
  txCount: BigInt!
  swaps: [Swap!]! @derivedFrom(field: "pool")
}

type Swap @entity {
  id: ID!                        # txHash + logIndex
  pool: Pool!
  sender: Bytes!
  recipient: Bytes!
  amount0: BigDecimal!
  amount1: BigDecimal!
  amountUSD: BigDecimal!
  timestamp: BigInt!
  blockNumber: BigInt!
}

@derivedFrom — віртуальний зв'язок, не зберігає масив ID у записі Pool. Це важливо для продуктивності: пул з тисячами своопів не буде рости в розмірі запису.

AssemblyScript handlers: де все ламається

AssemblyScript — строго типізована мова, компілюється в WebAssembly. Звички з TypeScript тут небезпечні:

// НЕПРАВИЛЬНО — null reference в AS викликає панік
let pool = Pool.load(event.address.toHexString())
pool.txCount = pool.txCount.plus(BigInt.fromI32(1)) // pool може бути null

// ПРАВИЛЬНО
let poolId = event.address.toHexString()
let pool = Pool.load(poolId)
if (pool === null) {
  pool = new Pool(poolId)
  pool.txCount = BigInt.fromI32(0)
  pool.totalVolumeUSD = BigDecimal.fromString("0")
}
pool.txCount = pool.txCount.plus(BigInt.fromI32(1))
pool.save()

BigDecimal для фінансових значень — обов'язково. BigInt з контракту потрібно конвертувати з урахуванням decimals токена:

function convertTokenToDecimal(tokenAmount: BigInt, exchangeDecimals: BigInt): BigDecimal {
  if (exchangeDecimals == BigInt.fromI32(0)) {
    return tokenAmount.toBigDecimal()
  }
  return tokenAmount.toBigDecimal().div(
    BigInt.fromI32(10).pow(exchangeDecimals.toI32() as u8).toBigDecimal()
  )
}

Call handlers і block handlers

Крім event handlers є два інших типи:

callHandlers — реагують на виклики конкретних функцій. Використовуються, коли контракт не емітує потрібні подій (зустрічається в старих контрактах). Значно повільніше індексування за подіями — The Graph мусить обробляти кожен call trace.

blockHandlers — викликаються на кожен блок. Надзвичайно дорогі для hosted service і decentralized network. Використовувати тільки якщо немає альтернативи, обов'язково з filter: { kind: once } або умовною логікою всередині.

Розгортання та робота з мережею

Hosted Service vs Decentralized Network

Hosted Service Decentralized Network
Вартість Безплатно (застаріває) GRT токени (Indexer fees)
Latency Низька Вища (~100–500ms)
Censorship resistance Ні (централізована) Так
SLA Без гарантій Залежить від Indexers
Підходить для Розробка, тестування Production з вимогою decentralization

Для production протоколів — decentralized network. Graph Explorer дозволяє моніторити статус синхронізації і вибирати Indexers.

# Розгортання в Subgraph Studio
graph auth --studio <deploy-key>
graph codegen && graph build
graph deploy --studio <subgraph-name>

Відладка повільної синхронізації

Якщо subgraph синхронізується повільніше ніж очікувалось:

  1. Перевірити кількість callHandlers — замінити на eventHandlers де можливо
  2. Переконатись що startBlock не занадто рано
  3. Перевірити кількість eth_call у handlers — кожен виклик контракту з mapping це додатковий RPC запит
  4. Використовувати ipfs.cat мінімально — повільна операція

Типова швидкість: ~2000–5000 блоків/хвилину для event-only subgraph на hosted service. З callHandlers — в 5–10 разів повільніше.

Що входить в роботу

  • Аналіз ABI контрактів і визначення потрібних events/calls
  • Проектування schema для конкретних фронтенд-запитів
  • Написання та тестування AssemblyScript handlers
  • Розгортання та моніторинг синхронізації
  • Документація GraphQL endpoints і приклади запитів