# Common Migration Mistakes and Solutions Common pitfalls when migrating from TheGraph to HyperIndex, with solutions. ## 1. Missing async/await **Problem:** Entity loading returns empty object `{}` instead of entity. ```typescript // WRONG - Missing await const token = context.Token.get(tokenId); if (token) { // token is {} not the actual entity } // CORRECT const token = await context.Token.get(tokenId); if (token) { // token is the actual entity } ``` **Note:** `context.Entity.set()` does NOT need await - it's synchronous. ## 2. Entity Arrays Without @derivedFrom **Problem:** Codegen fails with error "EE211: Arrays of entities is unsupported" ```graphql # WRONG - Missing @derivedFrom type Token { transfers: [Transfer!]! } # CORRECT - Must have @derivedFrom type Token { transfers: [Transfer!]! @derivedFrom(field: "token") } type Transfer { token_id: String! # Field referenced in @derivedFrom } ``` **Important:** @derivedFrom arrays are virtual fields - they exist only in GraphQL API, not in handlers. ## 3. Accessing @derivedFrom Arrays in Handlers **Problem:** Trying to access virtual arrays in handlers. ```typescript // WRONG - derivedFrom arrays don't exist in handlers const transfers = token.transfers; // undefined // CORRECT - Query using the relationship field // Use indexed field operations if available: const transfers = await context.Transfer.getWhere.token_id.eq(tokenId); // Or query by ID if you know specific transfer IDs const transfer = await context.Transfer.get(transferId); ``` ## 4. Mutating Entities Directly **Problem:** Entities are read-only, direct mutation doesn't work. ```typescript // WRONG - Entities are immutable token.totalSupply = newSupply; context.Token.set(token); // Changes not saved // CORRECT - Use spread operator context.Token.set({ ...token, totalSupply: newSupply, }); ``` ## 5. Missing Field Selection for Transaction Data **Problem:** `event.transaction.hash` is undefined. ```yaml # WRONG - No field selection events: - event: Transfer(address indexed from, address indexed to, uint256 value) # CORRECT - Add field_selection events: - event: Transfer(address indexed from, address indexed to, uint256 value) field_selection: transaction_fields: - hash ``` ## 6. Direct Relationship References **Problem:** Using entity objects instead of ID strings. ```typescript // WRONG - TheGraph style const transfer = { token: tokenObject, // Direct reference }; // CORRECT - HyperIndex uses _id fields const transfer = { token_id: tokenObject.id, // String ID reference }; ``` Schema must also use `_id` suffix: ```graphql type Transfer { token_id: String! # Not token: Token! } ``` ## 7. Bytes vs String Type Mismatch **Problem:** Using Bytes type which doesn't exist in HyperIndex. ```graphql # WRONG - TheGraph type sender: Bytes! transactionHash: Bytes! # CORRECT - HyperIndex uses String sender: String! transactionHash: String! ``` ## 8. Missing Multichain ID Prefixes **Problem:** ID collisions across chains in multichain indexers. ```typescript // WRONG - ID collision between chains const id = event.params.tokenId.toString(); // CORRECT - Prefix with chainId const id = `${event.chainId}-${event.params.tokenId}`; // For chain-specific singleton entities const bundleId = `${event.chainId}-1`; // Not just "1" ``` ## 9. Contract Address in Dynamic Contract Config **Problem:** Including address for dynamically registered contracts. ```yaml # WRONG - Dynamic contract shouldn't have address contracts: - name: Pair address: 0xSomeAddress # Remove this! handler: src/pair.ts events: - event: Swap(...) # CORRECT - No address field contracts: - name: Pair handler: src/pair.ts events: - event: Swap(...) ``` ## 10. Missing contractRegister **Problem:** Dynamic contracts not being indexed. ```typescript // WRONG - Only handler, no registration Factory.PairCreated.handler(async ({ event, context }) => { // Pairs won't be indexed! }); // CORRECT - Register before handler Factory.PairCreated.contractRegister(({ event, context }) => { context.addPair(event.params.pair); }); Factory.PairCreated.handler(async ({ event, context }) => { // Now pairs will be indexed }); ``` ## 11. Duplicate Contract Names in Multichain **Problem:** Defining handlers in network sections instead of globally. ```yaml # WRONG - Duplicates contract definition networks: - id: 1 contracts: - name: Factory handler: src/factory.ts # Don't repeat here events: [...] address: [...] - id: 10 contracts: - name: Factory handler: src/factory.ts # Don't repeat here events: [...] address: [...] # CORRECT - Global definition, network-specific addresses contracts: - name: Factory handler: src/factory.ts events: - event: PairCreated(...) networks: - id: 1 contracts: - name: Factory address: 0xEthereumAddress - id: 10 contracts: - name: Factory address: 0xOptimismAddress ``` ## 12. Losing BigDecimal Precision **Problem:** Converting financial values to JavaScript numbers. ```typescript // WRONG - Loses precision const price = Number(amount) / 10 ** 18; export const ZERO_BD = 0; // Wrong type // CORRECT - Maintain BigDecimal precision import { BigDecimal } from "generated"; const ZERO_BD = new BigDecimal(0); function convertToDecimal(amount: bigint, decimals: bigint): BigDecimal { return new BigDecimal(amount.toString()).div( new BigDecimal((10n ** decimals).toString()) ); } ``` ## 13. Wrong Entity Type Imports **Problem:** Importing contract handlers instead of entity types. ```typescript // WRONG - Imports contract import { Pair, Token } from "generated"; // CORRECT - Import entity types import { Pair_t, Token_t } from "generated/src/db/Entities.gen"; // Or use inferred types const pair: Pair_t = { id: "...", // ... }; ``` ## 14. External Calls Without Effect API **Problem:** Direct RPC calls with preload_handlers enabled. ```typescript // WRONG - Direct call runs twice during preload const balance = await client.readContract({ address: tokenAddress, abi: ERC20_ABI, functionName: "balanceOf", }); // CORRECT - Use Effect API import { createEffect, S } from "envio"; export const getBalance = createEffect({ name: "getBalance", input: S.string, output: S.string, cache: true, }, async ({ input: address }) => { const balance = await client.readContract({ address: address as `0x${string}`, abi: ERC20_ABI, functionName: "balanceOf", }); return balance.toString(); }); // In handler const balance = await context.effect(getBalance, tokenAddress); ``` ## 15. Hardcoded Factory Addresses **Problem:** Using hardcoded addresses instead of constants. ```typescript // WRONG - Hardcoded address const factory = await context.Factory.get("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"); // CORRECT - Use constants import { FACTORY_ADDRESS } from "./constants"; const factory = await context.Factory.get(`${event.chainId}-${FACTORY_ADDRESS}`); ``` ## 16. Schema Int vs BigInt Mismatch **Problem:** Using wrong type for numeric fields. ```graphql # Schema defines date: Int! # Expects JavaScript number amount: BigInt! # Expects bigint ``` ```typescript // WRONG - Type mismatch const entity = { date: BigInt(timestamp), // Schema expects number amount: 123, // Schema expects bigint }; // CORRECT - Match schema types const entity = { date: timestamp, // number for Int! amount: BigInt(123), // bigint for BigInt! }; ``` ## 17. Null vs Undefined for Optional Fields **Problem:** Using null instead of undefined. ```typescript // WRONG - TheGraph uses null const entity = { optionalField: null, }; // CORRECT - HyperIndex uses undefined const entity = { optionalField: undefined, }; // Or omit the field entirely const entity = { // optionalField not included }; ``` ## Migration Validation Checklist After migrating, verify: - [ ] `pnpm codegen` runs without errors - [ ] `pnpm tsc --noEmit` compiles successfully - [ ] `TUI_OFF=true pnpm dev` runs and indexes events - [ ] All handlers have async keyword - [ ] All context.Entity.get() have await - [ ] All entity updates use spread operator - [ ] All relationships use _id suffix - [ ] All entity arrays have @derivedFrom - [ ] Transaction access has field_selection - [ ] IDs are prefixed with chainId for multichain - [ ] Dynamic contracts use contractRegister - [ ] BigDecimal precision is maintained - [ ] External calls use Effect API (if preload_handlers enabled)