Expert guidance for developing subgraphs with The Graph protocol. Covers schema design, AssemblyScript mappings, manifest configuration, and deployment workflows.
Expert knowledge for developing subgraphs with The Graph protocol. This skill covers schema design, mapping handlers, data sources, and deployment workflows.
Subgraphs are open APIs that extract data from blockchain networks, process it, and store it for efficient querying via GraphQL. They consist of three main components:
my-subgraph/
├── schema.graphql # GraphQL schema
├── subgraph.yaml # Manifest file
├── src/
│ └── mapping.ts # Event handlers
├── abis/
│ └── Contract.json # Contract ABIs
├── generated/ # Auto-generated types
├── build/ # Compiled output
└── package.json
# Install graph-cli globally
npm install -g @graphprotocol/graph-cli
# Initialize new subgraph
graph init --product subgraph-studio
# Generate types from schema and ABIs
graph codegen
# Build the subgraph
graph build
# Authenticate with Subgraph Studio
graph auth --studio <DEPLOY_KEY>
# Deploy to Subgraph Studio
graph deploy --studio <SUBGRAPH_SLUG>
# Deploy to hosted service (deprecated)
graph deploy --product hosted-service <GITHUB_USER>/<SUBGRAPH_NAME>
type Token @entity {
id: Bytes! # Unique identifier
name: String! # Token name
symbol: String! # Token symbol
decimals: Int! # Decimal places
totalSupply: BigInt! # Total supply
holders: [TokenBalance!]! @derivedFrom(field: "token")
}
type TokenBalance @entity {
id: Bytes! # address + token address
token: Token! # Reference to token
account: Bytes! # Holder address
amount: BigInt! # Balance amount
}
| Type | Description | Example |
|------|-------------|---------|
| Bytes | Byte array (addresses, hashes) | id: Bytes! |
| String | UTF-8 string | name: String! |
| Int | 32-bit integer | decimals: Int! |
| Int8 | 64-bit integer | id: Int8! |
| BigInt | Arbitrary precision integer | totalSupply: BigInt! |
| BigDecimal | Arbitrary precision decimal | price: BigDecimal! |
| Boolean | True/false | active: Boolean! |
| Timestamp | Unix timestamp | timestamp: Timestamp! |
# One-to-many with @derivedFrom (recommended)
type Pool @entity {
id: Bytes!
swaps: [Swap!]! @derivedFrom(field: "pool")
}
type Swap @entity {
id: Bytes!
pool: Pool!
}
# Many-to-many
type User @entity {
id: Bytes!
pools: [PoolMembership!]! @derivedFrom(field: "user")
}
type PoolMembership @entity {
id: Bytes!
user: User!
pool: Pool!
}
specVersion: 1.3.0
schema:
file: ./schema.graphql
indexerHints:
prune: auto
dataSources:
- kind: ethereum/contract
name: ERC20
network: mainnet
source:
address: "0x..."
abi: ERC20
startBlock: 12345678
mapping:
kind: ethereum/events
apiVersion: 0.0.9
language: wasm/assemblyscript
entities:
- Token
- Transfer
abis:
- name: ERC20
file: ./abis/ERC20.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
file: ./src/mapping.ts
# Event handlers (most common)
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
# Call handlers (for function calls)
callHandlers:
- function: transfer(address,uint256)
handler: handleTransferCall
# Block handlers (every block or filtered)
blockHandlers:
- handler: handleBlock
- handler: handleBlockWithFilter
filter:
kind: call
For dynamically created contracts (e.g., factory patterns):
templates:
- kind: ethereum/contract
name: Pool
network: mainnet
source:
abi: Pool
mapping:
kind: ethereum/events
apiVersion: 0.0.9
language: wasm/assemblyscript
entities:
- Pool
- Swap
abis:
- name: Pool
file: ./abis/Pool.json
eventHandlers:
- event: Swap(indexed address,uint256,uint256)
handler: handleSwap
file: ./src/pool.ts
import { Transfer } from "../generated/ERC20/ERC20"
import { Token, TransferEvent } from "../generated/schema"
export function handleTransfer(event: Transfer): void {
// Load or create token entity
let token = Token.load(event.address)
if (token == null) {
token = new Token(event.address)
token.name = "Unknown"
token.symbol = "???"
token.decimals = 18
token.totalSupply = BigInt.zero()
}
token.save()
// Create transfer event entity
let transfer = new TransferEvent(
event.transaction.hash.concatI32(event.logIndex.toI32())
)
transfer.token = token.id
transfer.from = event.params.from
transfer.to = event.params.to
transfer.amount = event.params.value
transfer.timestamp = event.block.timestamp
transfer.blockNumber = event.block.number
transfer.save()
}
import { PoolCreated } from "../generated/Factory/Factory"
import { Pool as PoolTemplate } from "../generated/templates"
import { Pool } from "../generated/schema"
export function handlePoolCreated(event: PoolCreated): void {
// Create entity
let pool = new Pool(event.params.pool)
pool.token0 = event.params.token0
pool.token1 = event.params.token1
pool.createdAt = event.block.timestamp
pool.save()
// Start indexing the new pool contract
PoolTemplate.create(event.params.pool)
}
import { ERC20 } from "../generated/ERC20/ERC20"
export function handleTransfer(event: Transfer): void {
let contract = ERC20.bind(event.address)
// try_ methods return null on revert
let nameResult = contract.try_name()
let name = nameResult.reverted ? "Unknown" : nameResult.value
let symbolResult = contract.try_symbol()
let symbol = symbolResult.reverted ? "???" : symbolResult.value
}
import { BigInt, BigDecimal } from "@graphprotocol/graph-ts"
// BigInt operations
let a = BigInt.fromI32(100)
let b = BigInt.fromString("1000000000000000000")
let sum = a.plus(b)
let diff = b.minus(a)
let product = a.times(b)
let quotient = b.div(a)
// BigDecimal for precision
let decimals = BigInt.fromI32(18)
let divisor = BigInt.fromI32(10).pow(decimals.toI32() as u8)
let price = new BigDecimal(amount).div(new BigDecimal(divisor))
// Preferred: Use concatI32 for unique IDs
let id = event.transaction.hash.concatI32(event.logIndex.toI32())
// For address combinations
let balanceId = event.params.account.concat(event.address)
let entity = Entity.load(id)
if (entity == null) {
entity = new Entity(id)
// Initialize required fields
}
entity.save()
Combine multiple subgraphs into a single composed subgraph for data aggregation.
# composed-subgraph/subgraph.yaml
specVersion: 1.3.0
schema:
file: ./schema.graphql
dataSources:
- kind: subgraph
name: TokenSource
network: mainnet
source:
address: "QmSourceSubgraphId..." # Deployment ID
startBlock: 18000000
mapping:
kind: subgraph/triggers
apiVersion: 0.0.9
language: wasm/assemblyscript
entities:
- AggregatedData
triggers:
- entity: Token
handler: handleToken
file: ./src/composition.ts
import { Token } from "../generated/TokenSource/schema"
import { AggregatedData } from "../generated/schema"
export function handleToken(entity: Token): void {
let data = AggregatedData.load(entity.id)
if (data == null) {
data = new AggregatedData(entity.id)
}
data.tokenName = entity.name
data.save()
}
See subgraph-composition.md for full details.
Supported networks include:
mainnet (Ethereum)arbitrum-oneoptimismpolygonbaseavalanchebsc (BNB Chain)gnosisfantomceloSee full list: https://thegraph.com/docs/en/supported-networks/
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer