# 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:** ```yaml 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:** ```yaml 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:** ```yaml 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:** ```yaml templates: - kind: ethereum/contract name: Pair network: mainnet source: abi: Pair mapping: eventHandlers: - event: Swap(...) handler: handleSwap ``` **HyperIndex:** ```yaml 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 ```graphql # TheGraph type Token @entity { id: ID! } # HyperIndex - remove @entity type Token { id: ID! } ``` ### Immutable Entities ```graphql # TheGraph type Transfer @entity(immutable: true) { id: ID! } # HyperIndex - just remove decorator type Transfer { id: ID! } ``` ### Entity Relationships **Direct reference → _id field:** ```graphql # TheGraph type Transfer @entity { token: Token! } # HyperIndex type Transfer { token_id: String! # Reference by ID } ``` **Arrays must have @derivedFrom:** ```graphql # 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:** ```graphql 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:** ```graphql 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:** ```typescript 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:** ```typescript 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:** ```typescript 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:** ```typescript 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:** ```typescript 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:** ```typescript 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: ```typescript 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:** ```typescript 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:** ```typescript 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 ```typescript // 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`: ```yaml 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:** ```typescript 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:** ```typescript 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, }); } }); ```