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

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка subgraph для The Graph
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1056
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка 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 design: думать GraphQL-запросами

Схема должна проектироваться исходя из того, какие запросы нужны фронтенду — не из структуры событий контракта. Типичная ошибка: делать entities один-к-одному с событиями. Это ведёт к тому, что фронтенд делает N+1 запросов.

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

type Pool @entity {
  id: ID!                        # address пула
  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 Нет (centralized) Да
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 и примеры запросов