Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions apps/indexer/.claude/skills/envio-docs/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
name: envio-docs
description: >-
Use when something is unclear, confusing, or not covered by other skills.
Search and read the latest Envio documentation without leaving the terminal.
metadata:
managed-by: envio
---

- `envio tools search-docs "<query>"` — find docs pages by keyword
- `envio tools fetch-docs <url>` — read a page in full (use a URL from search results)
- `envio --help` — list available CLI commands and flags

Example: `envio tools search-docs "schema derivedFrom"` → pick a URL → `envio tools fetch-docs <url>`
71 changes: 71 additions & 0 deletions apps/indexer/.claude/skills/indexer-blocks/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
name: indexer-blocks
description: >-
Use when processing every block (or every Nth block) for time-series data,
periodic snapshots, or block-level aggregations. indexer.onBlock API, where
filter with block-number range and stride, and block handler context.
metadata:
managed-by: envio
---

# Block Handlers

Process every block (or every Nth block) using `indexer.onBlock`. No contract
address or `config.yaml` entry needed.

## Handler

Branch by `chain.id` with a `switch` so the type system flags any
unconfigured chain via the `default: never` exhaustiveness check:

```ts
import { indexer } from "envio";

indexer.onBlock(
{
name: "BlockTracker",
where: ({ chain }) => {
switch (chain.id) {
case 1:
return { block: { number: { _gte: 18000000, _every: 100 } } };
case 8453:
return { block: { number: { _every: 50 } } };
default: {
// Exhaustiveness check: TypeScript errors here if a new chain ID
// is added to config.yaml but not handled above.
const _exhaustive: never = chain.id;
return false;
}
}
},
},
async ({ block, context }) => {
context.BlockSnapshot.set({
id: `${block.number}`,
blockNumber: BigInt(block.number),
});
}
);
```

## Options

| Option | Type | Required | Description |
|--------|------|----------|-------------|
| `name` | `string` | yes | Handler name for logging |
| `where` | `({ chain }) => boolean \| filter` | no | Predicate evaluated once per configured chain at registration. Return `false` to skip a chain, `true` / omit to match every block, or `{block: {number: {_gte?, _lte?, _every?}}}` to restrict range and stride. `_every` aligns relative to `_gte`, preserving `(blockNumber - _gte) % _every === 0`. |

## Other ecosystems

- **Fuel**: same `indexer.onBlock` API; filter is keyed by `block.height` instead of `block.number`.
- **SVM**: use `indexer.onSlot`; filter shape is `{slot: {_gte?, _lte?, _every?}}` and the handler arg is `{slot: number, context}` (no `block` wrapper).

## Notes

- `indexer.onBlock` self-registers — no `config.yaml` entry needed
- No events or contract address required
- The handler context has the same entity API as event handlers
- If `where` returns `false` for every configured chain, a warning is logged at registration time
- For per-event startBlock (not stride), use `indexer.onEvent` with `where.block.number._gte` (see `indexer-filters`). Event filters accept `_gte` only; `_lte`/`_every` are reserved for `onBlock`.

> If something is unclear, use the `envio-docs` skill to search and read the latest documentation.
154 changes: 154 additions & 0 deletions apps/indexer/.claude/skills/indexer-configuration/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
---
name: indexer-configuration
description: >-
Use when writing or editing config.yaml. Chain/contract structure, addresses,
start_block, event selection, field_selection, custom event names, env vars,
address_format, schema/output paths, YAML validation, and deprecated options.
metadata:
managed-by: envio
---

# Config Reference (config.yaml)

## Structure Overview

```yaml
name: my-indexer
description: Optional description
schema: schema.graphql # custom path (default: schema.graphql)
address_format: checksum # checksum (default) | lowercase

contracts:
- name: MyContract
abi_file_path: ./abis/MyContract.json
handler: ./src/EventHandlers.ts # optional — auto-discovered from src/handlers/
events:
- event: Transfer(address indexed from, address indexed to, uint256 value)

chains:
- id: 1
start_block: 0
contracts:
- name: MyContract
address: "0x1234..."
```

Uses `chains` (not `networks`) and `max_reorg_depth` (not `confirmed_block_threshold`).

## Contract Addresses

```yaml
# Single address
- name: Token
address: "0x1234..."

# Multiple addresses
- name: Token
address:
- "0xaaa..."
- "0xbbb..."

# No address — wildcard indexing (all contracts matching ABI)
- name: Token
# address omitted — indexes all matching events chain-wide

# Factory-registered — see indexer-factory skill
```

For proxied contracts, use the **proxy address** (where events emit), not the implementation.

## start_block

```yaml
chains:
- id: 1
start_block: 0 # 0 = HyperSync auto-detects first event block
contracts:
- name: Token
address: "0x1234..."
start_block: 18000000 # per-contract override (takes precedence)
```

`start_block: 0` with HyperSync skips empty blocks automatically.

## Custom Event Names

When two events share the same name (different signatures), disambiguate:

```yaml
events:
- event: Transfer(address indexed from, address indexed to, uint256 value)
name: TransferERC20
- event: Transfer(address indexed from, address indexed to, uint256 indexed tokenId)
name: TransferERC721
```

## field_selection

Request additional transaction/block fields globally or per event:

```yaml
# Global (root level — applies to all events)
field_selection:
transaction_fields:
- hash
- from
- to
block_fields:
- number
- timestamp

contracts:
- name: MyContract
events:
# Per-event (overrides global for this event)
- event: Transfer(address indexed from, address indexed to, uint256 value)
field_selection:
transaction_fields:
- hash
- from
- to
- gasPrice
```

Global `field_selection` is at the root level (sibling to `contracts` and `chains`). Per-event `field_selection` is directly under the event entry. See `indexer-transactions` skill for full field lists.

## Environment Variables

```yaml
rpc:
- url: ${ENVIO_RPC_URL} # required — errors if missing
- url: ${ENVIO_RPC_URL:-http://localhost:8545} # with default value
```

Works in any string value in config. Set via `.env` file or shell environment.

**IMPORTANT:** All environment variables MUST use the `ENVIO_` prefix (e.g., `ENVIO_RPC_URL`, not `RPC_URL`). The hosted service requires the `ENVIO_` prefix — variables without it will not be available at runtime.

## Runtime Environment Variables

Set on the indexer process (not interpolated into config.yaml):

- `ENVIO_TUI` — `true` forces the terminal UI on, `false` forces it off. Unset (default) auto-disables under agents, CI, and non-TTY stdout, so plain `pnpm dev` produces line-buffered output suitable for log capture without manual intervention.

## YAML Validation

Add at top of file for IDE schema validation:

```yaml
# yaml-language-server: $schema=./node_modules/envio/evm.schema.json
```

## Deprecated Options (Do NOT Use)

- `loaders` / `preload_handlers` — replaced by async handler API
- `preRegisterDynamicContracts` — replaced by `contractRegistrations` in factory pattern
- `event_decoder` — removed
- `rpc_config` — replaced by `rpc:` under chains
- `unordered_multichain_mode` — removed

## RPC Configuration

RPC tuning parameters are documented in the `indexer-performance` skill.

> If something is unclear, use the `envio-docs` skill to search and read the latest documentation.
93 changes: 93 additions & 0 deletions apps/indexer/.claude/skills/indexer-external-calls/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
name: indexer-external-calls
description: >-
Use when making RPC calls, fetch requests, or any external I/O from handlers.
Effect API with createEffect, S schema validation, context.effect(), preload
optimization (handlers run twice), cache and rateLimit options.
metadata:
managed-by: envio
---

# External Calls (Effect API)

Handlers run twice: parallel **preload pass** (warms caches), then **sequential pass** (state changes). All external I/O (fetch, RPC, APIs) MUST go through `createEffect` + `context.effect()` — otherwise it double-executes and blocks parallelization.

## Define and call

```ts
import { S, createEffect } from "envio";

const getOwner = createEffect(
{
name: "getOwner",
input: { tokenId: S.bigint },
output: S.union([S.string, null]),
cache: true,
rateLimit: false,
},
async ({ input }) => {
const res = await fetch(`https://api.example.com/owner/${input.tokenId}`);
return res.json();
}
);

indexer.onEvent(
{ contract: "Token", event: "Transfer" },
async ({ event, context }) => {
const owner = await context.effect(getOwner, { tokenId: event.params.tokenId });
}
);
```

## Pass minimum input

`input` carries only what varies per call. Bake static config (URLs, tokens, channel IDs, env vars) into the effect body. Build payloads/strings inside the effect.

```ts
// ❌ input: { url, chatId, text } — config and pre-built strings leak into the call site
// ✅ input: { usd, blockNumber } — only the values that vary per call
```

Dedup is keyed by hash of `input`; leaner inputs dedupe better, validate faster, and let one effect serve many call sites.

## Schema (`S`)

`S.string`, `S.number`, `S.bigint`, `S.boolean`, `S.schema({ ... })`, `S.array(...)`, `S.union([..., null])`, `S.optional(...)`.
Full ref: https://raw.githubusercontent.com/DZakh/sury/refs/tags/v9.3.0/docs/js-usage.md

## RPC pattern (viem)

```ts
const client = createPublicClient({ transport: http(process.env.ENVIO_RPC_URL) });

const getTokenMetadata = createEffect(
{
name: "getTokenMetadata",
input: S.string,
output: { name: S.string, symbol: S.string, decimals: S.number },
cache: true,
rateLimit: false,
},
async ({ input: address }) => {
const args = { address: address as `0x${string}`, abi: ERC20_ABI };
const [name, symbol, decimals] = await Promise.all([
client.readContract({ ...args, functionName: "name" }),
client.readContract({ ...args, functionName: "symbol" }),
client.readContract({ ...args, functionName: "decimals" }),
]);
return { name, symbol, decimals: Number(decimals) };
}
);
```

## Options

| Option | Type | Default |
|---|---|---|
| `name` | `string` | — |
| `input` | `S.Schema` | — |
| `output` | `S.Schema` | — |
| `cache` | `boolean` | `false` |
| `rateLimit` | `false \| { calls, per }` | required |

> If something is unclear, use the `envio-docs` skill to search and read the latest documentation.
Loading