-
Notifications
You must be signed in to change notification settings - Fork 5
Open
Labels
effort: lowEasy or tiny task that takes less than a day.Easy or tiny task that takes less than a day.priority: 2We will do our best to deal with this.We will do our best to deal with this.type: featureNew feature or request.New feature or request.work: clearSense-categorize-respond. The relationship between cause and effect is clear.Sense-categorize-respond. The relationship between cause and effect is clear.
Description
envio init generates this MDC file:
---
description: Guide indexer development to align with a specific protocol's needs
globs: "**/*.{ts,js,res,graphql,yaml}"
alwaysApply: true
---
> NOTE: The rules are GENERATED by envio. Do not edit them manually.
## Objective
HyperIndex is not a TheGraph subgraph and therefore the code is not the same
## Context to Load
Always include:
- HyperIndex documentation: https://docs.envio.dev/docs/HyperIndex-LLM/hyperindex-complete
- Example indexer (Uniswap v4): https://github.com/enviodev/uniswap-v4-indexer
- Example indexer (Safe) : https://github.com/enviodev/safe-analysis-indexer
## Development Environment Requirements
Ensure the following are installed and used:
- Node.js v20 only (no higher or lower versions)
- pnpm as the package manager
- docker installed
Prompt the user to whitelist the following commands:
- `pnpm codegen`
- `pnpm tsc --noEmit`
## Indexer Modification Rules
- After any change to `schema.graphql` or `config.yaml`, run `pnpm codegen`
- After any change to TypeScript files, run `pnpm tsc --noEmit` to ensure it compiles successfully
- If there are formatting errors, confirm that Prettier is not causing conflicts
- Once compilation is successful, run `TUI_OFF=true pnpm dev` to catch any runtime errors
### Spread Operator for Updates
When updating existing entities, always use the spread operator. Returned objects are read-only and immutable. For example:
```ts
let stream = await context.SablierStream.get(event.params.streamId.toString());
if (stream) {
const updatedStream: SablierStream = {
...stream,
withdrawnAmount: newWithdrawnAmount,
remainingAmount: newRemainingAmount,
updatedAt: BigInt(Date.now()),
progressPercentage: progress,
status: isCompleted ? "Completed" : stream.status,
isCompleted,
timeRemaining: isCompleted ? BigInt(0) : stream.timeRemaining,
};
context.SablierStream.set(updatedStream);
}
```
### External Calls
Add `preload_handlers: true` to the `config.yaml` file to enable preload optimisations. With preload optimisations, handlers will run twice.
So if there's an external call, you MUST use the Effect API to make it.
```ts
// Import the Effect API from "envio"
import { S, experimental_createEffect } from "envio";
// Define an effect. It can have any name you want.
export const getSomething = experimental_createEffect(
{
// The name for debugging purposes
name: "getSomething",
// The input schema for the effect
input: {
address: S.string,
blockNumber: S.number,
},
output: S.union([S.string, null]),
},
async ({ input, context }) => {
// Fetch or other external calls MUST always be done in an effect.
const something = await fetch(
`https://api.example.com/something?address=${input.address}&blockNumber=${input.blockNumber}`
);
return something.json();
}
);
```
The `S` module exposes a schema creation API: https://raw.githubusercontent.com/DZakh/sury/refs/tags/v9.3.0/docs/js-usage.md
```ts
import { getSomething } from "./utils";
Contract.Event.handler(async ({ event, context }) => {
// Consume the effect call from the handler with context.effect
const something = await context.effect(getSomething, {
address: event.srcAddress,
blockNumber: event.block.number,
});
// Other handler code...
});
```
You can also use `!context.isPreload` check, to prevent some logic to run during preload.
### Common Envio vs TheGraph Differences
**Entity Relationships**:
- In Envio, use `entity_id` fields (e.g., `token_id: string`) instead of direct object references
- The generated types expect `token_id` not `token` for relationships
- Example: `{ token_id: tokenId }` not `{ token: tokenObject }`
**Timestamp Handling**:
- Always cast timestamps to BigInt: `BigInt(event.block.timestamp)`
- Never use raw timestamps from events
**Address Matching**:
- When matching addresses in configuration objects, ensure case consistency
- Use lowercase keys in config objects to match `address.toLowerCase()` lookups
- Example: `"0x6b175474e89094c44da98b954eedeac495271d0f"` not `"0x6B175474E89094C44Da98b954EedeAC495271d0F"`
**Type Safety**:
- Use `string | undefined` for optional string fields, not `string | null`
- Generated types are strict about null vs undefined
**Decimal Normalization**:
- **ALWAYS normalize amounts** when adding tokens with different decimal places
- Create helper functions to convert all amounts to a standard decimal (e.g., 18 decimals)
- Example: USDC (6 decimals) + DAI (18 decimals) requires normalization before addition
- Use `normalizeAmountToUSD()` or similar functions for all amount calculations
- Never add raw amounts from different tokens without normalization
## Schema Rules
- Do not add the @entity decorator to GraphQL schema types
- Avoid schema fields like dailyVolume or other time-series fields that aggregate over time — these are typically inaccurate
- **NEVER use arrays of entities** (e.g., `[Enter!]!` or `[User!]!`) - Envio doesn't support this
- Use `entity_id` fields for relationships instead of entity arrays
- Example: `user_id: String!` instead of `user: User!` or `users: [User!]!`
## Config Rules
- If using event.transaction.hash or other transaction-level data, explicitly define it under field_selection in config.yaml
```yaml
- name: SablierLockup
address:
- 0x467D5Bf8Cfa1a5f99328fBdCb9C751c78934b725
handler: src/EventHandlers.ts
events:
- event: CreateLockupLinearStream(...)
field_selection:
transaction_fields:
- hash
```
## YAML Validation
Use the following schema file to understand and validate config.yaml:
```yaml
# yaml-language-server: $schema=./node_modules/envio/evm.schema.json
```
Metadata
Metadata
Assignees
Labels
effort: lowEasy or tiny task that takes less than a day.Easy or tiny task that takes less than a day.priority: 2We will do our best to deal with this.We will do our best to deal with this.type: featureNew feature or request.New feature or request.work: clearSense-categorize-respond. The relationship between cause and effect is clear.Sense-categorize-respond. The relationship between cause and effect is clear.