packages/cli/templates/static/shared/.claude/skills/subgraph-migration/SKILL.md
Migrate a TheGraph subgraph to Envio HyperIndex using TDD. Covers schema conversion (remove @entity, Bytes->String, @derivedFrom), handler translation (save->set, store.get->context.get, templates->contractRegister), and verification against subgraph data.
npx skillsauth add enviodev/hyperindex subgraph-migrationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Migrate from TheGraph subgraph to Envio HyperIndex using Test-Driven Development.
Ask the user for:
https://api.thegraph.com/subgraphs/name/org/subgraph)The subgraph GraphQL is the source of truth — use it to verify HyperIndex produces identical data.
Prerequisites: Subgraph folder in workspace, Node.js v22+, pnpm, Docker
References:
| TheGraph | Envio |
|----------|-------|
| @entity decorator | No decorator needed |
| Bytes! | String! |
| entity.save() | context.Entity.set(entity) |
| store.get("Entity", id) | await context.Entity.get(id) |
| ContractTemplate.create(addr) | context.addContract(addr) in contractRegister |
| Direct array access | @derivedFrom (virtual, query via getWhere) |
| .bind() for RPC | Effect API with context.effect() |
After EVERY code change, run pnpm test. TypeScript compilation only catches syntax errors — runtime errors (database issues, missing entities, logic errors) only appear when running the indexer.
See references/step-by-step.md for the full runtime testing checklist.
import { describe, it, expect } from "vitest";
import { createTestIndexer } from "generated";
describe("Migration Verification", () => {
it("Should match subgraph data for Factory.PairCreated", async () => {
const indexer = createTestIndexer();
const result = await indexer.process({
chains: {
1: { startBlock: 10_000_000, endBlock: 10_000_100 },
},
});
expect(result.changes).toMatchInlineSnapshot(`...`);
});
});
Run tests: pnpm test
After each step, run: pnpm codegen && pnpm tsc --noEmit && pnpm test
Full step-by-step details with quality checks: references/step-by-step.md
Clear generated event handlers and replace with empty TODO handlers.
@entity decoratorsBytes! → String!BigInt!, BigDecimal!, ID!@derivedFrom (causes a codegen error without it)@derivedFrom arrays are virtual — cannot access in handlers, only API queries# WRONG — causes "Arrays of entities is unsupported" error
type Transaction { mints: [Mint!]! }
# CORRECT
type Transaction { mints: [Mint!]! @derivedFrom(field: "transaction") }
Run: pnpm codegen (required after schema changes)
Mirror subgraph file structure with exact filenames. Update config.yaml with global contract definitions and chain-specific addresses only.
For factory-created contracts: add contractRegister BEFORE handler, remove address from dynamic contracts in config.
Factory.PairCreated.contractRegister(({ event, context }) => {
context.addPair(event.params.pair);
});
Implementation order is critical. See references/step-by-step.md for full 5a/5b/5c/5d breakdown.
External calls MUST use Effect API — see references/migration-patterns.md for Effect API and contract state fetching patterns.
Systematic review of every handler and helper function against subgraph. Multiple passes — first pass always misses things. See references/step-by-step.md for full verification checklist.
Search for all process.env references and update .env.example.
Full patterns with examples: references/migration-patterns.md
// TheGraph:
let entity = new Entity(id);
entity.field = value;
entity.save();
// Envio:
const entity: Entity = {
id: `${event.chainId}-${id}`,
field: value,
blockNumber: BigInt(event.block.number),
transactionHash: event.transaction.hash,
};
context.Entity.set(entity);
let entity = await context.Entity.get(id);
if (entity) {
context.Entity.set({ ...entity, field: newValue });
}
// TheGraph: transaction.mints.push(mint);
// Envio:
const mints = await context.Mint.getWhere({ transaction_id: { _eq: transactionId } });
import { BigDecimal } from "generated";
// NOT: import { BigDecimal } from "bignumber.js";
export const ZERO_BD = new BigDecimal(0);
export const ONE_BD = new BigDecimal(1);
export const ZERO_BI = BigInt(0);
export const ONE_BI = BigInt(1);
TheGraph uses .bind() for RPC. Envio requires Effect API with viem. Full pattern: references/migration-patterns.md
Full quality check guide with 12 common fixes: references/quality-checks.md
_id suffix// WRONG: { token0: token0.id }
// CORRECT: { token0_id: token0.id }
// WRONG: const entity = context.Entity.get(id); // Returns {}
// CORRECT: const entity = await context.Entity.get(id);
Note: context.Entity.set() is synchronous — no await needed.
// WRONG: import { Pair } from "generated"; // Pair is a contract handler, not a type
// CORRECT (when name collides with contract):
import type { Entities } from "generated";
const p: Entities["Pair"] = { ... };
| Schema | TypeScript |
|--------|------------|
| Int! | number |
| BigInt! | bigint / ZERO_BI |
| BigDecimal! | BigDecimal / ZERO_BD |
| String! / Bytes! | string |
| Entity! | entity_id: string |
Prefix all entity IDs: ${event.chainId}-${originalId}
development
Write and run tests for HyperIndex indexers using Vitest and createTestIndexer(). Covers test setup, processing block ranges, asserting entity changes with toMatchInlineSnapshot, and TDD workflow. Use when writing tests, debugging handler output, or verifying indexer behavior.
data-ai
Use when indexing all instances of a contract across all addresses (e.g., all ERC-20 transfers on a chain). Config setup (no address), wildcard handler option, and event.srcAddress.
data-ai
Use when needing transaction-level data in handlers. Configure field_selection to include transaction fields on events, and access via event.transaction. No native transaction handler — access through event handlers.
tools
Use when needing call trace data from transactions. HyperSync supports trace queries at the data layer. No handler-level trace API currently — access traces via HyperSync client directly.