14 KiB
14 KiB
Complete Migration Patterns Reference
Comprehensive patterns for migrating from TheGraph subgraphs to HyperIndex.
File Structure Migration
Subgraph Structure
subgraph/
├── subgraph.yaml
├── schema.graphql
├── abis/
│ └── Contract.json
└── src/
├── mapping.ts
└── utils/
└── helpers.ts
HyperIndex Structure
hyperindex/
├── config.yaml
├── schema.graphql
├── abis/
│ └── Contract.json
└── src/
├── EventHandlers.ts # or split by contract
├── factory.ts
├── pair.ts
└── utils/
└── helpers.ts
Config Migration Patterns
Single Contract
Subgraph:
dataSources:
- kind: ethereum/contract
name: MyContract
network: mainnet
source:
address: "0x..."
startBlock: 12345678
abi: MyContract
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Entity1
- Entity2
abis:
- name: MyContract
file: ./abis/MyContract.json
eventHandlers:
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
HyperIndex:
name: my-indexer
networks:
- id: 1
start_block: 12345678
contracts:
- name: MyContract
address: 0x...
handler: src/EventHandlers.ts
events:
- event: Transfer(address indexed from, address indexed to, uint256 value)
Multichain Configuration
HyperIndex multichain:
name: multichain-indexer
unordered_multichain_mode: true
# Global contract definitions
contracts:
- name: Factory
handler: src/factory.ts
events:
- event: PairCreated(address indexed token0, address indexed token1, address pair)
networks:
- id: 1 # Ethereum
start_block: 10000835
contracts:
- name: Factory
address: 0xEthereumFactory
- id: 10 # Optimism
start_block: 1234567
contracts:
- name: Factory
address: 0xOptimismFactory
- id: 137 # Polygon
start_block: 9876543
contracts:
- name: Factory
address: 0xPolygonFactory
Key: Define handlers/events globally, addresses per network.
Dynamic Contracts (Templates)
Subgraph templates:
templates:
- kind: ethereum/contract
name: Pair
network: mainnet
source:
abi: Pair
mapping:
eventHandlers:
- event: Swap(...)
handler: handleSwap
HyperIndex:
contracts:
# Factory has address
- name: Factory
address: 0xFactoryAddress
handler: src/factory.ts
events:
- event: PairCreated(...)
# Pair has NO address - registered dynamically
- name: Pair
handler: src/pair.ts
events:
- event: Swap(...)
- event: Mint(...)
- event: Burn(...)
- event: Sync(...)
Schema Migration Patterns
Type Conversions
| TheGraph | HyperIndex |
|---|---|
Bytes! |
String! |
BigInt! |
BigInt! |
BigDecimal! |
BigDecimal! |
Int! |
Int! |
ID! |
ID! |
Boolean! |
Boolean! |
Entity Decorator
# TheGraph
type Token @entity {
id: ID!
}
# HyperIndex - remove @entity
type Token {
id: ID!
}
Immutable Entities
# TheGraph
type Transfer @entity(immutable: true) {
id: ID!
}
# HyperIndex - just remove decorator
type Transfer {
id: ID!
}
Entity Relationships
Direct reference → _id field:
# TheGraph
type Transfer @entity {
token: Token!
}
# HyperIndex
type Transfer {
token_id: String! # Reference by ID
}
Arrays must have @derivedFrom:
# HyperIndex - REQUIRED
type Token {
id: ID!
transfers: [Transfer!]! @derivedFrom(field: "token")
}
type Transfer {
id: ID!
token_id: String! # Field referenced by @derivedFrom
}
Complete Schema Example
TheGraph:
type Factory @entity {
id: ID!
pairCount: BigInt!
pairs: [Pair!]! @derivedFrom(field: "factory")
}
type Token @entity {
id: ID!
symbol: String!
name: String!
decimals: BigInt!
}
type Pair @entity {
id: ID!
factory: Factory!
token0: Token!
token1: Token!
reserve0: BigDecimal!
reserve1: BigDecimal!
swaps: [Swap!]! @derivedFrom(field: "pair")
}
type Swap @entity(immutable: true) {
id: ID!
pair: Pair!
sender: Bytes!
amount0In: BigDecimal!
amount1In: BigDecimal!
timestamp: BigInt!
transaction: Bytes!
}
HyperIndex:
type Factory {
id: ID!
pairCount: BigInt!
pairs: [Pair!]! @derivedFrom(field: "factory")
}
type Token {
id: ID!
symbol: String!
name: String!
decimals: BigInt!
}
type Pair {
id: ID!
factory_id: String!
token0_id: String!
token1_id: String!
reserve0: BigDecimal!
reserve1: BigDecimal!
swaps: [Swap!]! @derivedFrom(field: "pair")
}
type Swap {
id: ID!
pair_id: String!
sender: String!
amount0In: BigDecimal!
amount1In: BigDecimal!
timestamp: BigInt!
transactionHash: String!
}
Handler Migration Patterns
Basic Handler
TheGraph:
export function handleTransfer(event: TransferEvent): void {
let id = event.transaction.hash.toHexString() + "-" + event.logIndex.toString();
let transfer = new Transfer(id);
transfer.from = event.params.from;
transfer.to = event.params.to;
transfer.value = event.params.value;
transfer.timestamp = event.block.timestamp;
transfer.blockNumber = event.block.number;
transfer.save();
}
HyperIndex:
import { MyContract } from "generated";
MyContract.Transfer.handler(async ({ event, context }) => {
const id = `${event.chainId}-${event.transaction.hash}-${event.logIndex}`;
const transfer = {
id,
from: event.params.from,
to: event.params.to,
value: event.params.value,
timestamp: BigInt(event.block.timestamp),
blockNumber: BigInt(event.block.number),
};
context.Transfer.set(transfer);
});
Entity Loading and Updates
TheGraph:
export function handleApproval(event: ApprovalEvent): void {
let token = Token.load(event.address.toHexString());
if (token == null) {
token = new Token(event.address.toHexString());
token.symbol = "UNKNOWN";
token.name = "Unknown Token";
token.decimals = BigInt.fromI32(18);
token.totalSupply = BigInt.fromI32(0);
}
token.approvalCount = token.approvalCount.plus(BigInt.fromI32(1));
token.save();
}
HyperIndex:
MyContract.Approval.handler(async ({ event, context }) => {
const tokenId = `${event.chainId}-${event.srcAddress}`;
let token = await context.Token.get(tokenId);
if (!token) {
token = {
id: tokenId,
symbol: "UNKNOWN",
name: "Unknown Token",
decimals: BigInt(18),
totalSupply: BigInt(0),
approvalCount: BigInt(0),
};
}
// Use spread operator - entities are immutable
context.Token.set({
...token,
approvalCount: token.approvalCount + BigInt(1),
});
});
Dynamic Contract Registration
TheGraph:
import { Pair as PairTemplate } from "../generated/templates";
export function handlePairCreated(event: PairCreatedEvent): void {
// Create template instance
PairTemplate.create(event.params.pair);
// Create Pair entity
let pair = new Pair(event.params.pair.toHexString());
pair.token0 = event.params.token0.toHexString();
pair.token1 = event.params.token1.toHexString();
pair.save();
}
HyperIndex:
import { Factory, Pair } from "generated";
// Register contract BEFORE handler
Factory.PairCreated.contractRegister(({ event, context }) => {
context.addPair(event.params.pair); // Method name: add{ContractName}
});
Factory.PairCreated.handler(async ({ event, context }) => {
const pairId = `${event.chainId}-${event.params.pair}`;
const pair = {
id: pairId,
token0_id: `${event.chainId}-${event.params.token0}`,
token1_id: `${event.chainId}-${event.params.token1}`,
reserve0: BigInt(0),
reserve1: BigInt(0),
};
context.Pair.set(pair);
});
BigDecimal Handling
Maintain precision from original subgraph:
import { BigDecimal } from "generated";
// Constants
export const ZERO_BI = BigInt(0);
export const ONE_BI = BigInt(1);
export const ZERO_BD = new BigDecimal(0);
export const ONE_BD = new BigDecimal(1);
export const BI_18 = BigInt(18);
// Convert to decimal with proper precision
export function exponentToBigDecimal(decimals: bigint): BigDecimal {
let bd = ONE_BD;
for (let i = ZERO_BI; i < decimals; i = i + ONE_BI) {
bd = bd.times(new BigDecimal(10));
}
return bd;
}
export function convertTokenToDecimal(
tokenAmount: bigint,
exchangeDecimals: bigint
): BigDecimal {
if (exchangeDecimals === ZERO_BI) {
return new BigDecimal(tokenAmount.toString());
}
return new BigDecimal(tokenAmount.toString()).div(
exponentToBigDecimal(exchangeDecimals)
);
}
Effect API for RPC Calls
TheGraph contract bindings:
import { ERC20 } from "../generated/templates/Pair/ERC20";
export function fetchTokenSymbol(tokenAddress: Address): string {
let contract = ERC20.bind(tokenAddress);
let result = contract.try_symbol();
if (result.reverted) {
return "UNKNOWN";
}
return result.value;
}
HyperIndex Effect API:
import { createEffect, S } from "envio";
import { createPublicClient, http, parseAbi } from "viem";
const ERC20_ABI = parseAbi([
"function symbol() view returns (string)",
"function name() view returns (string)",
"function decimals() view returns (uint8)",
]);
const client = createPublicClient({
transport: http(process.env.RPC_URL),
});
export const fetchTokenSymbol = createEffect(
{
name: "fetchTokenSymbol",
input: S.string,
output: S.string,
cache: true,
},
async ({ input: tokenAddress }) => {
try {
const symbol = await client.readContract({
address: tokenAddress as `0x${string}`,
abi: ERC20_ABI,
functionName: "symbol",
});
return symbol;
} catch {
return "UNKNOWN";
}
}
);
// Usage in handler
const symbol = await context.effect(fetchTokenSymbol, tokenAddress);
Timestamp and Block Data
// TheGraph
entity.timestamp = event.block.timestamp;
entity.blockNumber = event.block.number;
// HyperIndex - always cast to BigInt
entity.timestamp = BigInt(event.block.timestamp);
entity.blockNumber = BigInt(event.block.number);
// For day-based aggregations
const dayTimestamp = Math.floor(event.block.timestamp / 86400) * 86400;
const dayId = `${event.chainId}-${dayTimestamp}-${tokenAddress}`;
Field Selection for Transaction Data
When handler needs event.transaction.hash:
events:
- event: Transfer(address indexed from, address indexed to, uint256 value)
field_selection:
transaction_fields:
- hash
- from # optional
- to # optional
Without field_selection, event.transaction will be {}.
Complete Handler Migration Example
Original TheGraph handler:
import { BigInt, BigDecimal, Address } from "@graphprotocol/graph-ts";
import { Swap as SwapEvent } from "../generated/templates/Pair/Pair";
import { Swap, Pair, Token, Factory } from "../generated/schema";
import { convertTokenToDecimal, ZERO_BD, ONE_BI } from "./helpers";
export function handleSwap(event: SwapEvent): void {
let pair = Pair.load(event.address.toHexString());
if (pair === null) return;
let token0 = Token.load(pair.token0);
let token1 = Token.load(pair.token1);
if (token0 === null || token1 === null) return;
let amount0In = convertTokenToDecimal(event.params.amount0In, token0.decimals);
let amount1In = convertTokenToDecimal(event.params.amount1In, token1.decimals);
let amount0Out = convertTokenToDecimal(event.params.amount0Out, token0.decimals);
let amount1Out = convertTokenToDecimal(event.params.amount1Out, token1.decimals);
let swap = new Swap(
event.transaction.hash.toHexString() + "-" + event.logIndex.toString()
);
swap.pair = pair.id;
swap.sender = event.params.sender;
swap.to = event.params.to;
swap.amount0In = amount0In;
swap.amount1In = amount1In;
swap.amount0Out = amount0Out;
swap.amount1Out = amount1Out;
swap.timestamp = event.block.timestamp;
swap.transaction = event.transaction.hash;
swap.save();
// Update pair
pair.txCount = pair.txCount.plus(ONE_BI);
pair.save();
// Update factory
let factory = Factory.load("1");
if (factory !== null) {
factory.txCount = factory.txCount.plus(ONE_BI);
factory.save();
}
}
Migrated HyperIndex handler:
import { Pair } from "generated";
import { convertTokenToDecimal, ZERO_BD, ONE_BI } from "./utils/helpers";
Pair.Swap.handler(async ({ event, context }) => {
const pairId = `${event.chainId}-${event.srcAddress}`;
const pair = await context.Pair.get(pairId);
if (!pair) return;
const token0 = await context.Token.get(pair.token0_id);
const token1 = await context.Token.get(pair.token1_id);
if (!token0 || !token1) return;
const amount0In = convertTokenToDecimal(event.params.amount0In, token0.decimals);
const amount1In = convertTokenToDecimal(event.params.amount1In, token1.decimals);
const amount0Out = convertTokenToDecimal(event.params.amount0Out, token0.decimals);
const amount1Out = convertTokenToDecimal(event.params.amount1Out, token1.decimals);
const swap = {
id: `${event.chainId}-${event.transaction.hash}-${event.logIndex}`,
pair_id: pairId,
sender: event.params.sender,
to: event.params.to,
amount0In,
amount1In,
amount0Out,
amount1Out,
timestamp: BigInt(event.block.timestamp),
transactionHash: event.transaction.hash,
};
context.Swap.set(swap);
// Update pair - use spread operator
context.Pair.set({
...pair,
txCount: pair.txCount + ONE_BI,
});
// Update factory
const factoryId = `${event.chainId}-factory`;
const factory = await context.Factory.get(factoryId);
if (factory) {
context.Factory.set({
...factory,
txCount: factory.txCount + ONE_BI,
});
}
});