Files
gh-enviodev-envio-plugins-p…/skills/subgraph-migration/references/common-mistakes.md
2025-11-29 18:26:05 +08:00

8.5 KiB

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.

// 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"

# 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.

// 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.

// 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.

# 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.

// 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:

type Transfer {
  token_id: String!  # Not token: Token!
}

7. Bytes vs String Type Mismatch

Problem: Using Bytes type which doesn't exist in HyperIndex.

# 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.

// 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.

# 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.

// 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.

# 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.

// 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.

// 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.

// 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.

// 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.

# Schema defines
date: Int!        # Expects JavaScript number
amount: BigInt!   # Expects bigint
// 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.

// 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)