Subgraph Development for The Graph

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Subgraph Development for The Graph
Medium
~3-5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1043
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Subgraph Development for The Graph

The problem faced by all dApp developers: smart contracts don't store state history in a query-friendly format. eth_getLogs with event filtering is a crude tool: no sorting, no aggregation, no relationships between events from different contracts. As a result, the frontend either fetches tons of data and processes it on the client, or the team runs its own indexing backend. The Graph solves this problem in a standard way — you describe what to index, and the network does it for you.

A subgraph is essentially a declaration: which contracts to listen to, which events to process, how to transform data into entities. Writing it correctly the first time is harder than it seems.

Subgraph Architecture and Typical Mistakes

Project Structure

A subgraph consists of three parts: subgraph.yaml (manifest), schema.graphql (data model), AssemblyScript handlers (transformation logic). The manifest is the most important place:

dataSources:
  - kind: ethereum
    name: UniswapV3Pool
    network: mainnet
    source:
      address: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"
      abi: UniswapV3Pool
      startBlock: 12369621  # contract deployment block — mandatory
    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 — critical parameter. If you specify 0, indexing will run from the genesis block, synchronization will take days. Always specify the contract deployment block. Find it via Etherscan, "Contract Creation" field.

Schema Design: Think in GraphQL Queries

The schema should be designed based on what queries the frontend needs — not based on contract event structure. A typical mistake: making entities one-to-one with events. This leads to the frontend making N+1 requests.

The right approach — denormalized entities with pre-aggregated data:

type Pool @entity {
  id: ID!                        # pool address
  token0: Token!
  token1: Token!
  feeTier: BigInt!
  totalVolumeUSD: BigDecimal!    # cumulative volume — update on every 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 — virtual relationship, doesn't store an array of IDs in the Pool record. This is important for performance: a pool with thousands of swaps won't grow in record size.

AssemblyScript Handlers: Where Everything Breaks

AssemblyScript is a strongly typed language that compiles to WebAssembly. TypeScript habits here are dangerous:

// WRONG — null reference in AS causes panic
let pool = Pool.load(event.address.toHexString())
pool.txCount = pool.txCount.plus(BigInt.fromI32(1)) // pool might be null

// RIGHT
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 for financial values — mandatory. BigInt from contracts needs to be converted accounting for token 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 and Block Handlers

Besides event handlers, there are two other types:

callHandlers — react to calls of specific functions. Used when the contract doesn't emit the necessary events (found in older contracts). Significantly slower than event-based indexing — The Graph must process each call trace.

blockHandlers — called on every block. Extremely expensive for hosted service and decentralized network. Use only if no alternative exists, mandatory with filter: { kind: once } or conditional logic inside.

Deployment and Network Operations

Hosted Service vs Decentralized Network

Hosted Service Decentralized Network
Cost Free (deprecated) GRT tokens (Indexer fees)
Latency Low Higher (~100–500ms)
Censorship resistance No (centralized) Yes
SLA No guarantees Depends on Indexers
Suitable for Development, testing Production with decentralization requirement

For production protocols — decentralized network. Graph Explorer allows you to monitor sync status and select Indexers.

# Deploy to Subgraph Studio
graph auth --studio <deploy-key>
graph codegen && graph build
graph deploy --studio <subgraph-name>

Debugging Slow Synchronization

If subgraph syncs slower than expected:

  1. Check the number of callHandlers — replace with eventHandlers where possible
  2. Ensure startBlock is not too early
  3. Check the number of eth_call in handlers — each contract call from mapping is an additional RPC request
  4. Use ipfs.cat minimally — slow operation

Typical speed: ~2000–5000 blocks/minute for event-only subgraph on hosted service. With callHandlers — 5–10 times slower.

What's Included

  • Analysis of contract ABIs and identifying needed events/calls
  • Schema design for specific frontend query patterns
  • Writing and testing AssemblyScript handlers
  • Deployment and sync monitoring
  • GraphQL endpoint documentation and query examples