Skip to content

Resolve event filters per-chain at registration time - #1396

Merged
DZakh merged 4 commits into
mainfrom
claude/static-event-filter-resolution-m65ojr
Jul 9, 2026
Merged

Resolve event filters per-chain at registration time#1396
DZakh merged 4 commits into
mainfrom
claude/static-event-filter-resolution-m65ojr

Conversation

@DZakh

@DZakh DZakh commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

Refactors event filter registration to resolve where callbacks per-chain immediately when handlers are registered, rather than deferring resolution until chain startup. This enables duplicate registrations to be detected at the user's call site with proper error context, and simplifies the runtime architecture by eliminating deferred filter parsing.

Key Changes

  • HandlerRegister: Restructured registration lifecycle to incrementally build per-chain registrations as handlers are registered:

    • pendingRegistrationspendingChainRegistrations (per-chain dict of event registrations + onBlock handlers)
    • New syncOnEventRegistrations resolves where callbacks for all configured chains immediately and validates against existing registrations
    • buildOnEventRegistrationWith extracted to accept pre-resolved where and other options, decoupling filter building from handler lookup
    • startRegistration now takes Config.t instead of Ecosystem.t to access chain definitions
    • Removed buildOnEventRegistrations (deferred per-chain resolution) — resolution now happens incrementally
  • LogSelection: Renamed and refactored filter parsing:

    • parseEventFiltersOrThrowparseWhereOrThrow (clearer intent)
    • Returns parsedWhere with resolvedWhere (topic selections + startBlock) instead of parsedEventFilters
    • Removed getEventFiltersOrThrow callback from registration — filters are now fully resolved at registration time
    • materializeTopicSelections added to expand ContractAddresses markers to concrete topic values when building queries
  • Internal: Updated event registration types:

    • onEventRegistration now carries resolvedWhere: resolvedWhere (pre-resolved topic selections + startBlock)
    • New topicFilter type distinguishes static Values from ContractAddresses markers
    • Removed getEventFiltersOrThrow callback field
  • EventConfigBuilder: Address values in topic filters are now lowercased before encoding to match lowercase hex topics from sources (handles mixed-case checksummed input)

  • RpcSource / HyperSyncSource: Simplified to work with pre-resolved filters:

    • Removed dynamic filter resolution logic
    • getSelectionConfig no longer takes chain parameter
    • Directly materialize topic selections from resolvedWhere
  • Tests: Updated to reflect new registration model:

    • New HandlerRegisterLifecycle_test.res covers per-chain immediate resolution and duplicate detection
    • EventFilters_test.res now accesses resolvedWhere.topicSelections directly
    • Mock helpers updated to use resolvedWhere structure

Notable Implementation Details

  • Duplicate registrations for the same event are now compared on the resolved where structure (topic selections + startBlock), not the callback reference. Two distinct callbacks that resolve to identical filters compose instead of throwing.
  • Invalid where configurations (e.g., referencing non-existent indexed parameters) now throw at the user's registration call site with proper stack context, rather than during chain startup.
  • preRegistered callbacks (registered before startRegistration) are replayed through the same syncOnEventRegistrations code path, ensuring consistent validation.
  • The ContractAddresses marker in resolved filters allows sources to defer address expansion until query time, supporting dynamic address registration.

https://claude.ai/code/session_01Rr49Jzbmp6CSDnC75rBxkY

Summary by CodeRabbit

  • New Features

    • Improved where-based event/block filtering with per-chain resolved topic selection.
    • Address-based filters now normalize mixed-case inputs consistently.
  • Bug Fixes

    • Duplicate event handler registrations now dedupe based on the resolved where criteria.
    • EVM and Fuel block filter validation now rejects unknown block fields.
    • Block handler ranges that exceed a chain’s bounds are no longer rejected at registration.
  • Breaking Changes

    • Handler registration APIs updated: startRegistration now takes full config; registerOnBlock now uses a where predicate plus a chain resolver.

claude added 2 commits July 9, 2026 12:33
Replace the lazy Static/Dynamic eventFilters closures with a structured
resolvedWhere resolved once per chain when the handler registers:

- Internal: add topicFilter (Values / ContractAddresses marker),
  resolvedTopicSelection and resolvedWhere; drop eventFilters and the
  getEventFiltersOrThrow field on evmOnEventRegistration.
- LogSelection: parseWhereOrThrow invokes the where callback exactly once
  per chain with the real chainId and the addresses Proxy sentinel; the
  sentinel becomes a ContractAddresses marker parsed into resolvedWhere.
  Address-typed topic values are lowercased so mixed-case input matches.
  Markers are expanded to the partition's addresses via
  materializeTopicSelections when source queries are built.
- HandlerRegister: startRegistration takes the config; registrations are
  resolved per chain incrementally on setHandler/setContractRegister, so
  invalid where throws at the user's registration call site, a false
  where drops the chain, and duplicate registrations are compared on the
  resolved structure instead of the raw where reference.
- registerOnBlock validates chainId presence and start/end block bounds
  at registration (moved out of ChainState).
- onBlock registration defers its per-chain loop until the registration's
  config is known.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rr49Jzbmp6CSDnC75rBxkY
…ter fields

- registerOnBlock now owns the full onBlock registration: where predicate
  normalization, per-chain evaluation, range parsing (blockRange schema and
  extractRange moved from Main), block bounds validation, and the zero-match
  warning. Main.onBlockFn only parses the raw options and delegates, passing
  a chains-object builder so the loop runs against the registration's config.
  Drops the withConfig wrapper added earlier.
- Make the inner block object of onEventBlockFilterSchema strict for EVM and
  Fuel so unknown fields in a where block filter (typos, or block.number on
  Fuel) throw a user-friendly error instead of being silently ignored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rr49Jzbmp6CSDnC75rBxkY
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 78df6c6a-d09b-4be3-9276-98b7859c2443

📥 Commits

Reviewing files that changed from the base of the PR and between 8549f44 and 1a430e1.

📒 Files selected for processing (6)
  • packages/cli/src/config_parsing/entity_parsing.rs
  • packages/cli/src/config_parsing/system_config.rs
  • packages/cli/src/config_parsing/validation.rs
  • packages/cli/src/executor/init.rs
  • packages/cli/src/hbs_templating/hbs_dir_generator.rs
  • packages/cli/src/type_schema.rs
✅ Files skipped from review due to trivial changes (5)
  • packages/cli/src/hbs_templating/hbs_dir_generator.rs
  • packages/cli/src/config_parsing/system_config.rs
  • packages/cli/src/config_parsing/validation.rs
  • packages/cli/src/executor/init.rs
  • packages/cli/src/config_parsing/entity_parsing.rs

📝 Walkthrough

Walkthrough

This PR replaces function-based event filter probing with resolved where data, rewires handler and block registration around per-chain pending state, updates log-selection consumers to materialize topic selections at query time, and applies small CLI formatting changes in Rust error paths.

Changes

Where resolution and registration flow

Layer / File(s) Summary
Resolved where parsing
packages/envio/src/Internal.res, packages/envio/src/LogSelection.res
New resolved where types are added, and LogSelection now parses where inputs into resolved topic selections, address markers, and per-registration start blocks.
Event registration wiring
packages/envio/src/EventConfigBuilder.res
buildEvmOnEventRegistration now parses where/chainId, stores resolvedWhere, and lowercases address topics before encoding.
Incremental handler registration
packages/envio/src/EnvioGlobal.res, packages/envio/src/HandlerRegister.res, packages/envio/src/HandlerLoader.res, packages/envio/src/HandlerRegister.resi
Shared global state backs per-chain pending registrations, duplicate handling uses resolved where comparisons, and startRegistration now takes Config.t.
Block registration and schema changes
packages/envio/src/HandlerRegister.res, packages/envio/src/HandlerRegister.resi, packages/envio/src/Main.res, packages/envio/src/ChainState.res, packages/envio/src/sources/Evm.res, packages/envio/src/sources/Fuel.res
Block registration now evaluates a per-chain where predicate, validates ranges with a shared block schema, and the surrounding block-filter checks and onBlock wiring are simplified.
Selection consumers
packages/envio/src/sources/HyperSyncSource.res, packages/envio/src/sources/RpcSource.res
getSelectionConfig drops the ~chain argument and builds log selections from resolvedWhere.topicSelections, materializing topic filters at query time.
Tests, mocks, and fixtures
scenarios/test_codegen/test/*, scenarios/test_codegen/src/handlers/EventHandlers.ts, packages/envio/src/SimulateItems.res
Test suites, mocks, and handler fixtures are updated to the new where/chainId/resolvedWhere shape, including lifecycle coverage and selection assertions.

Incremental CLI formatting cleanup

Layer / File(s) Summary
Formatting updates
packages/cli/src/config_parsing/entity_parsing.rs, packages/cli/src/config_parsing/system_config.rs, packages/cli/src/config_parsing/validation.rs, packages/cli/src/executor/init.rs, packages/cli/src/hbs_templating/hbs_dir_generator.rs, packages/cli/src/type_schema.rs
Error-message formatting calls remove unnecessary borrows and adjust string arguments across CLI parsing, initialization, templating, validation, and type schema rendering code.

Estimated code review effort: 4 (Complex) | ~75 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: event filters are resolved per chain during registration time.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

Move all cross-module-instance state into a single version-gated
globalThis.__envioGlobal record owned by the new EnvioGlobal module:
the HandlerRegister registration slots (eventRegistrations,
activeRegistration, preRegistered), Main's indexerState and persistence
refs, and RollbackCommit's callbacks. Slots are opaque in EnvioGlobal so
it stays at the bottom of the dependency graph; each owner casts its
slot once. Mutable fields replace the ref cells.

This closes the duplicate-envio-instance hole for the indexer.chains
getters and rollback-commit callbacks: previously only the registration
registry was shared across instances, so a duplicated envio silently
served static config values from never-assigned module refs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rr49Jzbmp6CSDnC75rBxkY

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
scenarios/test_codegen/test/helpers/MockIndexer.res (1)

997-1016: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Default topic1 selection can disagree with the computed dependsOnAddresses field.

dependsOnAddresses (the record field, Line 993, unchanged) defaults to !isWildcard when the caller omits ~dependsOnAddresses. But the new topic1 default here checks the raw ~dependsOnAddresses argument directly, so a non-wildcard registration built without explicitly passing ~dependsOnAddresses=true ends up with dependsOnAddresses: true on the record while topicSelections[0].topic1 stays Values([]) instead of ContractAddresses. Any test relying on the implicit default to exercise address-based topic filtering would silently miss it.

🔧 Suggested fix
-            topic1: switch dependsOnAddresses {
-            | Some(true) => ContractAddresses({contractName: contractName})
-            | _ => Values([])
-            },
+            topic1: switch dependsOnAddresses->Option.getOr(!isWildcard) {
+            | true => ContractAddresses({contractName: contractName})
+            | false => Values([])
+            },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scenarios/test_codegen/test/helpers/MockIndexer.res` around lines 997 - 1016,
The default topic1 selection in MockIndexer.res is using the raw
dependsOnAddresses option instead of the computed record field, which can make
it diverge from the registration’s effective setting. Update the
resolvedWhere.topicSelections default logic to base the topic1 branch on the
same derived dependsOnAddresses value used for the record (the one tied to
isWildcard), so implicit non-wildcard registrations consistently select
ContractAddresses. Use the existing dependsOnAddresses handling in the
surrounding registration setup to keep the default topic filtering aligned.
scenarios/test_codegen/test/EventFilters_test.res (1)

8-14: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Confusingly similar names: local getEvmEventConfig alias vs. module-level MockConfig.getEvmEventConfig.

Line 10 binds the local name getEvmEventConfig to a partial application of MockConfig.getEvmOnEventRegistration, while line 188 calls a distinctly-named module function MockConfig.getEvmEventConfig directly. Both are legitimately different functions (registered registration vs. static config lookup), but the near-identical naming is easy to misread during future edits.

Also applies to: 188-193

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scenarios/test_codegen/test/EventFilters_test.res` around lines 8 - 14, The
local alias `getEvmEventConfig` is too similar to the module-level
`MockConfig.getEvmEventConfig`, which makes the test hard to read and easy to
edit incorrectly. Rename the local binding in `EventFilters_test.res` to
something that clearly reflects `MockConfig.getEvmOnEventRegistration`, and
update the nearby usage so the partial application and the direct
`MockConfig.getEvmEventConfig` lookup are visually distinct.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/envio/src/HandlerRegister.res`:
- Around line 673-734: The repeated-registration fallback in HandlerRegister.res
rebuilds event registrations from the persistent dict, but onBlock handlers are
still lost when pending is None. Add the same replay/rebuild path for
onBlockRegistrations, either by storing them persistently alongside event
handlers or by reconstructing them during the fallback before updating
registrationsByChainId. Make sure the logic around pending,
registrationsByChainId->Dict.set, and the onBlockRegistrations field preserves
block handlers across restart cycles just like the event path does.

In `@packages/envio/src/LogSelection.res`:
- Around line 66-70: `materializeTopicFilter` is using raw `Address.t` values
for `ContractAddresses`, which can produce topic filters that differ from the
lowercase-normalized path used by `EventConfigBuilder.getTopicEncoder`. Update
the `materializeTopicFilter` branch in `LogSelection.res` to normalize addresses
before calling `TopicFilter.fromAddress`, or route it through the same
address-encoding logic used by `getTopicEncoder`, so `ContractAddresses`
materialize consistently with the rest of the topic filter encoding.

---

Nitpick comments:
In `@scenarios/test_codegen/test/EventFilters_test.res`:
- Around line 8-14: The local alias `getEvmEventConfig` is too similar to the
module-level `MockConfig.getEvmEventConfig`, which makes the test hard to read
and easy to edit incorrectly. Rename the local binding in
`EventFilters_test.res` to something that clearly reflects
`MockConfig.getEvmOnEventRegistration`, and update the nearby usage so the
partial application and the direct `MockConfig.getEvmEventConfig` lookup are
visually distinct.

In `@scenarios/test_codegen/test/helpers/MockIndexer.res`:
- Around line 997-1016: The default topic1 selection in MockIndexer.res is using
the raw dependsOnAddresses option instead of the computed record field, which
can make it diverge from the registration’s effective setting. Update the
resolvedWhere.topicSelections default logic to base the topic1 branch on the
same derived dependsOnAddresses value used for the record (the one tied to
isWildcard), so implicit non-wildcard registrations consistently select
ContractAddresses. Use the existing dependsOnAddresses handling in the
surrounding registration setup to keep the default topic filtering aligned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e14454bb-65ac-449d-a957-af5b000673be

📥 Commits

Reviewing files that changed from the base of the PR and between 23e2ce4 and bea2b8f.

📒 Files selected for processing (25)
  • packages/envio/src/ChainState.res
  • packages/envio/src/EventConfigBuilder.res
  • packages/envio/src/HandlerLoader.res
  • packages/envio/src/HandlerRegister.res
  • packages/envio/src/HandlerRegister.resi
  • packages/envio/src/Internal.res
  • packages/envio/src/LogSelection.res
  • packages/envio/src/Main.res
  • packages/envio/src/SimulateItems.res
  • packages/envio/src/sources/Evm.res
  • packages/envio/src/sources/Fuel.res
  • packages/envio/src/sources/HyperSyncSource.res
  • packages/envio/src/sources/RpcSource.res
  • scenarios/test_codegen/src/handlers/EventHandlers.ts
  • scenarios/test_codegen/test/ClientAddressFilter_test.res
  • scenarios/test_codegen/test/EventBlockFilter_test.res
  • scenarios/test_codegen/test/EventFilters_test.res
  • scenarios/test_codegen/test/HandlerRegisterLifecycle_test.res
  • scenarios/test_codegen/test/HyperSyncSource_test.res
  • scenarios/test_codegen/test/OnBlockSchema_test.res
  • scenarios/test_codegen/test/RpcSource_test.res
  • scenarios/test_codegen/test/SourceBlockHashes_test.res
  • scenarios/test_codegen/test/__mocks__/MockConfig.res
  • scenarios/test_codegen/test/helpers/MockIndexer.res
  • scenarios/test_codegen/test/lib_tests/SameSignatureEventDecode_test.res
💤 Files with no reviewable changes (1)
  • packages/envio/src/ChainState.res

Comment on lines +673 to +734
let registration = switch registration {
| Some(_) as registration => registration
| None =>
// No entry in the incremental store, but the persistent dict
// may still hold a handler: handler modules are import-cached,
// so a repeated registration cycle in the same process (tests
// restarting the indexer) never re-runs the `indexer.onEvent`
// calls. Rebuild from the dict in that case. Events without a
// handler/contractRegister aren't fetched or dispatched
// (unless raw events are enabled).
if hasRegistration(~contractName, ~eventName) || config.enableRawEvents {
Some(
buildOnEventRegistration(
~config,
~chainId=chainConfig.id,
~eventConfig,
~startBlock=?contract.startBlock,
),
)
} else {
let eventNames = switch notRegisteredEventsByContract->Utils.Dict.dangerouslyGetNonOption(
contractName,
) {
| Some(set) => set
| None => {
let set = Utils.Set.make()
notRegisteredEventsByContract->Dict.set(contractName, set)
set
}
}
eventNames->Utils.Set.add(eventName)->ignore
None
}
}

switch registration {
| Some(registration) =>
// A `where` that resolved to no topic selections (`false` for
// this chain) drops the chain's registration entirely — the
// event should never be fetched here.
let isDroppedByWhere =
config.ecosystem.name === Evm &&
(registration->getResolvedWhere).topicSelections->Utils.Array.isEmpty
if !isDroppedByWhere {
onEventRegistrations->Array.push(registration)
}
| None => ()
}
},
)
})

registrationsByChainId->Dict.set(
key,
{
onEventRegistrations,
onBlockRegistrations: switch pending {
| Some(pending) => pending.onBlockRegistrations
| None => []
},
},
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for repeated startRegistration/finishRegistration cycles and onBlock persistence
rg -nP -C3 '\b(startRegistration|finishRegistration)\b' packages/envio/src scenarios/test_codegen/test
# Any persistent store for onBlock registrations outside per-chain pending?
rg -nP -C3 'onBlockRegistration' packages/envio/src

Repository: enviodev/hyperindex

Length of output: 26793


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the registration lifecycle and any repeated startup paths.
sed -n '1,220p' packages/envio/src/HandlerLoader.res
printf '\n---\n'
sed -n '1,220p' scenarios/test_codegen/test/helpers/MockIndexer.res
printf '\n---\n'
sed -n '1,220p' scenarios/test_codegen/test/HandlerRegisterLifecycle_test.res
printf '\n---\n'
# Look for any direct repeated calls to registerAllHandlers/startRegistration/finishRegistration.
rg -n --no-heading '\b(registerAllHandlers|startRegistration|finishRegistration)\b' packages/envio/src scenarios/test_codegen/test

Repository: enviodev/hyperindex

Length of output: 18990


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '123,170p' packages/envio/src/HandlerRegister.res
printf '\n---\n'
sed -n '626,760p' packages/envio/src/HandlerRegister.res
printf '\n---\n'
sed -n '280,340p' scenarios/test_codegen/test/helpers/MockIndexer.res
printf '\n---\n'
sed -n '590,620p' packages/envio/src/Main.res

Repository: enviodev/hyperindex

Length of output: 10414


onBlock handlers need the same replay path as events
On repeated registration cycles in the same process, this fallback rebuilds only event registrations from the persistent dict. onBlockRegistrations has no persistent backing, so when pending is None it drops to [] and onBlock handlers disappear. Add a rebuild path or persistent store for onBlock registrations too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/envio/src/HandlerRegister.res` around lines 673 - 734, The
repeated-registration fallback in HandlerRegister.res rebuilds event
registrations from the persistent dict, but onBlock handlers are still lost when
pending is None. Add the same replay/rebuild path for onBlockRegistrations,
either by storing them persistently alongside event handlers or by
reconstructing them during the fallback before updating registrationsByChainId.
Make sure the logic around pending, registrationsByChainId->Dict.set, and the
onBlockRegistrations field preserves block handlers across restart cycles just
like the event path does.

Comment on lines +66 to +70
let materializeTopicFilter = (filter: Internal.topicFilter, ~addresses: array<Address.t>) =>
switch filter {
| Values(values) => values
| ContractAddresses(_) => addresses->Array.map(TopicFilter.fromAddress)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect TopicFilter.fromAddress and Address normalization behavior
fd -e res -e resi 'TopicFilter' --exec sh -c 'echo "== $1 =="; cat -n "$1"' _ {}
echo "===== Address module ====="
fd -e res -e resi '^Address' --exec sh -c 'echo "== $1 =="; sed -n "1,80p" "$1"' _ {}

Repository: enviodev/hyperindex

Length of output: 4856


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the call sites and surrounding logic for materializeTopicFilter
fd -e res 'LogSelection.res' --exec sh -c 'echo "== $1 =="; cat -n "$1"' _ {}

echo "===== TopicFilter / Address use sites ====="
rg -n "materializeTopicFilter|fromAddressLowercaseOrThrow|fromAddress\\b|getTopicEncoder|ContractAddresses|Values\\(" packages/envio/src -A4 -B4

echo "===== EventConfigBuilder around topic encoder ====="
fd -e res 'EventConfigBuilder.res' --exec sh -c 'echo "== $1 =="; rg -n "getTopicEncoder|lowercase|address" "$1" -A8 -B8' _ {}

Repository: enviodev/hyperindex

Length of output: 44107


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Trace how indexingAddresses are populated and whether they are normalized
rg -n "normalizeUserAddress|fromAddressLowercaseOrThrow|fromAddressOrThrow|IndexingAddresses|indexingAddresses" packages/envio/src -A4 -B4

echo "===== IndexingAddresses.res ====="
fd -e res 'IndexingAddresses.res' --exec sh -c 'echo "== $1 =="; cat -n "$1"' _ {}

echo "===== Config.res normalization sites ====="
fd -e res 'Config.res' --exec sh -c 'echo "== $1 =="; rg -n "normalizeUserAddress|lowercaseAddresses|fromAddressLowercaseOrThrow|fromAddressOrThrow" "$1" -A6 -B6' _ {}

Repository: enviodev/hyperindex

Length of output: 50375


Normalize ContractAddresses before encoding

materializeTopicFilter passes Address.t straight into TopicFilter.fromAddress, but EventConfigBuilder.getTopicEncoder lowercases address inputs first. With checksum-cased addresses, chain.<Contract>.addresses can materialize to topics that won’t match the lowercase topic filters used elsewhere. Normalize here too, or reuse the same address encoder path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/envio/src/LogSelection.res` around lines 66 - 70,
`materializeTopicFilter` is using raw `Address.t` values for
`ContractAddresses`, which can produce topic filters that differ from the
lowercase-normalized path used by `EventConfigBuilder.getTopicEncoder`. Update
the `materializeTopicFilter` branch in `LogSelection.res` to normalize addresses
before calling `TopicFilter.fromAddress`, or route it through the same
address-encoding logic used by `getTopicEncoder`, so `ContractAddresses`
materialize consistently with the rest of the topic filter encoding.

Unrelated to this branch's changes: main's Clippy step passed as of our
fork commit, but the CI toolchain (dtolnay/rust-toolchain@stable,
unpinned) has since picked up a newer stable Rust whose clippy enables
these lints more aggressively, breaking cargo-test for any PR right now.
Mechanical fixes only — drop redundant & before format!/anyhow! args,
and a redundant .to_string() call on an already-Display type.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rr49Jzbmp6CSDnC75rBxkY
@DZakh
DZakh merged commit 00bb717 into main Jul 9, 2026
8 checks passed
@DZakh
DZakh deleted the claude/static-event-filter-resolution-m65ojr branch July 9, 2026 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants