Skip to content

Commit adef8a9

Browse files
moose-codeclaude
andcommitted
feat: comprehensive USDC stablecoin indexer on Unichain
Rewrite ERC-20 indexer from UNI (Ethereum/Gnosis) to USDC on Unichain with Arbitrum support (commented out). Tracks per-chain account balances, approvals, total supply via mint/burn detection, daily aggregate snapshots, and per-account daily activity. - 6 entity types: Transfer, Account, Approval, ChainSupply, DailySnapshot, AccountDailyActivity - TransferType enum for MINT/BURN/TRANSFER classification - All entity IDs namespaced with chainId for multichain safety - field_selection for transaction hash access - Unit tests for transfer, mint, burn, and approval handlers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
0 parents  commit adef8a9

File tree

31 files changed

+6219
-0
lines changed

31 files changed

+6219
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
name: indexing-blocks
3+
description: >-
4+
Use when processing every block (or every Nth block) for time-series data,
5+
periodic snapshots, or block-level aggregations. onBlock API, interval
6+
option, and block handler context.
7+
---
8+
9+
# Block Handlers
10+
11+
Process every block (or every Nth block) using `onBlock` from `generated`. No contract address or config.yaml entry needed.
12+
13+
## Handler
14+
15+
```ts
16+
import { onBlock } from "generated";
17+
18+
onBlock(
19+
{ name: "BlockTracker", chain: 1, interval: 100 },
20+
async ({ block, context }) => {
21+
context.BlockSnapshot.set({
22+
id: `${block.number}`,
23+
blockNumber: BigInt(block.number),
24+
timestamp: BigInt(block.timestamp),
25+
});
26+
}
27+
);
28+
```
29+
30+
## Options
31+
32+
| Option | Type | Required | Description |
33+
|--------|------|----------|-------------|
34+
| `name` | `string` | yes | Handler name for logging |
35+
| `chain` | `number` | yes | Chain ID to process |
36+
| `interval` | `number` | no | Process every Nth block (default: 1) |
37+
| `startBlock` | `number` | no | Inclusive start block |
38+
| `endBlock` | `number` | no | Inclusive end block |
39+
40+
## Notes
41+
42+
- `onBlock` self-registers — no config.yaml entry needed
43+
- No events or contract address required
44+
- EVM chains only
45+
- The handler context has the same entity API as event handlers
46+
- `block` object provides `number` and `timestamp` (more fields may be added)
47+
48+
## Deep Documentation
49+
50+
Full reference: https://docs.envio.dev/docs/HyperIndex-LLM/hyperindex-complete
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
---
2+
name: indexing-config
3+
description: >-
4+
Use when writing or editing config.yaml. Chain/contract structure, addresses,
5+
start_block, event selection, field_selection, custom event names, env vars,
6+
address_format, schema/output paths, YAML validation, and deprecated options.
7+
---
8+
9+
# Config Reference (config.yaml)
10+
11+
## Structure Overview
12+
13+
```yaml
14+
name: my-indexer
15+
description: Optional description
16+
schema: schema.graphql # custom path (default: schema.graphql)
17+
output: generated/ # custom output path (default: generated/)
18+
address_format: checksum # checksum (default) | lowercase
19+
20+
contracts:
21+
- name: MyContract
22+
abi_file_path: ./abis/MyContract.json
23+
handler: ./src/EventHandlers.ts # optional — auto-discovered from src/handlers/
24+
events:
25+
- event: Transfer(address indexed from, address indexed to, uint256 value)
26+
27+
chains:
28+
- id: 1
29+
start_block: 0
30+
contracts:
31+
- name: MyContract
32+
address: "0x1234..."
33+
```
34+
35+
Uses `chains` (not `networks`) and `max_reorg_depth` (not `confirmed_block_threshold`).
36+
37+
## Contract Addresses
38+
39+
```yaml
40+
# Single address
41+
- name: Token
42+
address: "0x1234..."
43+
44+
# Multiple addresses
45+
- name: Token
46+
address:
47+
- "0xaaa..."
48+
- "0xbbb..."
49+
50+
# No address — wildcard indexing (all contracts matching ABI)
51+
- name: Token
52+
# address omitted — indexes all matching events chain-wide
53+
54+
# Factory-registered — see indexing-factory skill
55+
```
56+
57+
For proxied contracts, use the **proxy address** (where events emit), not the implementation.
58+
59+
## start_block
60+
61+
```yaml
62+
chains:
63+
- id: 1
64+
start_block: 0 # 0 = HyperSync auto-detects first event block
65+
contracts:
66+
- name: Token
67+
address: "0x1234..."
68+
start_block: 18000000 # per-contract override (takes precedence)
69+
```
70+
71+
`start_block: 0` with HyperSync skips empty blocks automatically.
72+
73+
## Custom Event Names
74+
75+
When two events share the same name (different signatures), disambiguate:
76+
77+
```yaml
78+
events:
79+
- event: Transfer(address indexed from, address indexed to, uint256 value)
80+
name: TransferERC20
81+
- event: Transfer(address indexed from, address indexed to, uint256 indexed tokenId)
82+
name: TransferERC721
83+
```
84+
85+
## field_selection
86+
87+
Request additional transaction/block fields globally or per event:
88+
89+
```yaml
90+
# Global (root level — applies to all events)
91+
field_selection:
92+
transaction_fields:
93+
- hash
94+
- from
95+
- to
96+
block_fields:
97+
- number
98+
- timestamp
99+
100+
contracts:
101+
- name: MyContract
102+
events:
103+
# Per-event (overrides global for this event)
104+
- event: Transfer(address indexed from, address indexed to, uint256 value)
105+
field_selection:
106+
transaction_fields:
107+
- hash
108+
- from
109+
- to
110+
- gasPrice
111+
```
112+
113+
Global `field_selection` is at the root level (sibling to `contracts` and `chains`). Per-event `field_selection` is directly under the event entry. See `indexing-transactions` skill for full field lists.
114+
115+
## Environment Variables
116+
117+
```yaml
118+
rpc:
119+
- url: ${RPC_URL} # required — errors if missing
120+
- url: ${RPC_URL:-http://localhost:8545} # with default value
121+
```
122+
123+
Works in any string value in config. Set via `.env` file or shell environment.
124+
125+
## YAML Validation
126+
127+
Add at top of file for IDE schema validation:
128+
129+
```yaml
130+
# yaml-language-server: $schema=./node_modules/envio/evm.schema.json
131+
```
132+
133+
## Deprecated Options (Do NOT Use)
134+
135+
- `loaders` / `preload_handlers` — replaced by async handler API
136+
- `preRegisterDynamicContracts` — replaced by `contractRegistrations` in factory pattern
137+
- `event_decoder` — removed
138+
- `rpc_config` — replaced by `rpc:` under chains
139+
- `unordered_multichain_mode` — removed
140+
141+
## RPC Configuration
142+
143+
RPC tuning parameters are documented in the `indexing-performance` skill.
144+
145+
## Deep Documentation
146+
147+
Full reference: https://docs.envio.dev/docs/HyperIndex-LLM/hyperindex-complete
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
---
2+
name: indexing-external-calls
3+
description: >-
4+
Use when making RPC calls, fetch requests, or any external I/O from handlers.
5+
Effect API with createEffect, S schema validation, context.effect(), preload
6+
optimization (handlers run twice), cache and rateLimit options.
7+
---
8+
9+
# External Calls (Effect API)
10+
11+
## Why Effects?
12+
13+
HyperIndex uses **Preload Optimization** — handlers run TWICE:
14+
15+
1. **Preload pass**: all handlers in the batch run in parallel (to warm caches)
16+
2. **Sequential pass**: handlers run in event order (actual state changes)
17+
18+
All external calls (fetch, RPC, APIs) MUST use the Effect API to prevent double execution and enable parallelization.
19+
20+
## Defining an Effect
21+
22+
```ts
23+
import { S, createEffect } from "envio";
24+
25+
export const getSomething = createEffect(
26+
{
27+
name: "getSomething",
28+
input: {
29+
address: S.string,
30+
blockNumber: S.number,
31+
},
32+
output: S.union([S.string, null]),
33+
cache: true,
34+
rateLimit: false,
35+
},
36+
async ({ input, context }) => {
37+
const something = await fetch(
38+
`https://api.example.com/something?address=${input.address}&blockNumber=${input.blockNumber}`
39+
);
40+
return something.json();
41+
}
42+
);
43+
```
44+
45+
## Consuming in Handlers
46+
47+
```ts
48+
import { getSomething } from "./utils";
49+
50+
Contract.Event.handler(async ({ event, context }) => {
51+
const something = await context.effect(getSomething, {
52+
address: event.srcAddress,
53+
blockNumber: event.block.number,
54+
});
55+
// Use the result...
56+
});
57+
```
58+
59+
## context.isPreload Guard
60+
61+
For non-effect side effects that should only run once:
62+
63+
```ts
64+
Contract.Event.handler(async ({ event, context }) => {
65+
const data = await context.effect(myEffect, input);
66+
67+
if (!context.isPreload) {
68+
console.log("Processing event", event.block.number);
69+
}
70+
});
71+
```
72+
73+
## S Schema Module
74+
75+
The `S` module exposes a schema creation API for input/output validation:
76+
https://raw.githubusercontent.com/DZakh/sury/refs/tags/v9.3.0/docs/js-usage.md
77+
78+
Common schemas:
79+
- `S.string`, `S.number`, `S.boolean`
80+
- `S.schema({ field: S.string })`
81+
- `S.array(S.string)`
82+
- `S.union([S.string, null])`
83+
- `S.optional(S.string)`
84+
85+
## RPC Call Pattern (viem)
86+
87+
```ts
88+
import { createEffect, S } from "envio";
89+
import { createPublicClient, http, parseAbi } from "viem";
90+
91+
const ERC20_ABI = parseAbi([
92+
"function name() view returns (string)",
93+
"function symbol() view returns (string)",
94+
"function decimals() view returns (uint8)",
95+
]);
96+
97+
const client = createPublicClient({
98+
transport: http(process.env.RPC_URL),
99+
});
100+
101+
export const getTokenMetadata = createEffect(
102+
{
103+
name: "getTokenMetadata",
104+
input: S.string,
105+
output: S.schema({
106+
name: S.string,
107+
symbol: S.string,
108+
decimals: S.number,
109+
}),
110+
cache: true,
111+
},
112+
async ({ input: address }) => {
113+
const [name, symbol, decimals] = await Promise.all([
114+
client.readContract({ address: address as `0x${string}`, abi: ERC20_ABI, functionName: "name" }),
115+
client.readContract({ address: address as `0x${string}`, abi: ERC20_ABI, functionName: "symbol" }),
116+
client.readContract({ address: address as `0x${string}`, abi: ERC20_ABI, functionName: "decimals" }),
117+
]);
118+
return { name, symbol, decimals: Number(decimals) };
119+
}
120+
);
121+
```
122+
123+
## Effect Options
124+
125+
| Option | Type | Description |
126+
|--------|------|-------------|
127+
| `name` | `string` | Name for debugging/logging |
128+
| `input` | `S.Schema` | Input validation schema |
129+
| `output` | `S.Schema` | Output validation schema |
130+
| `cache` | `boolean` | Cache results for identical inputs (default: `false`) |
131+
| `rateLimit` | `boolean \| { calls, per }` | Rate limit calls (default: `false`) |
132+
133+
## Deep Documentation
134+
135+
Full reference: https://docs.envio.dev/docs/HyperIndex-LLM/hyperindex-complete

0 commit comments

Comments
 (0)