Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:26:05 +08:00
commit 8bcde7080b
26 changed files with 5957 additions and 0 deletions

View File

@@ -0,0 +1,318 @@
---
name: Subgraph Migration
description: This skill should be used when the user asks to "migrate from subgraph", "convert subgraph to hyperindex", "migrate from thegraph", "port subgraph", "convert subgraph handlers", "migrate assemblyscript to typescript", or mentions TheGraph, subgraph migration, subgraph.yaml conversion, or converting from TheGraph to Envio. For core HyperIndex development patterns, refer to the hyperindex-development skill.
version: 1.0.0
---
# Subgraph to HyperIndex Migration
Migrate from TheGraph subgraphs to Envio HyperIndex. HyperIndex delivers up to 100x faster indexing with a developer-friendly TypeScript API.
## Migration Overview
Three major steps:
1. `subgraph.yaml``config.yaml`
2. Schema migration (near copy-paste)
3. Event handler migration (AssemblyScript → TypeScript)
## Step 1: Config Migration
### subgraph.yaml → config.yaml
**TheGraph:**
```yaml
specVersion: 0.0.4
dataSources:
- kind: ethereum/contract
name: Factory
network: mainnet
source:
address: "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"
startBlock: 10000835
abi: Factory
mapping:
eventHandlers:
- event: PairCreated(indexed address,indexed address,address,uint256)
handler: handlePairCreated
templates:
- name: Pair
source:
abi: Pair
```
**HyperIndex:**
```yaml
name: my-indexer
networks:
- id: 1
start_block: 10000835
contracts:
- name: Factory
address: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f
handler: src/factory.ts
events:
- event: PairCreated(address indexed token0, address indexed token1, address pair, uint256)
- name: Pair
handler: src/pair.ts # No address - dynamic
events:
- event: Swap(...)
```
**Key differences:**
- Remove `dataSources`, `templates`, `mapping` nesting
- Use `networks``contracts` structure
- Event signatures include parameter names
- Dynamic contracts have no address field
## Step 2: Schema Migration
### Remove @entity decorator
```graphql
# TheGraph
type Token @entity {
id: ID!
symbol: String!
}
# HyperIndex
type Token {
id: ID!
symbol: String!
}
```
### Convert Bytes to String
```graphql
# TheGraph
address: Bytes!
# HyperIndex
address: String!
```
### Entity Relationships
```graphql
# TheGraph
type Transfer @entity {
token: Token!
}
# HyperIndex - use _id suffix
type Transfer {
token_id: String!
}
```
### Entity Arrays MUST have @derivedFrom
```graphql
# TheGraph (sometimes implicit)
type Token @entity {
transfers: [Transfer!]!
}
# HyperIndex - REQUIRED explicit @derivedFrom
type Token {
transfers: [Transfer!]! @derivedFrom(field: "token")
}
type Transfer {
token_id: String! # The field referenced by @derivedFrom
}
```
**Critical:** Arrays without `@derivedFrom` cause codegen error EE211.
## Step 3: Handler Migration
### Basic Pattern
**TheGraph (AssemblyScript):**
```typescript
export function handleTransfer(event: TransferEvent): void {
let entity = new Transfer(event.transaction.hash.toHexString());
entity.from = event.params.from;
entity.to = event.params.to;
entity.amount = event.params.value;
entity.save();
}
```
**HyperIndex (TypeScript):**
```typescript
import { MyContract } from "generated";
MyContract.Transfer.handler(async ({ event, context }) => {
const entity = {
id: `${event.chainId}-${event.transaction.hash}-${event.logIndex}`,
from: event.params.from,
to: event.params.to,
amount: event.params.value,
blockNumber: BigInt(event.block.number),
};
context.Transfer.set(entity);
});
```
### Entity Loading
**TheGraph:**
```typescript
let token = Token.load(id);
if (token == null) {
token = new Token(id);
}
```
**HyperIndex:**
```typescript
let token = await context.Token.get(id);
if (!token) {
token = { id, name: "Unknown", /* ... */ };
}
```
### Entity Updates
**TheGraph:**
```typescript
token.totalSupply = newSupply;
token.save();
```
**HyperIndex (use spread - entities are immutable):**
```typescript
context.Token.set({
...token,
totalSupply: newSupply,
});
```
### Dynamic Contract Registration
**TheGraph:**
```typescript
import { Pair as PairTemplate } from "../generated/templates";
PairTemplate.create(event.params.pair);
```
**HyperIndex:**
```typescript
// Register BEFORE handler
Factory.PairCreated.contractRegister(({ event, context }) => {
context.addPair(event.params.pair);
});
Factory.PairCreated.handler(async ({ event, context }) => {
// Handle event...
});
```
### Contract State (RPC Calls)
**TheGraph:**
```typescript
let contract = ERC20.bind(address);
let name = contract.name();
```
**HyperIndex (use Effect API):**
```typescript
import { createEffect, S } from "envio";
export const getTokenName = createEffect({
name: "getTokenName",
input: S.string,
output: S.string,
cache: true,
}, async ({ input: address }) => {
// Use viem for RPC calls
const name = await client.readContract({
address: address as `0x${string}`,
abi: ERC20_ABI,
functionName: "name",
});
return name;
});
// In handler
const name = await context.effect(getTokenName, address);
```
## Common Migration Issues
### Missing async/await
```typescript
// WRONG - returns {} instead of entity
const token = context.Token.get(id);
// CORRECT
const token = await context.Token.get(id);
```
### Field Selection for Transaction Data
```yaml
# Add to config.yaml for event.transaction.hash access
events:
- event: Transfer(...)
field_selection:
transaction_fields:
- hash
```
### Multichain ID Prefixes
```typescript
// Always prefix with chainId for multichain
const id = `${event.chainId}-${originalId}`;
```
### BigDecimal Precision
Maintain precision from original subgraph:
```typescript
import { BigDecimal } from "generated";
const ZERO_BD = new BigDecimal(0);
const ONE_BD = new BigDecimal(1);
// Don't simplify to JavaScript numbers
```
## Migration Checklist
- [ ] Convert subgraph.yaml to config.yaml
- [ ] Remove @entity decorators from schema
- [ ] Change Bytes! to String!
- [ ] Use _id suffix for relationships
- [ ] Add @derivedFrom to all entity arrays
- [ ] Add async to all handlers
- [ ] Add await to all context.Entity.get() calls
- [ ] Use spread operator for entity updates
- [ ] Replace Template.create() with contractRegister
- [ ] Add field_selection for transaction data
- [ ] Prefix IDs with chainId for multichain
- [ ] Convert contract bindings to Effect API
## Additional Resources
### Reference Files
For detailed migration patterns:
- **`references/migration-patterns.md`** - Complete pattern reference
- **`references/common-mistakes.md`** - Pitfalls and solutions
### External Resources
- Full docs: https://docs.envio.dev/docs/HyperIndex-LLM/hyperindex-complete
- Migration guide: https://docs.envio.dev/docs/migration-guide
- Example indexers:
- Uniswap v4: https://github.com/enviodev/uniswap-v4-indexer
- Safe: https://github.com/enviodev/safe-analysis-indexer

View File

@@ -0,0 +1,372 @@
# 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)

View File

@@ -0,0 +1,648 @@
# 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,
});
}
});
```