# Envio docs: HyperIndex core > Build, run and test an indexer: getting started, config.yaml, schema, handlers, CLI, testing, troubleshooting. Generated at build time from the pages of https://docs.envio.dev. Each page below is the full markdown of one docs page, after a `` line. Append `.md` to that URL to fetch the page on its own. The index of every page is https://docs.envio.dev/llms.txt. Context files, one per product area: - llms-hyperindex.txt (this file): HyperIndex core, about 39k tokens. Build, run and test an indexer: getting started, config.yaml, schema, handlers, CLI, testing, troubleshooting. - [llms-hyperindex-advanced.txt](https://docs.envio.dev/llms-hyperindex-advanced.txt): HyperIndex advanced features, about 31k tokens. Factories, the Effect API, wildcard indexing, preload, contract state, IPFS, HyperSync and RPC sources, reorgs, WebSockets. - [llms-hyperindex-reference.txt](https://docs.envio.dev/llms-hyperindex-reference.txt): HyperIndex performance and reference, about 27k tokens. Config schema reference, observability, performance tuning, benchmarks, supported chains and licensing. - [llms-hyperindex-examples.txt](https://docs.envio.dev/llms-hyperindex-examples.txt): HyperIndex examples and tutorials, about 16k tokens. Example indexers (Uniswap v4, Sablier, Velodrome and Aerodrome) and EVM tutorials. - [llms-hyperindex-migration.txt](https://docs.envio.dev/llms-hyperindex-migration.txt): HyperIndex migration guides, about 13k tokens. Upgrade a V2 indexer to V3, or migrate from The Graph, Ponder or Alchemy, by hand or with AI. - [llms-hyperindex-solana-fuel.txt](https://docs.envio.dev/llms-hyperindex-solana-fuel.txt): HyperIndex on Solana and Fuel, about 20k tokens. Solana programs (instruction and slot handlers, decoding, config) and Fuel contracts. - [llms-envio-cloud.txt](https://docs.envio.dev/llms-envio-cloud.txt): Envio Cloud, about 18k tokens. Deploy, monitor and manage hosted indexers: features, CLI, billing, organisations, self-hosting. - [llms-hypersync.txt](https://docs.envio.dev/llms-hypersync.txt): HyperSync core, about 32k tokens. EVM data: quickstart, API tokens, clients, query reference, curl, stream tuning, supported networks, tutorials. - [llms-hypersync-solana-fuel.txt](https://docs.envio.dev/llms-hypersync-solana-fuel.txt): HyperSync for Solana and Fuel, about 14k tokens. Solana HyperSync (query schema, client, curl examples) and HyperFuel, HyperSync for the Fuel network. - [llms-hyperrpc.txt](https://docs.envio.dev/llms-hyperrpc.txt): HyperRPC, about 4k tokens. Read-only JSON-RPC endpoint powered by HyperSync: supported methods, usage and supported networks. Pages in this file: - HyperIndex: Fast Multichain Blockchain Indexer - HyperIndex Quickstart - Quickstart with AI - Configuration File (config.yaml) - Entities Schema (schema.graphql) - Event Handlers (src/handlers) - Block Handlers - Multichain Indexing - Running The Indexer Locally - Environment Variables - Envio CLI Reference - Testing - Navigating Hasura - Common Issues - Reserved Words --- # HyperIndex: Fast Multichain Blockchain Indexer > HyperIndex turns onchain events into a GraphQL API in minutes. Envio's multichain indexer, independently benchmarked as the fastest. **HyperIndex** is a blazing-fast, developer-friendly multichain indexer, optimized for both local development and reliable hosted deployment. It empowers developers to effortlessly build robust backends for blockchain applications. If you are new to indexing, see [what a blockchain indexer is](https://docs.envio.dev/blog/what-is-a-blockchain-indexer) for the wider context. ![Sync Process](https://docs.envio.dev/img/sync.gif) :::info HyperIndex & HyperSync **HyperIndex** is Envio's full-featured blockchain indexing framework that transforms on-chain events into structured, queryable databases with GraphQL APIs. [**HyperSync**](https://docs.envio.dev/docs/HyperSync/overview) is the high-performance data engine that powers HyperIndex. It provides the raw blockchain data access layer, delivering up to 2000x faster performance than traditional RPC endpoints. While HyperIndex gives you a complete indexing solution with schema management and event handling, HyperSync can be used directly for custom data pipelines and specialized applications. ::: --- ## Key Features - **[Quickstart templates](https://docs.envio.dev/docs/HyperIndex/greeter-tutorial)** – Rapidly bootstrap your indexer. - **[Real-time indexing](https://docs.envio.dev/docs/HyperIndex/latency-at-head)** – Instantly track blockchain events. - **[Multichain indexing](https://docs.envio.dev/docs/HyperIndex/multichain-indexing)** – Support multiple blockchains simultaneously. - **[Local development](https://docs.envio.dev/docs/HyperIndex/running-locally)** – A full-featured local environment with Docker. - **[Reorg support](https://docs.envio.dev/docs/HyperIndex/reorgs-support)** – Gracefully handle blockchain reorganizations without sacrificing latency. - **[GraphQL API](https://docs.envio.dev/docs/HyperIndex/navigating-hasura)** – Easily query indexed data. - **[Cross-platform support](https://docs.envio.dev/docs/HyperIndex/supported-networks)** – Index any EVM-, SVM-, or Fuel-compatible blockchain. - **[High performance](https://docs.envio.dev/docs/HyperIndex/benchmarking)** – Perform historical backfills at 30,000+ events per second. - **[Indexer auto-generation](https://docs.envio.dev/docs/HyperIndex/quickstart)** – Generate indexers directly from smart contract addresses. - **[Flexible language support](https://docs.envio.dev/docs/HyperIndex/terminology#programming-languages)** – TypeScript, JavaScript, and ReScript. - **[Factory contract support](https://docs.envio.dev/docs/HyperIndex/dynamic-contracts)** – Index data from over 1M dynamically registered contracts seamlessly. - **[On-chain and off-chain data integration](https://docs.envio.dev/docs/HyperIndex/contract-state)** – Easily combine multiple data sources. - **[Self-hosted and managed options](https://docs.envio.dev/docs/HyperIndex/hosted-service)** – Run your own setup or use Envio Cloud. - **[Detailed logging and observability](https://docs.envio.dev/docs/HyperIndex/observability)** – Debug and optimize with clarity. - **[External API actions](https://docs.envio.dev/docs/HyperIndex/ipfs)** – Trigger external services based on blockchain events. - **[Wildcard topic indexing](https://docs.envio.dev/docs/HyperIndex/wildcard-indexing)** – Flexibly index based on event topics. - **[Fallback RPC data sources](https://docs.envio.dev/docs/HyperIndex/rpc-sync#improving-resilience-with-rpc-fallback)** – Enhance reliability with RPC connections. --- ## Feature Roadmap Upcoming features on our development roadmap: - **Indexing 1,000,000+ events per second** - **Configurable Query/REST API layer** - **No-Code Indexers** - **Durable & Non-Blocking Effect API** Recently shipped: [isolated multichain mode](https://docs.envio.dev/docs/HyperIndex/multichain-indexing#per-chain-data-mode) (v3.6, with per-chain rollbacks in v3.10) and [stable Solana support](https://docs.envio.dev/docs/HyperIndex/solana) (v3.11). --- ## HyperSync API Token Requirements HyperSync (the data engine powering HyperIndex) requires an API token for all requests. You can generate one in the [Envio Cloud portal](https://envio.dev/app/api-tokens). Here's what you need to know: - **Local Development**: An API token is required. The CLI supports an automatic login flow to make this smoother. - **Self-Hosted Deployments**: API tokens are required for HyperSync access in self-hosted deployments. Set the token via the `ENVIO_API_TOKEN` environment variable in your indexer configuration. This can be read from the `.env` file in the root of your HyperIndex project. - **Envio Cloud**: Indexers deployed to Envio Cloud have special access that doesn't require a custom API token. - **Pricing**: Tiered packages are available for those self-hosting HyperIndex and using HyperSync. See the [HyperSync pricing page](https://envio.dev/pricing/hypersync) for details, or reach out to us on [Discord](https://discord.gg/envio) for preferred pricing based on your specific use case. For more details about API tokens, including how to generate and implement them, see our [API Tokens documentation](https://docs.envio.dev/docs/HyperSync/api-tokens). --- ## 🔗 Quick Links - [GitHub Repository](https://github.com/enviodev/hyperindex) ⭐ - [Join our Discord Community](https://discord.gg/envio) --- # HyperIndex Quickstart > Learn to quickly autogenerate and configure a HyperIndex indexer for any smart contract. The **Quickstart** enables you to instantly autogenerate a powerful blockchain indexer and start querying blockchain data in minutes. This is the fastest and easiest way to begin using HyperIndex. If you are new to indexing, see [what a blockchain indexer is](https://docs.envio.dev/blog/what-is-a-blockchain-indexer) for the wider context. **Example:** Autogenerate an indexer for the Eigenlayer contract and index its entire history in less than 5 minutes by simply running `pnpx envio init` and providing the contract address from [Etherscan](https://etherscan.io/address/0x858646372cc42e1a627fce94aa7a7033e7cf075a). --- ## Prerequisites - **[Node.js](https://nodejs.org/en/download/current)** _(v22 or newer recommended)_ - **[pnpm](https://pnpm.io/installation)** _(recommended but not required)_ - **[Docker Desktop](https://www.docker.com/products/docker-desktop/)** _(required to run the Envio indexer locally)_ > **Note:** Docker is only required if you plan to run your indexer locally. You can skip installing Docker if you'll only be using Envio Cloud. [Podman](https://podman.io/) also works if you prefer it to Docker. ### Additionally for Windows Users: - **[WSL](https://learn.microsoft.com/en-us/windows/wsl/install)** _Windows Subsystem for Linux_ --- ## Getting Started Run the following command to initialize your blockchain indexer: ```bash pnpx envio init ``` You'll then follow interactive prompts to customize your indexer. --- ### Video Tutorials [Video: Quickstart Guide](https://www.youtube.com/watch?v=zkVlGgf5XAo) [Video: Indexer Initialization Guide](https://www.youtube.com/watch?v=JOiLUysZf-s) [Video: Envio - Getting Started Guide](https://www.youtube.com/watch?v=LNhaN-Cikis) --- ## Indexer Initialization Options During initialization, you'll be presented with two options: - **Contract Import** _(recommended for existing smart contracts)_ - **Template** Choose the **Contract Import** option to auto-generate indexers directly from smart contracts. ```bash ? Choose an initialization option Template > Contract Import [↑↓ to move, enter to select] ``` --- ## Contract Import Methods There are two convenient methods to import your contract: - **Block Explorer** _(verified contracts on supported explorers like Etherscan and Blockscout)_ - **Local ABI** _(custom or unverified contracts)_ ### 1. Block Explorer Import This method uses a verified contract's address from a supported blockchain explorer (Etherscan, Routescan, etc.) to automatically fetch the ABI. #### Steps: **a. Select the blockchain** ```bash ? Which blockchain would you like to import a contract from? > ethereum-mainnet goerli optimism base bsc gnosis polygon [↑↓ to move, enter to select] ``` :::note HyperIndex supports all EVM-compatible chains. If your desired chain is not listed, you can import via the local ABI method or manually adjust the `config.yaml` file after initialization. ::: **b. Enter the contract address** ```bash ? What is the address of the contract? [Use proxy address if ABI is for a proxy implementation] ``` :::tip If using a proxy contract, always specify the **proxy address**, not the implementation address. ::: **c. Select events to index** ```bash ? Which events would you like to index? > [x] ClaimRewards(address indexed from, address indexed reward, uint256 amount) [x] Deposit(address indexed from, uint256 indexed tokenId, uint256 amount) [x] NotifyReward(address indexed from, address indexed reward, uint256 indexed epoch, uint256 amount) [x] Withdraw(address indexed from, uint256 indexed tokenId, uint256 amount) [space to select, → to select all, ← to deselect all] ``` **d. Finish or add more contracts** You'll be prompted to continue adding more contracts or to complete the setup: ```bash ? Would you like to add another contract? > I'm finished Add a new address for same contract on same network Add a new network for same contract Add a new contract (with a different ABI) ``` --- ### 2. Local ABI Import Choose this method if the contract ABI is unavailable from a block explorer or you're using an unverified contract. #### Steps: **a. Select Local ABI** ```bash ? Would you like to import from a block explorer or a local abi? Block Explorer > Local ABI [↑↓ to move, enter to select] ``` **b. Specify ABI JSON file** Provide the path to your local ABI file (JSON format): ```bash ? What is the path to your json abi file? ``` **c. Select events to index** ```bash ? Which events would you like to index? > [x] ClaimRewards(address indexed from, address indexed reward, uint256 amount) [x] Deposit(address indexed from, uint256 indexed tokenId, uint256 amount) [space to select, → to select all, ← to deselect all] ``` **d. Choose blockchain** Specify the blockchain your contract is deployed on: ```bash ? Choose network: > ethereum-mainnet goerli optimism base bsc gnosis [Custom Network ID] [↑↓ to move, enter to select] ``` **e. Enter contract details** - **Contract name** ```bash ? What is the name of this contract? ``` - **Contract address** ```bash ? What is the address of the contract? [Use proxy address if ABI is for a proxy implementation] ``` **f. Finish or add more contracts** Complete the import process or continue adding contracts: ```bash ? Would you like to add another contract? > I'm finished Add a new address for same contract on same network Add a new network for same contract Add a new contract (with a different ABI) ``` --- ## Generated Files & Configuration The Quickstart automatically generates key files: ### 1. `config.yaml` Automatically configured parameters include: - **Network ID** - **Start Block** - **Contract Name** - **Contract Address** - **Event Signatures** By default, all selected events are included, but you can manually adjust the file if needed. See the detailed guide on [`config.yaml`](https://docs.envio.dev/docs/HyperIndex/configuration-file.md). ### 2. GraphQL Schema - Entities are automatically generated for each selected event. - Fields match the event parameters emitted. See more details in the [schema file guide](https://docs.envio.dev/docs/HyperIndex/schema.md). ### 3. Event Handlers - Handlers are autogenerated for each event. - Handlers create event-specific entities. Learn more in the [event handlers guide](https://docs.envio.dev/docs/HyperIndex/event-handlers.md). --- **Congratulations!** Your HyperIndex indexer is now ready to run and query data! Next step: [Running your Indexer locally](https://docs.envio.dev/docs/HyperIndex/running-locally.md) or [Deploying to Envio Cloud](https://docs.envio.dev/docs/HyperIndex/hosted-service.md). --- ## Other Ways to Start Contract Import is the recommended path, but you can also bootstrap an indexer from: - **Templates** - pre-built `ERC20` or [Greeter](https://docs.envio.dev/docs/HyperIndex/greeter-tutorial.md) projects, selectable from the `pnpx envio init` interactive prompt. - **Examples** - copy and adapt an existing indexer from our [Examples](https://docs.envio.dev/docs/HyperIndex/example-uniswap-v4-multi-chain-indexer.md), our [Tutorials](https://docs.envio.dev/docs/HyperIndex/tutorial-erc20-token-transfers.md), or the [GitHub repositories](https://github.com/enviodev). --- # Quickstart with AI > Build an Envio HyperIndex indexer with an AI coding assistant using the Envio CLI, the envio-cloud CLI, and the built-in Claude skills in HyperIndex v3. Build an Envio HyperIndex indexer end-to-end with an AI coding assistant. Most developers now reach for an AI coding assistant before they open a file. This guide walks through an AI-centric flow for creating, developing, and deploying a HyperIndex indexer. It is semi-generic, so any capable AI coding assistant (Cursor, Windsurf, Copilot Agent, Continue, etc.) will work. That said, **we've seen the best results with [Claude Code](https://claude.com/claude-code)** and recommend starting there. :::tip Prefer the interactive flow? If you'd rather drive the CLI yourself, see the [Quickstart](https://docs.envio.dev/docs/HyperIndex/quickstart). ::: --- ## Prerequisites - **[Node.js](https://nodejs.org/en/download/current)** _(v22 or newer)_ - **[pnpm](https://pnpm.io/installation)** _(recommended but not required)_ - **[Docker Desktop](https://www.docker.com/products/docker-desktop/)** _(only needed to run the indexer locally)_ - An AI coding assistant (we recommend **[Claude Code](https://claude.com/claude-code)**) --- ## Step 1. Initialize The Indexer Open Claude/Cursor/Codex and prompt: ```bash pnpx envio init ``` ### Built for AI Agents When we notice a command is run by an agent instead of interactively, we output an AI-friendly prompt with the available options and step-by-step instructions on what to do next. We also provide tools and recommendations an agent can use to get the result, like `envio tools search-docs`, with more coming soon. After the project is initialized, we provide a curated set of skills that guide an agent through the codebase. Together with our [testing framework](https://docs.envio.dev/docs/HyperIndex/testing), they let it iterate quickly on indexer changes while keeping quality high. :::tip Upgrading Envio or have stale skills? Run `envio skills update` to pull the latest skills into your project. ::: ### About Envio API Token The **Envio API token** is your **HyperSync API token**. A few things to know: - The token **can't currently be created programmatically**. You generate one by logging in to [envio.dev/app/api-tokens](https://envio.dev/app/api-tokens) and copying it into `ENVIO_API_TOKEN` in your indexer's `.env`. - It's **only required for local development and self-hosted deployments**. Indexers running on **Envio Cloud** get special access and don't need a custom token. - It's **required when using Envio as the data provider (HyperSync)**. If you only use an external RPC as the data source, no token is needed - you can pass an empty string to skip the prompt. - To run `pnpm dev` locally, generate a token from the link above and set `ENVIO_API_TOKEN` in `.env` before starting the indexer. See [API Tokens](https://docs.envio.dev/docs/HyperSync/api-tokens) and [Environment Variables](https://docs.envio.dev/docs/HyperIndex/environment-variables) for full details. --- ## Step 2. The Development Loop The skills cover config, schema, handlers, loaders, dynamic contracts, testing, and migration checklists, so an agent can read them directly instead of inventing patterns. A productive loop looks like: 1. Describe the behavior you want in plain English. 2. Let the assistant edit `config.yaml`, `schema.graphql`, and `src/handlers`. 3. Have it follow a test-driven loop: write a failing test with [`createTestIndexer()`](https://docs.envio.dev/docs/HyperIndex/testing), implement the handler, then run `pnpm test` to capture and lock in snapshots. See the [Testing guide](https://docs.envio.dev/docs/HyperIndex/testing) for the full TDD workflow. 4. Iterate on failures together. The three files your agent will spend most of its time in: - **[`config.yaml`](https://docs.envio.dev/docs/HyperIndex/configuration-file)**: chains, contracts, events - **[`schema.graphql`](https://docs.envio.dev/docs/HyperIndex/schema)**: entities and relationships - **[`src/handlers`](https://docs.envio.dev/docs/HyperIndex/event-handlers)**: per-event logic --- ## Step 3. Migrating an Existing Indexer If you're porting from The Graph, Ponder, or another indexing framework, start with the AI migration workflow. It scales much better than hand-editing handlers. - **[Migrate Using AI](https://docs.envio.dev/docs/HyperIndex/migrate-with-ai)**: the recommended assistant-driven flow. It's written around subgraphs, but the same **monorepo-plus-phased-prompt** pattern works for **Ponder** and other frameworks. Point the assistant at the source project plus a freshly scaffolded HyperIndex indexer and let the skills guide it. - [Migrate from The Graph (manual)](https://docs.envio.dev/docs/HyperIndex/migration-guide) - [Migrate from Ponder](https://docs.envio.dev/docs/HyperIndex/migrate-from-ponder) - [Migrate from Alchemy](https://docs.envio.dev/docs/HyperIndex/migrate-from-alchemy) --- ## Step 4. Deploy Programmatically with `envio-cloud` Once your indexer runs locally, the [`envio-cloud` CLI](https://docs.envio.dev/docs/HyperIndex/envio-cloud-cli) lets an assistant (or a CI job) deploy and manage the hosted indexer without opening the dashboard. ```bash npm install -g envio-cloud envio-cloud login --token $ENVIO_GITHUB_TOKEN envio-cloud indexer add --name my-indexer --repo my-repo envio-cloud deployment status my-indexer --watch-till-synced envio-cloud deployment logs my-indexer --follow ``` Every command supports `-o json`, which makes it easy for assistants and scripts to parse results. Full reference: [Envio Cloud CLI](https://docs.envio.dev/docs/HyperIndex/envio-cloud-cli). --- ## Related Resources - [MCP Server](https://docs.envio.dev/docs/HyperIndex/mcp-server) - [Docs index for agents (llms.txt)](https://docs.envio.dev/llms.txt) - [HyperIndex context file for agents](https://docs.envio.dev/llms-hyperindex.txt) - [Envio CLI reference](https://docs.envio.dev/docs/HyperIndex/cli-commands) - [Envio Cloud CLI](https://docs.envio.dev/docs/HyperIndex/envio-cloud-cli) - [Migrate Using AI](https://docs.envio.dev/docs/HyperIndex/migrate-with-ai) - [HyperIndex v3 migration](https://docs.envio.dev/docs/HyperIndex/migrate-to-v3) --- # Configuration File (config.yaml) > Learn how to configure HyperIndex with contracts, events, chains, and advanced indexing options. The `config.yaml` file defines your indexer's behavior, including which blockchain events to index, contract addresses, which chains to index, and various advanced indexing options. It is a crucial step in configuring your HyperIndex setup. :::tip Whenever you make changes in `config.yaml` that should affect generated types (e.g. adding events, contracts, or chains), run `pnpm codegen` to regenerate types and code for your event handlers. ::: --- ## Example This is a basic ERC-20 `config.yaml` - it's enough on its own to get an indexer running across Ethereum Mainnet and Gnosis, tracking `Approval` and `Transfer` events on the UNI token. ```yaml # yaml-language-server: $schema=./node_modules/envio/evm.schema.json name: erc20-indexer description: ERC-20 Indexer contracts: - name: ERC20 events: - event: "Approval(address indexed owner, address indexed spender, uint256 value)" - event: "Transfer(address indexed from, address indexed to, uint256 value)" chains: - id: 1 # Ethereum Mainnet start_block: 0 contracts: - name: ERC20 address: "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984" # UNI - id: 100 # Gnosis Mainnet start_block: 0 contracts: - name: ERC20 address: "0x4537e328Bf7e4eFA29D05CAeA260D7fE26af9D74" # UNI ``` **Next steps:** - Define how each event updates your data → [`src/handlers`](https://docs.envio.dev/docs/HyperIndex/event-handlers.md) - Shape the entities you'll query → [`schema.graphql`](https://docs.envio.dev/docs/HyperIndex/schema.md) - Need more than the basics? Keep reading for the full set of configuration options below. --- ## Contracts Definition The top-level `contracts` block defines each contract once - its events and per-event options. Each chain then references these contracts by `name` and supplies chain-specific values like the on-chain address (see [Contracts (per chain)](#contracts-per-chain)). ```yaml contracts: - name: Greeter events: - event: "NewGreeting(address user, string greeting)" - event: "ClearGreeting(address user)" chains: - id: 1 start_block: 0 contracts: - name: Greeter address: "0x9D02A17dE4E68545d3a58D3a20BbBE0399E05c9c" ``` ### Events Selection The recommended way to declare events is by their **human-readable signature** directly under `events`: ```yaml contracts: - name: Greeter events: - event: "NewGreeting(address user, string greeting)" - event: "ClearGreeting(address user)" ``` Only the events listed here are indexed. To stop indexing an event, remove its entry. #### Custom Event Names You can assign custom names to events in `config.yaml`. This is handy when two events share the same name but have different signatures, or when you want a more descriptive name in your Envio project. ```yaml events: - event: Assigned(address indexed recipientId, uint256 amount, address token) - event: Assigned(address indexed recipientId, uint256 amount, address token, address sender) name: AssignedWithSender ``` #### Using an ABI File If you'd rather reference a JSON ABI file (e.g. when you have one already and want to pick a subset of events from it), use `abi_file_path` and refer to events by name: ```yaml contracts: - name: Greeter abi_file_path: ./abis/greeter.json events: - event: NewGreeting # signature comes from the ABI file ``` Event signatures are still the recommended default - they keep `config.yaml` self-contained and easier to review. ### Field Selection To improve indexing performance and reduce credits usage, the `block` and `transaction` fields on events contain only a subset of the fields available on the blockchain. Since **v3.7** the recommended way to ask for more is the handler's own [`fields` option](https://docs.envio.dev/docs/HyperIndex/event-handlers#selecting-block-and-transaction-fields), which lists the fields next to the code that reads them and applies to that registration only. `field_selection` in `config.yaml` is the older, coarser alternative: it applies to every handler of an event. Specify it under the event entry: ```yaml events: - event: "Assigned(address indexed user, uint256 amount)" field_selection: transaction_fields: - transactionIndex block_fields: - timestamp ``` See all possible options in the [Config File Reference](https://docs.envio.dev/docs/HyperIndex/config-schema-reference#fieldselection) or use IDE autocomplete for your help. #### Global Field Selection You can also specify fields globally for all events in the root of the config file: ```yaml field_selection: transaction_fields: - hash - gasUsed block_fields: - parentHash ``` Try to use this option sparingly as it can cause redundant Data Source calls and increased credits usage. A handler's `fields` option overrides `field_selection` for that registration. --- ## Chains Everything under the top-level `chains` field configures the chains your indexer connects to - data sources, start/end blocks, reorg behavior, and [multichain indexing](https://docs.envio.dev/docs/HyperIndex/multichain-indexing) semantics. ### RPC The `rpc` option configures an RPC data source per chain. It supports multiple URLs with explicit roles via the `for` field: - `sync` – use this RPC for historical sync. - `realtime` – use this RPC for low-latency head tracking once the indexer enters realtime mode. WebSocket endpoints (`wss://...`) are supported here. - `fallback` – use as a fallback when the primary source is unavailable. :::tip Optional for HyperSync chains For chains supported by [HyperSync](https://docs.envio.dev/docs/HyperIndex/hypersync.md), `rpc` is **not required** - HyperSync is used as the primary data source out of the box. You can still add an RPC entry as a fallback for extra reliability. For chains without HyperSync, `rpc` is required and acts as the primary data source. ::: ```yaml chains: - id: 1 rpc: - url: https://eth-mainnet.your-rpc-provider.com for: sync - url: wss://eth-mainnet.your-rpc-provider.com for: realtime - url: https://fallback.example.com for: fallback ``` A short form is also supported when you only need a single RPC URL: ```yaml chains: - id: 1 rpc: https://eth-mainnet.your-rpc-provider.com ``` After switching to a fallback source, HyperIndex automatically attempts to recover to the primary source 60 seconds later. See the [Rpc reference](https://docs.envio.dev/docs/HyperIndex/config-schema-reference#def-rpc) for advanced tuning options such as `initial_block_interval`, `polling_interval`, `query_timeout_millis`, and backoff parameters. #### Custom Headers Since v3.3, `headers` sets custom HTTP headers on an RPC entry - for endpoints gated behind an auth header rather than a key in the URL. Values support `${ENV_VAR}` interpolation: ```yaml chains: - id: 1 rpc: - url: https://eth-mainnet.your-rpc-provider.com for: sync headers: Authorization: "Bearer ${RPC_API_KEY}" ``` See [Custom HTTP Headers](https://docs.envio.dev/docs/HyperIndex/rpc-sync#custom-http-headers) for details. ### WebSocket Height Streaming Pair `wss://` URLs with `for: realtime` to improve head latency by streaming new block heights over a WebSocket connection. ```yaml chains: - id: 1 rpc: url: ${ENVIO_RPC_ENDPOINT} ws: ${ENVIO_WS_ENDPOINT} for: realtime ``` :::tip Experimental WebSocket support for RPC sources is experimental. Please open a GitHub issue if you hit any problems. ::: ### Start Block Set `start_block` on a chain to control the block at which the indexer begins ingesting data. Setting it to `0` is a safe default for HyperSync - it will automatically skip ahead to the first block that contains data for your configured contracts. ```yaml chains: - id: 1 start_block: 0 # HyperSync will fast-forward to the first relevant block contracts: - name: Greeter address: "0x9D02A17dE4E68545d3a58D3a20BbBE0399E05c9c" ``` Since **v3.11** `start_block` also accepts the literal `"latest"`, which starts the indexer from the chain's current head instead of a block you have to look up: ```yaml chains: - id: 1 start_block: latest # resolved once, when the indexer is first deployed ``` A chain on `latest` can't also carry per-contract `start_block` overrides. :::note The head is resolved once, when the indexer is first deployed, and the block is stored. Resuming reuses it, so downtime is backfilled rather than skipped. Restarting with `-r` resolves `latest` again. ::: For finer-grained control, see [Per-Contract Start Block Override](#per-contract-start-block-override) and the per-event start block tip below it. ### End Block Set `end_block` on a chain to stop indexing once that block is reached. Useful for backfills with a known cutoff or one-off snapshots. ```yaml chains: - id: 1 start_block: 18000000 end_block: 19000000 contracts: - name: Greeter address: "0x9D02A17dE4E68545d3a58D3a20BbBE0399E05c9c" ``` :::note Don't use this for tests If you're capping the range to make a deterministic test run, prefer the built-in [testing framework](https://docs.envio.dev/docs/HyperIndex/testing.md) instead - it lets you pin block ranges or feed synthetic events without spinning up a real indexer. ::: ### Contracts (per chain) Inside each chain you list the contracts you want to index on that chain - referenced by `name` from your top-level [Contracts Definition](#contracts-definition). The chain-level entry is where you pin the on-chain `address` (or list of addresses) and, optionally, a per-contract `start_block`. :::note Addresses can be provided in **checksum** format or in **lowercase**. Envio accepts both and normalizes them internally. ::: #### Addresses **Single address:** ```yaml chains: - id: 1 start_block: 0 contracts: - name: MyContract address: "0xContractAddress" ``` **Multiple addresses for the same contract:** ```yaml chains: - id: 1 start_block: 0 contracts: - name: MyContract address: - "0xAddress1" - "0xAddress2" ``` **The same address for several contracts:** Since v3.9, one address can be registered for more than one contract, so a single deployment can be decoded against several ABIs. This matters for proxies and factory patterns where you don't control the deployed address: ```yaml chains: - id: 1 start_block: 0 contracts: - name: ERC20 address: "0xAddress1" - name: ERC4626Vault address: "0xAddress1" ``` :::tip If using a **proxy contract**, always use the **proxy address**, not the implementation address. ::: #### Per-Contract Start Block Override By default, contracts use the chain `start_block`. You can also set a per-contract `start_block` to override it. Handy when: - Contracts were deployed at different blocks - You only need data from a contract starting at a specific block - You want to skip unnecessary historical data for some contracts - Works nicely with [Dynamic Contract Registration](https://docs.envio.dev/docs/HyperIndex/dynamic-contracts) ```yaml chains: - id: 1 # ethereum-mainnet start_block: 18000000 # Default start block for all contracts on this chain contracts: - name: ERC20 address: - "0x1111111111111111111111111111111111111111" - "0x2222222222222222222222222222222222222222" start_block: 18500000 # Override for this contract - name: Greeter address: "0x9D02A17dE4E68545d3a58D3a20BbBE0399E05c9c" # Uses chain default (18000000) ``` :::tip Per-event start block For even finer control, you can also specify a start block per event from your handler using the `where.block.number._gte` filter on `indexer.onEvent`. See [Event Handlers](https://docs.envio.dev/docs/HyperIndex/event-handlers.md) for the full API. ::: #### Indexer Without Contracts The `contracts` field is **optional** in V3. You can run an indexer that only uses block handlers (`indexer.onBlock`) without declaring any contracts: ```yaml name: BlockHandlerOnly chains: - id: 1 start_block: 18000000 ``` --- ## Storage How indexed data is persisted - which backends to use and whether to keep raw event records around. ### Storage Backends By default, HyperIndex writes entities to Postgres. In V3 you can additionally enable **ClickHouse** as a second storage backend (experimental): ```yaml storage: postgres: true clickhouse: true ``` When both backends are enabled, you must route each entity explicitly via the `@storage` directive in `schema.graphql`: ```graphql # Stored in both Postgres and ClickHouse type Transfer @storage(postgres: true, clickhouse: true) { id: ID! from: String! to: String! value: BigInt! } # Stored only in ClickHouse type Snapshot @storage(clickhouse: true) { id: ID! blockNumber: BigInt! } ``` Alternatively, mark one or both backends as `default` so every entity is written there automatically, and reach for the `@storage` directive only when you need to override routing for a specific entity: ```yaml storage: postgres: default: true clickhouse: default: true ``` :::note The `default` storage option was added in HyperIndex v3.2. ::: `envio dev` automatically spins up a ClickHouse Docker container for local development. For `envio start`, provide the connection via the environment variables `ENVIO_CLICKHOUSE_HOST`, `ENVIO_CLICKHOUSE_DATABASE`, `ENVIO_CLICKHOUSE_USERNAME`, and `ENVIO_CLICKHOUSE_PASSWORD`. :::warning Do not run multiple indexers writing to the same ClickHouse database at the same time. ::: Envio Cloud currently supports ClickHouse on the Dedicated Plan. Since v3.4, the `@storage` directive's `clickhouse` argument also accepts an options object for per-entity table tuning - partitioning, sorting key and TTL. See [Per-Entity ClickHouse Tuning](https://docs.envio.dev/docs/HyperIndex/schema#per-entity-clickhouse-tuning). ### Column Name Format Set `column_name_format: snake_case` on a backend to store column names in `snake_case` while keeping the original names in the GraphQL API and your handler types. It works for both Postgres and ClickHouse: ```yaml storage: postgres: column_name_format: snake_case clickhouse: column_name_format: snake_case ``` :::note Added in HyperIndex v3.2. ::: --- ## Address Format Use `address_format` to control how every address surfaced by the indexer (event fields like `event.srcAddress` / `event.transaction.from`, `chain..addresses`, addresses embedded in entity ids, etc.) is formatted. ```yaml address_format: lowercase # default: checksum ``` - `checksum` (default) – [EIP-55](https://eips.ethereum.org/EIPS/eip-55) checksummed mixed-case addresses. - `lowercase` – every address is lowercased globally. Useful when joining against another data source that stores addresses in lowercase, or when you want byte-for-byte deterministic ids without per-handler `.toLowerCase()` calls. You can still call `.toLowerCase()` ad-hoc inside a handler when you only need a single value lowercased. --- ## Bytes Representation `bytes_type` controls how the `Bytes` scalar from `schema.graphql` reaches handlers and storage. ```yaml bytes_type: uint8array # default: hex ``` - `hex` (default) – `0x`-prefixed hex strings, stored as text. - `uint8array` – `Uint8Array` values in handlers, stored as raw bytes (`BYTEA` in Postgres, `String` in ClickHouse). Roughly halves the storage taken by addresses and hashes. Available on EVM and Fuel. Solana always uses `Uint8Array` and has no option. Changing the setting changes the underlying column types, so switch it with a fresh sync. :::note Added in HyperIndex v3.10. ::: --- ## Per-Chain Entities By default one entity id means one row shared by every chain. Set `disable_default_cross_chain: true` (recommended for multichain indexers) to make entities and effect caches per-chain instead: ```yaml name: my-indexer disable_default_cross_chain: true ``` Entity tables then get a composite `(id, chainId)` primary key, so the same id on two chains is two independent rows, and sharing becomes explicit through the `@crossChain` directive. See [Per-Chain Data Mode](https://docs.envio.dev/docs/HyperIndex/multichain-indexing#per-chain-data-mode) for the full behavior. :::note Added in HyperIndex v3.6. ::: --- ## Ecosystem `ecosystem` selects which chain family your indexer targets. EVM is the default and what every example above assumes, but the same `config.yaml` schema is shared with Fuel and Solana (SVM). ```yaml ecosystem: evm # default — also: fuel, svm ``` Most projects don't set this field explicitly. Use `pnpx envio init fuel` or `pnpx envio init svm` to scaffold non-EVM indexers - the generated `config.yaml` will already have `ecosystem` set correctly. See the [Fuel](https://docs.envio.dev/docs/HyperIndex/fuel.md) and [Solana](https://docs.envio.dev/docs/HyperIndex/solana.md) guides for the ecosystem-specific options. --- ## Environment Variables Environment variable interpolation is supported anywhere in `config.yaml`, which is useful for keeping RPC URLs, addresses, or chain IDs out of source control. ```yaml chains: - id: ${ENVIO_CHAIN_ID:-ethereum-mainnet} contracts: - name: Greeter address: "${ENVIO_GREETER_ADDRESS}" ``` Run your indexer with custom environment variables: ```bash ENVIO_CHAIN_ID=optimism ENVIO_GREETER_ADDRESS=0xYourContractAddress pnpm dev ``` **Interpolation syntax:** - `${ENVIO_VAR}` – Use the value of `ENVIO_VAR` - `${ENVIO_VAR:-default}` – Use `ENVIO_VAR` if set, otherwise use `default` For more detailed information about environment variables, see our [Environment Variables Guide](https://docs.envio.dev/docs/HyperIndex/environment-variables.md). --- ## Advanced :::note In ~95% of cases you don't need to touch any of these - the defaults are tuned for the common path. Reach for them only when you have a specific reason. ::: ### Handler File Path Handlers are auto-discovered from `src/handlers/`. Override the directory with the top-level `handlers` option, or set a per-contract `handler` path when needed: ```yaml handlers: ./src/my-handlers # optional override of src/handlers contracts: - name: Greeter handler: ./src/GreeterHandler.ts # optional per-contract path ``` ### Schema File Path You can customize the path to the [schema file](https://docs.envio.dev/docs/HyperIndex/schema.md) using the `schema` option: ```yaml schema: ./path/to/schema.graphql ``` By default, the `schema.graphql` is expected to be in the root directory of your project. ### Block Lag Set `block_lag` on a chain to keep the indexer a fixed number of blocks behind the chain head. Defaults to `0`. ```yaml chains: - id: 1 start_block: 0 block_lag: 5 ``` :::warning Only set `block_lag` if you understand the trade-off - it intentionally trades head latency for additional reorg safety. ::: ### Rollback on Reorg HyperIndex automatically handles blockchain reorganizations by default. To disable or customize this behavior, set the `rollback_on_reorg` flag in your `config.yaml`: ```yaml rollback_on_reorg: true # default is true ``` See detailed configuration options [here](https://docs.envio.dev/docs/HyperIndex/reorgs-support.md). ### Full Batch Size Set `full_batch_size` to control how many events are processed in a single batch. ```yaml full_batch_size: 5000 ``` ### Raw Events Storage By default, HyperIndex doesn't store raw event data in the database to optimize performance and reduce storage requirements. However, you can enable this feature for debugging purposes or if you need to access the original event data. To enable storage of raw events, add the following to your `config.yaml`: ```yaml raw_events: true ``` When enabled, all indexed events will be stored in the `raw_events` table in the database, which you can view through the Hasura interface. This is particularly useful for: - Debugging event processing issues - Verifying that events are being captured correctly - Creating custom queries against raw blockchain data Note that enabling this option will increase database storage requirements and may slightly impact indexing performance. ### Skip Chain Set `skip: true` on a chain to exclude it from indexing and migrations without removing it from your config - handy for temporarily disabling a chain. ```yaml chains: - id: 137 skip: true start_block: 0 ``` :::note Added in HyperIndex v3.1. ::: --- ## Configuration Schema Reference Explore detailed configuration schema parameters here: - See the full, deep-linkable reference: [Config Schema Reference](https://docs.envio.dev/docs/HyperIndex/config-schema-reference) :::info For AI/LLM Systems **Recommended**: Use the [Config Schema Reference](https://docs.envio.dev/docs/HyperIndex/config-schema-reference) for programmatic access to schema information. The interactive viewer below is optimized for human users. ::: --- Now your configuration file is set, you're ready to start indexing with HyperIndex! --- # Entities Schema (schema.graphql) > Learn how to define GraphQL schemas, manage entities, and handle different types in HyperIndex. The **`schema.graphql`** file defines the data model for your HyperIndex indexer. Each entity type defined in this schema corresponds directly to a database table, with your event handlers responsible for creating and updating the records. HyperIndex automatically generates a GraphQL API based on these entity types, allowing easy access to the indexed data. --- ## Defining Entity Types Entities in your schema are defined as GraphQL object types: **Example:** ```graphql type User { id: ID! greetings: [String!]! latestGreeting: String! numberOfGreetings: Int! } ``` ### Requirements: - Every entity **must** have a unique `id` field, using one of these scalar types: - `ID!`, `String!`, `Int!`, or `BigInt!` - The `id` field must be non-nullable, must not be a list, and cannot be a [`@derivedFrom`](#relationships-one-to-many-derivedfrom) field. ### Numeric Entity IDs `ID` is the usual choice and behaves as a string. Since v3.5, you can also key an entity on `Int` or `BigInt`, which is a better fit when the identifier is genuinely a number - a block number, an auction id, a sequential position: ```graphql type Auction { id: BigInt! # the on-chain auction id, not a stringified copy of it seller: String! bids: [Bid!]! @derivedFrom(field: "auction") } type Bid { id: ID! auction: Auction! # inferred as BigInt to match Auction.id amount: BigInt! } ``` Relationship fields adopt the referenced entity's id type automatically. Codegen exposes `Bid.auction` above to your handlers as `auction_id`, typed `bigint` rather than `string`. You don't declare the foreign key type. Keep the two sides in sync by changing the referenced entity's `id`. --- ## Scalar Types Scalar types represent basic data types and map directly to JavaScript, TypeScript, or ReScript types. | **GraphQL Scalar** | **Description** | **JavaScript/TypeScript** | **ReScript** | | --- | --- | --- | --- | | `ID` | Unique identifier | `string` | `string` | | `String` | UTF-8 character sequence | `string` | `string` | | `Int` | Signed 32-bit integer | `number` | `int` | | `Float` | Signed floating-point number | `number` | `float` | | `Boolean` | `true` or `false` | `boolean` | `bool` | | `Bytes` | Byte string (see [`bytes_type`](#bytes-representation)) | `string` or `Uint8Array` | `string` | | `BigInt` | Signed integer (`int256` in Solidity) | `bigint` | `bigint` | | `BigDecimal` | Arbitrary-size floating-point | `BigDecimal` (imported) | `BigDecimal.t` | | `Timestamp` | Timestamp with timezone | `Date` | `Js.Date.t` | | `Json` | JSON object | `Json` | `Js.Json.t` | Learn more about GraphQL scalars [here](https://graphql.org/learn/). ### Bytes Representation Since v3.10, the [`bytes_type`](https://docs.envio.dev/docs/HyperIndex/configuration-file#bytes-representation) option in `config.yaml` decides how `Bytes` reaches handlers and storage: `hex` (the EVM and Fuel default) gives a `0x`-prefixed `string` stored as text, `uint8array` gives a `Uint8Array` stored as raw bytes (`BYTEA` in Postgres), which roughly halves the storage taken by addresses and hashes. Solana is always `Uint8Array`. --- ## Working with BigDecimal The `BigDecimal` scalar type in HyperIndex is based on the [bignumber.js](https://mikemcl.github.io/bignumber.js/) library, which provides arbitrary-precision decimal arithmetic. This is essential for financial calculations and handling numeric values that exceed JavaScript's native number precision. ### Importing BigDecimal ```typescript import { BigDecimal } from "envio"; ``` ```rescript open BigDecimal ``` ### Creating BigDecimal Instances ```typescript // From string (recommended for precision) const price = new BigDecimal("123.456789"); // From number (may lose precision for very large values) const amount = new BigDecimal(123.45); // From other BigDecimal const copy = new BigDecimal(price); ``` ### Arithmetic Operations BigDecimal instances are immutable. Operations return new BigDecimal instances: ```typescript // Basic arithmetic const a = new BigDecimal("123.45"); const b = new BigDecimal("67.89"); const sum = a.plus(b); // 191.34 const difference = a.minus(b); // 55.56 const product = a.times(b); // 8,381.03 const quotient = a.div(b); // 1.81839... // Power const squared = a.pow(2); // 15,239.9025 // Square root const root = a.sqrt(); // 11.11... // Absolute value const abs = new BigDecimal("-123.45").abs(); // 123.45 ``` ### Comparison Methods ```typescript const x = new BigDecimal("10.5"); const y = new BigDecimal("10.5"); const z = new BigDecimal("9.9"); x.eq(y); // true (equal) x.gt(z); // true (greater than) x.gte(y); // true (greater than or equal) x.lt(z); // false (less than) x.lte(y); // true (less than or equal) // Check for special values x.isZero(); // false x.isPositive(); // true x.isNegative(); // false x.isFinite(); // true ``` ### Rounding and Formatting ```typescript const value = new BigDecimal("123.456789"); // Get with specific decimal places value.dp(2); // 123.46 (rounded) value.dp(2, 1); // 123.45 (rounded down) // Format as string value.toString(); // "123.456789" value.toFixed(2); // "123.46" value.toExponential(2); // "1.23e+2" value.toPrecision(5); // "123.46" ``` ### Working with Schema-Defined BigDecimal Fields When you've defined a `BigDecimal` field in your schema: ```graphql type TokenPair { id: ID! name: String! price: BigDecimal! volume: BigDecimal! } ``` You can use it in your handlers: ```typescript // In your event handler context.TokenPair.set({ id: event.params.pairId, name: event.params.name, price: new BigDecimal(event.params.price), volume: new BigDecimal("0"), // Start with zero volume }); // Updating a field const tokenPair = await context.TokenPair.get(pairId); if (tokenPair) { const newVolume = tokenPair.volume.plus(new BigDecimal(tradeAmount)); context.TokenPair.set({ ...tokenPair, volume: newVolume, }); } ``` ### Example: Financial Calculation ```typescript function calculateFee(amount: BigDecimal, feeRate: BigDecimal): BigDecimal { // Calculate fee with proper rounding return amount.times(feeRate).dp(2); } const tradeAmount = new BigDecimal("1250.75"); const feeRate = new BigDecimal("0.0025"); // 0.25% const fee = calculateFee(tradeAmount, feeRate); // 3.13 ``` ### Best Practices for BigDecimal 1. **Always use strings for initialization** when precision matters: ```typescript // Preferred const value = new BigDecimal("123.456789"); // May lose precision const value = new BigDecimal(123.456789); ``` 2. **Set precision explicitly** when doing division: ```typescript // Set to 8 decimal places for crypto prices const price = totalValue.div(tokenAmount).dp(8); ``` 3. **Handle rounding appropriately** for financial calculations: ```typescript // Round down (floor) for user-favorable calculations const userReceives = amount.dp(2, 1); // ROUND_DOWN // Round up (ceil) for protocol-favorable calculations const protocolFee = amount.dp(2, 0); // ROUND_UP ``` 4. **Compare with equals method** instead of `==` or `===`: ```typescript // Correct if (value.eq(new BigDecimal(0))) { /* ... */ } // Incorrect - compares object references if (value === new BigDecimal(0)) { /* ... */ } ``` 5. **Chain operations carefully**, remembering that each operation returns a new instance: ```typescript // Calculate (a + b) * c with proper precision const result = a.plus(b).times(c).dp(8); ``` --- ## Enum Types Enums allow fields to accept only a predefined set of values. **Example:** ```graphql enum AccountType { ADMIN USER } type User { id: ID! balance: Int! accountType: AccountType! } ``` Enums translate to string unions (TypeScript/JavaScript) or polymorphic variants (ReScript): **TypeScript Example:** ```typescript import { type Enum } from "envio"; let user = { id: event.params.id, balance: event.params.balance, accountType: "USER" satisfies Enum<"AccountType">, // enum as string }; ``` **ReScript Example:** ```rescript let user: Types.userEntity = { id: event.params.id, balance: event.params.balance, accountType: #USER, // polymorphic variant }; ``` --- ## Relationships: One-to-Many (`@derivedFrom`) Define relationships between entities using the `@derivedFrom` directive, known as **reverse lookups**. **Example:** ```graphql type NftCollection { id: ID! contractAddress: Bytes! name: String! symbol: String! maxSupply: BigInt! currentSupply: Int! tokens: [Token!]! @derivedFrom(field: "collection") } type Token { id: ID! tokenId: BigInt! collection: NftCollection! owner: User! } ``` - The `tokens` field in `NftCollection` is a virtual field, populated automatically when querying the API. - Set relationships in your handlers by assigning `_id` with the related entity's `id`. For example, create or update a `Token` entity with `collection_id: collectionId`. --- ## Field Indexing (`@index`) Add an index to a field for optimized queries and loader performance: ```graphql type Token { id: ID! tokenId: BigInt! collection: NftCollection! owner: User! @index } ``` - All `id` fields and fields referenced via `@derivedFrom` are indexed automatically. - Declare `@index` for the fields your GraphQL consumers filter and sort by. You don't need it for fields your handlers query with [`getWhere`](https://docs.envio.dev/docs/HyperIndex/event-handlers#retrieving-entities-by-field) - since v3.5 HyperIndex creates those indices itself. See [Indices created on demand](https://docs.envio.dev/docs/HyperIndex/database-performance-optimization#indices-created-on-demand). --- ## Choosing a Storage Backend (`@storage`) When you enable more than one storage backend in `config.yaml`, the `@storage` directive controls where each entity is written: ```graphql # Queryable over GraphQL and mirrored into ClickHouse for analytics type Transfer @storage(postgres: true, clickhouse: true) { id: ID! amount: BigInt! } ``` Since v3.2 you can mark a backend as `default` in `config.yaml`, and entities without a `@storage` directive go there - you no longer need the directive on every entity. See [`storage`](https://docs.envio.dev/docs/HyperIndex/config-schema-reference#storage). ### Per-Entity ClickHouse Tuning Since v3.4, the `clickhouse` argument also accepts an options object that tunes that entity's ClickHouse history table: ```graphql type Transfer @storage( postgres: true clickhouse: { partitionBy: "toYYYYMM(timestamp)" orderBy: ["timestamp"] ttl: "timestamp + INTERVAL 2 YEAR" skippingIndexes: [ { name: "idx_amount", expr: "amount", type: "minmax", granularity: 4 } ] } ) { id: ID! timestamp: Timestamp! amount: BigInt! } ``` | Option | Type | Description | | --- | --- | --- | | `partitionBy` | ClickHouse expression | Emitted as `PARTITION BY `. Keeps queries and TTL deletes inside a partition instead of scanning the whole table. | | `orderBy` | list of entity field names | Fields that lead the table's sorting key, ahead of the default `id`. | | `ttl` | ClickHouse expression | Emitted as `TTL `. Ages rows out automatically. | | `skippingIndexes` | list of index objects | Since v3.8. Data skipping indexes emitted into the history table DDL as `INDEX TYPE GRANULARITY `. Each entry takes `name`, `expr`, `type` (e.g. `minmax`, `bloom_filter`, `set`) and an optional `granularity`, which defaults to ClickHouse's own default. | A few constraints, all caught at `envio codegen` rather than at runtime: - `orderBy` takes **entity field names**, not expressions - unlike `partitionBy` and `ttl`, which are ClickHouse expressions passed through as written. - `orderBy` can't list `id` (already the default sorting key), nor nullable, list or `@derivedFrom` fields, which ClickHouse doesn't allow in a sorting key. - An entity can carry only one `@storage` directive, and it must enable at least one backend. - With [`disable_default_cross_chain: true`](https://docs.envio.dev/docs/HyperIndex/multichain-indexing#per-chain-data-mode), `orderBy` can also list the generated `chainId` column (v3.9). --- ## Sharing Entities Across Chains (`@crossChain`) Entities are shared by every chain by default: one id means one row, whichever chain wrote it. Setting [`disable_default_cross_chain: true`](https://docs.envio.dev/docs/HyperIndex/multichain-indexing#per-chain-data-mode) in `config.yaml` (v3.6, recommended for multichain indexers) flips that - entities become per-chain, keyed on `(id, chainId)` - and `@crossChain` marks the individual entities that should stay shared: ```graphql type Counter { # per-chain: one row per (id, chain) id: ID! count: BigInt! } type GlobalCounter @crossChain { # one row shared by every chain id: ID! count: BigInt! } ``` The directive is only valid when `disable_default_cross_chain: true` is set - without it entities are already cross-chain, and `envio codegen` rejects the directive. Per-chain entities also reserve the `chainId` column name (`chain_id` under `column_name_format: snake_case`), so a schema field can't claim it. --- ## Hiding Entities from the API (`@internal`) Since v3.8.0, mark an entity with `@internal` to keep it out of the GraphQL API. The entity is stored and usable in handlers exactly as normal - `set`, `get`, `getWhere` and `getOrThrow` all work - but it is never exposed through GraphQL: no queries, no relationships, no introspection entry. ```graphql # Bookkeeping state the indexer needs, but API consumers shouldn't see type SwapCheckpoint @internal { id: ID! lastProcessedLogIndex: Int! pendingAmount: BigInt! } ``` Use it for internal indexing state (cursors, checkpoints, deduplication markers, intermediate aggregates) or for data you don't want on the public API surface. A few rules, enforced at `envio codegen`: - An exposed entity can't reference an `@internal` one - neither through an object reference nor a [`@derivedFrom`](#relationships-one-to-many-derivedfrom) field - because the relationship could never be served over GraphQL. Either mark the referencing entity `@internal` too, or store a plain id field (e.g. `checkpointId: String!`) instead of a relationship. - The reverse is fine: an `@internal` entity may freely reference exposed entities. --- ## Advanced: Precision and Scale (`@config` Directive) Customize the precision and scale for `BigInt` and `BigDecimal` fields using `@config`. **Syntax:** - `BigInt` (precision only): ```graphql amount: BigInt @config(precision: 76) ``` - `BigDecimal` (precision and scale): ```graphql price: BigDecimal @config(precision: 10, scale: 2) ``` **Example:** ```graphql type Payment { id: ID! amount: BigInt @config(precision: 76) price: BigDecimal @config(precision: 10, scale: 2) } ``` This controls PostgreSQL storage allocation and numerical accuracy.
Detailed Example with Arrays ```graphql type AdvancedEntity { exampleBigInt: BigInt @config(precision: 76) exampleBigIntRequired: BigInt! @config(precision: 77) exampleBigIntArray: [BigInt!] @config(precision: 78) exampleBigIntArrayRequired: [BigInt!]! @config(precision: 79) exampleBigDecimal: BigDecimal @config(precision: 10, scale: 5) exampleBigDecimalRequired: BigDecimal! @config(precision: 12, scale: 4) } ```
--- ## Documenting Entities, Fields, and Relationships You can document your entities, fields, and relationships directly in `schema.graphql` using GraphQL string descriptions. These descriptions are exposed through the generated GraphQL API and appear in introspection, making your API self-documenting. ```graphql """ A token transfer between two accounts """ type Transfer { id: ID! "The address the tokens were sent from" from: String! "The address the tokens were sent to" to: String! "The amount transferred, in wei" value: BigInt! } ``` Both single-line (`"..."`) and multi-line (`"""..."""`) descriptions are supported. :::note Only string descriptions are exposed in introspection. Hash (`#`) comments are ignored by the GraphQL parser and do **not** appear in the API. Descriptions on entities, fields, and relationships were added in HyperIndex v3.1. ::: --- ## Generating Types Once you've defined your schema, run this command to generate these entity types that can be accessed in your event handlers: ```bash pnpm envio codegen ``` --- ## Best Practices - Use camelCase for field names (`latestGreeting`, `numberOfGreetings`). - Keep entity and field names clear, descriptive, and intuitive. --- You're now ready to define powerful schemas and efficiently query your indexed data with HyperIndex! --- # Event Handlers > Learn how to register and manage event handlers for efficient data indexing. ## Registration A handler is a function that receives blockchain data, processes it, and inserts it into the database. You can register handlers in the file defined in the `handler` field in your `config.yaml` file. By default this is `src/handlers` file. ```typescript import { indexer } from "envio"; indexer.onEvent( { contract: "", event: "" }, async ({ event, context }) => { // Your logic here }, ); ``` :::note The `envio` module exposes the unified `indexer` value along with types based on your `config.yaml` and `schema.graphql` files. Run **`pnpm codegen`** whenever you change these files to regenerate the types in `.envio/`. ::: ### Basic Example Here's a handler example for the `NewGreeting` event. It belongs to the `Greeter` contract from our beginners [Greeter Tutorial](https://docs.envio.dev/docs/HyperIndex/greeter-tutorial): ```typescript import { indexer, type User } from "envio"; // Handler for the NewGreeting event indexer.onEvent( { contract: "Greeter", event: "NewGreeting" }, async ({ event, context }) => { const userId = event.params.user; // The id for the User entity const latestGreeting = event.params.greeting; // The greeting string that was added const currentUserEntity = await context.User.get(userId); // Optional user entity that may already exist // Update or create a new User entity const userEntity: User = currentUserEntity ? { id: userId, latestGreeting, numberOfGreetings: currentUserEntity.numberOfGreetings + 1, greetings: [...currentUserEntity.greetings, latestGreeting], } : { id: userId, latestGreeting, numberOfGreetings: 1, greetings: [latestGreeting], }; context.User.set(userEntity); // Set the User entity in the DB }, ); ``` ### Multiple Handlers for One Event Since v3.4, you can register as many handlers as you like for the same event, each with its own [`where`](https://docs.envio.dev/docs/HyperIndex/wildcard-indexing) filter. Earlier versions only accepted handlers whose filters matched. This lets you keep broad and narrow logic in separate functions instead of branching inside one handler: ```typescript import { indexer } from "envio"; const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; // Track every ERC20 transfer on the chain indexer.onEvent( { contract: "ERC20", event: "Transfer", wildcard: true }, async ({ event, context }) => { // Global accounting }, ); // ...and run extra logic for mints, where tokens come from the zero address indexer.onEvent( { contract: "ERC20", event: "Transfer", wildcard: true, where: () => ({ params: [{ from: ZERO_ADDRESS }] }), }, async ({ event, context }) => { // Mint-specific logic }, ); ``` An event matching more than one registration is delivered to each of them, in registration order. ### Top-Level Await Handler files are ES modules, so you can `await` at the top level. This is useful for loading data once before registering handlers: ```typescript import { indexer } from "envio"; const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; // Load data before registering handlers const addressesFromServer = await loadWhitelistedAddresses(); indexer.onEvent( { contract: "ERC20", event: "Transfer", wildcard: true, where: { params: [ { from: ZERO_ADDRESS, to: addressesFromServer }, { from: addressesFromServer, to: ZERO_ADDRESS }, ], }, }, async ({ event, context }) => { // ... your handler logic }, ); ``` ### Preload Optimization > **Important!** Preload optimization makes your handlers run **twice**. Preload optimization is always enabled in HyperIndex V3 - there is no config flag to toggle it. This optimization enables HyperIndex to efficiently preload entities used by handlers through batched database queries, while ensuring events are processed synchronously in their original order. When combined with the [Effect API](https://docs.envio.dev/docs/HyperIndex/effect-api) for external calls, this feature delivers performance improvements of multiple orders of magnitude compared to other indexing solutions. Read more in the dedicated guides: - [How Preload Optimization Works](https://docs.envio.dev/docs/HyperIndex/preload-optimization) - [Double-Run Footgun](https://docs.envio.dev/docs/HyperIndex/preload-optimization#double-run-footgun) - [Effect API](https://docs.envio.dev/docs/HyperIndex/event-handlers#external-calls) ### Advanced Use Cases HyperIndex provides many features to help you build more powerful and efficient indexers. There's definitely the one for you: - Handle Factory Contracts with [Dynamic Contract Registration](https://docs.envio.dev/docs/HyperIndex/dynamic-contracts) (with nested factories support) - Perform external calls to decide which contract address to register using [Async Contract Register](https://docs.envio.dev/docs/HyperIndex/dynamic-contracts#async-contract-register) - Index all ERC20 token transfers with [Wildcard Indexing](https://docs.envio.dev/docs/HyperIndex/wildcard-indexing) - Use [Topic Filtering](https://docs.envio.dev/docs/HyperIndex/wildcard-indexing) to ignore irrelevant events - With [multiple filters for single event](https://docs.envio.dev/docs/HyperIndex/wildcard-indexing#multiple-filters) - With [different filters per chain](https://docs.envio.dev/docs/HyperIndex/wildcard-indexing#different-filters-per-chain) - With [filter by dynamicly registered contract addresses](https://docs.envio.dev/docs/HyperIndex/wildcard-indexing#index-all-erc20-transfers-to-your-contract) (eg Index all ERC20 transfers to/from your Contract) - [Access Contract State](https://docs.envio.dev/docs/HyperIndex/contract-state) directly from handlers - Perform external calls from handlers by following the [IPFS Integration guide](https://docs.envio.dev/docs/HyperIndex/ipfs) --- ## Event Object Each handler receives an `event` object containing details about the emitted event, including parameters and blockchain metadata. ### Accessing Event Parameters Event parameters are accessed via: ```typescript event.params. ``` Example usage: ```typescript const sender = event.params.sender; const amount = event.params.amount; ``` ### Additional Event Information The event object also contains additional metadata: - `event.chainId` – Chain ID of the chain emitting the event. - `event.contractName` – Name of the contract the event belongs to, as set in `config.yaml` (eg `"ERC20"`). - `event.eventName` – Name of the event (eg `"Transfer"`). - `event.srcAddress` – Contract address emitting the event. - `event.logIndex` – Index of the log within the block. - `event.block` – Block fields (By default: `number`, `timestamp`, `hash`). - `event.transaction` – Transaction fields (eg `hash`, `gasUsed`, etc. Empty by default). :::tip By default, all addresses returned in the `event` object, such as `event.transaction.from`, `event.transaction.to`, and `event.srcAddress`, are [EIP-55](https://eips.ethereum.org/EIPS/eip-55) checksummed. Use `.toLowerCase()` on a single value if you specifically need it in lowercase, or set [`address_format: lowercase`](https://docs.envio.dev/docs/HyperIndex/configuration-file#address-format) in `config.yaml` to switch every address that the indexer surfaces (events, `chain..addresses`, entity ids, etc.) to lowercase globally. ::: :::note Which fields `event.block` and `event.transaction` carry is up to you - see [Selecting Block and Transaction Fields](#selecting-block-and-transaction-fields). ::: ### Selecting Block and Transaction Fields Since **v3.7**, a registration names the block and transaction fields its handler reads with the `fields` option: ```typescript indexer.onEvent( { contract: "MyContract", event: "Transfer", fields: { transaction: ["hash", "from"], block: ["timestamp"] }, }, async ({ event, context }) => { event.transaction.hash; // string event.block.timestamp; // number event.transaction.gasUsed; // Type error - not listed in fields }, ); ``` Reading a field you didn't list is a type error, so the selection stays honest as handlers change. `event.block.number` is always available without listing it. `fields` works on both `indexer.onEvent` and `indexer.contractRegister`, and two handlers on the same event can select different fields - the indexer only fetches and decodes what's actually asked for. Write the selection inline, as above. To share one across registrations, declare it `as const` so its element types still name the fields: ```typescript const txFields = { transaction: ["hash", "from"] } as const; indexer.onEvent( { contract: "MyContract", event: "Transfer", fields: txFields }, handler, ); ``` The `config.yaml` [`field_selection`](https://docs.envio.dev/docs/HyperIndex/configuration-file#field-selection) option still works and applies to every handler of an event (or of every event, at the root level). A handler's `fields` overrides it. Prefer `fields`: the list sits next to the code that reads it, and it doesn't make every other handler pay for the fetch. See [`FieldSelection`](https://docs.envio.dev/docs/HyperIndex/config-schema-reference#def-fieldselection) in the Config Schema Reference for the full list of available transaction and block fields. **Example event type definition:** ```typescript type Event = { params: Params; chainId: 1 | 137; srcAddress: `0x${string}`; logIndex: number; transaction: TransactionFields; block: BlockFields; }; ``` --- ## Context Object The handler `context` provides methods to interact with entities stored in the database. ### Retrieving Entities Retrieve entities from the database using `context.Entity.get` where `Entity` is the name of the entity you want to retrieve, which is defined in your [schema.graphql](https://docs.envio.dev/docs/HyperIndex/schema.md) file. ```typescript await context.Entity.get(entityId); ``` It'll return `Entity` object or `undefined` if the entity doesn't exist. Use `context.Entity.getOrThrow` to conveniently throw an error if the entity doesn't exist: ```typescript const pool = await context.Pool.getOrThrow(poolId); // Will throw: Entity 'Pool' with ID '...' is expected to exist. // Or you can pass a custom message as a second argument: const pool = await context.Pool.getOrThrow( poolId, `Pool with ID ${poolId} is expected.` ); ``` Or use `context.Entity.getOrCreate` to automatically create an entity with default values if it doesn't exist: ```typescript const pool = await context.Pool.getOrCreate({ id: poolId, totalValueLockedETH: 0n, }); // Which is equivalent to: let pool = await context.Pool.get(poolId); if (!pool) { pool = { id: poolId, totalValueLockedETH: 0n, }; context.Pool.set(pool); } ``` ### Retrieving Entities by Field ```typescript indexer.onEvent( { contract: "ERC20", event: "Approval" }, async ({ event, context }) => { // Find all approvals for this specific owner const currentOwnerApprovals = await context.Approval.getWhere({ owner_id: { _eq: event.params.owner }, }); // Process all the owner's approvals efficiently for (const approval of currentOwnerApprovals) { // Process each approval } }, ); ``` Beyond `_eq`, you can filter by value using comparison operators like `_gt`, `_gte`, `_lt`, `_lte`, and `_in`. To narrow results further, combine several conditions in a single call - they all have to match (`AND`): ```typescript // Find accounts matching a specific id AND a balance within a range const accounts = await context.Account.getWhere({ id: { _eq: event.params.account }, balance: { _gte: 1_000_000n, _lte: 10_000_000n }, }); ``` :::note Multi-field filtering with `getWhere` was added in HyperIndex v3.2. ::: **Important:** - Preload Optimization is always enabled in V3 and powers `getWhere`. See [How Preload Optimization Works](https://docs.envio.dev/docs/HyperIndex/preload-optimization). - Works with any entity field. Since v3.5, HyperIndex creates whatever database index the query needs, so you don't have to declare [`@index`](https://docs.envio.dev/docs/HyperIndex/database-performance-optimization#indices-created-on-demand) for it. - Potential Memory Issues: Very large `getWhere` queries might cause memory overflows. - Tip: Try to put the `getWhere` query to the top of the handler, to make sure it's being preloaded. Read more about how [Preload Optimization](https://docs.envio.dev/docs/HyperIndex/preload-optimization) works. ### Modifying Entities Use `context.Entity.set` to create or update an entity: ```typescript context.Entity.set({ id: entityId, ...otherEntityFields, }); ``` :::note Both `context.Entity.set` and `context.Entity.deleteUnsafe` methods use the In-Memory Storage under the hood and don't require `await` in front of them. ::: ### Referencing Linked Entities When your schema defines a field that links to another entity type, set the relationship using `_id` with the referenced entity's `id`. You are storing the ID, not the full entity object. ```graphql type A { id: ID! b: B! } type B { id: ID! } ``` ```typescript context.A.set({ id: aId, b_id: bId, // ID of the linked B entity }); ``` HyperIndex automatically resolves `A.b` based on the stored `b_id` when querying the API. ### Deleting Entities (Unsafe) To delete an entity: ```typescript context.Entity.deleteUnsafe(entityId); ``` :::warning The `deleteUnsafe` method is experimental and **unsafe**. You need to manually handle all entity references after deletion to maintain database consistency. ::: ### Updating Specific Entity Fields Use the following approach to update specific fields in an existing entity: ```typescript const pool = await context.Pool.get(poolId); if (pool) { context.Pool.set({ ...pool, totalValueLockedETH: pool.totalValueLockedETH.plus(newDeposit), }); } ``` ### `context.log` The context object also provides a logger that you can use to log messages to the console. Compared to `console.log` calls, these logs will be displayed on our [Envio Cloud](https://docs.envio.dev/docs/HyperIndex/hosted-service) runtime logs page. Read more in the [Observability Guide](https://docs.envio.dev/docs/HyperIndex/observability). ### `context.isPreload` If you need to skip the preload phase for CPU-intensive operations or to perform certain actions only once per event, you can use `context.isPreload`. ```typescript indexer.onEvent( { contract: "ERC20", event: "Transfer" }, async ({ event, context }) => { // Load existing data efficiently const [sender, receiver] = await Promise.all([ context.Account.getOrThrow(event.params.from), context.Account.getOrThrow(event.params.to), ]); // Skip expensive operations during preload if (context.isPreload) { return; } // CPU-intensive calculations only happen once const complexCalculation = performExpensiveOperation(event.params.value); // Placeholder function for demonstration // Create or update sender account context.Account.set({ id: event.params.from, balance: sender.balance - event.params.value, computedValue: complexCalculation, }); // Create or update receiver account context.Account.set({ id: event.params.to, balance: receiver.balance + event.params.value, }); }, ); ``` **Note:** While `context.isPreload` can be useful for bypassing double execution, it's recommended to use the [Effect API](https://docs.envio.dev/docs/HyperIndex/effect-api) for external calls instead, as it provides automatic batching and memoization benefits. ## External Calls Envio indexer runs using Node.js runtime. This means that you can use `fetch` or any other library like `viem` to perform external calls from your handlers. Note that with [Preload Optimization](https://docs.envio.dev/docs/HyperIndex/preload-optimization) all handlers run twice. But with [Effect API](https://docs.envio.dev/docs/HyperIndex/effect-api) this behavior makes your external calls run in parallel, while keeping the processing data consistent. Check out our [IPFS Integration](https://docs.envio.dev/docs/HyperIndex/ipfs), [Accessing Contract State](https://docs.envio.dev/docs/HyperIndex/contract-state) and [Effect API](https://docs.envio.dev/docs/HyperIndex/effect-api) guides for more information. ### `context.effect` Define an effect and use it in your handler with `context.effect`: ```typescript import { indexer, createEffect, S } from "envio"; // Define an effect that will be called from the handler. const getMetadata = createEffect( { name: "getMetadata", input: S.string, output: { description: S.string, value: S.bigint, }, rateLimit: { calls: 5, per: "second", }, cache: true, // Optionally persist the results in the database }, ({ input }) => { const response = await fetch(`https://api.example.com/metadata/${input}`); const data = await response.json(); return { description: data.description, value: data.value, }; } ); indexer.onEvent( { contract: "ERC20", event: "Transfer" }, async ({ event, context }) => { // Load metadata for the token. // This will be executed in parallel for all events in the batch. // The call is automatically memoized, so you don't need to worry about duplicate requests. const sender = await context.effect(getMetadata, event.params.from); // Process the transfer with the pre-loaded data }, ); ``` --- ## Accessing `config.yaml` Data in Handlers You can read your indexer configuration and live indexing state from the `indexer` value - either at the top level of a handler file or inside a handler. Use `indexer.chains[chainId]` (or one of the named entries on `indexer.chains`) to inspect a specific chain: ```typescript import { indexer } from "envio"; indexer.onEvent( { contract: "Greeter", event: "NewGreeting" }, async ({ event, context }) => { const chain = indexer.chains[event.chainId]; chain.id; // chain id chain.startBlock; // configured start block chain.endBlock; // configured end block (or undefined) chain.isRealtime; // true once this chain has reached the head chain.Greeter.name; // contract name chain.Greeter.abi; // parsed ABI chain.Greeter.addresses; // initial + dynamically registered addresses }, ); ``` Top-level fields like `indexer.name`, `indexer.description`, and `indexer.chainIds` are also available. After restart, addresses on `chain..addresses` include any contracts that were dynamically registered in previous runs, not just those declared in `config.yaml`. --- ## Performance Considerations For performance optimization and best practices, refer to: - [Benchmarking](https://docs.envio.dev/docs/HyperIndex/benchmarking) - [Preload Optimization](https://docs.envio.dev/docs/HyperIndex/preload-optimization) These guides offer detailed recommendations on optimizing entity loading and indexing performance. --- --- # Block Handlers > Learn how to run custom logic on every blockchain block or at set intervals using onBlock. Run logic on every block or an interval. --- `indexer.onBlock` lets you run logic on every block or an interval. This is useful for aggregations and time-series logic. To get started, import the `indexer` value from `envio` and call `onBlock` in one of your handler files. ```typescript import { indexer } from "envio"; indexer.onBlock( { name: "MyBlockHandler", }, async ({ block, context }) => { context.log.info(`Processing block ${block.number}`); } ); ``` > Block handlers don't require any config changes as well as codegen runs. In the example above, the handler runs on **every** chain configured in `config.yaml` (the V3 default). To restrict it to a single chain, pass a `where` callback - see below. ## Options `indexer.onBlock` accepts an options object as the first argument with the following properties: - `name` (required) - The name of the block handler. It's used for logging, debugging and metrics. - `where` (optional) - A callback `({ chain }) => false | true | { block: { number: { _gte?, _lte?, _every? } } }` that decides which chains the handler runs on and over which block range/interval. Omit it to run on every chain on every block. To express the V2-era `chain`, `startBlock`, `endBlock`, and `interval` options, return them from `where`: ```typescript import { indexer } from "envio"; indexer.onBlock( { name: "MyBlockHandler", where: ({ chain }) => { if (chain.id !== 1) return false; return { block: { number: { _gte: 19_000_000, // start block (inclusive) _lte: 20_000_000, // end block (inclusive) _every: 100, // run every Nth block }, }, }; }, }, async ({ block, context }) => { context.log.info(`Processing block ${block.number}`); } ); ``` ## Handler Function :::note Preload Optimization is always enabled in HyperIndex V3 and powers Block Handlers. Don't forget that it makes your handlers run twice. ::: The second argument is a handler function that receives the block object and the handler context. - `block` - The block object. - `number` - The block number. - More fields will be added in the future. Let us know in [Discord](https://discord.gg/envio) if you need any specific fields. You can also use [Effect API](https://docs.envio.dev/docs/HyperIndex/effect-api) to get the data from RPC. - `context` - Exactly the same as the Event Handlers [Context Object](https://docs.envio.dev/docs/HyperIndex/event-handlers#context-object). Use `context.chain.id` to read the current chain ID inside the handler. ## Multichain By default `indexer.onBlock` runs on every chain in your config. To run different parameters per chain, branch inside the `where` callback: ```typescript import { indexer } from "envio"; const perChain = { 1: { startBlock: 19783636, interval: (60 * 60) / 12 }, // Every 60 minutes (12s block time) 10: { startBlock: 119534316, interval: (60 * 60) / 2 }, // Every 60 minutes (2s block time) } as const; indexer.onBlock( { name: "HourlyPrice", where: ({ chain }) => { const cfg = perChain[chain.id as keyof typeof perChain]; if (!cfg) return false; return { block: { number: { _gte: cfg.startBlock, _every: cfg.interval }, }, }; }, }, async ({ block, context }) => { context.log.info(`Processing block ${block.number} on chain ${context.chain.id}`); } ); ``` ## Time Interval The `_every` option is a number of blocks. But quite often you want to run some logic on a time interval. To convert time interval to blocks, you can use the following formula: ```typescript // Every 60 minutes const timeIntervalInSeconds = 60 * 60; // 12 seconds per block const secondsPerBlock = 12; // 300 blocks per 60 minutes const blockInterval = timeIntervalInSeconds / secondsPerBlock; ``` ## Different Historical and Realtime Intervals Here's the recipe to speed up your historical sync by increasing the interval for historical blocks. You can achieve this by registering multiple block handlers with the same handler, but different `_gte`, `_lte`, and `_every` values. ```typescript import { indexer } from "envio"; const realtimeBlocks = { 1: 19783636, 10: 119534316, } as const; indexer.onBlock( { name: "HistoricalBlockHandler", where: ({ chain }) => { const realtime = realtimeBlocks[chain.id as keyof typeof realtimeBlocks]; if (!realtime) return false; return { block: { number: { _lte: realtime - 1, _every: 1000 } } }; }, }, async ({ block, context }) => { context.log.info(`Processing block ${block.number}`); } ); indexer.onBlock( { name: "RealtimeBlockHandler", where: ({ chain }) => { const realtime = realtimeBlocks[chain.id as keyof typeof realtimeBlocks]; if (!realtime) return false; return { block: { number: { _gte: realtime } } }; }, }, async ({ block, context }) => { context.log.info(`Processing block ${block.number}`); } ); ``` In this case we'll run the historical handler on every 1000 blocks and from the realtime block we'll start running the second handler on every block. > We recommend exploring the approach together with [HyperSync](https://docs.envio.dev/docs/HyperSync/overview) client to effectively query data for big block ranges. ## Preset Handler This is not an official feature, but a creative way to use block handlers. You can define a block handler that runs on a single block at the start of the chain and use it to populate the database with the initial data. ```typescript import { indexer } from "envio"; indexer.onBlock( { name: "Preset", where: ({ chain }) => { if (chain.id !== 1) return false; return { block: { number: { _gte: 0, _lte: 0 } } }; }, }, async ({ block, context }) => { // You don't need preload optimization here, // so don't forget to disable it to prevent double-run. if (context.isPreload) return; const users = await fetch("https://api.example.com/users"); users.forEach((user) => { context.User.set({ id: user.id, address: user.address, name: user.name, }); }); } ); ``` ## Current Limitations - Only block number is provided in the block object. We'll definitely add more fields in the future. --- # Understanding Multichain Indexing > Learn how to index and process events across multiple chains with HyperIndex. For a conceptual overview of what multichain indexing is and when to use it, see [What is multichain indexing?](https://docs.envio.dev/blog/what-is-multi-chain-indexing). This page covers the HyperIndex configuration and patterns. Multichain indexing allows you to monitor and process events from contracts deployed across multiple chains within a single indexer instance. This capability is essential for applications that: - Track the same contract deployed across multiple chains - Need to aggregate data from different chains into a unified view - Monitor cross-chain interactions or state ## How It Works With multichain indexing, events from contracts deployed on multiple chains can be used to create and update entities defined in your schema file. Your indexer will process events from all configured chains, maintaining proper synchronization across them. ## Configuration Requirements To implement multichain indexing, you need to: 1. Populate the `chains` section in your `config.yaml` file for each chain 2. Specify contracts to index from each chain 3. Create event handlers for the specified contracts ## Real-World Example: Uniswap V4 Multichain Indexer For a comprehensive, production-ready example of multichain indexing, we recommend exploring our [Uniswap V4 Multichain Indexer](https://docs.envio.dev/docs/HyperIndex/example-uniswap-v4-multi-chain-indexer). This official reference implementation: - Indexes Uniswap V4 deployments across 10 different blockchain chains - Powers the official [v4.xyz](https://v4.xyz) interface with real-time data - Demonstrates best practices for high-performance multichain indexing - Provides a complete, production-grade implementation you can study and adapt ![V4 indexer](https://docs.envio.dev/img/v4.gif) The Uniswap V4 indexer showcases how to effectively structure a multichain indexer for a complex DeFi protocol, handling high volumes of data across multiple chains while maintaining performance and reliability. ## Config File Structure for Multichain Indexing The `config.yaml` file for multichain indexing contains three key sections: 1. **Global contract definitions** - Define contracts, ABIs, and events once 2. **Chain-specific configurations** - Specify chain IDs and starting blocks 3. **Contract instances** - Reference global contracts with chain-specific addresses ```yaml # Example structure (simplified) contracts: - name: ExampleContract abi_file_path: ./abis/example-abi.json events: - event: ExampleEvent chains: - id: 1 # Ethereum Mainnet start_block: 0 contracts: - name: ExampleContract address: "0x1234..." - id: 137 # Polygon start_block: 0 contracts: - name: ExampleContract address: "0x5678..." ``` ### Key Configuration Concepts - The global `contracts` section defines the contract interface, ABI, handlers, and events once - The `chains` section lists each blockchain chain you want to index - Each chain entry references the global contract and provides the chain-specific address - This structure allows you to reuse the same handler functions and event definitions across chains ## Per-Chain Data Mode By default every entity row and effect cache is shared by all chains, so the same entity id on two chains resolves to a single row. That's why multichain indexers traditionally namespace ids with the chain id (`user-1`, `user-137`). Since **v3.6** you can instead make entities per-chain, which is the recommended setup for a multichain indexer: ```yaml name: my-indexer disable_default_cross_chain: true ``` With it on: - Entity tables get a composite `(id, chainId)` primary key. The same id on two chains is two independent rows - in memory, in Postgres and in ClickHouse - and entity history and reorg rollback are scoped per chain too. - Effects that don't state a `crossChain` option get one cache per chain. See [Per-Chain Effects](https://docs.envio.dev/docs/HyperIndex/effect-api#per-chain-effects). - Handler code is unchanged. A handler always runs on one chain, so `context.Token.get(id)` reads that chain's row and you can drop the id prefixing. Sharing becomes explicit. Mark an entity `@crossChain` when its rows should stay shared by every chain: ```graphql type Counter { # per-chain: one row per (id, chain) id: ID! count: BigInt! } type GlobalCounter @crossChain { # one row shared by every chain id: ID! count: BigInt! } ``` `@crossChain` is only valid together with `disable_default_cross_chain: true` - without the flag entities are already cross-chain and codegen rejects the directive. Per-chain entities reserve the `chainId` column name (`chain_id` under [`column_name_format: snake_case`](https://docs.envio.dev/docs/HyperIndex/configuration-file#column-name-format)), so a schema field can't claim it. Outside a handler there is no chain in context, so the [test framework's](https://docs.envio.dev/docs/HyperIndex/testing) chain-agnostic operations take one explicitly: ```typescript indexer.Counter.set({ id: "1", count: 0n, chainId: 1 }); // Throws if the id exists on more than one chain indexer.Counter.get("1"); // Narrow it instead indexer.Counter.getWhere({ chainId: { _eq: 1 } }); ``` Since **v3.10**, rollbacks are isolated too: when `disable_default_cross_chain: true` is set and no entity is marked `@crossChain`, a reorg rolls back only the chain that triggered it instead of every chain. ## Multichain Event Ordering In V3 the indexer always processes multichain events in **unordered** mode. Events from different chains are processed as soon as they're available, without waiting for the other chains, which keeps latency low. - Events are still processed in order within each individual chain. - Events across different chains may be processed out of order. - Processing happens as soon as events are emitted, so you don't wait for the slowest chain's block time. This is ideal when: - Operations on your entities are [commutative](https://docs.envio.dev/docs/HyperIndex/terminology.md#commutative-property) (order doesn't matter). - Entities from different chains never interact with each other. - Processing speed matters more than guaranteed cross-chain ordering. The V2 `unordered_multichain_mode` option, the `multichain: ordered` opt-in, and the `UNORDERED_MULTICHAIN_MODE` / `UNSTABLE__TEMP_UNORDERED_HEAD_MODE` environment variables have all been removed in V3 - there is nothing to configure. ## Ordered Multichain Mode HyperIndex V3 doesn't offer an ordered multichain mode, and it's a deliberate choice rather than a feature gap. Ordered mode worked by pausing every chain until events from the slowest chain had caught up, so the moment one chain hiccuped (RPC rate limits, a slow block, a brief reorg) every other chain stalled with it. In practice that meant a multichain indexer was only ever as reliable and as fast as its worst-performing chain, and even healthy chains paid for that coupling with significantly higher latency at the head. You almost always get better reliability and lower latency by keeping every chain unordered and modeling the cross-chain relationship in your schema instead. Each chain writes a small temporary entity when its side of a cross-chain interaction happens, and the second chain (whichever arrives last) reads those temporary entities and finalizes the unified entity. Because each chain progresses independently, none of them are blocked on each other. A typical pattern for an `A → B` cross-chain message: ```graphql type CrossChainMessage { id: ID! # The shared cross-chain message id (e.g. nonce + originChainId) sourceChainId: Int sourceTxHash: String destinationChainId: Int destinationTxHash: String status: String! # "sent" | "delivered" } ``` ```typescript import { indexer } from "envio"; // Chain A: the message was emitted. Create or update the entity with the // "sent" side of the data. The destination handler may run before or after. indexer.onEvent( { contract: "Bridge", event: "MessageSent" }, async ({ event, context }) => { const id = `${event.params.originChainId}-${event.params.nonce}`; const existing = await context.CrossChainMessage.get(id); context.CrossChainMessage.set({ id, sourceChainId: event.chainId, sourceTxHash: event.transaction.hash, destinationChainId: existing?.destinationChainId, destinationTxHash: existing?.destinationTxHash, status: existing?.destinationTxHash ? "delivered" : "sent", }); }, ); // Chain B: the message was delivered. Read the (maybe-already-existing) // entity and fill in the destination side, regardless of which side arrived // first. indexer.onEvent( { contract: "Bridge", event: "MessageDelivered" }, async ({ event, context }) => { const id = `${event.params.originChainId}-${event.params.nonce}`; const existing = await context.CrossChainMessage.get(id); context.CrossChainMessage.set({ id, sourceChainId: existing?.sourceChainId, sourceTxHash: existing?.sourceTxHash, destinationChainId: event.chainId, destinationTxHash: event.transaction.hash, status: existing?.sourceTxHash ? "delivered" : "sent", }); }, ); ``` The same shape works for any "rendezvous" where two chains contribute parts of one logical record (bridges, cross-chain governance, multichain user profiles, etc.). The "temporary" entity is just a regular entity that gets progressively completed as each chain's events arrive - there's no special API to learn, and the indexer stays fast and resilient because no chain ever waits on another. If you have a use case that genuinely cannot be expressed this way, reach out on [Discord](https://discord.gg/envio) - we'd like to hear it. ## Best Practices for Multichain Indexing ### 1. Keep Chains' Data Distinct Set [`disable_default_cross_chain: true`](#per-chain-data-mode) so entities are per-chain, and mark the few entities that should be shared with `@crossChain`. Prefer that over prefixing entity IDs with `${event.chainId}-`: the prefix has to be repeated at every read and write, it leaks the chain into ids your API consumers see, and one handler that forgets it silently merges two chains' data. Per-chain mode keeps the ids clean and makes the separation the database's job. ### 2. Error Handling Implement robust error handling for chain-specific issues. A failure on one chain shouldn't prevent indexing from continuing on other chains. ### 3. Testing - Test your indexer with realistic scenarios across all chains - Use testnet deployments for initial validation - Verify entity updates work correctly across chains ### 4. Performance Considerations - Consider your indexing frequency based on the block times of each chain. - Monitor resource usage, as indexing multiple chains increases load. - Adding more chains does **not** linearly degrade performance - chains are indexed in parallel. ### 5. Adding a New Chain to an Existing Indexer To add a new chain to a running indexer: 1. Add the new chain entry to your `config.yaml` with the appropriate `start_block` and contract addresses 2. Push the updated code to your deployment branch (for Envio Cloud) or restart locally with `pnpm envio dev -r` On **Envio Cloud**, this creates a new deployment that re-indexes all chains (including the new one). Your previous deployment continues serving queries with zero downtime until the new deployment is fully synced. See the [deployment guide](https://docs.envio.dev/docs/HyperIndex/hosted-service-deployment.md#updating-your-deployment) for details. **Locally**, adding a new chain requires a restart and will re-index all chains from their respective start blocks. Note that in V3, `envio dev` no longer auto-resets the database - pass `-r` (or `--restart`) explicitly when you want a fresh sync. `envio start` is now production-only. ## Troubleshooting Common Issues 1. **Entity Conflicts**: If you see one chain's updates overwriting another's, either turn on [per-chain data mode](#per-chain-data-mode) or verify that your entity IDs are properly namespaced with chain IDs. 2. **Memory Usage**: If your indexer uses excessive memory, consider optimizing your entity structure and implementing pagination in your queries. ## Next Steps - Explore our [Uniswap V4 Multichain Indexer](https://docs.envio.dev/docs/HyperIndex/example-uniswap-v4-multi-chain-indexer) for a complete implementation - Review [performance optimization techniques](https://docs.envio.dev/docs/HyperIndex/performance) for your indexer --- # Running The Indexer Locally > Learn how to start, run, and manage the Envio indexer locally with Docker and Hasura. ## Starting the Indexer Remember to `cd` into your project directory if you have defined one during `pnpx envio init`. Before running the Envio CLI command to start the indexer locally, please make sure you have [Docker](https://www.docker.com/products/docker-desktop/) (or [Podman](https://podman.io/)) running. Run the indexer ```bash pnpm dev ``` This will automatically open up the Hasura dashboard where you can view the data that has been indexed. Admin-secret / password for local Hasura is `testing`. ## Hot Reload When running `pnpm dev`, HyperIndex watches for changes to your **event handler** files. When you save a handler file, the indexer will automatically re-process events using your updated logic without needing to restart manually. **What triggers a hot reload:** - Changes to event handler files (e.g., `src/handlers`) **What requires a full restart:** - Changes to `config.yaml` (adding contracts, events, or chains) - Changes to `schema.graphql` (adding or modifying entities) - Changes to ABIs For config or schema changes, restart the indexer: ```bash pnpm envio start -r ``` :::tip If hot reload doesn't seem to be picking up your handler changes, try saving the file again or check the terminal output for compilation errors that may be preventing the reload. ::: ## Avoiding Full Re-indexing During Development By default, stopping and restarting the indexer will re-index from the start block. This can be slow for large datasets. Here are strategies to iterate faster: ### Use the testing framework The fastest way to validate handler logic is to skip syncing entirely. Envio's built-in [testing framework](https://docs.envio.dev/docs/HyperIndex/testing.md) lets you mock events, simulate database state, and assert handler behavior - all without connecting to a blockchain or waiting for blocks to sync. ```bash pnpm test ``` This is ideal for iterating on handler logic, catching bugs early, and running tests in CI. See the [Testing guide](https://docs.envio.dev/docs/HyperIndex/testing.md) for setup and examples. ### Use a recent start block During development, temporarily set `start_block` in your `config.yaml` to a recent block number. This lets you test handler logic against fresh data without waiting for a full historical sync: ```yaml - id: 1 # Ethereum mainnet start_block: 19000000 # Recent block for faster dev iteration ``` Remember to set it back to the correct start block before deploying to production. ### Force a clean re-index If you need to re-index from scratch (e.g., after schema changes or to clear corrupted state): ```bash pnpm envio start -r # Restarts and re-indexes from start_block ``` ## Stopping the Indexer To delete the docker images used for the local development environment, run ```bash pnpm envio stop ``` ## What next? ## Once you have successfully run the indexer locally, you can deploy the indexer onto Envio's [Envio Cloud](https://docs.envio.dev/docs/HyperIndex/hosted-service.md). --- # Environment Variables > Learn how to configure and manage Envio environment variables for your blockchain indexer. Environment variables are a crucial part of configuring your Envio blockchain indexer. They allow you to manage sensitive information and configuration settings without hardcoding them in your codebase. ## Naming Convention All environment variables used by Envio must be prefixed with `ENVIO_`. This naming convention: - Prevents conflicts with other environment variables - Makes it clear which variables are used by the Envio indexer - Ensures consistency across different environments ## Envio API Token (required for HyperSync) To ensure continued access to HyperSync, set an Envio API token in your environment. - Use `ENVIO_API_TOKEN` to provide your token at runtime - See the API Tokens guide for how to generate a token: [API Tokens](https://docs.envio.dev/docs/HyperSync/api-tokens) - A token is only required when using Envio as the data provider (HyperSync). Indexers that source data from an external RPC don't need one. ## Envio-specific environment variables The following variables are used by HyperIndex: - `ENVIO_API_TOKEN`: API token for HyperSync access (required when indexing via HyperSync - get one at [envio.dev/app/api-tokens](https://envio.dev/app/api-tokens)) - `ENVIO_HASURA`: Set to `false` to disable Hasura integration for self-hosted blockchain indexers - `ENVIO_TUI`: Set to `false` to disable the terminal UI (replaces the V2 `TUI_OFF=true` flag; the TUI is also auto-disabled in CI and under AI agents) - `ENVIO_INDEXER_PORT`: Port for the indexer's HTTP server, which serves `/metrics`, `/metrics/runtime`, and `/healthz` (default: `9898`) - see [Observability](https://docs.envio.dev/docs/HyperIndex/observability) - `ENVIO_PG_HOST`: Postgres host (self-hosted; defaults to `localhost` in dev) - `ENVIO_PG_PORT`: Host port for the Postgres service used by HyperIndex during local development (default: `5433`; the container itself always listens on `5432`) - `ENVIO_PG_PASSWORD`: Postgres password (self-hosted) - `ENVIO_PG_USER`: Postgres username (self-hosted) - `ENVIO_PG_DATABASE`: Postgres database name (self-hosted) - `ENVIO_PG_SCHEMA`: Postgres schema name override for the generated/public schema (replaces `ENVIO_PG_PUBLIC_SCHEMA`; the old name is still accepted until v4) - `ENVIO_PG_SSL_MODE`: Postgres SSL mode - `require`, `allow`, `prefer` or `verify-full` (disabled by default in local dev) When the indexer also writes to ClickHouse (`storage.clickhouse` in `config.yaml`), `envio start` reads the connection from `ENVIO_CLICKHOUSE_HOST`, `ENVIO_CLICKHOUSE_DATABASE`, `ENVIO_CLICKHOUSE_USERNAME` and `ENVIO_CLICKHOUSE_PASSWORD`, plus the optional `ENVIO_CLICKHOUSE_REPLICATED` and `ENVIO_CLICKHOUSE_DATABASE_ENGINE` for high-availability setups. `envio dev` spins up its own container and injects these for you. :::note The V2 variables `MAX_BATCH_SIZE`, `ENVIO_INDEXING_BLOCK_LAG`, `UNORDERED_MULTICHAIN_MODE`, and `UNSTABLE__TEMP_UNORDERED_HEAD_MODE` have been removed in V3. Use the `full_batch_size` config option in `config.yaml` instead of `MAX_BATCH_SIZE`, and use the per-chain `block_lag` option instead of `ENVIO_INDEXING_BLOCK_LAG`. Unordered multichain processing is now the default. ::: ## Example Environment Variables Here are some commonly used environment variables: ```bash # Envio API Token (required for HyperSync access) ENVIO_API_TOKEN=your-secret-token # Blockchain RPC URL ENVIO_RPC_URL=https://arbitrum.direct.dev/your-api-key # Coingecko API key ENVIO_COINGECKO_API_KEY=api-key # Disable the terminal UI ENVIO_TUI=false ``` ## Setting Environment Variables ### Local Development For local development, you can set environment variables in several ways: 1. Using a `.env` file in your project root: ```bash # .env ENVIO_API_TOKEN=your-secret-token ENVIO_RPC_URL=https://arbitrum.direct.dev/your-api-key ``` 2. Directly in your terminal: ```bash export ENVIO_API_TOKEN=your-secret-token export ENVIO_RPC_URL=https://arbitrum.direct.dev/your-api-key ``` ### Envio Cloud When using Envio Cloud, you can configure environment variables through the Envio platform's dashboard. Remember that all variables must still be prefixed with `ENVIO_`. For more information about environment variables in Envio Cloud, see the [Envio Cloud documentation](https://docs.envio.dev/docs/HyperIndex/hosted-service.md). ## Configuration File For use of environment variables in your configuration file, read the docs here: [Configuration File](https://docs.envio.dev/docs/HyperIndex/configuration-file.md). ## Best Practices 1. **Never commit sensitive values**: Always use environment variables for sensitive information like API keys and database credentials 1. **Never commit or use private keys**: Never commit or use private keys in your codebase 1. **Use descriptive names**: Make your environment variable names clear and descriptive 1. **Document your variables**: Keep a list of required environment variables in your project's README 1. **Use different values**: Use different environment variables for development, staging, and production environments 1. **Validate required variables**: Check that all required environment variables are set before starting your blockchain indexer ## Troubleshooting If you encounter issues with environment variables: 1. Verify that all required variables are set 2. Check that variables are prefixed with `ENVIO_` 3. Ensure there are no typos in variable names 4. Confirm that the values are correctly formatted For more help, see our [Observability Guide](https://docs.envio.dev/docs/HyperIndex/observability.md). --- # Envio Command Line Interface > Explore and manage indexer projects with Envio CLI, from setup to development and monitoring. This comprehensive reference guide covers all available commands and options in the Envio CLI tool for HyperIndex **V3**. Use this documentation to explore the full capabilities of the `envio` command and its subcommands for managing your blockchain indexing projects. :::tip Envio Cloud CLI Looking to manage your hosted indexers from the command line? See the **[Envio Cloud CLI](https://docs.envio.dev/docs/HyperIndex/envio-cloud-cli)** for deployment, monitoring, and management commands for Envio Cloud. ::: ## Getting Started The Envio CLI provides a powerful set of tools for creating, developing, and managing your blockchain indexers. Whether you're starting a new project, running a development server, or deploying to production, the CLI offers commands to simplify and automate your workflow. The fastest way to get going is `pnpx envio init`, which scaffolds a project interactively. From there, `envio dev` runs your indexer locally while you iterate, and `envio start` runs it in production. **Command Overview:** * [`envio`↴](#envio) * [`envio init`↴](#envio-init) * [`envio init contract-import`↴](#envio-init-contract-import) * [`envio init contract-import explorer`↴](#envio-init-contract-import-explorer) * [`envio init contract-import local`↴](#envio-init-contract-import-local) * [`envio init template`↴](#envio-init-template) * [`envio init svm`↴](#envio-init-svm) * [`envio init svm template`↴](#envio-init-svm-template) * [`envio init fuel`↴](#envio-init-fuel) * [`envio init fuel contract-import`↴](#envio-init-fuel-contract-import) * [`envio init fuel contract-import local`↴](#envio-init-fuel-contract-import-local) * [`envio init fuel template`↴](#envio-init-fuel-template) * [`envio dev`↴](#envio-dev) * [`envio stop`↴](#envio-stop) * [`envio codegen`↴](#envio-codegen) * [`envio local`↴](#envio-local) * [`envio local docker`↴](#envio-local-docker) * [`envio local docker up`↴](#envio-local-docker-up) * [`envio local docker down`↴](#envio-local-docker-down) * [`envio local db-migrate`↴](#envio-local-db-migrate) * [`envio local db-migrate up`↴](#envio-local-db-migrate-up) * [`envio local db-migrate down`↴](#envio-local-db-migrate-down) * [`envio local db-migrate setup`↴](#envio-local-db-migrate-setup) * [`envio start`↴](#envio-start) * [`envio metrics`↴](#envio-metrics) * [`envio metrics runtime`↴](#envio-metrics-runtime) * [`envio skills`↴](#envio-skills) * [`envio skills update`↴](#envio-skills-update) * [`envio tools`↴](#envio-tools) * [`envio tools search-docs`↴](#envio-tools-search-docs) * [`envio tools fetch-docs`↴](#envio-tools-fetch-docs) * [`envio config`↴](#envio-config) * [`envio config view`↴](#envio-config-view) ## `envio` **Usage:** `envio [OPTIONS] ` ###### **Subcommands:** * `init` - Create a new indexer * `dev` - Development commands for starting, stopping, and restarting the indexer. Runs codegen automatically before launching * `stop` - Stop the local environment - delete the database and stop all processes (including Docker) for the current directory * `codegen` - Generate indexing code from user-defined configuration & schema files * `local` - Prepare local environment for envio testing * `start` - Start the indexer. Runs codegen automatically before launching so the on-disk types stay in sync with `config.yaml` and `schema.graphql` * `metrics` - Fetch raw Prometheus metrics from the running indexer's /metrics endpoint * `skills` - Manage Envio-provided Claude Code skills under `.claude/skills/` * `tools` - Tools for people and AI agents (search-docs, fetch-docs). Run `envio tools help` for details * `config` - Inspect the indexer config ###### **Options:** * `-d`, `--directory ` - The directory of the project. Defaults to current dir ("./") * `--config ` - The config file path, resolved relative to the project directory. It can also be set via the `ENVIO_CONFIG` environment variable Default value: `config.yaml` ## `envio init` Create a new indexer Quick start - run with no arguments: pnpx envio init Guided step-by-step for humans and AI agents. Reach for the subcommands below only when you already know exactly what you want. **Usage:** `envio init [OPTIONS] [COMMAND]` ###### **Subcommands:** * `contract-import` - [Advanced] Initialize Evm indexer by importing config from a contract for a given chain * `template` - [Advanced] Initialize Evm indexer from an example template * `svm` - Initialization option for creating Svm indexer * `fuel` - Initialization option for creating Fuel indexer ###### **Options:** * `-n`, `--name ` - The name of your project * `-l`, `--language ` - The language used to write handlers Possible values: `typescript`, `rescript` * `--package-manager ` - The package manager used for `install` and post-init build steps (default: pnpm) Possible values: `pnpm`, `npm`, `yarn`, `bun` * `--api-token ` - The Envio API token to be initialized in your templates .env file. Falls back to the `ENVIO_API_TOKEN` environment variable. Create one at https://envio.dev/app/api-tokens ## `envio init contract-import` [Advanced] Initialize Evm indexer by importing config from a contract for a given chain **Usage:** `envio init contract-import [OPTIONS] [COMMAND]` ###### **Subcommands:** * `explorer` - Initialize by pulling the contract ABI from a block explorer * `local` - Initialize from a local json ABI file ###### **Options:** * `-c`, `--contract-address ` - Contract address to generate the config from * `--single-contract` - If selected, prompt will not ask for additional contracts/addresses/chains * `--all-events` - If selected, prompt will not ask to confirm selection of events on a contract ## `envio init contract-import explorer` Initialize by pulling the contract ABI from a block explorer **Usage:** `envio init contract-import explorer [OPTIONS]` ###### **Options:** * `-b`, `--blockchain ` - Network to import the contract from Possible values: `abstract`, `amoy`, `arbitrum-nova`, `arbitrum-one`, `arbitrum-sepolia`, `arbitrum-testnet`, `aurora`, `aurora-testnet`, `avalanche`, `b2-testnet`, `base`, `base-sepolia`, `berachain`, `blast`, `blast-sepolia`, `boba`, `bsc`, `bsc-testnet`, `celo`, `celo-alfajores`, `celo-baklava`, `citrea-testnet`, `crab`, `curtis`, `ethereum-mainnet`, `etherlink`, `evmos`, `fantom`, `fantom-testnet`, `fhenix-helium`, `flare`, `fraxtal`, `fuji`, `galadriel-devnet`, `gnosis`, `gnosis-chiado`, `goerli`, `harmony`, `holesky`, `hoodi`, `hyperliquid`, `kroma`, `linea`, `linea-sepolia`, `lisk`, `lukso`, `lukso-testnet`, `manta`, `mantle`, `mantle-testnet`, `megaeth-testnet2`, `metis`, `mode`, `mode-sepolia`, `monad`, `monad-testnet`, `moonbase-alpha`, `moonbeam`, `moonriver`, `morph`, `morph-testnet`, `neon-evm`, `opbnb`, `optimism`, `optimism-sepolia`, `plasma`, `poa-core`, `poa-sokol`, `polygon`, `polygon-zkevm`, `polygon-zkevm-testnet`, `robinhood`, `rsk`, `saakuru`, `scroll`, `scroll-sepolia`, `sei`, `sei-testnet`, `sepolia`, `shimmer-evm`, `sonic`, `sonic-testnet`, `sophon`, `swell`, `taiko`, `tangle`, `unichain`, `unichain-sepolia`, `worldchain`, `xdc`, `xdc-testnet`, `zeta`, `zksync-era`, `zora`, `zora-sepolia` * `--api-token ` - API token for the block explorer * `--single-contract` - If selected, prompt will not ask for additional contracts/addresses/chains * `--all-events` - If selected, prompt will not ask to confirm selection of events on a contract ## `envio init contract-import local` Initialize from a local json ABI file **Usage:** `envio init contract-import local [OPTIONS]` ###### **Options:** * `-a`, `--abi-file ` - The path to a json abi file * `--contract-name ` - The name of the contract * `-b`, `--blockchain ` - Name or ID of the contract network * `-r`, `--rpc-url ` - The rpc url to use if the network id used is unsupported by our hypersync * `-s`, `--start-block ` - The start block to use on this network * `--single-contract` - If selected, prompt will not ask for additional contracts/addresses/chains * `--all-events` - If selected, prompt will not ask to confirm selection of events on a contract ## `envio init template` [Advanced] Initialize Evm indexer from an example template **Usage:** `envio init template [OPTIONS]` ###### **Options:** * `-t`, `--template