Additionally, we noticeably improved the performance of getWhere with a single _eq or _in filter by optimizing the number of round trips to the database.
Multi-storage improvements
Now it's possible to mark storages as default to automatically assign an entity to the storage. It allows you not to set @storage attribute for every entity in schema.graphql and only use it as a default overwrite.
Configure automatic conversion for column names in your database to snake_case. It only affects the underlying database, so GraphQL and handler types will preserve the original names derived from schema.graphql.
storage:
postgres:
column_name_format: snake_case
More Experimental Solana Features
We added support for HyperSync-powered instruction handlers. Reach out to us if you're interested in becoming an early tester.
Critical Bug Fixes
Fix critical bugs causing an indexer crash introduced by v3.1
Flush in-flight batch writes before computing rollback diffs #1305
Duplicate history rows from concurrent batch writes #1312
Handle missing transaction data as retryable RPC errors - previously it would incorrectly disable the source #1304
Make config path root-relative in start command #1296
Validate duplicate addresses in config at parse time #1311
We are yet to reach our full potential. And the new HyperIndex release comes with a nice improvement: it requires up to 2x fewer HyperSync queries for backfill and is 2.5x faster in many indexing cases.
Descriptions On Entities, Fields, And Relationships
Describe your entities, fields, and relationships in the schema.graphql file, so that they are exposed in the GraphQL API.
# Hash comments are ignored by the GraphQL parser and should NOT appear
# in introspection. Only string descriptions ("""...""" or "...") are exposed.
"An on-chain account participating in transfers"
type Account {
"The account's Ethereum address"
id: ID!
"All transfers sent from this account"
outgoing: [Transfer!]! @derivedFrom(field: "from")
}
Agentic mode in envio init with AI-readable prompts
We fixed envio init to better work for agents. Now it'll produce a guiding output instead of a TTY error and a need to perform a web search for Envio docs.
Now running envio init by your agent will greet you with:
Welcome to Envio Indexer! Let's set up an indexer that will become a reliable blockchain backend you trust, love, and own.
Leave the rest to your favorite agent:
1. Prompt the user for the project intent if it is missing from context (what should the indexer track and surface?).
2. Determine the chain, contract, and addresses needed to produce that result. Use web search or block-explorer tool calls when the user hasn't supplied them.
3. To continue, call:
pnpx envio init contract-import explorer \
-n ${indexer-name} \
-c ${address} \
-b ${chainId} \
--single-contract \
--all-events \
-d ${directory}
Then `cd ${directory}` and run `pnpm test`. Don't hand the project off yet — keep iterating on the indexer with a TDD loop (extend tests, run them, fix handlers) until the user's goal is met.
Other Fixes
Fix raw_events write for indexers with a tuple param in an event signature #1220
Add Etherlink, Katana, and Tempo chains to the supported chains list #1219
Fix health check when HyperSync API has chains missing from the supported chains list #1223
Add ON CLUSTER clause to ClickHouse DDL for replicated databases #1227
We had an exciting journey with new features and improvements, and now that the five months of alpha releases are over, we are back to following SemVer. There are a number of breaking changes in the V3 release, but they will allow us to accelerate HyperIndex development and bring you even more features, performance, and reliability improvements.
We have big plans for HyperIndex to make it the ultimate tool for interacting with blockchain data, and you can help us build it: create GitHub Issues, reach out to us with feedback, and support us with a GitHub Star and a kind word to a friend.
Effect API, released on May 8, served us well, and we officially removed the experimental_ prefix from createEffect.
This change comes with two new features:
The rateLimit option (required) which allows you to set explicitely how freaquently the effect might be called. Use either false to disable, or provide an object with custom calls per duration. Duration might be second, minute or a number in milliseconds.
Ability to overwrite cache option for specific call. Set context.cache = false to prevent caching the result which failed.
With this version, the Development Console now provides more performance metrics for Effect API execution, as well as loading entities from your handlers.
See a detailed time overview to identify what's slowing down your processing time. Use Batched metric to find what can be better optimized with Preload Optimization. And get useful insigts about your indexer execution in general. As a nice bonus, the Development Console now works even with TUI disabled.
Tip: Hover over question marks to get better understanding of the data.
We completely rewrote our rollback on reorg logic to make it more robust, predictable, and performant. The update introduces numerous new tests and fixes for all previously identified indexing issues:
Fixes all known indexing and rollback on reorg issues
Optimizes database writing logic, which reduces latency by dozens of milliseconds
Reduces internal tables' size for managing reorg and rollback
The Events Processed counter became reorg-resistant and can be used to verify data consistency
Enables more amazing features
Nice Additions
SubGraph migration cursor rules to init template #778
Ability to get chains readiness status from context.chains#775
Extended supported entity name length up to 63 characters
Choose between checksum (default) and lowercase addresses.
The newly added lowercase format can facilitate migration from SubGraphs and improve performance in some instances.
Faster Event Decoder for RPC source
RPC source now uses the HyperSync event decoder, which is multiple times faster than the Viem one.
Fixes
Regression of 2.29, which broke its indexing at the head Prometheus metric. #769
Race condition during Hasura configuration, which could lead to some GraphQL entities not having read permissions. #770
Internal changes
Guarantee to include all block events in a single batch. Previously, it was possible to have events belonging to a single block processed in separate batches. #753
Persist dynamically registered addresses only for processed events. #752
Made the JS logic for batch creation more performant. This is relevant for indexers processing more than 100k events per second.
These changes are preparations for a bigger rollback on reorg refactoring.
This is useful for aggregations, time-series logic, and bulk updates using raw SQL. To get started, import the onBlock function from the generated module and call it in one of your handler files.
Read a dedicated documentation page to learn more about the feature and powerful use cases it enables:
Time intervals
Preset handler
Multichain mode
Different intervals for historical and real-time sync
For inspiration, check out the Example Indexer, which uses all the latest Envio features - Block handlers with different intervals, Effect API, and Traces indexing to get all contracts deployed on Mainnet.
Other changes
Removed internal db_write_timestamp field, which was automatically set for every entity.
Improved block range selection for logs query, halving the number of needed requests for some RPC providers.
Cheaper historical sync with HyperSync data source
Optimized response from HyperSync to make queries simpler, faster, and have smaller ingress.
Much faster indexing for big factories
Added a performance optimization for indexers with a large number of addresses (>500k). It reduced the indexing time of an indexer with 2 million addresses from 4 days to 2.
SubGraph migration Cursor rules cheatsheet
Find a Cursor rule example in our repo to help you effortlessly migrate your existing SubGraph to HyperIndex!
Other improvements
Started tracking a real progress block number, which made recovery on restart more efficient
The events processed counter became more reliable on restarts
Removed cache from .gitignore for initial templates
Improved start up time
Potential breaking changes
The release initiates a significant refactoring of internal tables, focusing on a single public entry point - _meta.
To achieve this, we hide all internal tables from Hasura, as well as change the internal representation of some of them. These are: chain_metadata, event_sync_state, persisted_state, end_of_block_range_scanned_data, and dynamic_contract_registry. Please let us know if this change affected you and we'll figure out a solution
Important! Preload optimization makes your handlers run twice.
Starting from envio@2.27 all new indexers are created with preload optimization pre-configured by default.
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 for external calls, this feature delivers performance improvements of multiple orders of magnitude compared to other indexing solutions.
Set a single line in your config and make your handlers multiple times faster without changing a single line of code:
preload_handlers: true
For power users - This is like Loaders, but for all handlers by default.
To support new contributors, we have updated our CONTRIBUTING.md file with a detailed guide on navigating the HyperIndex codebase, as well as examples of what a change would look like. To make it even easier, we added .cursor rules to help develop new HyperIndex features.
Embrace Vibe-Coding
All new projects are now created with an initial .cursor rules included to help you build indexers with the agent support.
Also, feel free to create PRs with the rules suggestions to improve the experience for everyone
Fixes & Improvements
Started including an address in a handler error log #658
Expose context.set, context.unsafeDelete, and context.getOrCreate in loaders. Add context.isPreload to distinguish between first and second loader run #600
Reusable and faster Read and Write PG interactions #594
Add context.Entity.getOrCreate and context.Entity.getOrThrow API
// Before:
// let pool = await context.Pool.get(poolId);
// if (!pool) {
// pool = {
// id: poolId,
// totalValueLockedETH: 0n
// }
// context.Pool.set(pool);
// }
const pool = await context.Pool.getOrCreate({
id: poolId,
totalValueLockedETH: 0n
})
// Before:
// const pool = await context.Pool.get(poolId);
// if (!pool) {
// throw new Error(`Pool with ID ${poolId} is expected.`)
// }
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.`)
These are additional helpers for DX improvements. You can access them from both handlers and loaders.
Loaders Consistency
Loaders are a powerful feature to optimise indexer performance (Docs). The loader function is run twice: the first time in parallel for all events in the batch, and the second time immediately before the handler execution, to obtain up-to-date data. This part is unchanged, but we've done several improvements here:
If the loader function fails on the first run, the error will be silently ignored. It may happen, depending on the entity, which will only be available on the second run. We don't want to abort indexing here, because it's a valid case.
The HyperIndex test framework runs the loaders twice to match the actual indexer logic.
Clever Batch Creation for Unordered Multichain Mode
Previously, even with Unordered Multichain Mode, we chose events to be batched from all available chains in the order of events on-chain. This is not bad, but for significant indexers relying on loader optimisation, it's better to have all events for a single chain in the batch. This way, there is a higher chance of achieving deduplication or batching optimization.
This is what we've done in the version. Now, we try to create a processing batch using events from one chain as much as possible, and then rotate to another chain for another batch.
Flexible Project Structure
To make HyperIndex run correctly, we used to have several requirements for the user's HyperIndex project, which was confusing and limited flexibility:
It required pnpm-workspaces.yaml file
It required .npmrc file with shamefully hoisting dependencies
It required to have the start script in your package.json with ts-node generated/src/Index.bs.js
Now, none of this is necessary, and you're free to remove them. You can also remove ts-node from dependencies, just replace ts-node generated/src/Index.bs.js with envio start.
Breaking Changes
Unfortunately, it required changing .bs.js suffix to .res.js in our internal code. So if you, for some reason, relied on some internal fiels with .bs.js extention, you need to change them to .res.js manually. Also, for ReScript users, it's now required to configure .res.js suffix in your rescript.json file.
Aggregate Queries Configuration
Note: Aggregate Queries might have a severe hit on Database performance. We generally recommend aggregating data using entities during indexing.
Now it's possible to enable Hasura Aggregate Queries using the ENVIO_HASURA_PUBLIC_AGGREGATE environment variable. You can set environment variables on our Hosted Service on Production plans.
ENVIO_HASURA_PUBLIC_AGGREGATE=["YourEntityName"]
Other Changes
Fixed test framework failing to set an entity with Json field the second time #565
Generated and exposed types for loaderArgs and handlerArgs per event #540
We made indexing with contract registrations much faster. It got so fast in the default mode that we disabled the preRegisterDynamicContracts option.
Handle 9 more RPC provider errors
For the RPC data source, when we perform requests for event logs, different RPC providers might have different errors, with suggestions to use a different block range for retry. In the release, we added support for 9 more RPC providers to improve our RPC sync retry logic.
Fix CLI to init the indexer in a non-interactive way
Fix the --single-contract and --all-events flags for contract import from explorer #514
We started experimenting with Envio MCP, and creating an indexer using a CLI is definitely a fantastic feature.
Fix casting chain_metadata db types as nullable rather than optional. (Fixes edge-case crash when null is passed to a function expecting undefined | number) #474
You can now specify RPC providers for redundancy and failover, ensuring 100% uptime for your indexer. If HyperSync becomes unavailable, your indexer will automatically switch to an RPC provider.
To enable this, add an rpc field to your network configuration. It can be a URL or a list of RPC configuration objects:
Fix empty dynamic contract registrations bug @JonoPrest in #384
(Edge case that can presents as a range of missed events after a contractRegister function adds no new contract addresses)
You can specify field selection for individual events. This feature is useful for optimizing RPC and HyperSync calls by fetching only the data relevant to specific events, avoiding over-fetching.
Per-event field selection overrides any global field selection, ensuring precise data handling for each event.
The Envio config file can now use Environment Variables to offer more flexibility. If you want to switch between different configurations quickly, you don't need to edit the config file each time; you can just set Environment Variables at run time.
For the Hosted Service users, it's now possible to set custom Environment Variables
Then you can run ENVIO_GREETER_ADDRESSES=0x9D02A17dE4E68545d3a58D3a20BbBE0399E05c9c pnpm dev, or set the values via the .env or the indexer settings page on the Hosted Service.
Interpolation syntax
For the interpolation to be applied, the Environment Variable name should be placed in braces after a dollar sign (${VAR}).
For braced expressions, the following formats are supported:
Direct substitution
${VAR} -> value of VAR
Default value
${VAR:-default} -> value of VAR if set and non-empty, otherwise default
${VAR-default} -> value of VAR if set, otherwise default
Other Improvements
Fix to allow naming entities in schema.graphql as Schema, Belt, EntityHistory, InternalEntity, Entity, Utils#353
Fix stale action invalidation in global state manager #313
(This fixes issues with using preRegisterDynamicContracts flag AND an edge case that can occur on rollbacks)
You can now add "preRegisterDynamicContracts" flag to your event config. For all the events that have this config, there will be an end to end indexer run just for these events to run all the relevant contractRegister functions and collect all the dynamic contract addresses. The indexer then restarts with all these addresses from the start block configured for the network (rather than from the block that the contract gets registered). For a standard factory contract setup this drastically reduces indexing time since it doesn't result in many small block range queries but rather larger grouped queries.
Customize decimal precision of BigInt and BigDecimal fields
When working with large integers or high-precision decimal numbers in your application, you might need to customize the precision and scale of your BigInt and BigDecimal fields. This ensures that your database stores these numbers accurately according to your specific requirements. If you know your numbers will not be too big, you can also optimize the database by not over-allocating on the precision.
Index the whole chain by event signature without specifying exact contract addresses.
Here's an example of indexing all ERC20 Transfer events. No need to change the config file. Simply add the wildcard: true option to the second argument in your handler:
Both Wildcard Indexing and Event Filters are currently only supported with HyperSync. We are working on adding support for RPC mode in the following versions.
Implemented
Fuel Merge
The fuel ecosystem indexer is integrated into the main repository code. This was a huge refactoring which came with a big list of benefits and a big list of opportunities.
If you use envio@x.x.x-fuel version, you can switch to envio@2.3.0 and get:
Now Fuel and Evm reuse the same code. This gives V2 API, much better test coverage, and many improvements/fixes compared to envio@x.x.x-fuel
Support for raw_events configuration
Support for Wildcard Indexing
Public repository
Active development and maintenance
Talking about opportunities, this unblocks:
Adding support for Mint/Burn/Call/TransferIn/TransferOut events
Indexer became more modular and library-like, simplifying the integration of new ecosystems in the future
New Timestamp scalar type available in schema.graphql. It's represented as an instance of Date, making your handlers and queries more flexible, fast, and explicit.
Fixes & Polishing
Update Viem version to the latest V2 to fix type conflicts with the Viem version on user-side #159
Be careful if you're using Viem V1 in your indexer handlers. This change might cause unexpected behavior.
Fix incorrect import of a type, which caused tsc --build to fail. Also, prevent this from happening in the future by adding a TypeScript type check to CI pipeline #165
Fix codegen with HyperSync/RPC endpoint in the config having a trailing slash #164
Update the supported chains list by adding new chains and removing deprecated ones #163
Replacing lists with arrays in v2.1.0 incurred a significant performance regression due to using Array.slice method. This removes the use of Array.slice and improves performance.
Refactor array slicing code to reversed arrays with pop #157
Allow custom names for events #132
This is useful when you want to have a different name on the Envio side; or index events with the same name but different signatures: