Conversation
🦋 Changeset detectedLatest commit: 89f6bb0 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
WalkthroughAdds a new Substreams Rust/WASM package (codegen, proto, handlers, manifest, Docker), integrates substreams DB schema and drizzle config, adds a Firehose line-processor and test orchestration changes, updates CI/workflows, workspace/NX targets, ignores, editor recommendations, and four changeset files. Changes
Sequence DiagramsequenceDiagram
participant Block as Blockchain Block
participant MapAccounts as map_exa_accounts
participant StoreAccounts as store_exa_accounts
participant MapPlugins as map_exa_plugins
participant DbOut as db_out
participant Database as PostgreSQL
Block->>MapAccounts: deliver block + params (factories)
MapAccounts->>MapAccounts: parse events -> Accounts
MapAccounts-->>StoreAccounts: emit Accounts
StoreAccounts->>StoreAccounts: persist flags for accounts
Block->>MapPlugins: deliver block
StoreAccounts-->>MapPlugins: provide account lookup
MapPlugins->>MapPlugins: scan PluginInstalled, filter by new/store -> Plugins
MapPlugins-->>DbOut: emit Plugins
DbOut->>DbOut: build DatabaseChanges (upsert exa_plugins, insert block)
DbOut->>Database: apply DatabaseChanges
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR SummaryImplements on-chain indexing of EXA accounts and plugins via Substreams and persists results for server use.
Written by Cursor Bugbot for commit 89f6bb0. This will update automatically on new commits. Configure here. |
Summary of ChangesHello @cruzdanilo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request marks the initial implementation of substreams, laying the groundwork for a robust data processing pipeline. It establishes the core Rust project, defines the logic for extracting and storing critical blockchain data related to Exa accounts and plugins, and integrates the necessary database schemas and development tools. This foundational work is a prerequisite for subsequent deployment and full integration into the server, as outlined in the linked issue. Highlights
Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces the initial implementation of substreams to index on-chain data. It adds a new Rust-based substreams package, along with the necessary configuration for building, testing, and deploying it as a Docker service. The changes also include updates to the database schema, test setup, and CI/CD configurations to integrate this new service. My review focuses on the new substreams implementation and its integration. I've found a critical logic bug in the Rust code and several medium-severity issues related to maintainability, reproducibility, and potential correctness in the supporting scripts and Dockerfile. Overall, this is a great first step, and addressing these points will improve the robustness and maintainability of the new service.
There was a problem hiding this comment.
This PR is being reviewed by Cursor Bugbot
Details
You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.
To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.
There was a problem hiding this comment.
Actionable comments posted: 13
🤖 Fix all issues with AI agents
In @.github/workflows/substreams-build.yaml:
- Line 2: Convert compact inline mappings to expanded multi-line YAML for
readability and to satisfy yamllint: replace the single-line "on: { push: {
tags: [\"@exactly/substreams@*\"] }, workflow_dispatch: {} }" with an expanded
mapping using "on:" as a parent key and nested "push:" with "tags:" array and a
separate "workflow_dispatch:" mapping; apply the same transformation to other
compact mappings in this file (the occurrences using { ... } syntax such as the
ones around push/tags and workflow_dispatch) so each mapping is written in
standard block form.
In @.github/workflows/test.yaml:
- Around line 37-41: Update the workflow run step that downloads binaries so it
(1) adds $HOME/.local/bin to the GitHub Actions PATH by appending
"$HOME/.local/bin" to $GITHUB_PATH immediately after installation, (2) enables
fail-fast behavior (e.g., set -e) so download/extract errors fail the job, (3)
replaces the "latest" GitHub API query with pinned release tags or specific
asset URLs for the three repos (firehose-ethereum, substreams,
substreams-sink-sql) to ensure reproducible builds, and (4) split the long
for-loop into explicit download/extract commands per repo to simplify debugging
and error reporting; update the run step that currently creates and populates
$HOME/.local/bin and uses curl/jq/tar accordingly to implement these changes.
In @contracts/test/mocks/BobExecute.s.sol:
- Around line 61-71: Add Natspec comments to the BatchDeploy contract and its
constructor: include a @notice describing the contract's purpose
(batch-deploying ExaAccounts for tests), a @dev explaining the internal behavior
(creates `count` accounts by populating owners array and calling
`factory.createAccount`), and @param tags for the constructor parameters
`factory` (ExaAccountFactory used to create accounts) and `count` (number of
accounts to create). Place the comments immediately above the contract
declaration and the constructor signature using @notice, @dev, and @param tags
referencing `factory` and `count`.
In @server/database/schema.ts:
- Around line 70-100: The new tables cursors, substreamsHistory, blocks, and
exaPlugins are missing explicit Drizzle relation declarations; add relations for
each table in server/database/schema.ts (like the existing
credentials/cards/transactions pattern) so Drizzle can generate typed query
results—locate the table symbols cursors, substreamsHistory, blocks, and
exaPlugins and add a relations callback/object that declares their foreign keys
and navigation properties (e.g., which column references which parent table and
any one-to-many or one-to-one mappings), keeping the existing primaryKey
callback for exaPlugins.
In @substreams/Dockerfile:
- Line 13: Replace the non-deterministic "releases/latest" fetch in the
Dockerfile RUN line with a pinned release tag (e.g., v1.10.0); specifically,
update the curl URL used in the RUN line to query the GitHub release for that
tag (replace "releases/latest" with "releases/tag/v1.10.0" or use the
releases/download/v1.10.0 endpoint) so the asset selection and tar extraction
always pull the exact desired substreams binary version.
- Around line 1-33: Add a HEALTHCHECK instruction to the Dockerfile to enable
container monitoring: insert a HEALTHCHECK line before ENTRYPOINT that
periodically probes the running process or endpoint (for example, check the
substreams process or an HTTP/status endpoint if available) and set sensible
intervals, timeouts, retries and a failure action; reference the existing
ENTRYPOINT and CMD symbols (ENTRYPOINT ["dumb-init", "/app/substreams-sink-sql",
"run"] and CMD ["postgres://...","substreams.yaml"]) to place the HEALTHCHECK
just above ENTRYPOINT and ensure the check uses the same command/port that
indicates the app is healthy.
- Line 33: The CMD entry currently hardcodes
"postgres://host.docker.internal:5432/postgres?schemaName=substreams&sslmode=disable"
which is not portable; change the Dockerfile CMD so the Postgres connection
string is configurable (e.g., read from an environment variable like
POSTGRES_DSN or use a clear local-dev placeholder such as
postgres://localhost:5432/... ), and keep the second argument "substreams.yaml"
as-is; update the CMD invocation (the CMD array that sets the two arguments) to
reference the env var or the new portable default so the container works outside
Docker Desktop.
In @substreams/package.json:
- Around line 43-49: The test target's dependsOn references a non-existent
"test:proto" task while the actual script/target is named "test:protolint";
update the dependsOn array for the "test" target to reference "test:protolint"
(or rename the script to "test:proto" if you prefer that name) so the dependency
matches the defined target name and Nx can resolve it.
In @substreams/proto/exa.proto:
- Line 3: The protobuf package declaration "package exa;" should be placed in a
file that matches its package directory; move the proto file so the package and
directory align (e.g., rename/move exa.proto into a directory named exa and use
exa/exa.proto) and then update any build/config references that point to the old
location (for example entries in substreams.yaml and related build scripts) so
tooling like buf sees package and path consistency.
In @substreams/rust-toolchain.toml:
- Around line 1-4: The toolchain is set to unpinned "nightly" while Cargo.toml
claims an invalid or future edition; either change the [toolchain] channel to
"stable" if Cargo.toml uses a valid edition like edition = "2021", or if you
truly require edition = "2024" keep nightly but pin it to a reproducible
snapshot (e.g., channel = "nightly-YYYY-MM-DD"); update rust-toolchain.toml's
channel accordingly and ensure Cargo.toml's edition field matches a
valid/intentional value so builds are reproducible.
In @substreams/src/lib.rs:
- Around line 54-71: The map_exa_plugins function inverts dedup logic for
new.accounts: the branch uses !seen.insert(address.to_vec()) which rejects the
first occurrence and accepts duplicates; change this so the function keeps the
first plugin from a new account and filters later duplicates by removing the
negation (use seen.insert(address.to_vec()) to allow first insertion and return
false for duplicates). Keep the existing behavior for existing accounts using
accounts.get_last(Hex(&address).to_string()) == 1 and ensure the overall closure
still returns Plugin { address: event.plugin, account: address.to_vec(),
ordinal: log.ordinal() } when appropriate.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsubstreams/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (35)
.changeset/breezy-planes-repeat.md.changeset/rare-readers-tell.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.dockerignore.github/matcher.json.github/workflows/server-build.yaml.github/workflows/substreams-build.yaml.github/workflows/test.yaml.vscode/extensions.jsoncommon/package.jsoncontracts/test/mocks/BobExecute.s.solcspell.jsondocs/package.jsonpnpm-workspace.yamlserver/Dockerfileserver/database/schema.tsserver/drizzle.config.tsserver/package.jsonserver/script/firehose.tsserver/test/anvil.tsserver/test/database.tsserver/test/e2e.tssubstreams/.cargo/config.tomlsubstreams/.gitignoresubstreams/Cargo.tomlsubstreams/Dockerfilesubstreams/buf.gen.yamlsubstreams/build.rssubstreams/package.jsonsubstreams/proto/exa.protosubstreams/rust-toolchain.tomlsubstreams/rustfmt.tomlsubstreams/src/lib.rssubstreams/substreams.yaml
🧰 Additional context used
📓 Path-based instructions (11)
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/test/anvil.tsserver/test/e2e.tsserver/test/database.tsserver/drizzle.config.tsserver/database/schema.tsserver/script/firehose.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/test/anvil.tsserver/test/e2e.tsserver/test/database.tsserver/drizzle.config.tscontracts/test/mocks/BobExecute.s.solserver/database/schema.tsserver/script/firehose.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/test/anvil.tsserver/test/e2e.tsserver/test/database.tsserver/drizzle.config.tsserver/database/schema.tsserver/script/firehose.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/test/anvil.tsserver/test/e2e.tsserver/test/database.tsserver/drizzle.config.tsserver/database/schema.tsserver/script/firehose.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/test/anvil.tsserver/test/e2e.tsserver/test/database.tsserver/drizzle.config.tsserver/database/schema.tsserver/script/firehose.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/test/anvil.tsserver/test/e2e.tsserver/test/database.tsserver/drizzle.config.tsserver/database/schema.tsserver/script/firehose.ts
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/violet-poems-heal.md.changeset/breezy-planes-repeat.md.changeset/rare-readers-tell.md.changeset/red-toys-pump.md
**/+(app|src)/**
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use kebab-case for directory names and public Expo router routes
Files:
substreams/src/lib.rs
contracts/**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/contracts.mdc)
contracts/**/*.sol: Import order must be sorted alphabetically and grouped by type (openzeppelin, solady, internal, etc.), enforced by forge fmt
All functions and state variables must have explicit visibility modifiers (public, private, internal, external)
Use custom errors instead of require() statements with string messages; require() with messages is forbidden
console.log is forbidden in contract code
All external and public functions that modify state must be protected by a reentrancy guard, unless explicitly proven unnecessary
Use modifiers like onlyOwner or function-level checks for privileged functions; default to strictest access level (private > internal > external > public)
Use named parameters in mappings for clarity (e.g., mapping(address user => uint256 balance))
All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Follow standard Solidity import order: external libraries (openzeppelin, solady) first, then project-internal contracts
All public and external functions and state variables must have complete natspec comments (@notice, @dev, @param, @return)
Use custom errors instead of require statements with string messages for better gas efficiency and error information
Internal and private variables should be prefixed with an underscore (_); function arguments should not be prefixed
Use constant keyword for true constants; use immutable for configurable values like contract addresses
Minimize writes to storage; read data into memory or calldata whenever possible for gas optimization
Use smallest integer size that is safe for the variable (e.g., uint32 for timestamps) and be aware of struct packing for gas efficiency
Prefer using the solady library over openzeppelin where possible, as it is highly optimized for gas
Correctly label functions as view or pure when they do not modify state
contracts/**/*.sol: Smart contracts: use custom errorserror MyError();instead of s...
Files:
contracts/test/mocks/BobExecute.s.sol
**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
**/*.sol: Use camelCase for Solidity variables and functions
Use PascalCase for Solidity contracts and structs
Files:
contracts/test/mocks/BobExecute.s.sol
server/database/schema.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/database/schema.ts: Database schema must be defined inserver/database/schema.tsas the single source of truth for database structure
Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Files:
server/database/schema.ts
🧠 Learnings (16)
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/violet-poems-heal.md.changeset/breezy-planes-repeat.md.changeset/rare-readers-tell.md.changeset/red-toys-pump.md
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
server/test/e2e.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Applies to {src/generated/**,server/generated/**,contracts/broadcast/**,contracts/cache/**} : Do not manually edit files in generated directories: `src/generated/`, `server/generated/`, `contracts/broadcast/`, and `contracts/cache/`
Applied to files:
substreams/.gitignore.dockerignore
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to server/**/*.{ts,tsx} : Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use `c.var.db`
Applied to files:
server/test/database.tsserver/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
Applied to files:
server/test/database.tsserver/drizzle.config.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Applied to files:
server/test/database.tsserver/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/migrations/** : All schema changes must be accompanied by a migration file generated by `drizzle-kit`; never edit the database schema manually
Applied to files:
server/test/database.tsserver/drizzle.config.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Server (exactly/server) uses Hono (framework), Node.js, Drizzle ORM (database), and PostgreSQL
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Drizzle ORM best practices using ESLint `plugin:drizzle/all` configuration, including requiring `where` clauses for `update` and `delete` operations
Applied to files:
server/test/database.tsserver/drizzle.config.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words from names like `data`, `state`, `manager`, `engine`, `object`, `entity`, and `instance`
Applied to files:
cspell.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
Applied to files:
cspell.json
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Database schema must be defined in `server/database/schema.ts` as the single source of truth for database structure
Applied to files:
server/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use `nx` for running tasks (build, lint, test, e2e) instead of underlying tooling directly
Applied to files:
common/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Prepare the environment by running `pnpm prepare`, which handles git hooks, code generation (wagmi), and versioning
Applied to files:
common/package.json
🪛 Buf (1.62.1)
substreams/proto/exa.proto
3-3: Files with package "exa" must be within a directory "exa" relative to root but were in directory "substreams/proto".
(PACKAGE_DIRECTORY_MATCH)
🪛 Checkov (3.2.334)
substreams/Dockerfile
[low] 1-33: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
🪛 YAMLlint (1.37.1)
.github/workflows/substreams-build.yaml
[warning] 2-2: truthy value should be one of [false, true]
(truthy)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
🔇 Additional comments (36)
.changeset/breezy-planes-repeat.md (1)
1-5: LGTM! Changeset follows the guidelines.The changeset summary correctly uses lowercase and imperative present tense as required by the coding guidelines.
.changeset/red-toys-pump.md (1)
1-5: LGTM! Changeset follows the guidelines.The changeset summary correctly uses lowercase and imperative present tense as required by the coding guidelines.
substreams/.gitignore (1)
1-8: LGTM! Appropriate ignore rules for generated artifacts.The ignore rules correctly exclude generated directories (src/contracts, src/proto) and build artifacts (target, abi), which aligns with the project's code generation workflow.
pnpm-workspace.yaml (1)
7-7: LGTM! Workspace package addition is correct.The addition of the
substreamspackage to the workspace is appropriate and necessary for the new substreams project integration..vscode/extensions.json (1)
6-6: LGTM! Extensions align with new Substreams development.The addition of Protobuf and Rust analyzer extensions appropriately supports the new Rust-based Substreams package and Protobuf definitions introduced in this PR.
Also applies to: 16-16
substreams/.cargo/config.toml (1)
1-2: LGTM! Standard WASM build configuration.This configuration correctly sets the default build target for WebAssembly, which is required for Substreams module compilation.
.github/workflows/server-build.yaml (1)
36-37: LGTM! Correct Dockerfile path configuration.The explicit
fileparameter correctly points to the relocated Dockerfile while maintaining the root context for Docker build access.common/package.json (1)
43-50: LGTM! Enhanced build dependency tracking.The additional environment input and dependent task output files improve NX's incremental build accuracy by ensuring wagmi regenerates when contract artifacts or deployment broadcasts change.
.changeset/violet-poems-heal.md (1)
5-5: Remove emoji to follow changeset guidelines.As per coding guidelines, changeset summaries should use "a lowercase sentence in the imperative present tense" without decorative elements.
📝 Suggested fix
-✨ insert exa plugins into database +insert exa plugins into database⛔ Skipped due to learnings
Learnt from: CR Repo: exactly/exa PR: 0 File: .cursor/rules/style.mdc:0-0 Timestamp: 2025-12-30T15:03:28.449Z Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summariesLearnt from: CR Repo: exactly/exa PR: 0 File: .cursor/rules/git.mdc:0-0 Timestamp: 2025-12-23T19:57:14.465Z Learning: Select the most precise gitmoji that best communicates the intent of the change (e.g., ✨ for new feature, 🐛 for bug fix, ♻️ for refactoring, ⚡️ for performance improvement, 📝 for documentation, 🚀 for deployment)Learnt from: cruzdanilo Repo: exactly/exa PR: 610 File: .changeset/ready-experts-fly.md:1-2 Timestamp: 2025-12-31T00:24:05.835Z Learning: In the exactly/exa repository, empty changeset files (containing only `---` separators) are acceptable and required for CI to pass when changes are not user-facing and don't warrant a version bump, as the CI runs `changeset status --since origin/main` which requires a changeset file to be present.server/drizzle.config.ts (1)
8-8: LGTM! Schema filter correctly configured for multi-schema support.The addition of
schemaFilterenables drizzle-kit to introspect both thepublicandsubstreamsPostgreSQL schemas, which aligns with the new substreams infrastructure introduced in this PR.cspell.json (2)
6-9: LGTM! Ignore paths correctly added for generated files.The ignore paths for
.github/matcher.jsonandsubstreams/targetappropriately exclude configuration and build artifacts from spell-checking.
14-138: LGTM! Dictionary appropriately expanded for Substreams tooling.The additions cover Rust tooling (clippy, rustup, serde, prost), Protobuf tools (bufbuild, protogen, protolint), and Substreams ecosystem terms (fireeth, streamingfast, substreams, spkg), all relevant to the new substreams implementation.
substreams/buf.gen.yaml (1)
1-8: LGTM! Buf configuration correctly set up for Rust code generation.The configuration uses appropriate prost plugins for generating Rust bindings from Protobuf definitions, with sensible options for a substreams project.
substreams/Dockerfile (1)
9-9: The Rust toolchain is properly specified viarust-toolchain.toml(located in the substreams directory), which configures the nightly channel with required components (clippy, rustfmt) and the wasm32-unknown-unknown target. Using--default-toolchain nonein the Dockerfile is correct—rustup will automatically detect and apply the toolchain from the configuration file when the build runs.Likely an incorrect or invalid review comment.
.github/matcher.json (1)
63-92: LGTM! Well-structured Rust problem matchers.The new problem matchers correctly parse Rust compiler warnings/errors and formatter diffs, with proper ANSI escape code handling for CI environments.
.dockerignore (1)
6-25: LGTM! Comprehensive Docker build optimization.The ignore patterns appropriately exclude build artifacts, generated files, and development-only files while preserving necessary runtime artifacts like
run-latest.json.docs/package.json (1)
37-44: LGTM! Proper Nx cache tracking.The inputs configuration correctly tracks production sources and the dependent
openapi.jsonoutput for accurate build caching and invalidation.contracts/test/mocks/BobExecute.s.sol (2)
39-39: LGTM! Semantic change to address retrieval.Changing from
createAccounttogetAddresscorrectly retrieves the account address rather than creating it, which aligns with the test's intent.
55-55: LGTM! Batch deployment pattern for testing.Instantiating
BatchDeployfor its constructor side effects is an acceptable pattern in test scripts for batch account creation.substreams/package.json (1)
20-62: LGTM! Well-configured Nx build target.The build target correctly tracks Rust source files, Cargo manifests, environment variables, and dependent task outputs for proper cache invalidation. The WASM output path is appropriate for the target platform.
server/database/schema.ts (1)
36-36: LGTM: Updated index definition follows Drizzle ORM patterns.The function-based selector for the unique index is a valid Drizzle ORM pattern and functionally equivalent to the previous definition.
server/script/firehose.ts (2)
4-65: LGTM: ProtoCursor implementation is correct.The minimal Protobuf parser correctly implements varint reading, tag parsing, and field skipping. The non-null assertion at line 20 is safe due to the preceding bounds check.
67-107: LGTM: Timestamp extraction logic is sound.The nested cursor pattern correctly navigates the Protobuf structure (block → header → timestamp → seconds/nanoseconds) and gracefully returns null when fields are missing.
.changeset/rare-readers-tell.md (1)
1-5: LGTM: Changeset follows formatting guidelines.The changeset summary uses lowercase imperative present tense as required by the coding guidelines.
server/test/anvil.ts (1)
159-159: LGTM: Added test filter for targeted execution.The
--tc BobExecuteScriptflag narrows the forge script execution to a specific test contract, improving test precision and potentially performance.server/test/e2e.ts (2)
7-7: LGTM: Type import for Sentry mock typing.Correctly uses
import typefor the type-only import needed by the mock implementation.
58-67: LGTM: Sentry mock enhances test observability.The mock correctly wraps
captureExceptionto log error details during tests while preserving the original behavior. The implementation is properly typed and follows the established mocking pattern in this file.substreams/rustfmt.toml (1)
1-6: LGTM: Standard rustfmt configuration.The rustfmt settings are appropriate and follow common Rust formatting practices. The 2-space indentation aligns with project conventions, and the 120-character line width provides good readability.
substreams/Cargo.toml (3)
11-21: LGTM: Dependencies are appropriate for Ethereum substreams.The dependency list includes standard crates for Ethereum contract interaction, protobuf handling, and substreams processing. Versions appear current and compatible.
32-35: LGTM: Release profile optimizations are appropriate.The release profile correctly optimizes for size with LTO enabled and debug info stripped, which is suitable for a production substreams library that may be deployed in size-constrained environments.
1-9: Edition 2024 implicitly enables resolver version 3—verify dependency resolution is working correctly.The Cargo manifest specifies
edition = "2024", which was stabilized in Rust 1.85.0 (February 20, 2025). While dependencies likesubstreams(0.7) andsubstreams-ethereum(0.11) are compatible with this edition, Rust 2024 implicitly enables resolver version 3, which can cause subtle dependency resolution differences. The nightly toolchain is appropriate, but ensurecargo checkand local builds pass without resolver-related warnings or version conflicts, particularly with the implicit resolver upgrade.substreams/proto/exa.proto (1)
1-17: LGTM: Protobuf definitions are clear and appropriate.The message structures are straightforward and suitable for representing accounts and plugins in the substreams context. The use of
bytesfor addresses and accounts is standard for Ethereum data.server/package.json (2)
64-64: The@exactly/substreamsdevDependency is actively used inserver/test/database.tsfor test setup and initialization of substreams services. The dependency is referenced in test commands and as a working directory path for shell execution, making it a necessary development dependency.Likely an incorrect or invalid review comment.
143-146: No actionable concern — the patternout/**/*.jsonis correct. It properly tracks the build outputs from@exactly/pluginthat thegenerate:broadcaststask depends on. The broadcast output files are a separate, downstream output of this target, not part of its input dependencies.server/test/database.ts (1)
51-71: Verify error handling during teardown doesn't mask legitimate failures.The catch blocks check
controller.signal.abortedand silently return, which correctly avoids re-throwing cancellation errors during teardown. However, this pattern could also mask legitimate process failures that occur afterabort()is called but before the signal is checked.Consider whether non-abort errors during teardown should be logged or handled differently to aid debugging.
substreams/substreams.yaml (1)
78-82: The anvil factory address is hardcoded and must be kept in sync with the external plugin deployment.The factory address
Eb2454D494082f2294Fc5d4Cd67096f13E64306fin substreams.yaml line 82 is static, while the test setup (server/test/database.ts line 84) extracts the actual deployed address fromnode_modules/@exactly/plugin/broadcast/ExaAccountFactory.s.sol/31337/run-latest.json. If the external plugin is updated or redeployed, the hardcoded manifest address could become stale and cause the test environment to use the wrong factory. Consider extracting this address dynamically from the plugin at build time or adding a validation step to ensure they remain synchronized.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #627 +/- ##
==========================================
- Coverage 54.12% 53.69% -0.43%
==========================================
Files 50 51 +1
Lines 7912 8072 +160
Branches 641 651 +10
==========================================
+ Hits 4282 4334 +52
- Misses 3615 3723 +108
Partials 15 15
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 22
🤖 Fix all issues with AI agents
In @.github/workflows/test.yaml:
- Around line 37-41: The workflow currently downloads and extracts binaries in
the for loop using curl and tar without integrity checks; modify the loop that
iterates over repos (firehose-ethereum, substreams, substreams-sink-sql) so that
for each release you (a) resolve a pinned release/tag or fetch the release
assets URL and also fetch the associated checksum file or signature, (b)
download both the binary and its checksum/signature, (c) verify the binary with
sha256sum (or gpg verify if a signature is provided) before running tar zxf, and
fail the job if verification fails; ensure the curl + tar sequence is replaced
with a download-then-verify-then-extract flow and prefer pinning a specific
release version instead of using "latest".
- Around line 37-41: The workflow step that downloads binaries in
.github/workflows/test.yaml (the for loop iterating repos: firehose-ethereum,
substreams, substreams-sink-sql) should use actions/cache to avoid
re-downloading unchanged release assets: add a cache restore step before the
curl/tar loop that computes a key from each repository's latest release
identifier (e.g., the GitHub API release tag or asset URL) and the runner
architecture, run the existing download loop only if the cache is missing, and
then add a cache-save step after the downloads to store $HOME/.local/bin under
the same computed key so subsequent runs restore the cached binaries instead of
fetching them again.
- Around line 37-41: Wrap the download loop in a robust shell block that enables
strict failure handling (set -euo pipefail), use the GITHUB_TOKEN for
authenticated GitHub API requests by adding an Authorization header to curl,
split the nested curl into two steps: first query the release API to capture
ASSET_URL into a variable (using uname mapping for arch), validate ASSET_URL is
non-empty and fail with a clear message if not, then download with curl -fL
"$ASSET_URL" and extract with tar; ensure each repo iteration
(firehose-ethereum, substreams, substreams-sink-sql) checks curl exit codes and
aborts on error so failures are reported clearly.
In @.vscode/extensions.json:
- Line 6: Replace the invalid VS Code extension ID "drblury.protobuf-vsc" in the
extensions.json recommendations with a maintained protobuf extension; update the
string to one of the suggested IDs such as "bufbuild.vscode-buf" (recommended),
"zxh404.vscode-proto3", or "peterj.vscode-protobuf" so the recommendation
resolves correctly in the Marketplace.
In @common/package.json:
- Around line 44-50: Update the three "dependentTasksOutputFiles" entries in
common/package.json so they point to Foundry's actual broadcast outputs: replace
"out/**/*.json" and "broadcast/*/31337/*.json" with a single pattern that
matches contracts/broadcast locations (e.g., "contracts/broadcast/**/*.json");
edit the array item(s) containing the "dependentTasksOutputFiles" key to use the
corrected "contracts/broadcast/**/*.json" pattern so NX change detection for the
generate:wagmi task will pick up Foundry script broadcasts.
In @contracts/test/mocks/BobExecute.s.sol:
- Around line 61-71: The BatchDeploy contract constructor lacks natspec and uses
an unexplained magic number; add NatSpec comments for the contract and its
constructor (include @notice and @param for factory and count and, if added,
startAddr) and replace or document the hardcoded 420 by either introducing a
constructor parameter (e.g., startAddr) and using that instead of 420 in the
loop or adding a clear inline comment explaining why 420 is chosen; update the
constructor signature in BatchDeploy and its NatSpec accordingly so the intent
and the starting address are explicit.
- Line 39: bobAccount is only computed via factory.getAddress() but never
deployed, so calls to bobAccount.executeProposal() in run() will fail; replace
the address-only call with an actual deployment (use factory.createAccount(...)
with the same salt and owners.toPublicKeys() or otherwise ensure the account is
created before run()), and update the BatchDeploy usage so it deploys Bob’s
account (salt 0 with owner 0xb0b / owners index 0) rather than creating accounts
for salts 0/owners 420–423; ensure the deployed account reference (bobAccount)
points to the real deployed contract prior to invoking run() and its
executeProposal() calls.
In @server/database/schema.ts:
- Around line 79-86: The audit table definition for substreamsHistory currently
leaves critical audit columns nullable; update the column definitions on the
substreamsHistory table so that op, tableName, and pk are marked .notNull()
(e.g., change char("op", { length: 1 }) → char(...).notNull(), and
text("table_name")/text("pk") → text(...).notNull()), while leaving prevValue
nullable to allow INSERTs with no previous value; ensure the serial id and
blockNum definitions remain unchanged.
In @server/script/firehose.ts:
- Line 127: The variable name payloadB64 in the array destructuring (const [, ,
num, hash, libNum, parentHash, payloadB64] = parts) uses an abbreviation; rename
it to payloadBase64 and update every subsequent reference in this module to the
new identifier (e.g., any use in parsing, decoding, or function calls) so the
code compiles and consistently uses the full word "Base64" instead of "B64".
- Around line 59-64: readBytes() does not validate that the length read via
this.readVarint() keeps this.offset within this.buffer bounds, risking
out-of-range reads; update readBytes() to check that length is non-negative and
that (this.offset + length) <= this.buffer.length before advancing this.offset,
throw or return a clear error if the check fails, and use the same symbols
(readBytes, this.readVarint, this.offset, this.buffer) so callers get a
deterministic failure instead of corrupted data.
In @server/test/database.ts:
- Around line 92-95: The teardown currently calls controller.abort() then
immediately awaits postgres.stop(), which can race with running sub-processes
(forceKillAfterDelay: 33_333) and leave connections open; update the setup to
capture the started process promises (e.g., store process/child promises in an
array or variables when launching substreams-sink-sql) and in the teardown
function do controller.abort(); await Promise.all(processPromises) (or await
each process termination promise) before calling await postgres.stop();
alternatively, if capturing promises is not possible, insert a small await delay
after controller.abort() to give processes time to exit before calling
postgres.stop().
In @substreams/build.rs:
- Around line 22-35: The current contracts.iter().try_for_each block uses
assert! on the Command::new("bash") .status() which panics without context when
jq/bash fails; change this to run the command with .output(), check the exit
status, and return a descriptive Err (or use anyhow::bail!) including mod_name
and contract plus stderr/stdout content so failures are propagated rather than
panicked; update the Command invocation site and its error handling (the closure
passed to try_for_each and the Command::new("bash") call) to use the command
output for richer error context before calling Abigen::new.
In @substreams/Dockerfile:
- Around line 32-33: The Dockerfile's CMD hardcodes a Postgres URL with
host.docker.internal (ENTRYPOINT ["dumb-init", "/app/substreams-sink-sql",
"run"] and CMD
["postgres://host.docker.internal:5432/postgres?schemaName=substreams&sslmode=disable",
"substreams.yaml"]), which only works on Docker Desktop; change CMD to use an
environment variable (e.g., POSTGRES_URL) or switch to a shell/wrapper script
that expands ${POSTGRES_URL} (falling back to a sensible local default) and
document that production deployments must override POSTGRES_URL via
orchestration; ensure the ENTRYPOINT remains the exec form and add README notes
about local vs production usage and overriding CMD/env.
- Around line 12-13: The Dockerfile currently fetches the "latest" release which
makes builds non-deterministic; update the RUN that calls GitHub API and curl to
pin the substreams CLI to v1.15.6 (replace the "releases/latest" URL or the
queried tag with "releases/tag/v1.15.6" and ensure the jq filter still selects
the linux_$arch asset), so the WORKDIR /usr/local/bin RUN curl ... command
downloads and extracts the v1.15.6 asset instead of the latest.
In @substreams/package.json:
- Around line 43-50: The workspace "test" target depends on a non-existent
target named "test:proto"; update the NX targets so the dependency matches the
actual script/target name (either add a new target named "test:proto" or change
the dependency to "test:protolint"), ensuring the "test" target's dependsOn
array references the existing "test:protolint" target or create a corresponding
target that runs the protolint script; update the dependsOn entry for "test"
(and/or add the missing target definition) to eliminate the naming mismatch
between "test:proto" and "test:protolint".
In @substreams/proto/exa.proto:
- Around line 1-7: The proto file declares package "exa" (message Accounts) but
lives at substreams/proto/exa.proto which triggers Buf's
package-directory-mismatch lint; either move the file into a matching directory
(substreams/proto/exa/exa.proto) and update substreams.yaml importPaths
accordingly, or if the layout is intentional add/adjust buf.yaml to disable or
override the package-directory-mismatch rule for this file; locate the file
defining package exa and message Accounts and apply one of these two fixes so
Buf no longer warns.
- Around line 5-17: Add concise proto comments for the messages and fields to
document expected formats: annotate message Accounts and its field accounts with
a comment like "repeated 20-byte Ethereum addresses" (or the correct
byte-length/format), annotate message Plugins and nested Plugin plus fields
address, account with descriptions (e.g., "20-byte Ethereum address",
"associated account bytes/format"), and annotate ordinal with its semantic
meaning (e.g., "plugin ordering index"). Update comments inline above symbols
Accounts, accounts, Plugins, Plugin, address, account, and ordinal so future
readers understand the expected byte formats and semantics.
In @substreams/rust-toolchain.toml:
- Around line 1-4: Replace the toolchain channel setting from "nightly" to
"stable" in the [toolchain] block (change channel = "nightly" to channel =
"stable") so the file uses the stable Rust toolchain while keeping components =
["clippy", "rustfmt"] and targets = ["wasm32-unknown-unknown"] unchanged.
In @substreams/src/lib.rs:
- Around line 53-71: The deduplication in map_exa_plugins is inverted because
the negation on !seen.insert(address.to_vec()) causes first occurrences to be
dropped and duplicates kept; change the logic to use
seen.insert(address.to_vec()) (remove the !) so that the first time an address
from new.accounts is encountered it returns true (included) and subsequent
duplicates return false (excluded); keep the rest of the conditional (the
accounts.get_last(...) branch and construction of Plugin with event.plugin,
account: address.to_vec(), ordinal: log.ordinal()) unchanged.
- Around line 27-44: The nested iterator chain in map_exa_accounts (parsing
params, decoding Hex::decode, collecting into Vec, then mapping to slices)
reduces readability; refactor by extracting the parsed factories and their
decoded byte vectors into temporary variables (e.g., parse
serde_qs::from_str::<Factories>(¶ms)? to a factories variable, decode into a
Vec<Vec<u8>> via Hex::decode and ? for errors, then create a Vec<&[u8]> by
mapping Vec::as_slice) and pass that slice vector to
block.events::<ExaAccountInitialized>(&slices) and proceed to map and collect as
before to simplify the flow and avoid deeply nested iterator expressions.
In @substreams/substreams.yaml:
- Around line 43-48: The sink configuration in substreams.yaml points schema:
../server/generated/schema.sql which doesn't match the Dockerfile copy
destination; update the sink.config.schema value to ../server/schema.sql (or
alternatively change the Dockerfile to copy into ../server/generated/schema.sql)
so the path referenced by the sink.module db_out (type
sf.substreams.sink.sql.v1.Service) matches the actual file location at runtime.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsubstreams/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (36)
.changeset/breezy-planes-repeat.md.changeset/rare-readers-tell.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.dockerignore.github/matcher.json.github/workflows/server-build.yaml.github/workflows/substreams-build.yaml.github/workflows/test.yaml.vscode/extensions.jsoncommon/package.jsoncontracts/package.jsoncontracts/test/mocks/BobExecute.s.solcspell.jsondocs/package.jsonpnpm-workspace.yamlserver/Dockerfileserver/database/schema.tsserver/drizzle.config.tsserver/package.jsonserver/script/firehose.tsserver/test/anvil.tsserver/test/database.tsserver/test/e2e.tssubstreams/.cargo/config.tomlsubstreams/.gitignoresubstreams/Cargo.tomlsubstreams/Dockerfilesubstreams/buf.gen.yamlsubstreams/build.rssubstreams/package.jsonsubstreams/proto/exa.protosubstreams/rust-toolchain.tomlsubstreams/rustfmt.tomlsubstreams/src/lib.rssubstreams/substreams.yaml
🧰 Additional context used
📓 Path-based instructions (11)
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/drizzle.config.tsserver/test/anvil.tsserver/script/firehose.tsserver/test/e2e.tsserver/test/database.tsserver/database/schema.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/drizzle.config.tsserver/test/anvil.tsserver/script/firehose.tsserver/test/e2e.tscontracts/test/mocks/BobExecute.s.solserver/test/database.tsserver/database/schema.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/drizzle.config.tsserver/test/anvil.tsserver/script/firehose.tsserver/test/e2e.tsserver/test/database.tsserver/database/schema.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/drizzle.config.tsserver/test/anvil.tsserver/script/firehose.tsserver/test/e2e.tsserver/test/database.tsserver/database/schema.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/drizzle.config.tsserver/test/anvil.tsserver/script/firehose.tsserver/test/e2e.tsserver/test/database.tsserver/database/schema.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/drizzle.config.tsserver/test/anvil.tsserver/script/firehose.tsserver/test/e2e.tsserver/test/database.tsserver/database/schema.ts
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/rare-readers-tell.md.changeset/breezy-planes-repeat.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md
**/+(app|src)/**
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use kebab-case for directory names and public Expo router routes
Files:
substreams/src/lib.rs
contracts/**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/contracts.mdc)
contracts/**/*.sol: Import order must be sorted alphabetically and grouped by type (openzeppelin, solady, internal, etc.), enforced by forge fmt
All functions and state variables must have explicit visibility modifiers (public, private, internal, external)
Use custom errors instead of require() statements with string messages; require() with messages is forbidden
console.log is forbidden in contract code
All external and public functions that modify state must be protected by a reentrancy guard, unless explicitly proven unnecessary
Use modifiers like onlyOwner or function-level checks for privileged functions; default to strictest access level (private > internal > external > public)
Use named parameters in mappings for clarity (e.g., mapping(address user => uint256 balance))
All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Follow standard Solidity import order: external libraries (openzeppelin, solady) first, then project-internal contracts
All public and external functions and state variables must have complete natspec comments (@notice, @dev, @param, @return)
Use custom errors instead of require statements with string messages for better gas efficiency and error information
Internal and private variables should be prefixed with an underscore (_); function arguments should not be prefixed
Use constant keyword for true constants; use immutable for configurable values like contract addresses
Minimize writes to storage; read data into memory or calldata whenever possible for gas optimization
Use smallest integer size that is safe for the variable (e.g., uint32 for timestamps) and be aware of struct packing for gas efficiency
Prefer using the solady library over openzeppelin where possible, as it is highly optimized for gas
Correctly label functions as view or pure when they do not modify state
contracts/**/*.sol: Smart contracts: use custom errorserror MyError();instead of s...
Files:
contracts/test/mocks/BobExecute.s.sol
**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
**/*.sol: Use camelCase for Solidity variables and functions
Use PascalCase for Solidity contracts and structs
Files:
contracts/test/mocks/BobExecute.s.sol
server/database/schema.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/database/schema.ts: Database schema must be defined inserver/database/schema.tsas the single source of truth for database structure
Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Files:
server/database/schema.ts
🧠 Learnings (21)
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to server/**/*.{ts,tsx} : Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use `c.var.db`
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Database schema must be defined in `server/database/schema.ts` as the single source of truth for database structure
Applied to files:
server/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/migrations/** : All schema changes must be accompanied by a migration file generated by `drizzle-kit`; never edit the database schema manually
Applied to files:
server/drizzle.config.tsserver/test/database.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Applies to {src/generated/**,server/generated/**,contracts/broadcast/**,contracts/cache/**} : Do not manually edit files in generated directories: `src/generated/`, `server/generated/`, `contracts/broadcast/`, and `contracts/cache/`
Applied to files:
substreams/.gitignore.dockerignore
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/rare-readers-tell.md.changeset/breezy-planes-repeat.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words from names like `data`, `state`, `manager`, `engine`, `object`, `entity`, and `instance`
Applied to files:
cspell.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
Applied to files:
cspell.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
server/test/e2e.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run all tests using `pnpm test`, which includes TypeScript compilation, ESLint, spell checking, markdown linting, and contract tests
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run contract tests using `pnpm --filter plugin test` and check formatting using `pnpm --filter plugin test:fmt`
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use `nx` for running tasks (build, lint, test, e2e) instead of underlying tooling directly
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Test files must end with .t.sol and mirror the name of the contract they test (e.g., ExaPlugin.sol -> ExaPlugin.t.sol)
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
Applied to files:
server/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Server (exactly/server) uses Hono (framework), Node.js, Drizzle ORM (database), and PostgreSQL
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Drizzle ORM best practices using ESLint `plugin:drizzle/all` configuration, including requiring `where` clauses for `update` and `delete` operations
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries
Applied to files:
.changeset/violet-poems-heal.md
🪛 Buf (1.62.1)
substreams/proto/exa.proto
3-3: Files with package "exa" must be within a directory "exa" relative to root but were in directory "substreams/proto".
(PACKAGE_DIRECTORY_MATCH)
🪛 Checkov (3.2.334)
substreams/Dockerfile
[low] 1-33: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
🪛 YAMLlint (1.37.1)
.github/workflows/substreams-build.yaml
[warning] 2-2: truthy value should be one of [false, true]
(truthy)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
🔇 Additional comments (42)
contracts/package.json (1)
60-101: Consider whether the sequential test chain is optimal for CI performance.The new dependency structure creates a fully sequential execution path:
test:sizes→test:gas→test:slither→test:coverage. While this provides fail-fast behavior, it may significantly increase total test time compared to running independent tasks in parallel.Specifically:
test:slither(static analysis) does not inherently requiretest:gas(snapshot validation) to complete firsttest:coveragedoes not inherently requiretest:slitherto complete firstBoth could potentially run in parallel after the build step, unless:
- The sequential chain is intentional to fail fast and avoid wasting CI resources when earlier checks fail
- Shared output directories (
{projectRoot}/out,{projectRoot}/cache) would cause conflicts with parallel execution- There are resource contention concerns in the CI environment
If the linear execution was not an intentional optimization, consider restructuring to:
"test:slither": { "dependsOn": ["test:sizes"], "outputs": ["{projectRoot}/out", "{projectRoot}/cache"] }, "test:coverage": { "dependsOn": ["test:gas"], "outputs": ["{projectRoot}/out", "{projectRoot}/cache", "{projectRoot}/lcov.info"] }This would allow
test:slitherandtest:coverageto run in parallel after their respective prerequisites complete, potentially halving the test suite duration.server/test/e2e.ts (2)
7-7: LGTM!Correct use of
import typefor the type-only import, following the coding guidelines.
58-67: LGTM!The Sentry mock correctly wraps
captureExceptionto provide test observability while preserving the original behavior. The destructuring pattern cleanly separates the function being wrapped from the rest of the module exports.server/test/anvil.ts (1)
159-160: LGTM!Adding the
--tc BobExecuteScripttest case selector makes the forge script invocation more explicit, ensuring only the intended contract is executed.server/test/database.ts (3)
6-6: LGTM!Import of
$from execa is appropriate for managing external processes.
45-46: LGTM!Cleaning up the
.firehosedirectory before starting ensures a fresh state for the firehose processes.
49-72: Verify substreams-sink-sql readiness before tests run if they depend on substreams data.Currently, fireeth waits for port 10016 (line 60), but
substreams-sink-sqlstarts without a readiness check. If tests depend on substreams data, add a wait that polls the database (e.g., check thesubstreams.cursorstable) rather than a port check, sincesubstreams-sink-sqlwrites to the database, not a port. However, this may not be necessary if tests don't immediately query substreams tables.The teardown already properly awaits
postgres.stop()with a 33-second graceful shutdown window before force kill, so no changes needed there..github/workflows/test.yaml (1)
37-41: Remove the binary download step or clarify its purpose.The workflow downloads three binaries (
firehose-ethereum,substreams,substreams-sink-sql) and extracts them to~/.local/bin, but these binaries are not referenced or invoked anywhere in the repository. If they are not needed for the test suite, this step should be removed to reduce workflow overhead. If they are intended for future use or debugging, add a comment explaining their purpose.Likely an incorrect or invalid review comment.
docs/package.json (1)
37-44: LGTM! Proper NX incremental build configuration.The inputs array correctly tracks the OpenAPI file dependency, ensuring the docs build invalidates its cache when the generated OpenAPI file changes. This aligns well with the existing
dependsOnconfiguration and follows NX best practices.server/package.json (2)
142-146: LGTM! Consistent dependency tracking configuration.The additions properly track the
CHAIN_IDenvironment variable and contract build artifacts (out/**/*.json) as inputs to the broadcasts generation. This is consistent with similar patterns incommon/package.jsonand ensures proper incremental builds.Note: The
out/**/*.jsonpattern should be verified as mentioned in the review ofcommon/package.json.
64-64: The substreams package is correctly configured in the workspace.The
@exactly/substreamspackage exists atsubstreams/package.jsonwith version0.0.1, which correctly matches the workspace dependency referenceworkspace:^0.0.1in server/package.json.contracts/test/mocks/BobExecute.s.sol (1)
55-55: LGTM: Side-effect constructor pattern.The
BatchDeploycontract is instantiated solely for its constructor side effects (creating 4 accounts). The discarded contract address is intentional, as the contract serves no purpose after deployment. This pattern is acceptable for deployment scripts.pnpm-workspace.yaml (1)
7-7: LGTM!The addition of the substreams package to the workspace is consistent with the PR's introduction of the substreams module and aligns with related files (substreams/package.json, Dockerfile, etc.).
substreams/.cargo/config.toml (1)
1-2: LGTM!Standard Cargo configuration for WebAssembly builds. The
wasm32-unknown-unknowntarget is appropriate for the substreams WASM module architecture..changeset/violet-poems-heal.md (1)
1-5: LGTM!The changeset summary correctly follows the coding guidelines: lowercase, imperative present tense ("insert"), and clearly describes the change.
substreams/.gitignore (1)
1-8: LGTM!The ignore patterns are appropriate for the Rust/WASM build environment, covering generated artifacts (
src/contracts,src/proto), build outputs (target,*.spkg), test data (firehose-data), and logs..changeset/red-toys-pump.md (1)
1-5: LGTM! Changeset follows the guideline.The summary uses lowercase imperative present tense as required.
.changeset/rare-readers-tell.md (1)
1-5: LGTM! Changeset follows the guideline.The summary uses lowercase imperative present tense as required.
substreams/rustfmt.toml (1)
1-6: LGTM! Standard Rust formatting configuration.The rustfmt settings are reasonable and follow common Rust project conventions.
cspell.json (1)
6-6: LGTM! Appropriate spell-check updates for substreams.The added ignore paths and dictionary words are legitimate technical terms related to Rust tooling, blockchain infrastructure, and substreams functionality.
Also applies to: 9-9, 14-138
.github/workflows/substreams-build.yaml (1)
1-38: LGTM! Workflow follows security best practices.The workflow correctly implements:
- Least-privilege permissions with granular grants
- Build provenance attestation for supply chain security
- Proper tag generation patterns for semantic versioning
- Secure authentication using GITHUB_TOKEN
The YAMLlint style warnings about inline braces are purely stylistic preferences—the YAML syntax is valid and appears consistent with the project's formatting style.
substreams/Cargo.toml (2)
1-35: Remaining configuration looks appropriate for a substreams cdylib.The dependencies, build-dependencies, and release profile are well-suited for a WASM-targeted substreams module. LTO and
opt-level = 's'are sensible choices for size optimization in WASM builds.
4-4: Edition 2024 is valid and stable — this will compile correctly.Rust edition 2024 was stabilized and released in Rust 1.85.0 on February 20, 2025. As of January 2026, the latest stable release is Rust 1.92.0. Using
edition = "2024"in Cargo.toml is correct and will compile without issues.Likely an incorrect or invalid review comment.
.github/matcher.json (1)
63-92: Well-structured problem matchers for Rust tooling.The regex patterns correctly handle ANSI escape codes and capture the relevant groups for both compiler diagnostics (warning/error with optional code, file location on second line) and rustfmt diffs. The two-pattern array for "rust" properly matches the multi-line compiler output format.
server/script/firehose.ts (2)
1-65: Clean protobuf cursor implementation.The
ProtoCursorclass provides a well-structured minimal protobuf parser. The varint decoding, tag extraction, and field skipping logic are correctly implemented for the supported wire types.
67-148: Timestamp extraction and line processing look correct.The nested field navigation (field 5 → field 12 → fields 1,2) correctly extracts the timestamp. The FIRE INIT normalization and FIRE BLOCK transformation with error handling are well-implemented.
server/database/schema.ts (2)
70-100: New substreams schema tables are well-structured.The schema design with separate tables for cursors, history, blocks, and plugins is appropriate. The composite primary key on
exaPluginscorrectly enforces uniqueness on the address-account pair.Note: No relations are defined for these tables. If cross-schema queries with typed results are needed, consider adding relations. Based on learnings, relations should be defined explicitly to enable Drizzle typed query results.
36-36: LGTM!The destructured parameter syntax for the unique index callback is cleaner and equivalent to the previous implementation.
server/drizzle.config.ts (1)
8-8: LGTM!The
schemaFiltercorrectly includes the newsubstreamsschema alongsidepublic, ensuring drizzle-kit processes both schemas for migrations and introspection..changeset/breezy-planes-repeat.md (1)
1-5: LGTM!The changeset correctly declares a minor version bump for the new substreams functionality. The summary follows the imperative present tense as required.
.github/workflows/server-build.yaml (1)
36-37: LGTM!Explicitly specifying the Dockerfile path while keeping the build context at the repository root is the correct approach when the Dockerfile needs access to files outside its directory (e.g., shared dependencies or configs at the repo root).
substreams/buf.gen.yaml (1)
1-8: LGTM!The Buf configuration is well-structured with pinned plugin versions for reproducibility. The output path aligns with the build.rs protogen configuration and the options are appropriate for the Rust/WASM target.
substreams/package.json (1)
1-42: Well-structured NX configuration.The build configuration properly tracks all relevant inputs (Cargo files, source files, environment variables) and outputs (WASM binary). The dependency chain with
rustupensures the toolchain is available before building.substreams/Dockerfile (1)
20-30: Good security practices in the runtime stage.The runtime stage correctly:
- Uses
dumb-initas PID 1 for proper signal handling- Creates and switches to a non-root
substreamsuser- Sets appropriate file ownership with
--chownRegarding the static analysis hint about HEALTHCHECK: While useful for container orchestration, it's optional if health checks are configured at the orchestrator level (e.g., Kubernetes liveness/readiness probes).
substreams/build.rs (1)
12-18: Good use ofrerun-if-changeddirectives.The build script correctly declares its dependencies for incremental rebuilds, ensuring Cargo re-runs the script when proto files, configuration, or source dependencies change.
.dockerignore (2)
21-21: Verify intent:**/*.mdexcludes all Markdown including README files.This pattern will exclude README.md and other documentation from Docker builds. If any README content is needed in containers (e.g., for reference or license compliance), consider a more targeted pattern like
**/CHANGELOG.mdor use negation for specific files.If intentional (to reduce image size), this is fine. Just confirming documentation isn't needed in the built images.
6-18: Good refinement of ignore patterns for substreams artifacts.The patterns correctly exclude:
- Generated contracts/proto code that
build.rsregenerates- Build artifacts (target, abi directories)
- Broadcast artifacts except
run-latest.jsonneeded for deployment referencesThis reduces Docker context size and ensures reproducible builds.
substreams/substreams.yaml (2)
54-82: Verify factory addresses match deployed contracts.The factory addresses for each network are critical configuration. Ensure these addresses are verified against actual deployments:
- optimism: 4 factories starting at block 124,728,176
- base: 1 factory starting at block 38,172,919
- op-sepolia: 1 factory starting at block 26,170,889
- base-sepolia: 1 factory starting at block 29,999,489
- anvil: 1 factory starting at block 0
Note: The factory addresses appear to be checksummed (mixed case) for mainnet but lowercase for testnets. Consider using consistent casing (preferably checksummed) for all addresses to catch copy-paste errors.
1-42: Well-structured Substreams manifest.The module pipeline is logically organized:
map_exa_accounts→ filters account creation eventsstore_exa_accounts→ persists accounts for lookupsmap_exa_plugins→ maps plugin installations using account storedb_out→ transforms to database changesThe dependency chain and input/output types align with the Rust implementation in
src/lib.rs.substreams/src/lib.rs (3)
1-1: Verify the necessity of this clippy expectation.No unsafe pointer dereferences are visible in the source code. This expectation likely relates to code generated by the
#[substreams::handlers::*]macros. Confirm that this directive is required and consider adding a comment explaining why.
46-51: LGTM!The store logic is straightforward and correct.
73-88: LGTM!The database change logic correctly upserts plugin records and creates a corresponding blocks row when changes exist. The
unwrap_or_default()on line 85 safely handles missing timestamps.
8421280 to
8b6cd00
Compare
There was a problem hiding this comment.
Actionable comments posted: 14
🤖 Fix all issues with AI agents
In @contracts/package.json:
- Around line 73-95: The scripts test:gas, test:slither, and test:coverage are
chained sequentially but only need test:sizes; change their dependsOn so each
depends directly on "test:sizes" instead of chaining (i.e., set
test:slither.dependsOn and test:coverage.dependsOn to ["test:sizes"]), confirm
outputs remain the same, and ensure there are no shared-directory concurrency
issues before enabling parallel execution.
- Around line 83-91: The "test:slither" npm task declares outputs that Slither
doesn't produce; open the package.json entry for the "test:slither" script and
remove "{projectRoot}/out" and "{projectRoot}/cache" from the "outputs" array
(or remove the "outputs" key entirely if no outputs remain) so that only real
Slither outputs are declared and Forge-managed directories aren't incorrectly
marked as produced by Slither.
- Around line 60-101: Remove "{projectRoot}/out" and "{projectRoot}/cache" from
the "outputs" array of the "test:slither" script (slither is static analysis and
doesn't produce those artifacts), and restructure dependencies so "test:sizes"
is the single prerequisite: make "test:gas", "test:slither", and "test:coverage"
each list "test:sizes" in their "dependsOn" (instead of chaining test:sizes →
test:gas → test:slither → test:coverage) so they can run in parallel after sizes
completes.
In @server/database/schema.ts:
- Around line 70-100: The drizzle config is missing the "out" output directory
so migrations for the new substreams tables aren't generated; update your
drizzle.config.ts (the defineConfig export) to include an out path, then run
drizzle-kit generate:pg to produce migration files for the new tables (cursors,
substreams_history, blocks, exa_plugins corresponding to the substreams schema
objects substreams.table entries: cursors, substreamsHistory, blocks,
exaPlugins), review the generated SQL, and commit the migration files to version
control.
In @server/script/firehose.ts:
- Around line 100-104: The timestamp parsing loop in server/script/firehose.ts
assumes fields 1 (seconds) and 2 (nanoseconds) are varints and calls
tsCursor.readVarint() unconditionally; validate tag.wireType === 0 before
calling readVarint() and only assign seconds/nanoseconds when the wire type
matches, otherwise call tsCursor.skip(tag.wireType) (or handle/log the
unexpected wire type) to avoid misreading malformed messages; update the loop
that uses tsCursor.readTag(), tag.fieldNumber, tag.wireType,
tsCursor.readVarint(), and tsCursor.skip to perform this check.
- Around line 59-64: readBytes() advances this.offset by a length decoded from
readVarint() without checking remaining bytes, which can cause silent
truncation; add a bounds check before advancing (compute length =
Number(this.readVarint()), ensure length >= 0 and this.offset + length <=
this.buffer.length), and if the check fails throw a clear Error (do not modify
this.offset). Apply the same pre-check pattern to the skip() implementation(s)
that advance this.offset (use the same validation and error behavior) so no
offset is advanced when the requested skip/length exceeds remaining buffer.
- Around line 130-131: The local variable named "ts" is an abbreviation; rename
it to a descriptive name like "extractedTimestamp" (or "rawTimestamp") where it
is defined from extractTimestamp(buffer) and update its usage in the ternary
that assigns to the existing "timestamp" variable so the code reads e.g. const
extractedTimestamp = extractTimestamp(buffer); const timestamp =
extractedTimestamp === null ? "0" : extractedTimestamp.toString(); ensure all
references to "ts" in the scope are replaced accordingly.
In @substreams/buf.gen.yaml:
- Around line 1-8: Replace the nonexistent plugin versions by updating the two
plugin remote strings: change "buf.build/community/neoeinstein-prost:v0.5.0" to
"buf.build/community/neoeinstein-prost:v0.2.3" and change
"buf.build/community/neoeinstein-prost-crate:v0.5.0" to
"buf.build/community/neoeinstein-prost-crate:v0.3.1" in the buf.gen.yaml plugin
entries so the config references valid releases.
In @substreams/build.rs:
- Around line 48-57: The block that invokes Command::new("substreams") uses
assert! so failures panic instead of propagating an error; replace the assert!
by executing the command, using ? to propagate the Result from .status() (remove
the .expect("protogen failed")), then inspect the returned ExitStatus and return
an Err (e.g., via anyhow::anyhow or a suitable Err variant) if
!status.success(), including context such as the command and its exit code; keep
the same env construction (canonicalize("node_modules/.bin") and var("PATH"))
and span the change around the Command::new("substreams").arg("protogen")...
.status() call so the function returns a Result instead of panicking.
- Around line 59-65: The code uses assert! around the rustfmt Command invocation
which panics instead of returning an error; change the build step that runs
Command::new("rustfmt") (the block currently wrapped by assert!) to capture the
Command status via .status()? and return a Result-based error on failure (e.g.,
if !status.success() { return Err(anyhow::format_err!("rustfmt failed: {:?}",
status)); }) so it matches the protogen pattern that uses ? and early returns;
also update the surrounding function signature to return Result<()> if it does
not already.
In @substreams/Dockerfile:
- Line 13: The Dockerfile currently downloads the Substreams CLI from the
repository's "releases/latest" which makes builds non-reproducible; modify the
RUN curl command that fetches the asset (the long command starting with RUN curl
-L "$(curl -s
https://api.github.com/repos/streamingfast/substreams/releases/latest ...)") to
target a specific release tag instead of /releases/latest (e.g., replace the
latest URL with one that includes a pinned tag like /releases/download/vX.Y.Z)
so the tarball URL is deterministic and repeatable.
In @substreams/package.json:
- Around line 25-41: The package.json build target's "inputs" array is missing
the protobuf sources so changes to .proto files won't trigger rebuilds; update
the inputs list used for the substreams build (the JSON object containing
"inputs" and "outputs" in substreams/package.json) to include the proto sources
by adding an entry for "{projectRoot}/proto/**" (or equivalent glob) alongside
the existing entries so that modifications to .proto files invalidate the cache
and trigger a rebuild of generated code in src/proto.
In @substreams/proto/exa.proto:
- Around line 1-17: Proto package declaration "package exa" in exa.proto does
not match the repository directory layout expected by Buf; either move the file
into an exa/ directory or change the package name to match the current
directory. Update the filesystem by moving substreams/proto/exa.proto into
substreams/proto/exa/exa.proto (or rename the package in the file) so the
package exa aligns with the directory, keeping the message definitions Accounts
and Plugins (and nested Plugin) unchanged.
In @substreams/rust-toolchain.toml:
- Around line 1-4: Update the toolchain specification in rust-toolchain.toml to
use the stable channel instead of nightly: change the [toolchain] channel value
from "nightly" to a stable pin such as "stable" or a specific version like
"1.85" and keep the existing components ("clippy","rustfmt") and targets
("wasm32-unknown-unknown") unchanged to ensure reproducible builds.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsubstreams/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (36)
.changeset/breezy-planes-repeat.md.changeset/rare-readers-tell.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.dockerignore.github/matcher.json.github/workflows/server-build.yaml.github/workflows/substreams-build.yaml.github/workflows/test.yaml.vscode/extensions.jsoncommon/package.jsoncontracts/package.jsoncontracts/test/mocks/BobExecute.s.solcspell.jsondocs/package.jsonpnpm-workspace.yamlserver/Dockerfileserver/database/schema.tsserver/drizzle.config.tsserver/package.jsonserver/script/firehose.tsserver/test/anvil.tsserver/test/database.tsserver/test/e2e.tssubstreams/.cargo/config.tomlsubstreams/.gitignoresubstreams/Cargo.tomlsubstreams/Dockerfilesubstreams/buf.gen.yamlsubstreams/build.rssubstreams/package.jsonsubstreams/proto/exa.protosubstreams/rust-toolchain.tomlsubstreams/rustfmt.tomlsubstreams/src/lib.rssubstreams/substreams.yaml
🧰 Additional context used
📓 Path-based instructions (11)
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/test/e2e.tsserver/drizzle.config.tsserver/test/anvil.tsserver/test/database.tsserver/script/firehose.tsserver/database/schema.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/test/e2e.tsserver/drizzle.config.tsserver/test/anvil.tsserver/test/database.tscontracts/test/mocks/BobExecute.s.solserver/script/firehose.tsserver/database/schema.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/test/e2e.tsserver/drizzle.config.tsserver/test/anvil.tsserver/test/database.tsserver/script/firehose.tsserver/database/schema.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/test/e2e.tsserver/drizzle.config.tsserver/test/anvil.tsserver/test/database.tsserver/script/firehose.tsserver/database/schema.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/test/e2e.tsserver/drizzle.config.tsserver/test/anvil.tsserver/test/database.tsserver/script/firehose.tsserver/database/schema.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/test/e2e.tsserver/drizzle.config.tsserver/test/anvil.tsserver/test/database.tsserver/script/firehose.tsserver/database/schema.ts
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/breezy-planes-repeat.md.changeset/rare-readers-tell.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md
contracts/**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/contracts.mdc)
contracts/**/*.sol: Import order must be sorted alphabetically and grouped by type (openzeppelin, solady, internal, etc.), enforced by forge fmt
All functions and state variables must have explicit visibility modifiers (public, private, internal, external)
Use custom errors instead of require() statements with string messages; require() with messages is forbidden
console.log is forbidden in contract code
All external and public functions that modify state must be protected by a reentrancy guard, unless explicitly proven unnecessary
Use modifiers like onlyOwner or function-level checks for privileged functions; default to strictest access level (private > internal > external > public)
Use named parameters in mappings for clarity (e.g., mapping(address user => uint256 balance))
All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Follow standard Solidity import order: external libraries (openzeppelin, solady) first, then project-internal contracts
All public and external functions and state variables must have complete natspec comments (@notice, @dev, @param, @return)
Use custom errors instead of require statements with string messages for better gas efficiency and error information
Internal and private variables should be prefixed with an underscore (_); function arguments should not be prefixed
Use constant keyword for true constants; use immutable for configurable values like contract addresses
Minimize writes to storage; read data into memory or calldata whenever possible for gas optimization
Use smallest integer size that is safe for the variable (e.g., uint32 for timestamps) and be aware of struct packing for gas efficiency
Prefer using the solady library over openzeppelin where possible, as it is highly optimized for gas
Correctly label functions as view or pure when they do not modify state
contracts/**/*.sol: Smart contracts: use custom errorserror MyError();instead of s...
Files:
contracts/test/mocks/BobExecute.s.sol
**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
**/*.sol: Use camelCase for Solidity variables and functions
Use PascalCase for Solidity contracts and structs
Files:
contracts/test/mocks/BobExecute.s.sol
**/+(app|src)/**
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use kebab-case for directory names and public Expo router routes
Files:
substreams/src/lib.rs
server/database/schema.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/database/schema.ts: Database schema must be defined inserver/database/schema.tsas the single source of truth for database structure
Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Files:
server/database/schema.ts
🧠 Learnings (30)
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
server/test/e2e.tscontracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/breezy-planes-repeat.md.changeset/rare-readers-tell.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to server/**/*.{ts,tsx} : Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use `c.var.db`
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/migrations/** : All schema changes must be accompanied by a migration file generated by `drizzle-kit`; never edit the database schema manually
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Database schema must be defined in `server/database/schema.ts` as the single source of truth for database structure
Applied to files:
server/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Drizzle ORM best practices using ESLint `plugin:drizzle/all` configuration, including requiring `where` clauses for `update` and `delete` operations
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2026-01-08T19:54:26.885Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: substreams/rust-toolchain.toml:1-4
Timestamp: 2026-01-08T19:54:26.885Z
Learning: Rust edition 2024 was released as stable on February 20, 2025 with Rust 1.85.0 and does not require nightly channel.
Applied to files:
substreams/rust-toolchain.toml
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Server (exactly/server) uses Hono (framework), Node.js, Drizzle ORM (database), and PostgreSQL
Applied to files:
server/test/database.ts
📚 Learning: 2026-01-08T19:45:38.852Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .github/workflows/substreams-build.yaml:2-2
Timestamp: 2026-01-08T19:45:38.852Z
Learning: In the exactly/exa repository, prefer concise inline YAML syntax (e.g., `on: { push: { tags: ["pattern"] }, workflow_dispatch: {} }`) over expanded multi-line format in GitHub workflow files, even if it triggers YAMLlint warnings about formatting.
Applied to files:
.github/workflows/substreams-build.yaml
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries
Applied to files:
.changeset/rare-readers-tell.md.changeset/violet-poems-heal.md
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : Use custom errors instead of require statements with string messages for better gas efficiency and error information
Applied to files:
substreams/build.rs
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Applies to {src/generated/**,server/generated/**,contracts/broadcast/**,contracts/cache/**} : Do not manually edit files in generated directories: `src/generated/`, `server/generated/`, `contracts/broadcast/`, and `contracts/cache/`
Applied to files:
substreams/.gitignore.dockerignorecommon/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All public and external functions and state variables must have complete natspec comments (notice, dev, param, return)
Applied to files:
contracts/test/mocks/BobExecute.s.sol
📚 Learning: 2026-01-08T19:46:37.829Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: contracts/test/mocks/BobExecute.s.sol:61-71
Timestamp: 2026-01-08T19:46:37.829Z
Learning: Natspec documentation requirements do not apply to test/mock/dev scripts (files in contracts/test/ or with .s.sol extension). Only production contract code in contracts/src/ requires complete natspec comments.
Applied to files:
contracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Use vm.prank to simulate calls from different addresses; avoid changing msg.sender through other means
Applied to files:
contracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use `nx` for running tasks (build, lint, test, e2e) instead of underlying tooling directly
Applied to files:
substreams/package.jsoncontracts/package.json
📚 Learning: 2026-01-08T20:26:33.396Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .vscode/extensions.json:6-6
Timestamp: 2026-01-08T20:26:33.396Z
Learning: For validating VS Code extension IDs in this repository, check both the official VS Code Marketplace (marketplace.visualstudio.com) and the Open VSX registry (open-vsx.org) before flagging an extension as invalid. Open VSX is a widely-used alternative registry, so a valid ID may appear in either registry. If an ID is not present in both registries, flag it as invalid. Apply this check specifically when validating the IDs listed in .vscode/extensions.json.
Applied to files:
.vscode/extensions.json
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Do not use abbreviations or cryptic names; write out full words like `error`, `parameters`, `request` instead of `err`, `params`, `req`
Applied to files:
server/script/firehose.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
Applied to files:
server/script/firehose.tscspell.json
📚 Learning: 2026-01-08T19:54:10.673Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: substreams/src/lib.rs:54-71
Timestamp: 2026-01-08T19:54:10.673Z
Learning: In substreams/src/lib.rs in the map_exa_plugins function, the deduplication logic using `!seen.insert(address.to_vec())` is intentional. The first plugin installed for a new account is an owner plugin (not an exa plugin), so it must be filtered out. The negation ensures the first plugin is skipped and subsequent plugins (the actual exa plugins) are kept.
Applied to files:
substreams/src/lib.rs
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words from names like `data`, `state`, `manager`, `engine`, `object`, `entity`, and `instance`
Applied to files:
cspell.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run contract tests using `pnpm --filter plugin test` and check formatting using `pnpm --filter plugin test:fmt`
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run all tests using `pnpm test`, which includes TypeScript compilation, ESLint, spell checking, markdown linting, and contract tests
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Test files must end with .t.sol and mirror the name of the contract they test (e.g., ExaPlugin.sol -> ExaPlugin.t.sol)
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Applied to files:
contracts/package.json
🪛 Buf (1.62.1)
substreams/proto/exa.proto
3-3: Files with package "exa" must be within a directory "exa" relative to root but were in directory "substreams/proto".
(PACKAGE_DIRECTORY_MATCH)
🪛 Checkov (3.2.334)
substreams/Dockerfile
[low] 1-33: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
🪛 YAMLlint (1.37.1)
.github/workflows/substreams-build.yaml
[warning] 2-2: truthy value should be one of [false, true]
(truthy)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
🔇 Additional comments (47)
server/script/firehose.ts (1)
135-135: Verify intentional use oflibNumtwice in output.The output format uses
libNumin both its original position and again beforetimestamp. The input has 7 tokens but the output structure differs:
- Input:
FIRE BLOCK num hash libNum parentHash payloadB64- Output:
FIRE BLOCK num hash libNum parentHash libNum timestamp newPayloadIs the second
libNumintentional, or should a different value be used there?.github/workflows/test.yaml (1)
37-41: [Your rewritten review comment text here]
[Exactly ONE classification tag]substreams/.cargo/config.toml (1)
1-2: LGTM! Standard WASM build configuration.The build target is correctly set to
wasm32-unknown-unknown, which is the standard target for Substreams modules that compile to WebAssembly.substreams/.gitignore (1)
1-8: LGTM! Appropriate ignore patterns for Substreams artifacts.The gitignore correctly excludes:
- Generated code directories (abi, src/contracts, src/proto)
- Build outputs (target)
- Runtime data (firehose-data)
- Build artifacts (*.log, *.spkg)
This aligns with the project's practice of not committing generated or build artifacts.
.changeset/breezy-planes-repeat.md (1)
1-5: LGTM! Changeset follows the required format.The changeset correctly:
- Uses lowercase imperative present tense ("map exa accounts")
- Targets the appropriate package with a minor version bump
- Follows the standard changeset file structure
.changeset/red-toys-pump.md (1)
1-5: LGTM! Changeset follows the required format.The changeset correctly:
- Uses lowercase imperative present tense ("map exa plugins")
- Targets the appropriate package with a minor version bump
- Follows the standard changeset file structure
server/drizzle.config.ts (1)
8-8: LGTM! Correct schema filter for Substreams integration.The addition of
"substreams"to the schema filter enables drizzle-kit to manage migrations for the new Substreams database schema alongside the existing public schema..changeset/rare-readers-tell.md (1)
1-5: LGTM!The changeset follows the required format with a lowercase imperative present tense summary. The emoji prefix is consistent with common changeset conventions, and the description accurately reflects the PR's objective of storing Exa accounts.
cspell.json (1)
6-6: LGTM!The additions to ignore paths and the word list appropriately support the new Rust/Substreams tooling introduced in this PR. The ignored paths (
substreams/targetfor Rust build artifacts and.github/matcher.jsonfor error patterns) and technical terms (Rust ecosystem tools likeclippy,rustup,serde, and Substreams-related terms) are all contextually appropriate.Also applies to: 9-9, 14-138
server/test/e2e.ts (2)
7-7: LGTM!Correctly uses
import typefor the type-only import, following TypeScript best practices.
58-67: LGTM!The Sentry mock implementation correctly preserves the original
captureExceptionbehavior while adding console logging for improved e2e test debugging. The approach of destructuring the original module and wrapping the function is sound..github/workflows/server-build.yaml (1)
36-37: Dockerfile path verified. The explicitfile: server/Dockerfileparameter is appropriate for the multi-component repository structure and the file exists at the specified location..vscode/extensions.json (1)
6-6: No action needed. The extension IDdrblury.protobuf-vscis valid and available in both the VS Code Marketplace and Open VSX registry.Likely an incorrect or invalid review comment.
docs/package.json (1)
38-43: LGTM! NX inputs properly track OpenAPI generation.The
inputsarray correctly wires the OpenAPI output from@exactly/serverinto the docs build's incremental tracking, ensuring the docs rebuild when the API spec changes.server/package.json (2)
64-64: LGTM! Substreams dependency added.The addition of
@exactly/substreamsas a dev dependency aligns with the PR objectives for initial substreams implementation.
142-146: LGTM! Enhanced incremental build tracking.The expanded inputs properly track the
CHAIN_IDenvironment variable and dependent task outputs (out/**/*.json), ensuring correct incremental builds for broadcast generation.substreams/Cargo.toml (2)
32-35: LGTM! Release profile optimized for WASM.The release profile configuration with LTO, size optimization (
's'), and stripped debug info is appropriate for WASM targets in a Substreams context.
1-4: Fix invalid Rust edition.Rust edition "2024" does not exist. Valid Rust editions are
2015,2018, and2021. Use2021as it's the latest stable edition.🐛 Proposed fix
[package] name = "exactly-substreams" version = "0.1.0" -edition = "2024" +edition = "2021" repository = "https://github.com/exactly/exa"⛔ Skipped due to learnings
Learnt from: cruzdanilo Repo: exactly/exa PR: 627 File: substreams/rust-toolchain.toml:1-4 Timestamp: 2026-01-08T19:54:26.885Z Learning: Rust edition 2024 was released as stable on February 20, 2025 with Rust 1.85.0 and does not require nightly channel.contracts/test/mocks/BobExecute.s.sol (2)
61-71: LGTM! BatchDeploy helper simplifies multi-account creation.The
BatchDeploycontract provides a clean way to create multiple test accounts in a single transaction. The implementation is straightforward and appropriate for test scaffolding.
39-39: Document script execution order dependency.This script calls
executeProposalonbobAccount(lines 50-53), which requires the account to already exist. The change fromcreateAccounttogetAddressassumes Bob's account with salt 0 has been created in the priorBob.s.solscript. Ensure this dependency is documented or consider embedding the account creation here to avoid failures if scripts run out of order.server/test/anvil.ts (1)
159-159: LGTM! Test case selector disambiguates script execution.Adding
--tc BobExecuteScriptis appropriate given thatBobExecute.s.solnow contains multiple contracts. This ensures onlyBobExecuteScript.run()is executed as the entry point.common/package.json (1)
44-50: LGTM!The additional
dependentTasksOutputFilesinputs correctly wire contract compilation outputs (out/**/*.json) and local chain broadcast artifacts (broadcast/*/31337/*.json) into the wagmi generation target, ensuring proper cache invalidation when these artifacts change..dockerignore (1)
6-25: LGTM!The granular ignore patterns appropriately reduce Docker build context while preserving necessary artifacts like
run-latest.json. The substreams-specific ignores (abi,target,src/contracts,src/proto) correctly exclude generated and build directories..github/matcher.json (1)
64-92: LGTM!The Rust problem matchers are well-structured. The two-pattern approach for the
rustowner correctly handles the multi-line compiler output format (message line followed by location line). The ANSI escape sequence handling (\x1b\[[\d;]+m) ensures colored terminal output is properly parsed.server/database/schema.ts (1)
36-36: LGTM!The destructured syntax
({ account }) => [uniqueIndex(...).on(account)]is cleaner and aligns with the pattern used in the newexaPluginstable definition.server/test/database.ts (1)
49-72: The URL concatenation on line 66 is correct. The pattern${postgresURL}&schemaName=substreamsproperly follows standard URL syntax: the first query parameter uses?(in postgresURL ending with?sslmode=disable), and subsequent parameters use&. The absence of whitespace in the template literal is not an error—it's standard practice and produces the correct URL stringpostgres://postgres:postgres@localhost:5432/postgres?sslmode=disable&schemaName=substreams. No changes needed.Likely an incorrect or invalid review comment.
pnpm-workspace.yaml (1)
7-7: LGTM!The workspace is correctly expanded to include the new substreams package, maintaining the existing ordering convention.
.changeset/violet-poems-heal.md (1)
1-5: LGTM!The changeset summary follows the coding guidelines: lowercase sentence in imperative present tense. Based on coding guidelines.
substreams/rustfmt.toml (1)
1-6: LGTM!The rustfmt configuration is well-suited for the Rust nightly toolchain specified in this PR. The settings promote consistent formatting with sensible defaults.
substreams/Dockerfile (1)
20-33: Runtime stage setup looks good.The runtime stage follows security best practices: runs as non-root user, uses dumb-init for proper signal handling, and pins the base image version. The default
CMDwithhost.docker.internalis appropriate for local development; production deployments will override this.Regarding the static analysis hint about missing
HEALTHCHECK: for a sink process that may not expose HTTP endpoints, a healthcheck would need to be tailored to the specific runtime behavior. Consider adding one if the sink exposes a health endpoint..github/workflows/substreams-build.yaml (2)
1-38: LGTM!The workflow is well-structured with proper permissions for container registry operations and build attestation. The inline YAML syntax follows the repository conventions. Based on learnings.
20-20: Verify the tag pattern intent.The pattern
^(?!@)(.*)$matches strings that don't start with@, but the workflow only triggers on tags matching@exactly/substreams@*. This pattern may never produce matches given the trigger constraint. Was this intended for a different trigger scenario?substreams/package.json (1)
51-62: LGTM on test targets.The test orchestration via NX is well-structured with proper dependency ordering: clippy runs first (with prepare dependencies), then fmt, and protolint runs in parallel via the aggregate test target.
substreams/build.rs (3)
14-18: LGTM: Cargo rebuild triggers are correctly configured.The rerun triggers appropriately cover proto definitions, configuration files, and contract sources in node_modules.
20-34: LGTM: ABI extraction and binding generation logic is correct.The code correctly:
- Extracts ABI from Forge artifacts at the expected path
- Generates Rust bindings using Abigen
- Handles errors appropriately with
?operator
36-46: LGTM: Generated module file creation is correct.The code properly generates a module file with:
// @generatedmarker for tooling#[expect(clippy::style, clippy::complexity)]to suppress lints on generated code- Proper module declarations
substreams/substreams.yaml (5)
1-9: LGTM: Package and protobuf configuration is correct.
10-13: LGTM: Imports and network configuration are appropriate.The default network is set to
anvilfor local development, which aligns with the PR objectives of deferring deployment.
14-42: LGTM: Module pipeline is correctly wired.The four-stage pipeline (map accounts → store accounts → map plugins → database output) has proper input/output types and follows Substreams best practices.
43-52: LGTM: Sink and binary configuration is correct.The database sink configuration properly references the schema file and the compiled WASM binary path follows Rust conventions.
53-82: LGTM: Network configurations are correct.The initial blocks and factory addresses are properly configured for each network. Factory addresses use EIP-55 checksummed format (except line 77), which is good practice but not strictly required.
substreams/src/lib.rs (6)
1-21: LGTM: Clippy configuration and imports are appropriate.The
#![expect(clippy::not_unsafe_ptr_arg_deref)]attribute is necessary for Substreams FFI boundaries, where raw pointers are used in the handler interface.
22-25: LGTM: Factories struct is correctly defined.
27-44: LGTM: Account extraction logic is correct.The function properly:
- Parses factory addresses from query string parameters
- Decodes hex addresses with error handling
- Filters block events for ExaAccountInitialized from specified factories
- Collects account addresses
The double
.collect()pattern (lines 36 and 39) is necessary to handle error propagation and type conversion.
46-51: LGTM: Account storage logic is correct.The function properly stores a flag (value
1) for each account using the hex-encoded address as the key. Using ordinal0is appropriate for existence flags.
53-71: LGTM: Plugin extraction logic is correct.The function properly:
- Filters PluginInstalled events from block logs
- Handles new accounts (from current block) with deduplication to skip the first plugin (owner plugin)
- Handles existing accounts by checking the store
- Creates Plugin entries with address, account, and ordinal
Based on learnings, the negation in line 63 (
!seen.insert(address.to_vec())) is intentional to filter out the first plugin (owner plugin) for new accounts.
73-88: Code correctly implements database operations with proper schema alignment.The function properly:
- Creates upsert operations for each plugin in the
exa_pluginstable with composite primary key(address, account)as defined in the database schema- Uses hex-encoded addresses for database keys
- Creates a blocks row with timestamp when changes exist
- Uses
unwrap_or_default()as a defensive fallback for clock timestamps (though substreams should always provide this)The schema in
server/database/schema.tsconfirms the table structures match the code:exa_pluginshas a composite primary key on(address, account), andblockshasnumberandtimestampfields as expected.
28ec4bd to
e13495d
Compare
There was a problem hiding this comment.
Actionable comments posted: 16
🤖 Fix all issues with AI agents
In @.changeset/breezy-planes-repeat.md:
- Line 5: The changeset summary "✨ map exa accounts" is missing terminal
punctuation; update the summary to be a complete lowercase imperative
present-tense sentence by adding a period (e.g., "✨ map exa accounts.") and
ensure it remains lowercase and in imperative present tense inside
.changeset/breezy-planes-repeat.md.
In @.changeset/rare-readers-tell.md:
- Line 5: Update the changeset summary line in .changeset/rare-readers-tell.md
by removing the emoji and ensuring it is a lowercase, imperative-present tense
sentence; replace the current "✨ store exa accounts" header with a plain
sentence like "store exa accounts." to conform to the guidelines.
In @.changeset/violet-poems-heal.md:
- Line 5: The changeset summary "✨ insert exa plugins into database" is missing
terminal punctuation; edit the .changeset/violet-poems-heal.md changeset summary
line to end with a period and ensure it remains a lowercase,
imperative-present-tense sentence (e.g., "✨ insert exa plugins into database.").
In @.github/workflows/test.yaml:
- Around line 37-41: The run step that loops over repos (for repo in
firehose-ethereum substreams substreams-sink-sql) currently pipes curl output
straight into tar and does not validate the download or handle errors; change it
to (1) enable safe shell flags (e.g., set -euo pipefail) and use curl --fail so
HTTP errors cause failure, (2) first query the GitHub API and store the
resulting browser_download_url in a variable and check it is non-empty before
attempting download, (3) perform the download and verify curl's exit status
before passing data to tar, and (4) add integrity verification by fetching a
checksum/signature asset (or pin a specific release/tag) and validating the
downloaded archive with sha256sum (or signature verification), exiting with an
error if any step fails.
In @server/database/schema.ts:
- Around line 70-100: Add B-tree indexes for expected query predicates to avoid
full table scans: update the schema definitions created with substreams.table
(e.g., exaPlugins, blocks, substreamsHistory, cursors) to declare indexes on
exaPlugins.account, blocks.timestamp, substreamsHistory.blockNum (and optionally
substreamsHistory.tableName and cursors.blockNum) using the schema library’s
index/primaryKey callback form so the DB creates those indexes alongside the
tables.
- Around line 93-100: Add an explicit Drizzle relation tying exaPlugins.account
to credentials.account: create exaPluginsRelations using relations(exaPlugins,
({ one }) => ({ credential: one(credentials, { fields: [exaPlugins.account],
references: [credentials.account] }) })); then update credentialsRelations to
include the inverse (e.g., a has/many relation pointing back to exaPlugins) so
queries return typed related entities; reference the exaPlugins, credentials,
exaPluginsRelations and credentialsRelations symbols when making the changes.
In @server/script/firehose.ts:
- Line 127: The destructured variable payloadBase64 should be renamed to a
non-abbreviated, clearer name (e.g., encodedPayload or payload) to follow naming
guidelines; update the array destructuring (replace payloadBase64 with
encodedPayload) and then update all subsequent references in the same function
(decoding, parsing, logging, etc.) from payloadBase64 to the new identifier
(encodedPayload) so behavior remains identical.
- Around line 100-104: The timestamp parsing loop reads seconds and nanoseconds
without checking wire types; update the loop that uses
tsCursor.readTag()/tsCursor.readVarint() so that before calling readVarint() for
fieldNumber 1 or 2 you verify tag.wireType === 0 (the varint wire type) and only
then assign seconds/nanoseconds, otherwise call tsCursor.skip(tag.wireType) to
avoid misinterpreting unexpected types; ensure you still skip unknown
fieldNumbers as before.
In @server/test/database.ts:
- Around line 53-55: The complex template literal constructing the fireeth
command (the string containing "fireeth start
reader-node,merger,relayer,substreams-tier1 ... --reader-node-arguments=${'-c
\"...firehose.ts\"'}") is hard to read and fragile; extract that construction
into a named helper like buildFireethStartCommand (or move the inner piped
command to a standalone script e.g., scripts/firehose.sh) and have the test call
that helper or script, and if invoking from Node use spawn/exec with an args
array instead of bash -c to avoid nested quoting and ensure proper escaping.
In @substreams/build.rs:
- Around line 47-56: Replace the assert! around Command::new("substreams") with
proper Result-based error handling: run the command with
Command::new("substreams")... (use .output() to capture stdout/stderr or
.status() if you prefer), map any spawn/error via ? or map_err to return an Err,
then check the exit status and, if not successful, return an Err containing a
clear diagnostic string including the exit code and captured stderr/stdout;
update the surrounding function signature to return Result if needed so failures
propagate rather than panicking.
In @substreams/proto/exa.proto:
- Around line 1-7: The proto file declares package exa (message Accounts) but
lives at proto/exa.proto; move the file into an exa subdirectory (e.g.,
proto/exa/exa.proto) so the filesystem matches the package name, and update any
build/import paths (buf, compiler, or other proto imports) that reference
proto/exa.proto to the new path; ensure any tooling/config (e.g., buf.yaml,
go/ts import mappings) is updated to avoid breakage.
In @substreams/src/lib.rs:
- Around line 28-43: The iterator chain in map_exa_accounts is hard to read;
extract intermediate steps: parse Factories from params into a variable, map
factories to decoded Vec<u8> using Hex::decode into a Vec<Result<_,_>> and then
collect to Vec<Vec<u8>> (handling the ? on error), convert those Vecs to slices
in a separate variable (e.g., factory_slices), and pass factory_slices to
block.events::<ExaAccountInitialized>(); finally map the events to event.account
and collect into Accounts. Keep the same error propagation but split into named
variables to improve readability and debugging.
- Around line 62-66: The conditional combining new.accounts,
seen.insert(address.to_vec()), and accounts.get_last(Hex(&address).to_string())?
is dense and needs an inline explanatory comment: add a short comment above the
whole expression describing the deduplication strategy (check if address is part
of the incoming batch, dedupe via the seen HashSet returning true only for
duplicates via the negation of seen.insert, and fallback to checking persistent
store with accounts.get_last for non-batched addresses), and explicitly note why
the first plugin instance is filtered out (i.e., the negation means already-seen
addresses are excluded so the first occurrence is kept and subsequent ones are
filtered). Reference the symbols new.accounts, seen.insert, accounts.get_last,
Hex(&address), and address in the comment so readers can map explanation to the
code.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsubstreams/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (36)
.changeset/breezy-planes-repeat.md.changeset/rare-readers-tell.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.dockerignore.github/matcher.json.github/workflows/server-build.yaml.github/workflows/substreams-build.yaml.github/workflows/test.yaml.vscode/extensions.jsoncommon/package.jsoncontracts/package.jsoncontracts/test/mocks/BobExecute.s.solcspell.jsondocs/package.jsonpnpm-workspace.yamlserver/Dockerfileserver/database/schema.tsserver/drizzle.config.tsserver/package.jsonserver/script/firehose.tsserver/test/anvil.tsserver/test/database.tsserver/test/e2e.tssubstreams/.cargo/config.tomlsubstreams/.gitignoresubstreams/Cargo.tomlsubstreams/Dockerfilesubstreams/buf.gen.yamlsubstreams/build.rssubstreams/package.jsonsubstreams/proto/exa.protosubstreams/rust-toolchain.tomlsubstreams/rustfmt.tomlsubstreams/src/lib.rssubstreams/substreams.yaml
🧰 Additional context used
📓 Path-based instructions (11)
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/breezy-planes-repeat.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.changeset/rare-readers-tell.md
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/test/anvil.tsserver/test/database.tsserver/test/e2e.tsserver/script/firehose.tsserver/drizzle.config.tsserver/database/schema.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/test/anvil.tsserver/test/database.tsserver/test/e2e.tsserver/script/firehose.tscontracts/test/mocks/BobExecute.s.solserver/drizzle.config.tsserver/database/schema.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/test/anvil.tsserver/test/database.tsserver/test/e2e.tsserver/script/firehose.tsserver/drizzle.config.tsserver/database/schema.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/test/anvil.tsserver/test/database.tsserver/test/e2e.tsserver/script/firehose.tsserver/drizzle.config.tsserver/database/schema.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/test/anvil.tsserver/test/database.tsserver/test/e2e.tsserver/script/firehose.tsserver/drizzle.config.tsserver/database/schema.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/test/anvil.tsserver/test/database.tsserver/test/e2e.tsserver/script/firehose.tsserver/drizzle.config.tsserver/database/schema.ts
contracts/**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/contracts.mdc)
contracts/**/*.sol: Import order must be sorted alphabetically and grouped by type (openzeppelin, solady, internal, etc.), enforced by forge fmt
All functions and state variables must have explicit visibility modifiers (public, private, internal, external)
Use custom errors instead of require() statements with string messages; require() with messages is forbidden
console.log is forbidden in contract code
All external and public functions that modify state must be protected by a reentrancy guard, unless explicitly proven unnecessary
Use modifiers like onlyOwner or function-level checks for privileged functions; default to strictest access level (private > internal > external > public)
Use named parameters in mappings for clarity (e.g., mapping(address user => uint256 balance))
All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Follow standard Solidity import order: external libraries (openzeppelin, solady) first, then project-internal contracts
All public and external functions and state variables must have complete natspec comments (@notice, @dev, @param, @return)
Use custom errors instead of require statements with string messages for better gas efficiency and error information
Internal and private variables should be prefixed with an underscore (_); function arguments should not be prefixed
Use constant keyword for true constants; use immutable for configurable values like contract addresses
Minimize writes to storage; read data into memory or calldata whenever possible for gas optimization
Use smallest integer size that is safe for the variable (e.g., uint32 for timestamps) and be aware of struct packing for gas efficiency
Prefer using the solady library over openzeppelin where possible, as it is highly optimized for gas
Correctly label functions as view or pure when they do not modify state
contracts/**/*.sol: Smart contracts: use custom errorserror MyError();instead of s...
Files:
contracts/test/mocks/BobExecute.s.sol
**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
**/*.sol: Use camelCase for Solidity variables and functions
Use PascalCase for Solidity contracts and structs
Files:
contracts/test/mocks/BobExecute.s.sol
**/+(app|src)/**
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use kebab-case for directory names and public Expo router routes
Files:
substreams/src/lib.rs
server/database/schema.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/database/schema.ts: Database schema must be defined inserver/database/schema.tsas the single source of truth for database structure
Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Files:
server/database/schema.ts
🧠 Learnings (33)
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/breezy-planes-repeat.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.changeset/rare-readers-tell.md
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to server/**/*.{ts,tsx} : Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use `c.var.db`
Applied to files:
server/test/database.tsserver/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
Applied to files:
server/test/database.tsserver/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Applied to files:
server/test/database.tsserver/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/migrations/** : All schema changes must be accompanied by a migration file generated by `drizzle-kit`; never edit the database schema manually
Applied to files:
server/test/database.tsserver/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Server (exactly/server) uses Hono (framework), Node.js, Drizzle ORM (database), and PostgreSQL
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Drizzle ORM best practices using ESLint `plugin:drizzle/all` configuration, including requiring `where` clauses for `update` and `delete` operations
Applied to files:
server/test/database.tsserver/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
server/test/e2e.tscontracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Applies to {src/generated/**,server/generated/**,contracts/broadcast/**,contracts/cache/**} : Do not manually edit files in generated directories: `src/generated/`, `server/generated/`, `contracts/broadcast/`, and `contracts/cache/`
Applied to files:
.dockerignorecommon/package.jsonsubstreams/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : Use custom errors instead of require statements with string messages for better gas efficiency and error information
Applied to files:
substreams/build.rs
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run contract tests using `pnpm --filter plugin test` and check formatting using `pnpm --filter plugin test:fmt`
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run all tests using `pnpm test`, which includes TypeScript compilation, ESLint, spell checking, markdown linting, and contract tests
Applied to files:
contracts/package.json
📚 Learning: 2026-01-08T19:46:37.829Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: contracts/test/mocks/BobExecute.s.sol:61-71
Timestamp: 2026-01-08T19:46:37.829Z
Learning: Natspec documentation requirements do not apply to test/mock/dev scripts (files in contracts/test/ or with .s.sol extension). Only production contract code in contracts/src/ requires complete natspec comments.
Applied to files:
contracts/package.jsoncontracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to contracts/**/*.test.sol : Smart contracts testing: fuzzing required for inputs and gas snapshots mandatory
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Use fuzz testing (testFuzz_...) extensively for functions with numerical or address inputs to cover a wide range of scenarios
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Rely on `pnpm` scripts for all tasks; all setup, testing, and generation must be automated
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use `nx` for running tasks (build, lint, test, e2e) instead of underlying tooling directly
Applied to files:
contracts/package.jsonsubstreams/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Test files must end with .t.sol and mirror the name of the contract they test (e.g., ExaPlugin.sol -> ExaPlugin.t.sol)
Applied to files:
contracts/package.json
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Do not use abbreviations or cryptic names; write out full words like `error`, `parameters`, `request` instead of `err`, `params`, `req`
Applied to files:
server/script/firehose.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
Applied to files:
server/script/firehose.tscspell.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All public and external functions and state variables must have complete natspec comments (notice, dev, param, return)
Applied to files:
contracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Use vm.prank to simulate calls from different addresses; avoid changing msg.sender through other means
Applied to files:
contracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Database schema must be defined in `server/database/schema.ts` as the single source of truth for database structure
Applied to files:
server/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2026-01-08T19:54:10.673Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: substreams/src/lib.rs:54-71
Timestamp: 2026-01-08T19:54:10.673Z
Learning: In substreams/src/lib.rs in the map_exa_plugins function, the deduplication logic using `!seen.insert(address.to_vec())` is intentional. The first plugin installed for a new account is an owner plugin (not an exa plugin), so it must be filtered out. The negation ensures the first plugin is skipped and subsequent plugins (the actual exa plugins) are kept.
Applied to files:
substreams/src/lib.rs
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words from names like `data`, `state`, `manager`, `engine`, `object`, `entity`, and `instance`
Applied to files:
cspell.json
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries
Applied to files:
.changeset/violet-poems-heal.md
📚 Learning: 2026-01-08T19:45:38.852Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .github/workflows/substreams-build.yaml:2-2
Timestamp: 2026-01-08T19:45:38.852Z
Learning: In the exactly/exa repository, prefer concise inline YAML syntax (e.g., `on: { push: { tags: ["pattern"] }, workflow_dispatch: {} }`) over expanded multi-line format in GitHub workflow files, even if it triggers YAMLlint warnings about formatting.
Applied to files:
.github/workflows/substreams-build.yaml
📚 Learning: 2026-01-08T19:54:26.885Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: substreams/rust-toolchain.toml:1-4
Timestamp: 2026-01-08T19:54:26.885Z
Learning: Rust edition 2024 was released as stable on February 20, 2025 with Rust 1.85.0 and does not require nightly channel.
Applied to files:
substreams/rust-toolchain.toml
📚 Learning: 2026-01-08T20:26:33.396Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .vscode/extensions.json:6-6
Timestamp: 2026-01-08T20:26:33.396Z
Learning: For validating VS Code extension IDs in this repository, check both the official VS Code Marketplace (marketplace.visualstudio.com) and the Open VSX registry (open-vsx.org) before flagging an extension as invalid. Open VSX is a widely-used alternative registry, so a valid ID may appear in either registry. If an ID is not present in both registries, flag it as invalid. Apply this check specifically when validating the IDs listed in .vscode/extensions.json.
Applied to files:
.vscode/extensions.json
🧬 Code graph analysis (1)
server/test/database.ts (1)
server/database/schema.ts (1)
substreams(70-70)
🪛 Buf (1.62.1)
substreams/proto/exa.proto
3-3: Files with package "exa" must be within a directory "exa" relative to root but were in directory "substreams/proto".
(PACKAGE_DIRECTORY_MATCH)
🪛 Checkov (3.2.334)
substreams/Dockerfile
[low] 1-33: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
🪛 YAMLlint (1.37.1)
.github/workflows/substreams-build.yaml
[warning] 2-2: truthy value should be one of [false, true]
(truthy)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
🔇 Additional comments (42)
substreams/.gitignore (1)
1-8: Standard and appropriate ignore patterns.The gitignore entries properly cover build artifacts (target, *.log, *.spkg) and generated code directories (src/proto, src/contracts) typical for a Rust/WASM project. The file structure is well-organized.
server/drizzle.config.ts (1)
8-8: LGTM! Schema filter addition is appropriate.The
schemaFilterproperty correctly limits drizzle-kit operations to thepublicandsubstreamsschemas, which aligns with the PR's objective of introducing substreams functionality.cspell.json (1)
6-6: LGTM! Dictionary additions align with new tooling.The ignore paths and dictionary words appropriately support the Rust, Protobuf, and Substreams toolchain being introduced in this PR:
- Ignore paths exclude build artifacts (
substreams/target) and generated config (.github/matcher.json)- Dictionary terms include legitimate Rust ecosystem tools (
clippy,rustup,serde,prost), Protobuf tools (protogen,protolint,bufbuild), and Substreams-related identifiersAlso applies to: 9-9, 14-14, 26-26, 32-32, 35-35, 55-55, 72-72, 114-116, 122-123, 131-131, 134-134, 138-138
.changeset/red-toys-pump.md (1)
1-5: LGTM! Changeset follows conventions.The changeset is properly formatted with a minor version bump for
@exactly/substreamsand a summary in lowercase imperative present tense as required by the coding guidelines.server/test/e2e.ts (1)
7-7: LGTM! Sentry mock enhances test observability.The mock correctly wraps
captureExceptionto log exceptions during e2e tests while preserving the original behavior. The implementation properly:
- Imports and uses TypeScript types
- Preserves all original exports
- Maintains the function signature and return value
- Adds debugging visibility with
console.log(appropriate use of eslint-disable in test code)Also applies to: 58-67
.vscode/extensions.json (1)
6-6: LGTM! Extension recommendations support new toolchain.The additions of
rust-lang.rust-analyzeranddrblury.protobuf-vscare appropriate for the Rust and Protobuf code being introduced in the substreams implementation. These recommendations will enhance the developer experience when working with the new codebase.Also applies to: 16-16
.github/workflows/server-build.yaml (1)
36-37: LGTM: Explicit Dockerfile path improves clarity.Making the Dockerfile location explicit is a good practice, especially in a monorepo with multiple build contexts.
docs/package.json (1)
37-44: LGTM: Build inputs properly configured.The NX inputs configuration correctly tracks the OpenAPI artifact dependency for incremental builds.
server/package.json (2)
64-64: LGTM: Substreams dependency added.The substreams package is properly declared as a dev dependency, aligning with the PR's objective of initial substreams integration.
143-146: LGTM: Build inputs extended appropriately.The additional inputs for
CHAIN_IDand contract outputs properly support the enhanced build pipeline.server/test/database.ts (6)
6-6: LGTM: Using execa for command execution.The
$template from execa provides a modern, promise-based interface for running external commands.
49-49: LGTM: AbortController for process lifecycle management.Using AbortController to coordinate cancellation across multiple processes is the appropriate pattern.
91-92: LGTM: Proper teardown with graceful process termination.Using
Promise.allSettledensures teardown completes even if individual processes fail to stop gracefully. The AbortController properly signals cancellation to all spawned processes.
60-70: Error handling may hide non-abort failures.Similar to the firehose process, the catch block suppresses all errors when aborted but also silently swallows errors from normal failures (e.g., substreams-sink-sql not found, connection issues).
🔧 Proposed fix for error handling
const substreams = $({ cancelSignal: controller.signal, forceKillAfterDelay: 33_333, cwd: "node_modules/@exactly/substreams", env: { SUBSTREAMS_ENDPOINTS_CONFIG_ANVIL: "localhost:10016" }, })`substreams-sink-sql run ${postgresURL}&schemaName=substreams substreams.yaml --plaintext --batch-block-flush-interval 1 --batch-row-flush-interval 0`.catch( - (error: unknown) => { - if (controller.signal.aborted) return; + (error) => { + if (controller.signal.aborted) return undefined; throw error; }, );Likely an incorrect or invalid review comment.
50-58: Error handling may hide non-abort failures.The catch block swallows all errors when the controller is aborted, but if the process fails during normal operation (e.g., fireeth not found, port conflict), the error is caught and the promise resolves successfully. This masks genuine startup failures.
🔧 Proposed fix for error handling
const firehose = $({ cancelSignal: controller.signal, forceKillAfterDelay: 33_333, })`fireeth start reader-node,merger,relayer,substreams-tier1 --advertise-chain-name=anvil --config-file= \ --data-dir=node_modules/@exactly/.firehose --reader-node-path=bash --reader-node-arguments=${'-c "\ - fireeth tools poll-rpc-blocks http://localhost:8545 0 | tsx script/firehose.ts"'}`.catch((error: unknown) => { - if (controller.signal.aborted) return; + fireeth tools poll-rpc-blocks http://localhost:8545 0 | tsx script/firehose.ts"'}`.catch((error) => { + if (controller.signal.aborted) return undefined; throw error; });This ensures errors are only suppressed when explicitly aborted, not when the process fails to start or crashes unexpectedly.
Likely an incorrect or invalid review comment.
65-65: No action required. The&schemaName=substreamsparameter format is correct per substreams-sink-sql documentation and properly appended to the PostgreSQL URL.contracts/test/mocks/BobExecute.s.sol (2)
39-39: LGTM!Using
getAddressinstead ofcreateAccountis appropriate here since it only computes the counterfactual address without deploying, which is correct for test setup where the account already exists from prior deployment scripts.
61-71: LGTM!The
BatchDeploycontract efficiently creates multiple accounts in its constructor. Using++ifor the loop increment is gas-efficient, and the deterministic address generation from420 + iis suitable for test purposes.server/test/anvil.ts (1)
159-160: LGTM!Adding
--tc BobExecuteScriptis necessary becauseBobExecute.s.solnow contains multiple contracts (BobExecuteScriptandBatchDeploy). The flag disambiguates which contract's entry point to execute..dockerignore (2)
6-18: LGTM!The granular ignore patterns are well-structured:
- Correctly preserves
run-latest.jsonbroadcast files needed for deployments while excluding other broadcast artifacts- Appropriately excludes substreams build artifacts (
target,abi,src/contracts,src/proto)- Reduces Docker build context size effectively
21-25: LGTM!Excluding documentation, logs, and build metadata files (
.tsbuildinfo,lcov.info,expo-env.d.ts) from the Docker context is appropriate—these are not needed at runtime..github/matcher.json (1)
81-91: LGTM!The rust-formatter matcher correctly captures rustfmt's diff output format with file and line information.
server/script/firehose.ts (1)
135-135: Verify intentional use oflibNumin two positions.The output format uses
libNumfor both positions 4 and 5:FIRE BLOCK ${num} ${hash} ${libNum} ${parentHash} ${libNum} ${timestamp} ${newPayload}. The input has 7 tokens but the output appears to replace position 5 withlibNumagain. Is this intentional to match a specific protocol version?contracts/package.json (1)
60-100: LGTM!The restructured NX targets create a clear dependency chain (
test:sizes→test:gas→test:slither→test:coverage) that ensures build artifacts are available for each subsequent step. The output declarations enable proper NX caching, andtest:allcorrectly aggregates the top-level checks.common/package.json (1)
44-50: LGTM!The additional inputs correctly establish dependencies between contract compilation outputs and wagmi code generation. This ensures the generated TypeScript bindings stay synchronized with contract ABIs when either the compiled outputs (
contracts/out/**/*.json) or local broadcast artifacts (contracts/broadcast/*/31337/*.json) change.substreams/.cargo/config.toml (1)
1-2: LGTM!The wasm32-unknown-unknown target configuration is correct for building Substreams WASM modules and aligns with the toolchain setup and cdylib crate type.
pnpm-workspace.yaml (1)
7-7: LGTM!Adding "substreams" to the workspace packages correctly integrates the new substreams package into the monorepo structure.
substreams/rustfmt.toml (1)
1-6: LGTM!The rustfmt configuration is well-structured with reasonable settings that complement the toolchain setup and support formatting of generated Rust sources.
substreams/rust-toolchain.toml (1)
2-2: Changechannelfrom "nightly" to "stable".The project uses Rust edition 2024 (stable as of February 20, 2025) and contains no nightly-specific features (
#![feature(...)]) or unstable attributes. The wasm32-unknown-unknown target and components (clippy, rustfmt) are all supported on the stable channel. Using stable improves reliability and build times.⛔ Skipped due to learnings
Learnt from: cruzdanilo Repo: exactly/exa PR: 627 File: substreams/rust-toolchain.toml:1-4 Timestamp: 2026-01-08T19:54:26.885Z Learning: Rust edition 2024 was released as stable on February 20, 2025 with Rust 1.85.0 and does not require nightly channel.substreams/buf.gen.yaml (1)
1-8: LGTM!The Buf code generation configuration is well-structured with pinned plugin versions for reproducibility. The output path aligns with the protobuf workflow referenced in
build.rs.substreams/Cargo.toml (2)
32-35: LGTM!The release profile settings are well-suited for WASM targets: LTO for dead code elimination,
opt-level = 's'for size optimization, and stripping debug info to minimize the binary size.
1-9: Consider using stable Rust 1.85+ instead of nightly for edition 2024 compatibility.Rust 2024 edition is stable (released February 2025 with Rust 1.85) and does not require the nightly channel. The toolchain is currently pinned to nightly, which adds an unnecessary constraint. Review whether nightly is needed for other features in this project; if not, switching to a stable toolchain would simplify CI and local setup.
⛔ Skipped due to learnings
Learnt from: cruzdanilo Repo: exactly/exa PR: 627 File: substreams/rust-toolchain.toml:1-4 Timestamp: 2026-01-08T19:54:26.885Z Learning: Rust edition 2024 was released as stable on February 20, 2025 with Rust 1.85.0 and does not require nightly channel..github/workflows/substreams-build.yaml (1)
1-38: LGTM!The workflow is well-structured with comprehensive tag patterns for semver extraction, proper permissions for package publishing and attestation, and build provenance attestation for supply chain security. Based on learnings, the inline YAML syntax is the preferred style for this repository.
substreams/proto/exa.proto (1)
9-17: LGTM!The
Pluginsmessage with nestedPluginis well-structured. Usingbytesfor Ethereum addresses anduint64for ordinal tracking is appropriate for the blockchain indexing use case.substreams/Dockerfile (2)
32-33: Default CMD is suitable for local development only.The
host.docker.internalhostname andsslmode=disableare appropriate for local development but won't work in production deployments. Ensure the CMD is overridden with proper connection strings in deployment configurations.
20-30: LGTM!Good security practices: runs as a non-root
substreamsuser withdumb-initfor proper signal handling. File ownership is correctly set during COPY operations.substreams/build.rs (1)
23-33: LGTM!The ABI extraction and Rust binding generation logic is well-structured with proper error context using
with_context.substreams/substreams.yaml (2)
14-42: LGTM!The module dependency graph is correctly structured:
map_exa_accounts→store_exa_accounts→map_exa_plugins→db_out. Input/output types align with the proto definitions inexa.proto.
53-82: Address configuration inconsistency between wagmi.config.ts and substreams.yaml for Base and Anvil networks.The Base and Anvil factory addresses in substreams.yaml are hardcoded values, but common/wagmi.config.ts loads these addresses dynamically from deployment scripts (
ExaAccountFactory.s.sol) for these networks. This creates a maintenance risk where the hardcoded addresses in substreams.yaml may become stale if deployments are re-run.For Op-sepolia and Base-sepolia, the hardcoded addresses align with the default used elsewhere. For Optimism, verify that the four configured factories are intentional (they may represent different deployments or rollout phases).
Recommendation: Either update substreams.yaml to use dynamic deployment records for Base and Anvil, or document why the hardcoded values should be used instead of the deployment script outputs.
substreams/src/lib.rs (1)
85-85: LGTM: Safe handling of optional timestamp.The use of
unwrap_or_default()ensures that a missing timestamp is handled gracefully by providing a default value rather than panicking.server/database/schema.ts (1)
88-91: Consider the implications of usingnumericfor block number and timestamp.The
blockstable usesnumericfor bothnumberandtimestamprather thanbigintor PostgreSQL's native timestamp types. Whilenumericprovides arbitrary precision, it may have performance implications for range queries and comparisons.Verify that
numericis the appropriate type choice for your query patterns and indexing needs, especially if you'll be performing frequent range scans on block numbers.substreams/package.json (1)
52-58: Thetest:clippytarget has an unsatisfied dependency on thepreparetask.The target depends on
"prepare"and"^prepare", but nopreparetarget is defined insubstreams/package.json. The bare"prepare"dependency will fail whentest:clippyis invoked, while"^prepare"may only succeed if substreams has dependencies with prepare tasks defined. Define apreparetarget in this package to satisfy the build dependency, or remove the localpreparereference if not needed.
There was a problem hiding this comment.
Actionable comments posted: 24
🤖 Fix all issues with AI agents
In @.changeset/breezy-planes-repeat.md:
- Line 5: The changeset summary line contains an emoji prefix ("✨ map exa
accounts"); edit the .changeset/breezy-planes-repeat.md changeset summary to
remove the emoji and ensure the summary is a plain, lowercase sentence in
imperative present tense (e.g., "map exa accounts") so it conforms to the
project's changeset format.
In @.changeset/rare-readers-tell.md:
- Line 5: Update the changeset summary "✨ store exa accounts" to remove the
emoji and use a lowercase imperative present-tense sentence; e.g., replace it
with "store exa accounts" (or rephrase to a clear imperative like "add exa
account storage" if more precise), ensuring no emoji or capitalization is
present.
In @.changeset/violet-poems-heal.md:
- Line 5: Update the changeset message (the line currently "✨ insert exa plugins
into database") to be a complete lowercase imperative present-tense sentence by
adding the article and adjusting word order, e.g., "insert exa plugins into the
database".
In @.github/workflows/test.yaml:
- Around line 37-41: The workflow step that downloads binaries uses brittle
commands without fail-fast behavior, verification, or guaranteed PATH export;
update the shell block to enable strict error handling with set -euo pipefail,
validate that the jq/curl asset lookup returns a non-empty URL before attempting
the second curl (and fail with a clear error if no asset is found), add checksum
or signature verification for each downloaded archive before extraction (fail if
verification fails), and append the created bin directory to the runner PATH
with echo "$HOME/.local/bin" >> $GITHUB_PATH so the installed binaries are
available to later steps; refer to the existing mkdir -p $HOME/.local/bin && cd
$HOME/.local/bin loop and the nested curl | jq asset selection to locate where
to add these changes.
In @contracts/test/mocks/BobExecute.s.sol:
- Line 39: bobAccount is only assigned via factory.getAddress() (so not
deployed) but run() calls executeProposal() on it; either deploy bobAccount in
setUp() by replacing factory.getAddress(...) with factory.createAccount(...)
(assigning the returned deployed IExaAccount to bobAccount) or explicitly
deploy/create bobAccount at the start of run() before any executeProposal()
calls; update references to bobAccount accordingly so executeProposal() operates
on a deployed account and not the address-only value.
In @server/database/schema.ts:
- Around line 88-100: Add indexes for query performance by updating the table
definitions for blocks and exaPlugins: in the blocks table (symbol: blocks) add
an index on the timestamp column (symbol: timestamp) and in exaPlugins (symbol:
exaPlugins) add an index on the account column (symbol: account) by extending
the table factory callbacks to include index(...) entries; also import index
from "drizzle-orm/pg-core" alongside existing imports.
- Around line 72-77: The cursors table definition lacks NOT NULL constraints for
fields required by the sink; update the substreams.table("cursors", ...) schema
so that the column definitions for cursor, blockNum, and blockId call .notNull()
(e.g., change text("cursor") to text("cursor").notNull(), bigint("block_num", {
mode: "bigint" }) to bigint("block_num", { mode: "bigint" }).notNull(), and
text("block_id") to text("block_id").notNull()) while leaving id as the primary
key.
In @server/drizzle.config.ts:
- Line 8: Missing migration files for the "substreams" schema: run drizzle-kit
to generate migrations that create the "substreams" schema and its tables (the
schema is defined via pgSchema() in schema.ts and schemaFilter currently
includes "substreams"), commit the generated migration files, and ensure the
migration config in drizzle.config.ts (schemaFilter) aligns with the generated
migrations so the database can be migrated from a clean state using drizzle-kit.
In @server/script/firehose.ts:
- Line 127: The destructured variables use abbreviated names; replace the
variables in the array destructuring (currently declared via const [, , num,
hash, libNum, parentHash, payloadBase64] = parts) with descriptive names such as
blockNumber and lastIrreversibleBlockNumber (keep payloadBase64), and then
update all subsequent references in this file to use blockNumber and
lastIrreversibleBlockNumber instead of num and libNum; retain or remove the
eslint-disable comment as appropriate after renaming.
- Around line 130-131: The variable name "ts" is an unclear abbreviation; rename
it to a descriptive identifier such as "extractedTimestamp" where it's declared
from extractTimestamp(buffer) and update the subsequent line that computes
"timestamp" to reference extractedTimestamp instead of ts; ensure all
occurrences in the surrounding scope (e.g., in the function or block containing
extractTimestamp(buffer)) are updated to the new name to keep code consistent
with naming guidelines.
- Around line 59-64: The readBytes method can silently truncate when the decoded
length exceeds remaining buffer; before updating this.offset or calling
subarray, validate the length from readVarint() (ensure it's a non-negative
integer and <= this.buffer.length - this.offset) and throw a descriptive error
(e.g., "Unexpected end of buffer in readBytes") if it would overflow; only after
validation advance this.offset and return this.buffer.subarray(start,
this.offset). Include the check in the readBytes function to prevent silent data
truncation.
- Around line 100-104: The loop that parses timestamp fields using
tsCursor.readTag assumes fields 1 and 2 are varints but never checks
tag.wireType; update the parsing inside the for-loop to verify tag.wireType ===
0 before calling tsCursor.readVarint for fieldNumber 1 and 2, and if the wire
type is different, either call tsCursor.skip(tag.wireType) or surface an
error/log (choose consistent behavior with remaining parsing). Reference the
existing symbols tsCursor.readTag, tag.fieldNumber, tag.wireType,
tsCursor.readVarint, and tsCursor.skip when applying the change.
- Line 96: Rename the abbreviated variable tsCursor to timestampCursor where
it's declared (new ProtoCursor(timestampBuffer)) and update all subsequent
references to use timestampCursor (e.g., the usages around the existing lines
referencing tsCursor). Ensure you update any related variable names or
destructuring that depend on tsCursor to match timestampCursor and preserve the
ProtoCursor(timestampBuffer) initialization and behavior.
- Line 135: The console.log template repeats libNum twice; locate the statement
that prints FIRE BLOCK (the console.log using variables num, hash, libNum,
parentHash, libNum, timestamp, newPayload) and replace the second libNum with
the correct variable from the surrounding scope (e.g., parentNum or parentLibNum
as defined nearby) so the seven output tokens match the seven input values (num,
hash, libNum, parentHash, <correctVariable>, timestamp, newPayload); verify the
chosen variable exists and adjust the template accordingly.
- Around line 141-144: Remove the unnecessary else and make the
malformed-FIRE-BLOCK case an early return: when detecting the malformed block
(check using parts.length and trimmedLine), log the error with console.error and
immediately return from the surrounding function, instead of using an else block
followed by a standalone return; update the code around the malformed handling
in server/script/firehose.ts (the console.error call that mentions FIRE BLOCK
and the subsequent return) to consolidate into a single early-return branch.
In @substreams/buf.gen.yaml:
- Around line 1-8: Add a new buf.yaml in the substreams/ directory so buf can
find your .proto files; create substreams/buf.yaml containing version v1 and a
modules entry that points to the proto module (modules: - path: proto) so that
the existing buf.gen.yaml plugins (remote neoeinstein-prost and
neoeinstein-prost-crate) can locate files like substreams/proto/exa.proto for
compilation.
In @substreams/build.rs:
- Around line 25-36: The closure that generates contract bindings calls
Abigen::new(...).generate()?.write_to_file(format!("src/contracts/{mod_name}.rs"))
but doesn't ensure the target directory exists; add a call to
create_dir_all("src/contracts") (propagating errors with ?) before calling
write_to_file (e.g., inside the contracts.iter closure before Abigen::... or
once before the loop) so the src/contracts directory is created if missing.
- Around line 50-67: The rustfmt invocation silently ignores glob errors because
of filter_map(Result::ok); change this to propagate or surface glob errors by
collecting the iterator of Results and handling errors instead of dropping them:
gather the two glob iterators (the glob("src/contracts/**/*.rs")? and
glob("src/proto/**/*.rs")?) into a single iterator, collect into a
Result<Vec<PathBuf>, glob::GlobError> (e.g. .chain(...).collect::<Result<Vec<_>,
_>>()?), then either propagate the error with ? or call expect with a clear
message, and pass that Vec to Command::new("rustfmt").args(paths) so any glob
I/O/pattern errors are not silently ignored.
In @substreams/Dockerfile:
- Around line 32-33: The Dockerfile currently lacks a HEALTHCHECK and the
default CMD hardcodes host.docker.internal which only works on Docker Desktop;
add a HEALTHCHECK instruction that probes the service exposed by
substreams-sink-sql (e.g., TCP check or an HTTP/health endpoint if available)
using the HEALTHCHECK directive and sensible interval/retries, and change the
CMD to use an environment variable (e.g., POSTGRES_DSN or DB_HOST) with a
fallback (localhost) instead of host.docker.internal so users can override the
DSN at runtime; reference ENTRYPOINT ["dumb-init", "/app/substreams-sink-sql",
"run"] and CMD to update them accordingly.
In @substreams/proto/exa.proto:
- Line 3: Move the proto file into a directory that matches its package name:
create substreams/proto/exa/ and relocate substreams/proto/exa.proto →
substreams/proto/exa/exa.proto, keeping the package declaration `package exa;`
unchanged; then update any build/config references to the proto path (e.g.,
buf.gen.yaml, substreams.yaml, and any import statements that reference
substreams/proto/exa.proto) so they point to the new
substreams/proto/exa/exa.proto location to satisfy Buf conventions and eliminate
linter warnings.
In @substreams/rust-toolchain.toml:
- Line 2: Update the rust-toolchain.toml to use the stable toolchain by changing
the channel value from "nightly" to "stable" (modify the channel = "nightly"
line to channel = "stable"); after updating, run cargo check/build (including
any wasm32-unknown-unknown target builds) to confirm there are no nightly-only
features or attributes remaining and revert only if a nightly-gated dependency
is discovered.
In @substreams/src/lib.rs:
- Line 1: Add a brief comment immediately above the crate-level attribute
#![expect(clippy::not_unsafe_ptr_arg_deref)] explaining why the Clippy lint is
suppressed (e.g., that the suppression is required because the substreams
framework generates code via macros that intentionally uses patterns triggering
clippy::not_unsafe_ptr_arg_deref and those usages are safe/contextually
validated), and mention any specific macro or generated module (such as the
substreams codegen or the procedural macro name) and a reference to an issue/PR
or test that verifies the generated code is safe; keep the comment concise and
colocated with the attribute so future maintainers understand the reason for the
global suppression.
- Around line 27-44: The map_exa_accounts function creates unnecessary
intermediate allocations by collecting decoded hex addresses into a Vec<Vec<u8>>
and then immediately converting them to slices; instead, decode the addresses
once into a local variable (e.g., let decoded: Vec<Vec<u8>> =
serde_qs::from_str::<Factories>(¶ms)?.factories.iter().map(Hex::decode).collect::<Result<_,
_>>()?), then pass decoded.iter().map(Vec::as_slice).collect::<Vec<_>>() into
block.events::<ExaAccountInitialized>(), avoiding the transient two-step
collection and redundant allocations while keeping the existing error
propagation via ?.
In @substreams/substreams.yaml:
- Around line 53-77: Update the factory address in the base-sepolia network
config so it uses the EIP-55 checksummed form instead of all-lowercase: locate
the base-sepolia entry and the params.map_exa_accounts value containing the
factory address `343efd4e2073afc4cbe304b5dd50f8d6480e2dd2` and replace it with
its checksummed EIP-55 variant (generate it programmatically using
ethers.utils.getAddress or web3.utils.toChecksumAddress to avoid manual error).
Ensure only the casing changes and the hex value remains the same.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsubstreams/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (36)
.changeset/breezy-planes-repeat.md.changeset/rare-readers-tell.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.dockerignore.github/matcher.json.github/workflows/server-build.yaml.github/workflows/substreams-build.yaml.github/workflows/test.yaml.vscode/extensions.jsoncommon/package.jsoncontracts/package.jsoncontracts/test/mocks/BobExecute.s.solcspell.jsondocs/package.jsonpnpm-workspace.yamlserver/Dockerfileserver/database/schema.tsserver/drizzle.config.tsserver/package.jsonserver/script/firehose.tsserver/test/anvil.tsserver/test/database.tsserver/test/e2e.tssubstreams/.cargo/config.tomlsubstreams/.gitignoresubstreams/Cargo.tomlsubstreams/Dockerfilesubstreams/buf.gen.yamlsubstreams/build.rssubstreams/package.jsonsubstreams/proto/exa.protosubstreams/rust-toolchain.tomlsubstreams/rustfmt.tomlsubstreams/src/lib.rssubstreams/substreams.yaml
🧰 Additional context used
📓 Path-based instructions (11)
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/drizzle.config.tsserver/test/e2e.tsserver/test/database.tsserver/database/schema.tsserver/test/anvil.tsserver/script/firehose.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/drizzle.config.tsserver/test/e2e.tsserver/test/database.tsserver/database/schema.tsserver/test/anvil.tsserver/script/firehose.tscontracts/test/mocks/BobExecute.s.sol
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/drizzle.config.tsserver/test/e2e.tsserver/test/database.tsserver/database/schema.tsserver/test/anvil.tsserver/script/firehose.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/drizzle.config.tsserver/test/e2e.tsserver/test/database.tsserver/database/schema.tsserver/test/anvil.tsserver/script/firehose.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/drizzle.config.tsserver/test/e2e.tsserver/test/database.tsserver/database/schema.tsserver/test/anvil.tsserver/script/firehose.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/drizzle.config.tsserver/test/e2e.tsserver/test/database.tsserver/database/schema.tsserver/test/anvil.tsserver/script/firehose.ts
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/rare-readers-tell.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.changeset/breezy-planes-repeat.md
**/+(app|src)/**
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use kebab-case for directory names and public Expo router routes
Files:
substreams/src/lib.rs
server/database/schema.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/database/schema.ts: Database schema must be defined inserver/database/schema.tsas the single source of truth for database structure
Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Files:
server/database/schema.ts
contracts/**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/contracts.mdc)
contracts/**/*.sol: Import order must be sorted alphabetically and grouped by type (openzeppelin, solady, internal, etc.), enforced by forge fmt
All functions and state variables must have explicit visibility modifiers (public, private, internal, external)
Use custom errors instead of require() statements with string messages; require() with messages is forbidden
console.log is forbidden in contract code
All external and public functions that modify state must be protected by a reentrancy guard, unless explicitly proven unnecessary
Use modifiers like onlyOwner or function-level checks for privileged functions; default to strictest access level (private > internal > external > public)
Use named parameters in mappings for clarity (e.g., mapping(address user => uint256 balance))
All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Follow standard Solidity import order: external libraries (openzeppelin, solady) first, then project-internal contracts
All public and external functions and state variables must have complete natspec comments (@notice, @dev, @param, @return)
Use custom errors instead of require statements with string messages for better gas efficiency and error information
Internal and private variables should be prefixed with an underscore (_); function arguments should not be prefixed
Use constant keyword for true constants; use immutable for configurable values like contract addresses
Minimize writes to storage; read data into memory or calldata whenever possible for gas optimization
Use smallest integer size that is safe for the variable (e.g., uint32 for timestamps) and be aware of struct packing for gas efficiency
Prefer using the solady library over openzeppelin where possible, as it is highly optimized for gas
Correctly label functions as view or pure when they do not modify state
contracts/**/*.sol: Smart contracts: use custom errorserror MyError();instead of s...
Files:
contracts/test/mocks/BobExecute.s.sol
**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
**/*.sol: Use camelCase for Solidity variables and functions
Use PascalCase for Solidity contracts and structs
Files:
contracts/test/mocks/BobExecute.s.sol
🧠 Learnings (33)
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to server/**/*.{ts,tsx} : Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use `c.var.db`
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/migrations/** : All schema changes must be accompanied by a migration file generated by `drizzle-kit`; never edit the database schema manually
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Database schema must be defined in `server/database/schema.ts` as the single source of truth for database structure
Applied to files:
server/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Drizzle ORM best practices using ESLint `plugin:drizzle/all` configuration, including requiring `where` clauses for `update` and `delete` operations
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/rare-readers-tell.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.changeset/breezy-planes-repeat.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries
Applied to files:
.changeset/rare-readers-tell.md.changeset/violet-poems-heal.md.changeset/breezy-planes-repeat.md
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
server/test/e2e.tscontracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use `nx` for running tasks (build, lint, test, e2e) instead of underlying tooling directly
Applied to files:
substreams/package.jsoncontracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Applies to {src/generated/**,server/generated/**,contracts/broadcast/**,contracts/cache/**} : Do not manually edit files in generated directories: `src/generated/`, `server/generated/`, `contracts/broadcast/`, and `contracts/cache/`
Applied to files:
.dockerignorecommon/package.json
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Server (exactly/server) uses Hono (framework), Node.js, Drizzle ORM (database), and PostgreSQL
Applied to files:
server/test/database.ts
📚 Learning: 2026-01-08T20:26:33.396Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .vscode/extensions.json:6-6
Timestamp: 2026-01-08T20:26:33.396Z
Learning: For validating VS Code extension IDs in this repository, check both the official VS Code Marketplace (marketplace.visualstudio.com) and the Open VSX registry (open-vsx.org) before flagging an extension as invalid. Open VSX is a widely-used alternative registry, so a valid ID may appear in either registry. If an ID is not present in both registries, flag it as invalid. Apply this check specifically when validating the IDs listed in .vscode/extensions.json.
Applied to files:
.vscode/extensions.json
📚 Learning: 2026-01-08T19:54:10.673Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: substreams/src/lib.rs:54-71
Timestamp: 2026-01-08T19:54:10.673Z
Learning: In substreams/src/lib.rs in the map_exa_plugins function, the deduplication logic using `!seen.insert(address.to_vec())` is intentional. The first plugin installed for a new account is an owner plugin (not an exa plugin), so it must be filtered out. The negation ensures the first plugin is skipped and subsequent plugins (the actual exa plugins) are kept.
Applied to files:
substreams/src/lib.rs
📚 Learning: 2026-01-08T19:45:38.852Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .github/workflows/substreams-build.yaml:2-2
Timestamp: 2026-01-08T19:45:38.852Z
Learning: In the exactly/exa repository, prefer concise inline YAML syntax (e.g., `on: { push: { tags: ["pattern"] }, workflow_dispatch: {} }`) over expanded multi-line format in GitHub workflow files, even if it triggers YAMLlint warnings about formatting.
Applied to files:
.github/workflows/substreams-build.yaml
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Applied to files:
contracts/package.json
📚 Learning: 2026-01-08T19:46:37.829Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: contracts/test/mocks/BobExecute.s.sol:61-71
Timestamp: 2026-01-08T19:46:37.829Z
Learning: Natspec documentation requirements do not apply to test/mock/dev scripts (files in contracts/test/ or with .s.sol extension). Only production contract code in contracts/src/ requires complete natspec comments.
Applied to files:
contracts/package.jsoncontracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run contract tests using `pnpm --filter plugin test` and check formatting using `pnpm --filter plugin test:fmt`
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to contracts/**/*.test.sol : Smart contracts testing: fuzzing required for inputs and gas snapshots mandatory
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Use fuzz testing (testFuzz_...) extensively for functions with numerical or address inputs to cover a wide range of scenarios
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Rely on `pnpm` scripts for all tasks; all setup, testing, and generation must be automated
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run all tests using `pnpm test`, which includes TypeScript compilation, ESLint, spell checking, markdown linting, and contract tests
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Test files must end with .t.sol and mirror the name of the contract they test (e.g., ExaPlugin.sol -> ExaPlugin.t.sol)
Applied to files:
contracts/package.json
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Do not use abbreviations or cryptic names; write out full words like `error`, `parameters`, `request` instead of `err`, `params`, `req`
Applied to files:
server/script/firehose.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
Applied to files:
server/script/firehose.tscspell.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All public and external functions and state variables must have complete natspec comments (notice, dev, param, return)
Applied to files:
contracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Use vm.prank to simulate calls from different addresses; avoid changing msg.sender through other means
Applied to files:
contracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words from names like `data`, `state`, `manager`, `engine`, `object`, `entity`, and `instance`
Applied to files:
cspell.json
📚 Learning: 2026-01-08T19:54:26.885Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: substreams/rust-toolchain.toml:1-4
Timestamp: 2026-01-08T19:54:26.885Z
Learning: Rust edition 2024 was released as stable on February 20, 2025 with Rust 1.85.0 and does not require nightly channel.
Applied to files:
substreams/rust-toolchain.toml
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : Use custom errors instead of require statements with string messages for better gas efficiency and error information
Applied to files:
substreams/build.rs
🧬 Code graph analysis (1)
server/test/database.ts (1)
server/database/schema.ts (1)
substreams(70-70)
🪛 Buf (1.62.1)
substreams/proto/exa.proto
3-3: Files with package "exa" must be within a directory "exa" relative to root but were in directory "substreams/proto".
(PACKAGE_DIRECTORY_MATCH)
🪛 Checkov (3.2.334)
substreams/Dockerfile
[low] 1-33: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
🪛 YAMLlint (1.37.1)
.github/workflows/substreams-build.yaml
[warning] 2-2: truthy value should be one of [false, true]
(truthy)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
🔇 Additional comments (45)
substreams/.cargo/config.toml (1)
1-2: LGTM! Standard WASM build configuration.The build target configuration is correct and aligns with the wasm32-unknown-unknown target specified in rust-toolchain.toml.
pnpm-workspace.yaml (1)
7-7: LGTM! Correctly adds substreams to workspace.The addition of the substreams package to the workspace is properly configured and consistent with the project structure.
substreams/.gitignore (1)
1-8: LGTM!The ignore patterns correctly exclude generated artifacts (codegen outputs, build directories, package files, and logs) from version control.
server/test/anvil.ts (1)
159-160: LGTM!The test case selector correctly targets the specific
BobExecuteScripttest case, aligning with the related contract changes mentioned in the AI summary.cspell.json (2)
6-11: LGTM!The new ignore paths correctly exclude CI configuration, build artifacts, patches, and lockfiles from spell checking.
14-138: LGTM!The dictionary additions cover legitimate technical terms related to Rust, Protocol Buffers, and Substreams tooling introduced in this PR.
.changeset/red-toys-pump.md (1)
5-5: Consider a more complete sentence for clarity.While "map exa plugins" is imperative present tense, consider making it a fuller sentence for clarity (e.g., "map exa plugins to database schema" or similar, depending on what the mapping does).
Based on coding guidelines requiring lowercase sentence in imperative present tense.
⛔ Skipped due to learnings
Learnt from: CR Repo: exactly/exa PR: 0 File: .cursor/rules/style.mdc:0-0 Timestamp: 2025-12-30T15:03:28.449Z Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summariesLearnt from: CR Repo: exactly/exa PR: 0 File: .cursor/rules/docs.mdc:0-0 Timestamp: 2025-12-23T19:57:07.685Z Learning: Use clear, concise, and direct writing with imperative verbs (action-oriented language) and consistent terminology that matches the application's UI and codebase.github/workflows/server-build.yaml (1)
36-37: LGTM! Explicit Dockerfile path improves clarity.The addition of an explicit
fileparameter alongside the existingcontextis a good practice for monorepo setups, making it clear where the Dockerfile resides while maintaining the build context at the repository root.docs/package.json (1)
38-44: LGTM! Proper nx inputs configuration for incremental builds.The
inputsconfiguration correctly tracks the generated OpenAPI file as a build input, ensuring nx cache invalidation when the file changes. This complements the existingdependsOnconfiguration for proper incremental build behavior.substreams/Cargo.toml (4)
7-9: LGTM! Correct crate type for WASM/FFI targets.The
cdylibcrate type is appropriate for building dynamic libraries that can be loaded by non-Rust code, including WASM targets used in substreams.
32-35: LGTM! Appropriate release optimizations for WASM targets.The release profile is well-configured for WASM/substreams use cases:
lto = trueenables link-time optimization for smaller binariesopt-level = 's'optimizes for size rather than speedstrip = "debuginfo"removes debug symbols to reduce binary sizeThese settings are ideal for WASM modules where binary size is critical.
1-6: No changes needed—Rust edition "2024" is stable.Rust edition 2024 was released as stable on February 20, 2025 with Rust 1.85.0 and does not require the nightly channel. The manifest correctly specifies
edition = "2024".Likely an incorrect or invalid review comment.
11-21: Update prost and prost-types to 0.14.1.Most dependencies are reasonably constrained and current. However, prost and prost-types are pinned to version 0.13, while the latest stable releases are 0.14.1 (June 2025). Upgrade these for the latest improvements and stability. No critical security vulnerabilities exist in the currently specified versions, and the current major-version constraints are appropriate and standard practice.
.vscode/extensions.json (1)
6-6: Extension IDs are valid. Bothrust-lang.rust-analyzeranddrblury.protobuf-vscare confirmed in the VS Code Marketplace and Open VSX registry.server/test/e2e.ts (1)
7-7: LGTM!The Sentry mock follows the established pattern in this file, correctly preserving the original
captureExceptionbehavior while adding console logging for test observability. The type-only import is appropriate per coding guidelines.Also applies to: 58-67
contracts/test/mocks/BobExecute.s.sol (1)
61-71: LGTM!The
BatchDeploycontract efficiently batches account creation for testing purposes. The sequential address generation starting at 420 is clear and the loop bounds are safe. Based on learnings, natspec is not required for test/mock contracts.common/package.json (1)
44-50: LGTM!The new
dependentTasksOutputFilesentries correctly wire contract build outputs into the wagmi code generation pipeline, ensuring regeneration when contract artifacts change.server/test/database.ts (2)
49-70: LGTM!The process orchestration is well-structured:
AbortControllerenables coordinated shutdown across firehose and substreams processes.- Error suppression when aborted prevents noisy failures during teardown.
forceKillAfterDelayensures processes are terminated even if they hang.- The substreams schema name aligns with the schema definition in
server/database/schema.ts.
90-93: LGTM!Using
Promise.allSettledfor teardown ensures all cleanup operations complete regardless of individual failures.substreams/Dockerfile (1)
1-18: LGTM!The build stage is well-structured:
- Uses
pipefailfor safer bash execution.- Consolidates apt-get operations to reduce layers.
- Pins specific versions for reproducibility (pnpm 9.15.9, foundry v1.3.6).
- Uses build cache for pnpm store.
- Runs as non-root in runtime stage for security.
.github/workflows/substreams-build.yaml (1)
1-38: LGTM!The workflow is well-structured for Docker image publishing:
- Proper permissions for GHCR push and attestations.
- Multiple tag patterns for flexible version referencing.
- Build provenance attestation enhances supply chain security.
- Explicit Dockerfile path aligns with the new
substreams/Dockerfile.The inline YAML syntax is consistent with repository conventions (per learnings).
.dockerignore (2)
6-18: LGTM!The granular ignore patterns correctly exclude:
- Broadcast artifacts except
run-latest.json(negation pattern works correctly).- Local chain (31337) and dry-run broadcasts.
- Generated directories (
contracts/out,substreams/target,substreams/src/contracts,substreams/src/proto).This reduces Docker build context size without losing necessary artifacts.
21-25: LGTM!Excluding
*.md,*.log,*.tsbuildinfo,expo-env.d.ts, andlcov.infoappropriately keeps documentation, logs, and TypeScript build metadata out of the Docker context..github/matcher.json (1)
64-92: LGTM! Well-structured problem matchers for Rust tooling.The regex patterns correctly handle ANSI escape sequences and capture the relevant groups for GitHub Actions problem matching. The two-pattern approach for the "rust" matcher properly handles both the error/warning message line and the subsequent file location line, which aligns with Rust compiler output format.
contracts/package.json (1)
60-101: LGTM! Well-structured test dependency chain.The refactored nx targets establish a clear dependency chain (test:sizes → test:gas → test:slither → test:coverage) that ensures proper build artifacts are available for each subsequent test phase. The explicit output declarations improve caching behavior over the previous implicit cache configuration.
substreams/rustfmt.toml (1)
1-6: LGTM! Reasonable Rustfmt configuration.The configuration choices are appropriate for the project:
- 2-space indentation aligns with the TypeScript/JavaScript formatting
- 120 character line width provides good balance between readability and compactness
- Crate-level import granularity and field init shorthand are idiomatic Rust practices
Ensure CI workflows run
cargo fmt --checkto enforce these formatting rules.server/package.json (2)
64-64: LGTM!The workspace dependency addition correctly integrates the new substreams package into the server's development toolchain.
142-146: LGTM!The new NX inputs correctly model the dependencies for broadcast generation:
- The
CHAIN_IDenvironment variable ensures cache invalidation when targeting different chains- The contract outputs dependency ensures broadcasts regenerate when contracts change
substreams/proto/exa.proto (1)
1-17: LGTM!The protobuf definitions are well-structured:
- Appropriate use of
bytesfor Ethereum addresses- Clean nested message structure for
Plugin- Proper field numbering and types
substreams/package.json (4)
1-11: LGTM!The package metadata and scripts are well-configured:
- Build uses appropriate cargo flags
- Test suite correctly uses
nx(per learnings) and includes Rust quality checks (clippy, fmt) and proto validation- The
--deny warningsflag in clippy ensures strict code quality
12-16: LGTM!The devDependencies correctly include tooling for protobuf generation, contract integration, and linting.
20-43: LGTM!The NX build target is properly configured:
- Comprehensive input tracking ensures correct cache invalidation
- Dependencies on
rustupand upstream builds are appropriate- WASM output path matches standard Rust target structure for
wasm32-unknown-unknown
44-64: LGTM!The NX test target configuration properly orchestrates the test suite:
- Aggregates quality checks (clippy, fmt, protolint) through a noop executor
- Ensures proper setup with
prepareandrustupdependencies before running checkssubstreams/substreams.yaml (4)
1-13: LGTM!The manifest header and protobuf configuration are correctly structured:
- External imports use pinned release versions for reproducibility
- Protobuf configuration properly references the proto file and import paths
- Default network set to
anvilis appropriate for development
14-42: LGTM!The module pipeline is well-structured:
- Clear data flow: accounts → store → plugins → database changes
- Appropriate module types (map for transformations, store for state)
- Correct input/output type references to defined proto messages
43-52: LGTM!The sink and binaries configuration correctly integrates with the server:
- Sink references the server's generated SQL schema, ensuring consistency
- Postgres engine matches the server's database
- Binary path aligns with the package.json build output
78-82: LGTM!The anvil network configuration is appropriate for local development with
initialBlock: 0and a single factory address.substreams/build.rs (2)
1-23: LGTM!The imports and rerun triggers are properly configured:
- Comprehensive rerun-if-changed directives ensure correct cache invalidation
- Contract definitions clearly map module names to contract types
- Per-contract rerun triggers correctly track ABI changes
38-48: LGTM!The module file generation correctly:
- Exports all contract modules
- Suppresses clippy warnings appropriate for generated code
- Uses idiomatic Rust string formatting
substreams/src/lib.rs (3)
46-51: LGTM!The store function correctly persists account flags for later lookup in
map_exa_plugins.
53-71: This is an intentional and appropriate pattern, not a concern.The
?operator insidefilter_mapdoesn't represent problematic silent error handling—it's the standard Rust idiom for filtering. Not all logs arePluginInstalledevents, andmatch_and_decodereturningNonefor non-matching logs is expected behavior. Similarly, store lookups may fail for addresses not in the store, which is also expected in the filtering context. The handler correctly returnsOk(Plugins { ... })with the successfully filtered results, making this pattern appropriate for Ethereum log filtering where heterogeneous logs and partial datasets are normal.Likely an incorrect or invalid review comment.
73-88: Confirm timestamp handling and blocks row creation strategy.The code creates
blocksrows only when plugins are detected in a block and defaults missing timestamps to 0:
Line 85:
unwrap_or_default()will write timestamp 0 to the database ifclock.timestampis None. The schema requirestimestamp NOT NULL, so a missing timestamp becomes a zero value—verify this doesn't impact downstream queries that assume valid timestamps.Lines 82–86: Blocks without detected plugins won't have a row created, effectively skipping empty blocks. Confirm this aligns with your indexing strategy (the CHANGELOG notes "skip empty alchemy blocks," suggesting this is intentional).
server/database/schema.ts (3)
3-17: LGTM!Import additions support the new substreams schema definitions below.
36-36: LGTM!Destructuring pattern improves consistency with other table definitions.
79-86: Consider whether nullable fields in substreams_history are appropriate.All fields except
idare nullable. Verify this aligns with the substreams history tracking requirements, as missing operation type (op), table name, or block number could make history rows difficult to interpret.
There was a problem hiding this comment.
Actionable comments posted: 20
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
server/test/database.ts (1)
39-47: Restoreprocess.stdout.writeviatry/finallyto avoid leaking global state on failures.
IfpushSchema(...)/waitOn(...)rejects, Line 47 never runs and the test process can remain permanently muted.Proposed fix
const postgresURL = "postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable"; // cspell:ignore sslmode const stdoutWrite = process.stdout.write; // eslint-disable-line @typescript-eslint/unbound-method const database = drizzle(postgresURL, { schema }); - process.stdout.write = () => true; - await Promise.all([ - pushSchema(schema, database as never).then(({ apply }) => apply()), - waitOn({ resources: ["tcp:localhost:8545"], timeout: 33_333 }), - rm("node_modules/@exactly/.firehose", { recursive: true, force: true }), - ]); - process.stdout.write = stdoutWrite; + try { + process.stdout.write = () => true; + await Promise.all([ + pushSchema(schema, database as never).then(({ apply }) => apply()), + waitOn({ resources: ["tcp:localhost:8545"], timeout: 33_333 }), + rm("node_modules/@exactly/.firehose", { recursive: true, force: true }), + ]); + } finally { + process.stdout.write = stdoutWrite; + }
🤖 Fix all issues with AI agents
In @.changeset/breezy-planes-repeat.md:
- Line 5: Update the changeset summary to remove the emoji prefix and ensure it
is a lowercase imperative present-tense sentence; replace the current line "✨
map exa accounts" with the plain text summary "map exa accounts" (no emoji,
punctuation, or capitalization).
In @.changeset/rare-readers-tell.md:
- Around line 1-5: Update the changeset summary string "✨ store exa accounts" to
remove the leading emoji and ensure it is a lowercase sentence (e.g., change it
to "store exa accounts") so the entry for "@exactly/substreams" complies with
changeset summary style.
In @.changeset/red-toys-pump.md:
- Around line 1-5: Remove the leading emoji from the changeset summary so it
complies with style: replace the summary line "✨ map exa plugins" with "map exa
plugins" (no emoji) in the .changeset/red-toys-pump.md file and keep the rest of
the changeset metadata unchanged.
In @.changeset/violet-poems-heal.md:
- Line 5: Edit the changeset summary line that currently reads "✨ insert exa
plugins into database" and remove the emoji prefix so the summary is plain text
and in imperative present tense: "insert exa plugins into database"; ensure no
other gitmojis or extra punctuation remain in the changeset header.
In @.dockerignore:
- Around line 6-9: The negation for "!**/contracts/broadcast/**/run-latest.json"
is ineffective because the later pattern "**/contracts/broadcast/*/31337"
excludes the parent directory containing run-latest.json; remove or adjust the
directory-ignore pattern so the negation can take effect — specifically delete
or comment out the "**/contracts/broadcast/*/31337" entry (or narrow it to not
exclude the 31337 directory that holds run-latest.json) and keep the JSON-level
ignore "**/contracts/broadcast/**/*.json" plus the explicit include
"!**/contracts/broadcast/**/run-latest.json" so run-latest.json is included in
the Docker context.
In @.github/workflows/docs.yaml:
- Line 14: Replace the single-line inline mapping "env: { NX_DAEMON: false }"
with a linter-friendly format; either remove the inner spaces to "env:
{NX_DAEMON: false}" or, preferably, expand to a multi-line mapping: "env:"
followed by an indented "NX_DAEMON: false" to satisfy YAML linting.
In @.github/workflows/test.yaml:
- Around line 38-42: The step that downloads and extracts binaries into
$HOME/.local/bin doesn't export that directory to the PATH, so later steps can't
find the executables; after the loop that extracts into $HOME/.local/bin, append
that directory to the GitHub Actions PATH by adding a command like: echo
"$HOME/.local/bin" >> $GITHUB_PATH (or export PATH="$HOME/.local/bin:$PATH"
within the same run), ensuring the change is persisted for subsequent workflow
steps that need the downloaded binaries.
In @server/script/firehose.ts:
- Around line 59-64: The readBytes() method advances offset by the decoded
length without verifying bounds, risking reading past buffer; add a bounds check
like skip() does: compute length from readVarint(), ensure this.offset + length
<= this.buffer.length and throw an error (or handle) if not, then proceed to
slice/subarray and advance this.offset; update readBytes() (and mirror the error
type/message used by skip()) to prevent out-of-range reads.
- Around line 100-104: The loop that parses the timestamp tags uses
tsCursor.readVarint() for fieldNumber 1 and 2 without verifying the protobuf
wire type; update the logic in the loop that calls tsCursor.readTag() so that
before calling tsCursor.readVarint() you check tag.wireType === 0 (varint) for
fields 1 (seconds) and 2 (nanoseconds) and only call readVarint() when true,
otherwise call tsCursor.skip(tag.wireType) to safely skip malformed or
unexpected wire types; keep the existing assignments to seconds and nanoseconds
unchanged.
- Around line 130-131: The local variable `ts` is an abbreviated name; replace
it with a descriptive name (e.g., `extractedTimestamp` or `rawTimestamp`) where
it is declared from extractTimestamp(buffer) and in the ternary that sets
`timestamp` so the code becomes: const extractedTimestamp =
extractTimestamp(buffer); const timestamp = extractedTimestamp === null ? "0" :
extractedTimestamp.toString(); update any subsequent references to `ts` to use
the new name.
In @substreams/buf.gen.yaml:
- Around line 1-8: Check the Buf plugin version pins for neoeinstein-prost and
neoeinstein-prost-crate (the remote entries specifying v0.5.0); confirm those
v0.5.0 releases actually exist in the registry and are accessible, and if they
do not, update the versions to the latest published stable releases (e.g.,
neoeinstein-prost -> v0.4.0 and neoeinstein-prost-crate -> v0.3.1) in the
plugins list so the buf.gen.yaml uses valid, available plugin versions.
In @substreams/build.rs:
- Line 55: The current build.rs line uses canonicalize("node_modules/.bin")?
which will crash if that path doesn't exist; update the PATH setup in build.rs
to check for the node_modules/.bin path first (e.g., use
Path::new("node_modules/.bin").exists() or attempt canonicalize in a match), and
if it exists use its canonicalized display value, otherwise fall back to either
the literal "node_modules/.bin" or omit prepending to PATH and log/return a
clearer error message; update the expression around .env("PATH", ...) so it does
not use the ? operator on canonicalize directly and handles the
missing-directory case gracefully.
- Line 63: The current
.args(glob("src/contracts/**/*.rs")?.chain(glob("src/proto/**/*.rs")?).filter_map(Result::ok))
silently drops IO/errors from the glob iterator; instead collect the chained
iterator of Result<Path, _> into a Result<Vec<PathBuf>, _> and propagate any
error with ? (e.g. let paths: Vec<_> =
glob("src/contracts/**/*.rs")?.chain(glob("src/proto/**/*.rs")?).collect::<Result<Vec<_>,
_>>()?; then pass paths to .args(paths) or .args(paths.iter())). Replace the
filter_map(Result::ok) usage so glob failures are returned rather than ignored.
- Around line 28-31: The code currently indexes the parsed JSON with ["abi"]
which will panic if the "abi" field is missing; update the block that calls
from_reader::<_, Value>(File::open(&path)...) and then
to_writer(File::create(...), &...["abi"]) to first parse into a Value, call
.get("abi") on that Value, and if .get("abi") returns None return a descriptive
error (via anyhow::bail! or .with_context) referencing path/mod_name; then pass
the borrowed abi Value into to_writer so missing or malformed "abi" is handled
gracefully instead of panicking.
In @substreams/Dockerfile:
- Around line 32-33: The Dockerfile currently hardcodes a development-only CMD
with a Postgres URL using host.docker.internal and may leak credentials; replace
the hardcoded CMD with one of two safe options: either remove the CMD entirely
so ENTRYPOINT ["dumb-init","/app/substreams-sink-sql","run"] requires explicit
runtime args, or change CMD to use an environment variable (e.g. CMD
["${DATABASE_URL:-}","substreams.yaml"]) and document that DATABASE_URL must be
set; ensure you remove any literal host.docker.internal/credentials from the
file and update docs to instruct running with DATABASE_URL set (and avoid
printing secrets in logs).
In @substreams/package.json:
- Around line 59-63: The test:fmt npm script currently lists test:clippy in its
dependsOn array, which creates an unnecessary serial dependency; open
package.json, locate the "test:fmt" script object and remove "test:clippy" from
its "dependsOn" array so formatting checks run independently of clippy, leaving
"test:clippy" defined separately if needed and ensuring any CI/workflow that
relied on serial ordering is updated to invoke both scripts in parallel or
separately.
In @substreams/proto/exa.proto:
- Around line 1-3: Buf is warning that proto package "exa" must live in a
matching directory; either move the file to a directory that matches the package
(e.g., put exa.proto under proto/exa/ so package exa lives in
proto/exa/exa.proto) or disable the PACKAGE_DIRECTORY_MATCH lint for this repo
by adding a substreams/buf.yaml that excludes PACKAGE_DIRECTORY_MATCH; update
the repository accordingly and commit the file move or the buf.yaml change so
the buf lint no longer reports PACKAGE_DIRECTORY_MATCH for package "exa".
In @substreams/substreams.yaml:
- Line 47: The sink references a generated schema file that is missing: update
or generate the referenced file "../server/generated/schema.sql" by running the
server-side export script; cd into the server directory and run the npm script
"generate:sql" with POSTGRES_URL pointing to your Postgres instance (e.g.,
POSTGRES_URL=postgres npm run generate:sql) to export the schema defined in
server/database/schema.ts using Drizzle Kit, then commit the produced
generated/schema.sql so the "schema: ../server/generated/schema.sql" entry is
valid before deployment.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsubstreams/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (39)
.changeset/breezy-planes-repeat.md.changeset/rare-readers-tell.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.dockerignore.github/matcher.json.github/workflows/docs.yaml.github/workflows/mobile-expo.yaml.github/workflows/server-build.yaml.github/workflows/substreams-build.yaml.github/workflows/test.yaml.github/workflows/version.yaml.vscode/extensions.jsoncommon/package.jsoncontracts/package.jsoncontracts/test/mocks/BobExecute.s.solcspell.jsondocs/package.jsonpnpm-workspace.yamlserver/Dockerfileserver/database/schema.tsserver/drizzle.config.tsserver/package.jsonserver/script/firehose.tsserver/test/anvil.tsserver/test/database.tsserver/test/e2e.tssubstreams/.cargo/config.tomlsubstreams/.gitignoresubstreams/Cargo.tomlsubstreams/Dockerfilesubstreams/buf.gen.yamlsubstreams/build.rssubstreams/package.jsonsubstreams/proto/exa.protosubstreams/rust-toolchain.tomlsubstreams/rustfmt.tomlsubstreams/src/lib.rssubstreams/substreams.yaml
🧰 Additional context used
📓 Path-based instructions (11)
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/rare-readers-tell.md.changeset/violet-poems-heal.md.changeset/breezy-planes-repeat.md.changeset/red-toys-pump.md
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/test/database.tsserver/test/anvil.tsserver/drizzle.config.tsserver/test/e2e.tsserver/database/schema.tsserver/script/firehose.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/test/database.tscontracts/test/mocks/BobExecute.s.solserver/test/anvil.tsserver/drizzle.config.tsserver/test/e2e.tsserver/database/schema.tsserver/script/firehose.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/test/database.tsserver/test/anvil.tsserver/drizzle.config.tsserver/test/e2e.tsserver/database/schema.tsserver/script/firehose.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/test/database.tsserver/test/anvil.tsserver/drizzle.config.tsserver/test/e2e.tsserver/database/schema.tsserver/script/firehose.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/test/database.tsserver/test/anvil.tsserver/drizzle.config.tsserver/test/e2e.tsserver/database/schema.tsserver/script/firehose.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/test/database.tsserver/test/anvil.tsserver/drizzle.config.tsserver/test/e2e.tsserver/database/schema.tsserver/script/firehose.ts
**/+(app|src)/**
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use kebab-case for directory names and public Expo router routes
Files:
substreams/src/lib.rs
contracts/**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/contracts.mdc)
contracts/**/*.sol: Import order must be sorted alphabetically and grouped by type (openzeppelin, solady, internal, etc.), enforced by forge fmt
All functions and state variables must have explicit visibility modifiers (public, private, internal, external)
Use custom errors instead of require() statements with string messages; require() with messages is forbidden
console.log is forbidden in contract code
All external and public functions that modify state must be protected by a reentrancy guard, unless explicitly proven unnecessary
Use modifiers like onlyOwner or function-level checks for privileged functions; default to strictest access level (private > internal > external > public)
Use named parameters in mappings for clarity (e.g., mapping(address user => uint256 balance))
All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Follow standard Solidity import order: external libraries (openzeppelin, solady) first, then project-internal contracts
All public and external functions and state variables must have complete natspec comments (@notice, @dev, @param, @return)
Use custom errors instead of require statements with string messages for better gas efficiency and error information
Internal and private variables should be prefixed with an underscore (_); function arguments should not be prefixed
Use constant keyword for true constants; use immutable for configurable values like contract addresses
Minimize writes to storage; read data into memory or calldata whenever possible for gas optimization
Use smallest integer size that is safe for the variable (e.g., uint32 for timestamps) and be aware of struct packing for gas efficiency
Prefer using the solady library over openzeppelin where possible, as it is highly optimized for gas
Correctly label functions as view or pure when they do not modify state
contracts/**/*.sol: Smart contracts: use custom errorserror MyError();instead of s...
Files:
contracts/test/mocks/BobExecute.s.sol
**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
**/*.sol: Use camelCase for Solidity variables and functions
Use PascalCase for Solidity contracts and structs
Files:
contracts/test/mocks/BobExecute.s.sol
server/database/schema.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/database/schema.ts: Database schema must be defined inserver/database/schema.tsas the single source of truth for database structure
Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Files:
server/database/schema.ts
🧠 Learnings (35)
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use `nx` for running tasks (build, lint, test, e2e) instead of underlying tooling directly
Applied to files:
.github/workflows/docs.yamlsubstreams/package.json.github/workflows/mobile-expo.yaml.github/workflows/version.yamlcontracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Install dependencies using `pnpm install`
Applied to files:
.github/workflows/docs.yaml.github/workflows/version.yaml
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/rare-readers-tell.md.changeset/violet-poems-heal.md.changeset/breezy-planes-repeat.md.changeset/red-toys-pump.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries
Applied to files:
.changeset/rare-readers-tell.md.changeset/violet-poems-heal.md.changeset/breezy-planes-repeat.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Select the most precise gitmoji that best communicates the intent of the change (e.g., ✨ for new feature, 🐛 for bug fix, ♻️ for refactoring, ⚡️ for performance improvement, 📝 for documentation, 🚀 for deployment)
Applied to files:
.changeset/rare-readers-tell.md.changeset/breezy-planes-repeat.md
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/test/database.tsserver/script/firehose.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to server/**/*.{ts,tsx} : Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use `c.var.db`
Applied to files:
server/test/database.tsserver/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
Applied to files:
server/test/database.tsserver/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Applied to files:
server/test/database.tsserver/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/migrations/** : All schema changes must be accompanied by a migration file generated by `drizzle-kit`; never edit the database schema manually
Applied to files:
server/test/database.tsserver/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Server (exactly/server) uses Hono (framework), Node.js, Drizzle ORM (database), and PostgreSQL
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Drizzle ORM best practices using ESLint `plugin:drizzle/all` configuration, including requiring `where` clauses for `update` and `delete` operations
Applied to files:
server/test/database.tsserver/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Applies to {src/generated/**,server/generated/**,contracts/broadcast/**,contracts/cache/**} : Do not manually edit files in generated directories: `src/generated/`, `server/generated/`, `contracts/broadcast/`, and `contracts/cache/`
Applied to files:
substreams/package.jsonsubstreams/.gitignoresubstreams/build.rs.dockerignorecommon/package.json
📚 Learning: 2026-01-08T19:54:26.885Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: substreams/rust-toolchain.toml:1-4
Timestamp: 2026-01-08T19:54:26.885Z
Learning: Rust edition 2024 was released as stable on February 20, 2025 with Rust 1.85.0 and does not require nightly channel.
Applied to files:
substreams/rust-toolchain.toml
📚 Learning: 2026-01-08T19:54:10.673Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: substreams/src/lib.rs:54-71
Timestamp: 2026-01-08T19:54:10.673Z
Learning: In substreams/src/lib.rs in the map_exa_plugins function, the deduplication logic using `!seen.insert(address.to_vec())` is intentional. The first plugin installed for a new account is an owner plugin (not an exa plugin), so it must be filtered out. The negation ensures the first plugin is skipped and subsequent plugins (the actual exa plugins) are kept.
Applied to files:
substreams/src/lib.rs
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All public and external functions and state variables must have complete natspec comments (notice, dev, param, return)
Applied to files:
contracts/test/mocks/BobExecute.s.sol
📚 Learning: 2026-01-08T19:46:37.829Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: contracts/test/mocks/BobExecute.s.sol:61-71
Timestamp: 2026-01-08T19:46:37.829Z
Learning: Natspec documentation requirements do not apply to test/mock/dev scripts (files in contracts/test/ or with .s.sol extension). Only production contract code in contracts/src/ requires complete natspec comments.
Applied to files:
contracts/test/mocks/BobExecute.s.solcontracts/package.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
contracts/test/mocks/BobExecute.s.solserver/test/e2e.ts
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Use vm.prank to simulate calls from different addresses; avoid changing msg.sender through other means
Applied to files:
contracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Database schema must be defined in `server/database/schema.ts` as the single source of truth for database structure
Applied to files:
server/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2026-01-08T20:26:33.396Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .vscode/extensions.json:6-6
Timestamp: 2026-01-08T20:26:33.396Z
Learning: For validating VS Code extension IDs in this repository, check both the official VS Code Marketplace (marketplace.visualstudio.com) and the Open VSX registry (open-vsx.org) before flagging an extension as invalid. Open VSX is a widely-used alternative registry, so a valid ID may appear in either registry. If an ID is not present in both registries, flag it as invalid. Apply this check specifically when validating the IDs listed in .vscode/extensions.json.
Applied to files:
.vscode/extensions.json
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words from names like `data`, `state`, `manager`, `engine`, `object`, `entity`, and `instance`
Applied to files:
cspell.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
Applied to files:
cspell.json
📚 Learning: 2026-01-08T19:45:38.852Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .github/workflows/substreams-build.yaml:2-2
Timestamp: 2026-01-08T19:45:38.852Z
Learning: In the exactly/exa repository, prefer concise inline YAML syntax (e.g., `on: { push: { tags: ["pattern"] }, workflow_dispatch: {} }`) over expanded multi-line format in GitHub workflow files, even if it triggers YAMLlint warnings about formatting.
Applied to files:
.github/workflows/substreams-build.yaml
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Do not use abbreviations or cryptic names; write out full words like `error`, `parameters`, `request` instead of `err`, `params`, `req`
Applied to files:
server/script/firehose.ts
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commits must start with a single gitmoji from the official gitmoji list; this is not optional and the emoji visually communicates the intent of the change
Applied to files:
.changeset/breezy-planes-repeat.md
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : Use custom errors instead of require statements with string messages for better gas efficiency and error information
Applied to files:
substreams/build.rs
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run contract tests using `pnpm --filter plugin test` and check formatting using `pnpm --filter plugin test:fmt`
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run all tests using `pnpm test`, which includes TypeScript compilation, ESLint, spell checking, markdown linting, and contract tests
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to contracts/**/*.test.sol : Smart contracts testing: fuzzing required for inputs and gas snapshots mandatory
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Use fuzz testing (testFuzz_...) extensively for functions with numerical or address inputs to cover a wide range of scenarios
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Rely on `pnpm` scripts for all tasks; all setup, testing, and generation must be automated
Applied to files:
contracts/package.json
🪛 Buf (1.62.1)
substreams/proto/exa.proto
3-3: Files with package "exa" must be within a directory "exa" relative to root but were in directory "substreams/proto".
(PACKAGE_DIRECTORY_MATCH)
🪛 Checkov (3.2.334)
substreams/Dockerfile
[low] 1-33: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
🪛 YAMLlint (1.37.1)
.github/workflows/docs.yaml
[error] 14-14: too many spaces inside braces
(braces)
[error] 14-14: too many spaces inside braces
(braces)
.github/workflows/substreams-build.yaml
[warning] 2-2: truthy value should be one of [false, true]
(truthy)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Seer Code Review
🔇 Additional comments (46)
contracts/test/mocks/BobExecute.s.sol (3)
39-39: LGTM: Correct use ofgetAddressin setUp.Switching from
createAccounttogetAddressis appropriate here since the account is created by theBob.s.solscript (executed earlier in the test setup). This correctly computes the deterministic address without attempting to redeploy.
47-58: LGTM: BatchDeploy instantiation correctly generates test accounts.The
BatchDeployinstantiation creates multiple accounts for testing purposes, likely to populate blockchain data for the new substreams functionality. While using constructor side effects is unconventional, it's acceptable in test scripts.
61-71: LGTM: BatchDeploy contract correctly implements batch account creation.The constructor creates
countaccounts with sequential owner addresses. The pattern of using a constructor solely for side effects is unconventional but acceptable in test scripts. The logic correctly:
- Creates accounts with deterministic addresses (
uint160(420 + i))- Uses a single-owner configuration for each account
- Leverages the factory's
createAccountmethodserver/test/anvil.ts (1)
159-159: LGTM: Correct use of--tcflag to filter script execution.Adding
--tc BobExecuteScriptensures that only theBobExecuteScriptcontract is executed by forge, preventing it from attempting to run theBatchDeployhelper contract as a separate script. This is the correct approach after introducing multiple contracts in the same file..vscode/extensions.json (1)
6-6: Replace the invalid extension IDdrblury.protobuf-vscwith a valid alternative.The extension
drblury.protobuf-vscdoes not exist in either the VS Code Marketplace or Open VSX registry. Use the widely-availablemike-lischke.protobufextension instead, or verify the correct publisher/extension ID.The
rust-lang.rust-analyzerextension is valid and available in both registries—no changes needed there.⛔ Skipped due to learnings
Learnt from: cruzdanilo Repo: exactly/exa PR: 627 File: .vscode/extensions.json:6-6 Timestamp: 2026-01-08T20:26:33.396Z Learning: When validating VS Code extension IDs, check both the official VS Code Marketplace (marketplace.visualstudio.com) and Open VSX registry (open-vsx.org) before flagging extensions as invalid, as Open VSX is a widely-used alternative extension registry.substreams/.gitignore (1)
1-8: LGTM!The gitignore correctly excludes generated artifacts (src/contracts, src/proto), build outputs (target), and intermediate files (*.log, *.spkg) appropriate for a Rust/WASM Substreams package.
pnpm-workspace.yaml (1)
1-8: Workspace inclusion looks correct.Adding
substreamsunderpackagesis consistent with the existing workspace entries.server/drizzle.config.ts (1)
1-12: VerifyschemaFilteris supported by your drizzle-kit version and matches the intended Postgres schema layout.Please confirm (a) the option name/behavior is correct for your
drizzle-kitversion, and (b)substreamsis an actual Postgres schema you want drizzle-kit to include for introspection/migration generation.cspell.json (1)
3-12: cspell updates are scoped appropriately (generated paths + expected Rust/substreams terms).Also applies to: 13-166
.github/workflows/server-build.yaml (1)
36-37: LGTM!Making the Dockerfile path explicit improves clarity and maintainability. This aligns well with the pattern established in the new substreams workflow.
docs/package.json (1)
37-44: LGTM!The addition of
dependentTasksOutputFilesproperly tracks the OpenAPI schema dependency, ensuring the docs rebuild when the server's API specification changes. This improves incremental build reliability.server/package.json (2)
64-64: LGTM!The addition of the substreams package as a devDependency aligns with the PR objective of implementing initial substreams functionality. The workspace protocol and version are appropriate for this initial integration.
143-146: LGTM!Adding contract output tracking to the broadcasts generation inputs ensures that changes to contract artifacts correctly trigger regeneration. This improves build cache accuracy and is consistent with similar NX dependency tracking patterns elsewhere in the monorepo.
server/test/e2e.ts (1)
7-7: LGTM!The Sentry mock enhances test observability by logging exceptions to console during e2e tests. The implementation correctly preserves the original behavior while adding debug output, follows the same pattern as other mocks in the file, and complies with coding guidelines for type imports and comments.
Also applies to: 57-67
.github/workflows/substreams-build.yaml (1)
1-38: Well-structured workflow with good security practices.The workflow correctly implements Docker image building with appropriate permissions, tag patterns, and supply chain attestation. The inline YAML syntax aligns with repository preferences.
Likely an incorrect or invalid review comment.
contracts/package.json (2)
79-104: Nxoutputsadditions look consistent with forge/slither artifact locations.
Tracking{projectRoot}/out,{projectRoot}/cache, and{projectRoot}/.gas-snapshotshould improve cache correctness and artifact collection.
13-16: No issue withslither . --skip-clean. This flag is the recommended practice in CI. It prevents Slither from running framework clean commands (e.g.,forge clean) that would delete build artifacts. Your workflow correctly follows the safe pattern: build first (which happens via the test target'sbuilddependency andtest:gascompilation), then run Slither with--skip-cleanto preserve those artifacts. Each CI run is isolated on a fresh runner, so there's no cross-run artifact pollution.server/test/database.ts (1)
49-70: The execa$template defaults to no shell interpretation, so the&literal is safe as written.The review comment's concern is incorrect. By default, execa's
$tagged template does not invoke a shell (shell: falseby default per execa documentation), and noshell: trueoption is passed in this code. This means the&character in the literal portion of the template string is treated as a regular character passed to the command, not a shell metacharacter. The interpolated${postgresURL}is automatically escaped by execa regardless. The proposed refactoring to extract the URL to a variable does not address a real safety issue.Regarding
--config-file=(empty value): this appears intentional and cannot be confirmed as a typo without additional context about thefireethcommand's expected behavior.Likely an incorrect or invalid review comment.
common/package.json (1)
42-50: ThedependentTasksOutputFilesconfiguration is correctly wired to actual dependencies. The^builddependency includes@exactly/plugin's (contracts')buildtarget, which outputs to{projectRoot}/out(mapped tocontracts/out/**/*.jsonat workspace root). Thecontracts/broadcast/*/31337/*.jsonfiles are covered by the explicit@exactly/server:generate:broadcastsdependency. Cache inputs will behave as intended—no changes needed.server/script/firehose.ts (1)
135-135: Verify the duplicatedlibNumin output.The output format uses
libNumtwice:FIRE BLOCK ${num} ${hash} ${libNum} ${parentHash} ${libNum} ${timestamp}. The secondlibNumlikely should be a different value. If intentional, please add a comment explaining why.server/database/schema.ts (4)
72-77: Consider adding.notNull()constraints.The
cursor,blockNum, andblockIdcolumns lack.notNull()constraints. If these columns should always have values, add explicit constraints to ensure data integrity.
79-86: Consider adding.notNull()constraints to history table columns.The
op,tableName,pk,prevValue, andblockNumcolumns are nullable. If these are required fields for history tracking, add.notNull()constraints.
88-91: Verify use ofnumerictype for block number and timestamp.Using
numeric(arbitrary precision) instead ofbigintfornumberandtimestampis uncommon for block data. While this preserves precision, it returns strings in JavaScript and has performance implications. If this is intentional for handling very large values, consider adding a comment explaining the choice.
70-100: Define relations for new substreams tables.Per coding guidelines, relations should be defined explicitly in the schema file to enable Drizzle to generate typed query results. If the substreams tables have relationships (e.g., between
exaPluginsandcredentialsviaaccount), consider defining them.Based on learnings, database schema should define relations explicitly.
.github/workflows/version.yaml (1)
25-29: LGTM!The cache path update to
.nx/cachealigns with the modern Nx cache location and is consistent with other workflow changes in this PR.substreams/.cargo/config.toml (1)
1-2: LGTM!Setting
wasm32-unknown-unknownas the default build target is correct for Substreams WASM modules.substreams/rust-toolchain.toml (1)
1-4: Verify nightly channel requirement.Using
nightlychannel introduces potential instability. If stable toolchain features suffice for the Substreams build, consider pinning to a specific stable version for reproducibility. If nightly is required for specific features (e.g., unstable rustfmt options inrustfmt.toml), consider adding a comment explaining the requirement..github/workflows/mobile-expo.yaml (1)
44-48: LGTM!Cache path update to
.nx/cacheis consistent with the other workflow changes in this PR.substreams/rustfmt.toml (1)
1-6: Configuration requires nightly rustfmt.The
imports_granularityoption is unstable and requires nightly rustfmt, which explains the nightly toolchain choice. Consider adding a comment inrust-toolchain.tomlnoting this dependency.Also,
tab_spaces = 2deviates from Rust's community standard of 4 spaces. If this is intentional to align with the project's TypeScript/JavaScript style, that's reasonable but worth documenting..github/workflows/docs.yaml (1)
31-35: LGTM!Cache path update to
.nx/cachealigns with modern NX conventions and is consistent with other workflow files in this PR..github/workflows/test.yaml (1)
9-9: LGTM!NX_DAEMON and cache path changes are consistent with other workflow updates in this PR.
Also applies to: 34-34
substreams/package.json (1)
1-50: LGTM on overall structure!The package manifest is well-organized with proper NX target configuration, input/output tracking for incremental builds, and appropriate dependency chains. Based on learnings, using
nxfor running tasks aligns with repository conventions.substreams/proto/exa.proto (1)
5-17: LGTM on proto message definitions!The message structures are well-designed for the substreams use case. Using
bytesfor addresses/accounts is appropriate for blockchain data, and theordinalfield correctly tracks event ordering.substreams/Cargo.toml (2)
32-35: LGTM on release profile!Good optimization choices for WASM: LTO for dead code elimination,
opt-level = 's'for size optimization, and stripping debug info reduces binary size.
4-4: No action needed — Rust 2024 edition is stable (Rust 1.85.0+) and does not require special toolchain handling.substreams/Dockerfile (1)
1-30: Well-structured multi-stage Dockerfile!Good practices observed:
- Multi-stage build minimizes final image size
dumb-initas PID 1 handles signal forwarding properly- Non-root user (
substreams) for runtime security- Cache mount for pnpm speeds up rebuilds
For production readiness, consider adding a
HEALTHCHECKinstruction as flagged by Checkov.substreams/substreams.yaml (3)
14-42: LGTM: Module dependency chain is well-structured.The module definitions correctly wire the data flow: accounts are extracted, stored, then used to identify plugins, which are finally converted to database changes.
11-12: The external import URLs in the manifest are valid and accessible. Both packages are hosted at official GitHub releases from trusted streamingfast repositories and return proper HTTP responses.
58-62: Verify multiline parameter whitespace handling in Substreams runtime.The multiline parameter format for Optimism factories uses backslash continuation within a quoted string. The indentation on continuation lines (60-62) may preserve leading whitespace in the final parameter value, resulting in
factories[]=ADDR1 &factories[]=ADDR2with unintended spaces. Confirm that the Substreams runtime or downstream parameter parser correctly handles or strips this whitespace.All factory addresses are valid Ethereum addresses (40 hex characters each).
substreams/build.rs (2)
14-23: LGTM: Comprehensive rebuild triggers.The rerun-if-changed directives properly cover all input sources (proto files, configs, contracts, and dependencies) to ensure the build script re-executes when necessary.
38-48: LGTM: Clean generated module structure.The generated
mod.rsfile correctly exports contract modules with appropriate clippy directives for generated code.substreams/src/lib.rs (5)
1-21: LGTM: Clean module structure and appropriate imports.The module organization and imports are well-structured. The clippy allow for
not_unsafe_ptr_arg_derefis likely necessary for Substreams FFI integration.
27-44: LGTM: Correct account extraction logic.The function correctly parses factory addresses from params, filters ExaAccountInitialized events, and collects account addresses. Error handling via
?operator is appropriate.
46-51: LGTM: Simple and correct account storage.The function correctly stores a flag (value 1) for each account using the hex-encoded address as the key.
53-71: LGTM: Correct plugin filtering logic.The function properly filters PluginInstalled events:
- For new accounts: Uses
!seen.insert()to skip the first plugin (owner plugin) as intended- For existing accounts: Checks the stored flag
The deduplication logic is correct per the established pattern. Based on learnings, the first plugin for new accounts is an owner plugin that should be filtered.
73-88: LGTM: Correct database change generation.The function properly generates database changes by upserting plugin rows to the
exa_pluginstable and creating a blocks row when changes exist. The schema defines the expected tables with matching columns (addressandaccountas text fields), and the code correctly converts hex values to strings. The use ofunwrap_or_default()for timestamp is safe.
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
contracts/package.json (1)
60-74: Add the actual forge test execution to the test:parallel target.The
test:paralleltarget (lines 75-81) has no executor or commands — it only runs its dependencies (test:fmt,test:solhint,test:sizes), which are formatting, linting, and size checks. The actualforge testunit test suite is missing entirely from the compositetesttarget, which callstest:parallelas its first step. The subsequent steps (test:gas,test:slither,test:coverage) run profiling, static analysis, and coverage but not the core unit tests.Define
test:parallelwith an executor and command to runforge test, or add a dedicated dependency that executes it.
🤖 Fix all issues with AI agents
In @.github/workflows/docs.yaml:
- Line 14: YAMLlint flags extra spaces inside the inline mapping braces on the
env line; update the line that currently reads env: { NX_DAEMON: false } to
remove the spaces inside the braces so the inline mapping has no inner spaces
(env: {NX_DAEMON: false}), ensuring the YAML passes linting.
In @.github/workflows/substreams-build.yaml:
- Around line 26-33: The build step using docker/build-push-action@v6 (step id:
build) does not enable Docker layer caching, causing repeated reinstall of
Rust/Foundry/Node deps; update the build step to include cache-from and cache-to
inputs (e.g., cache-from: type=gha,ref=${{ github.sha }} and cache-to:
type=gha,mode=max,ref=${{ github.sha }}) and add buildx-options or cache key
usage as needed so layers are pulled and pushed between CI runs; keep existing
inputs (context, file, tags, labels) and only add these cache parameters to the
step.
In @.github/workflows/test.yaml:
- Around line 38-42: Wrap the download loop with strict shell error handling
(e.g., enable -euo pipefail) and make each curl call use fail-on-error, check
its exit status and the tar extraction result for each repo (firehose-ethereum,
substreams, substreams-sink-sql), aborting with a clear error message if any
download or tar fails; after creating ~/.local/bin ensure you export PATH by
prepending ~/.local/bin to PATH (export PATH="$HOME/.local/bin:$PATH") so
subsequent steps can find the binaries and add an existence/check step to
confirm the expected binaries are present before proceeding.
In @substreams/build.rs:
- Around line 61-67: The build script currently uses assert! around the rustfmt
invocation (Command::new("rustfmt")... .status().expect(...).success()), which
panics instead of returning a proper Result; change this to explicit
Result-based error handling like the protogen flow: run the
Command::new("rustfmt") with the same args
(glob("src/contracts/**/*.rs")?.chain(glob("src/proto/**/*.rs")?).filter_map(Result::ok)),
capture the status via .status()? and check .success(), and if it is not
successful return Err(std::io::Error::new(...) or anyhow::anyhow!(...)) (or map
the child error) so build.rs returns a Result<(), Error> instead of using
assert! or expect; ensure the function signature and callers are adjusted to
propagate the error.
In @substreams/Dockerfile:
- Around line 12-13: The Dockerfile currently fetches the "latest" substreams
CLI release in the RUN curl pipeline (the RUN line that calls GitHub API and
jq), which makes builds non-reproducible; change this to pin a specific release
by introducing a build ARG or ENV like SUBSTREAMS_VERSION and use that value in
the curl/download URL instead of querying "latest" so the RUN command downloads
a known tag (e.g., v1.10.14); update the RUN line that constructs the
browser_download_url to reference the pinned SUBSTREAMS_VERSION and consider
adding a checksum verification step after download for extra safety.
In @substreams/package.json:
- Around line 44-63: The test:protolint target lacks an explicit dependsOn entry
which makes its standalone execution unclear; add a dependsOn array to the
test:protolint target (e.g., include "prepare" or the same prerequisites as
test:clippy such as "prepare", "^prepare", "rustup") so it has consistent
prerequisites like test:clippy and test:fmt and can be run independently; update
the package.json test:protolint target to include that dependsOn configuration.
In @substreams/proto/exa.proto:
- Around line 5-17: Add clear protobuf field comments for the public API:
document message Accounts and its field accounts (e.g., whether entries are
fixed 20-byte Ethereum addresses or variable-length byte arrays and expected
encoding), document message Plugins and its repeated Plugin entries, and inside
Plugin add comments for Plugin.address (format and semantics), Plugin.account
(relationship to Accounts.accounts and whether it indexes into that list or
duplicates an address), and Plugin.ordinal (what it represents — insertion
order, sequence number, block number, etc.). Ensure each comment is concise and
placed immediately above the corresponding message/field (Accounts, accounts,
Plugins, Plugin, address, account, ordinal) so generated docs and codegen will
include them.
In @substreams/rust-toolchain.toml:
- Around line 1-4: Update the Rust toolchain configuration in
rust-toolchain.toml by changing the channel value from "nightly" to "stable"
(replace channel = "nightly" with channel = "stable"); keep the existing
components ("clippy", "rustfmt") and targets ("wasm32-unknown-unknown")
unchanged and ensure CI/workflow configs that reference the toolchain continue
to use the stable channel.
In @substreams/src/lib.rs:
- Line 1: Add a brief crate-level comment above the existing
#![expect(clippy::not_unsafe_ptr_arg_deref)] attribute that documents why the
lint is being suppressed for this crate: state that substreams exposes
FFI/extern "C" functions which accept raw pointers and rely on the caller to
guarantee validity, causing clippy false positives when it warns about
non-unsafe pointer derefs; mention that the suppression is intentional to avoid
noisy warnings while keeping safety expectations documented for maintainers
(referencing the attribute name clippy::not_unsafe_ptr_arg_deref and the
FFI/extern "C" context).
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsubstreams/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (39)
.changeset/breezy-planes-repeat.md.changeset/rare-readers-tell.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.dockerignore.github/matcher.json.github/workflows/docs.yaml.github/workflows/mobile-expo.yaml.github/workflows/server-build.yaml.github/workflows/substreams-build.yaml.github/workflows/test.yaml.github/workflows/version.yaml.vscode/extensions.jsoncommon/package.jsoncontracts/package.jsoncontracts/test/mocks/BobExecute.s.solcspell.jsondocs/package.jsonpnpm-workspace.yamlserver/Dockerfileserver/database/schema.tsserver/drizzle.config.tsserver/package.jsonserver/script/firehose.tsserver/test/anvil.tsserver/test/database.tsserver/test/e2e.tssubstreams/.cargo/config.tomlsubstreams/.gitignoresubstreams/Cargo.tomlsubstreams/Dockerfilesubstreams/buf.gen.yamlsubstreams/build.rssubstreams/package.jsonsubstreams/proto/exa.protosubstreams/rust-toolchain.tomlsubstreams/rustfmt.tomlsubstreams/src/lib.rssubstreams/substreams.yaml
🧰 Additional context used
📓 Path-based instructions (11)
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.tsserver/script/firehose.tsserver/test/e2e.tsserver/test/anvil.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.tsserver/script/firehose.tscontracts/test/mocks/BobExecute.s.solserver/test/e2e.tsserver/test/anvil.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.tsserver/script/firehose.tsserver/test/e2e.tsserver/test/anvil.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.tsserver/script/firehose.tsserver/test/e2e.tsserver/test/anvil.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.tsserver/script/firehose.tsserver/test/e2e.tsserver/test/anvil.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.tsserver/script/firehose.tsserver/test/e2e.tsserver/test/anvil.ts
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/breezy-planes-repeat.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.changeset/rare-readers-tell.md
server/database/schema.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/database/schema.ts: Database schema must be defined inserver/database/schema.tsas the single source of truth for database structure
Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Files:
server/database/schema.ts
contracts/**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/contracts.mdc)
contracts/**/*.sol: Import order must be sorted alphabetically and grouped by type (openzeppelin, solady, internal, etc.), enforced by forge fmt
All functions and state variables must have explicit visibility modifiers (public, private, internal, external)
Use custom errors instead of require() statements with string messages; require() with messages is forbidden
console.log is forbidden in contract code
All external and public functions that modify state must be protected by a reentrancy guard, unless explicitly proven unnecessary
Use modifiers like onlyOwner or function-level checks for privileged functions; default to strictest access level (private > internal > external > public)
Use named parameters in mappings for clarity (e.g., mapping(address user => uint256 balance))
All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Follow standard Solidity import order: external libraries (openzeppelin, solady) first, then project-internal contracts
All public and external functions and state variables must have complete natspec comments (@notice, @dev, @param, @return)
Use custom errors instead of require statements with string messages for better gas efficiency and error information
Internal and private variables should be prefixed with an underscore (_); function arguments should not be prefixed
Use constant keyword for true constants; use immutable for configurable values like contract addresses
Minimize writes to storage; read data into memory or calldata whenever possible for gas optimization
Use smallest integer size that is safe for the variable (e.g., uint32 for timestamps) and be aware of struct packing for gas efficiency
Prefer using the solady library over openzeppelin where possible, as it is highly optimized for gas
Correctly label functions as view or pure when they do not modify state
contracts/**/*.sol: Smart contracts: use custom errorserror MyError();instead of s...
Files:
contracts/test/mocks/BobExecute.s.sol
**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
**/*.sol: Use camelCase for Solidity variables and functions
Use PascalCase for Solidity contracts and structs
Files:
contracts/test/mocks/BobExecute.s.sol
**/+(app|src)/**
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use kebab-case for directory names and public Expo router routes
Files:
substreams/src/lib.rs
🧠 Learnings (36)
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to server/**/*.{ts,tsx} : Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use `c.var.db`
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Database schema must be defined in `server/database/schema.ts` as the single source of truth for database structure
Applied to files:
server/drizzle.config.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/migrations/** : All schema changes must be accompanied by a migration file generated by `drizzle-kit`; never edit the database schema manually
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Drizzle ORM best practices using ESLint `plugin:drizzle/all` configuration, including requiring `where` clauses for `update` and `delete` operations
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/test/database.tsserver/script/firehose.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Server (exactly/server) uses Hono (framework), Node.js, Drizzle ORM (database), and PostgreSQL
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries
Applied to files:
.changeset/breezy-planes-repeat.md.changeset/violet-poems-heal.md.changeset/rare-readers-tell.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Select the most precise gitmoji that best communicates the intent of the change (e.g., ✨ for new feature, 🐛 for bug fix, ♻️ for refactoring, ⚡️ for performance improvement, 📝 for documentation, 🚀 for deployment)
Applied to files:
.changeset/breezy-planes-repeat.md.changeset/rare-readers-tell.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commits must start with a single gitmoji from the official gitmoji list; this is not optional and the emoji visually communicates the intent of the change
Applied to files:
.changeset/breezy-planes-repeat.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Do not use conventional commit prefixes like `feat:`, `fix:`, or `docs(...)`; do not use title case or sentence case in the commit subject line; do not create commits without a scope
Applied to files:
.changeset/breezy-planes-repeat.md
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/breezy-planes-repeat.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.changeset/rare-readers-tell.md
📚 Learning: 2026-01-08T19:54:26.885Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: substreams/rust-toolchain.toml:1-4
Timestamp: 2026-01-08T19:54:26.885Z
Learning: Rust edition 2024 was released as stable on February 20, 2025 with Rust 1.85.0 and does not require nightly channel.
Applied to files:
substreams/rust-toolchain.toml
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Applies to {src/generated/**,server/generated/**,contracts/broadcast/**,contracts/cache/**} : Do not manually edit files in generated directories: `src/generated/`, `server/generated/`, `contracts/broadcast/`, and `contracts/cache/`
Applied to files:
.dockerignoresubstreams/.gitignorecommon/package.jsonsubstreams/build.rs
📚 Learning: 2026-01-08T19:45:38.852Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .github/workflows/substreams-build.yaml:2-2
Timestamp: 2026-01-08T19:45:38.852Z
Learning: In the exactly/exa repository, prefer concise inline YAML syntax (e.g., `on: { push: { tags: ["pattern"] }, workflow_dispatch: {} }`) over expanded multi-line format in GitHub workflow files, even if it triggers YAMLlint warnings about formatting.
Applied to files:
.github/workflows/substreams-build.yaml
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use `nx` for running tasks (build, lint, test, e2e) instead of underlying tooling directly
Applied to files:
.github/workflows/mobile-expo.yaml.github/workflows/docs.yamlsubstreams/package.json.github/workflows/version.yamlcontracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Install dependencies using `pnpm install`
Applied to files:
.github/workflows/docs.yaml.github/workflows/version.yaml
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Do not use abbreviations or cryptic names; write out full words like `error`, `parameters`, `request` instead of `err`, `params`, `req`
Applied to files:
server/script/firehose.ts
📚 Learning: 2026-01-08T20:26:33.396Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .vscode/extensions.json:6-6
Timestamp: 2026-01-08T20:26:33.396Z
Learning: For validating VS Code extension IDs in this repository, check both the official VS Code Marketplace (marketplace.visualstudio.com) and the Open VSX registry (open-vsx.org) before flagging an extension as invalid. Open VSX is a widely-used alternative registry, so a valid ID may appear in either registry. If an ID is not present in both registries, flag it as invalid. Apply this check specifically when validating the IDs listed in .vscode/extensions.json.
Applied to files:
.vscode/extensions.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All public and external functions and state variables must have complete natspec comments (notice, dev, param, return)
Applied to files:
contracts/test/mocks/BobExecute.s.sol
📚 Learning: 2026-01-08T19:46:37.829Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: contracts/test/mocks/BobExecute.s.sol:61-71
Timestamp: 2026-01-08T19:46:37.829Z
Learning: Natspec documentation requirements do not apply to test/mock/dev scripts (files in contracts/test/ or with .s.sol extension). Only production contract code in contracts/src/ requires complete natspec comments.
Applied to files:
contracts/test/mocks/BobExecute.s.solcontracts/package.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
contracts/test/mocks/BobExecute.s.solserver/test/e2e.ts
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Use vm.prank to simulate calls from different addresses; avoid changing msg.sender through other means
Applied to files:
contracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run contract tests using `pnpm --filter plugin test` and check formatting using `pnpm --filter plugin test:fmt`
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run all tests using `pnpm test`, which includes TypeScript compilation, ESLint, spell checking, markdown linting, and contract tests
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to contracts/**/*.test.sol : Smart contracts testing: fuzzing required for inputs and gas snapshots mandatory
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Use fuzz testing (testFuzz_...) extensively for functions with numerical or address inputs to cover a wide range of scenarios
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Rely on `pnpm` scripts for all tasks; all setup, testing, and generation must be automated
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : Use custom errors instead of require statements with string messages for better gas efficiency and error information
Applied to files:
substreams/build.rs
📚 Learning: 2026-01-08T19:54:10.673Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: substreams/src/lib.rs:54-71
Timestamp: 2026-01-08T19:54:10.673Z
Learning: In substreams/src/lib.rs in the map_exa_plugins function, the deduplication logic using `!seen.insert(address.to_vec())` is intentional. The first plugin installed for a new account is an owner plugin (not an exa plugin), so it must be filtered out. The negation ensures the first plugin is skipped and subsequent plugins (the actual exa plugins) are kept.
Applied to files:
substreams/src/lib.rs
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words from names like `data`, `state`, `manager`, `engine`, `object`, `entity`, and `instance`
Applied to files:
cspell.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
Applied to files:
cspell.json
🧬 Code graph analysis (1)
server/test/database.ts (1)
server/database/schema.ts (1)
substreams(70-70)
🪛 Buf (1.62.1)
substreams/proto/exa.proto
3-3: Files with package "exa" must be within a directory "exa" relative to root but were in directory "substreams/proto".
(PACKAGE_DIRECTORY_MATCH)
🪛 Checkov (3.2.334)
substreams/Dockerfile
[low] 1-33: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
🪛 YAMLlint (1.37.1)
.github/workflows/substreams-build.yaml
[warning] 2-2: truthy value should be one of [false, true]
(truthy)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
.github/workflows/docs.yaml
[error] 14-14: too many spaces inside braces
(braces)
[error] 14-14: too many spaces inside braces
(braces)
🔇 Additional comments (57)
cspell.json (2)
6-9: LGTM!The new
ignorePathsentries are appropriate—.github/matcher.jsoncontains technical regex patterns, andsubstreams/targetis a Rust build output directory that should be excluded from spell checking.
14-138: LGTM!The new dictionary words correctly cover the Rust tooling (
clippy,rustup,serde,prost), Substreams/StreamingFast ecosystem (substreams,streamingfast,spkg,fireeth), and protobuf tooling (bufbuild,protogen,protolint) introduced by this PR..github/matcher.json (2)
64-79: LGTM!The Rust compiler matcher correctly uses a two-pattern approach: the first captures severity, optional code, and message from warning/error lines, while the second captures file location from
-->lines. The ANSI escape sequence handling (\x1b\[[\d;]+m) is comprehensive.
81-91: LGTM!The
rust-formattermatcher appropriately defaults to"severity": "warning"for formatting diffs and captures the file/line fromrustfmt --checkoutput..vscode/extensions.json (1)
6-6: Remove or replace "drblury.protobuf-vsc"—this extension does not exist in VS Code Marketplace or Open VSX registry.The extension ID "drblury.protobuf-vsc" at line 6 cannot be found in either the VS Code Marketplace or the Open VSX registry. This will prevent users from installing the recommended extensions list. "rust-lang.rust-analyzer" at line 16 is valid and published in both registries.
Either remove "drblury.protobuf-vsc" or replace it with a valid protobuf extension (e.g., "panzhihao.protobuf-language-server" or "Protobuf Language Support").
⛔ Skipped due to learnings
Learnt from: cruzdanilo Repo: exactly/exa PR: 627 File: .vscode/extensions.json:6-6 Timestamp: 2026-01-08T20:26:33.396Z Learning: When validating VS Code extension IDs, check both the official VS Code Marketplace (marketplace.visualstudio.com) and Open VSX registry (open-vsx.org) before flagging extensions as invalid, as Open VSX is a widely-used alternative extension registry.contracts/package.json (4)
9-9: LGTM: Test delegation to nx orchestration.Correctly delegates test execution to nx, providing a clean entry point while leveraging nx's task orchestration capabilities.
13-13: LGTM: Slither optimization flag.The
--skip-cleanflag is appropriate since the nxtesttarget ensures a fresh build viadependsOn: ["build"](line 61-63), allowing Slither to reuse build artifacts.
82-100: LGTM: Output configurations for nx caching.The output paths are correctly specified for each test target, enabling effective nx caching. The
.gas-snapshotfile is appropriately included only intest:gas, and standard build artifacts (out,cache) are consistently declared across all targets.
101-107: LGTM: Coverage output configuration.Correctly includes
lcov.infoalong with standard build artifacts, ensuring coverage reports are cached by nx.substreams/.cargo/config.toml (1)
1-2: LGTM!The Cargo configuration correctly sets the build target to
wasm32-unknown-unknown, which aligns with the toolchain targets specified inrust-toolchain.toml.substreams/.gitignore (1)
1-8: LGTM!The gitignore entries appropriately exclude build artifacts, generated code directories (
src/contracts,src/proto), and output files, consistent with the repository's policy on generated directories..changeset/breezy-planes-repeat.md (1)
1-5: LGTM!The changeset correctly follows the repository's conventions: lowercase sentence in imperative present tense with appropriate gitmoji.
pnpm-workspace.yaml (1)
7-7: LGTM!The addition of the
substreamspackage to the workspace is correct and aligns with the new substreams package being introduced in this PR..changeset/rare-readers-tell.md (1)
1-5: LGTM!The changeset follows the correct format with lowercase imperative present tense and appropriate gitmoji for a new feature.
server/test/anvil.ts (1)
159-160: LGTM!Adding the
--tc BobExecuteScripttest case selector ensures the correct test case is executed. This is good practice, especially when a script file contains multiple test cases.substreams/rustfmt.toml (1)
1-6: LGTM!The Rustfmt configuration uses standard formatting options that will ensure consistent code formatting across the substreams Rust package. The settings are reasonable and align with Rust best practices.
.changeset/red-toys-pump.md (1)
1-5: LGTM!The changeset follows the correct format with lowercase imperative present tense and appropriate gitmoji for a new feature.
server/drizzle.config.ts (1)
8-8: schemaFilter is configuration, not a schema change—no additional migrations required.The
substreamsschema is already defined inserver/database/schema.tswithpgSchema("substreams")and includes all its tables (cursors, substreamsHistory, blocks, exaPlugins). Adding it toschemaFilterin the drizzle config is merely configuration telling drizzle-kit to introspect this schema alongside the public schema. This is not a schema change that requires new migrations.Likely an incorrect or invalid review comment.
.github/workflows/server-build.yaml (1)
36-37: LGTM! Explicit Dockerfile path improves clarity.Making the build context and Dockerfile path explicit is a best practice that enhances workflow maintainability and prevents ambiguity.
.changeset/violet-poems-heal.md (1)
1-5: LGTM! Changeset follows the guidelines.The changeset summary uses lowercase imperative present tense ("insert exa plugins into database") and is consistent with the team's convention of using emoji prefixes.
docs/package.json (1)
37-44: LGTM! Proper NX input tracking for OpenAPI dependency.The inputs configuration correctly tracks the OpenAPI output from the server, ensuring the docs build target properly detects changes in the API specification.
server/package.json (2)
64-64: LGTM! Workspace dependency properly added.The new
@exactly/substreamsdev dependency is correctly specified as a workspace reference with the initial version.
143-146: LGTM! Enhanced NX input tracking for broadcasts generation.The extended inputs correctly track the
CHAIN_IDenvironment variable and contract compilation outputs, ensuring the broadcasts generation task properly detects changes in its dependencies.substreams/Cargo.toml (4)
7-9: LGTM! Correct crate type for WASM modules.The
cdylibcrate type is the appropriate choice for building WASM modules that will be loaded by the substreams runtime.
11-21: LGTM! Dependencies are appropriate for substreams development.The dependency set includes the necessary substreams framework crates along with standard Ethereum and serialization libraries.
32-35: LGTM! Release profile optimized for WASM deployment.The release profile settings are well-suited for WASM:
lto = trueenables link-time optimizationopt-level = 's'optimizes for binary sizestrip = "debuginfo"removes debug symbolsThese settings will produce compact, production-ready WASM modules.
4-4: Rust edition "2024" is stable and properly supported. It was released as stable in February 2025 with Rust 1.85.0 and does not require the nightly channel. Using this edition in the Cargo manifest is correct..github/workflows/test.yaml (2)
9-9: LGTM: NX daemon disabled for CI.Disabling the NX daemon in CI environments is a best practice to avoid state persistence issues in ephemeral runners.
34-34: LGTM: Cache path updated consistently.The cache path change to
.nx/cachealigns with similar updates across other workflows in this PR..github/workflows/docs.yaml (1)
33-33: LGTM: Cache path updated consistently.The cache path change to
.nx/cachealigns with similar updates across other workflows in this PR..github/workflows/version.yaml (1)
27-27: LGTM: Cache path updated consistently.The cache path change to
.nx/cachealigns with similar updates across other workflows in this PR..github/workflows/mobile-expo.yaml (1)
46-46: LGTM: Cache path updated consistently.The cache path change to
.nx/cachealigns with similar updates across other workflows in this PR.server/test/e2e.ts (2)
7-7: LGTM: Type import follows TypeScript best practices.Using
import typefor type-only imports is correct and aligns with the coding guidelines.
58-67: LGTM: Sentry mock correctly preserves original behavior while adding observability.The mock implementation properly intercepts
captureExceptionto log arguments before delegating to the original implementation. This is a sound pattern for debugging test failures.common/package.json (1)
44-50: LGTM!The additional
dependentTasksOutputFilesinputs correctly wire contract compilation outputs to the wagmi generation task, ensuring proper cache invalidation when contract artifacts change.contracts/test/mocks/BobExecute.s.sol (1)
61-71: LGTM!The
BatchDeploycontract is a clean utility for batch account creation in test scenarios. The use of pre-increment (++i) is gas-efficient, and the sequential address generation starting at 420 provides deterministic test accounts.server/test/database.ts (2)
49-70: LGTM! Well-structured process orchestration.The use of
AbortControllerwithforceKillAfterDelayand proper error filtering for aborted signals is a clean pattern for managing external processes in tests. The sequentialwaitOncalls ensure proper startup order.
90-92: Good use ofPromise.allSettledfor cleanup.Using
allSettledensures all cleanup operations complete regardless of individual failures, which is the correct pattern for teardown logic..dockerignore (1)
21-21: Verify that excluding all markdown files is intentional.The pattern
**/*.mdexcludes all markdown files including README.md from the Docker build context. If any markdown files are needed during the build (e.g., for documentation generation or content rendering), this pattern may cause issues.server/script/firehose.ts (2)
4-65: LGTM! Clean minimal protobuf cursor implementation.The
ProtoCursorclass correctly handles the four main wire types (0=varint, 1=64-bit, 2=length-delimited, 5=32-bit) with appropriate bounds checking and error handling for unknown wire types.
124-140: Verify the FIRE BLOCK output format.The output on line 135 uses
${libNum}in the 5th position (where the original input hasparentNumbased on typical firehose protocol). The format becomes:FIRE BLOCK num hash libNum parentHash libNum timestamp payload.If intentional (e.g., to satisfy a specific firehose version requirement), consider adding a brief comment explaining why
libNumis used twice.server/database/schema.ts (3)
36-36: LGTM!Good use of destructuring in the index callback for cleaner syntax.
88-91: Consider usingbigintfor block number and timestamp.The
blockstable usesnumericfor bothnumberandtimestamp. Whilenumericprovides arbitrary precision,bigintis more typical for block numbers and provides better type safety in TypeScript. The other substreams tables (cursors,substreams_history) already usebigintforblockNum.Was
numericchosen intentionally (e.g., for compatibility with substreams-sink-sql schema requirements)?
70-100: Schema additions look well-structured.The substreams schema correctly separates concerns using
pgSchema("substreams"). The table definitions align with typical substreams-sink-sql patterns:
cursorsfor checkpoint trackingsubstreams_historyfor CDC/auditblocksfor indexed block dataexa_pluginsfor domain-specific data with composite primary key.github/workflows/substreams-build.yaml (2)
20-20: Clarify the purpose of this tag pattern.The pattern
^(?!@)(.*)$matches any tag not starting with@. Given the workflow only triggers on@exactly/substreams@*tags, this pattern may never match or could create unexpected tags if the workflow is manually dispatched on a different branch/tag.Consider whether this pattern is needed, or if it's a remnant from a template.
34-38: Excellent: Build provenance attestation included.The use of
attest-build-provenanceenhances supply chain security by creating verifiable attestations for the built image.substreams/proto/exa.proto (1)
3-3: Address the Buf package directory mismatch warning.Buf's style guide expects package
exato reside in anexa/directory. The current structure places it insubstreams/proto/. Consider either:
- Moving to
substreams/proto/exa/exa.proto(keeps proto in standard location)- Changing package to
exa.substreamsorsubstreams.exa(aligns with directory)- Configuring Buf to ignore this rule if the current structure is intentional for monorepo organization
Based on learnings, if this structure is intentional for the monorepo, you may want to add a Buf lint exception.
substreams/Dockerfile (4)
1-11: Good: Pinned versions for reproducible builds.The Dockerfile pins specific versions for PNPM (9.15.9) and Foundry (v1.3.6), ensuring reproducible builds. The minimal Rust profile and deferred toolchain selection (likely via
rust-toolchain.toml) are appropriate for WASM builds.
22-30: Excellent: Non-root user and proper ownership.Running as a dedicated
substreamsuser with appropriately owned artifacts follows security best practices.
32-33: Document that the default CMD is for local development only.The hardcoded connection string
postgres://host.docker.internal:5432/postgresis only valid for local Docker Desktop environments. Production deployments should override this CMD with environment-specific configuration.Consider adding a comment in the Dockerfile or documentation noting this is a development default.
20-20: No action needed—the base image is already at the latest stable release.The image
ghcr.io/streamingfast/substreams-sink-sql:v4.11.3is the current latest stable version available. No update is required.substreams/substreams.yaml (5)
14-42: Well-structured module dependency chain.The module pipeline is logically organized:
- Extract accounts from blocks with parameterized factories
- Store accounts with set policy for tracking
- Extract plugins based on stored accounts
- Transform to database changes
The dependency chain and module types are appropriate for the use case.
46-47: Verify the schema path resolution at runtime.The schema path
../server/generated/schema.sqlis relative and crosses package boundaries (substreams → server). When the Docker container runs, the working directory is/app(line 25 in Dockerfile), and the schema is copied to../server/generated/schema.sqlrelative to/app. Verify that this path resolves correctly at runtime, as the COPY in the Dockerfile places it at/server/generated/schema.sql.The Dockerfile copies to
/server/generated/schema.sql(absolute), but the manifest references../server/generated/schema.sql(relative). Confirm these paths align when the sink runs from/app.
58-62: Review the multi-line parameter format for correctness.The Optimism factories parameter uses backslashes for line continuation and ampersands for query string concatenation. Verify that this multi-line YAML string correctly produces the intended query string format when parsed by the substreams runtime.
Test that the resulting parameter string is correctly interpreted as:
factories[]=8D493AF799162Ac3f273e8918B2842447f702163&factories[]=3427a595eD6E05Cc2D8115e28BAd151cB879616e&factories[]=cbeaAF42Cc39c17e84cBeFe85160995B515A9668&factories[]=961EbA47650e2198A959Ef5f337E542df5E4F61b
49-52: Binary configuration is correct.The WASM binary path matches the standard Rust target directory structure and aligns with the Dockerfile's COPY command at line 28.
11-12: Both SPKG versions are already current. The manifest importssubstreams-sink-database-changesv3.0.0 (latest as of November 2025) andsubstreams-sink-sql-protodefsv1.0.7 (latest in the protodefs release track). No updates are needed.Likely an incorrect or invalid review comment.
substreams/buf.gen.yaml (1)
3-6: Verify plugin versions on buf.build directly.The neoeinstein-prost plugins are pinned to v0.5.0. While v0.5.0 crates exist upstream, official documentation examples reference v0.4.0. Confirm that v0.5.0 is available on the Buf registry and is the intended version before merging.
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.github/workflows/version.yaml (1)
8-8: AddNX_DAEMON: falsefor consistency across workflows.Both
test.yamlanddocs.yamlsetNX_DAEMON: false, but this workflow omits it. Sinceversion.yamlperforms substantial build operations (pnpm install and changeset publishing), disabling the daemon should be consistent across all CI workflows.
🤖 Fix all issues with AI agents
In @.github/workflows/test.yaml:
- Around line 38-42: Replace the fragile "latest" binary download loop with a
robust, pinned-and-verified installer: require a version variable per repo
(e.g., FIREHOSE_VERSION, SUBSTREAMS_VERSION, SINK_SQL_VERSION) and use those
instead of querying /releases/latest; add strict error handling (set -euo
pipefail) so the job fails on any download/parsing error; add curl timeouts and
retries (e.g., --fail --retry 3 --retry-delay 5 --max-time 60) and check exit
status; download and validate checksums/signatures (fetch the release checksum
file and verify the tarball before extracting); ensure $HOME/.local/bin is added
to PATH by echoing it to $GITHUB_PATH; simplify jq selection by matching
explicit asset names that include the pinned version and mapped arch (map uname
-m -> arm64/x86_64) rather than a fragile regex.
In @contracts/package.json:
- Line 13: The npm script "test:slither" currently includes the --skip-clean
flag which can mask stale-artifact false negatives; update the scripts and
pipeline so CI runs Slither against a fresh build (remove --skip-clean or add a
separate "test:slither:ci" without --skip-clean) and ensure a clean compile step
(rebuild artifacts, verify solc/remappings/dependencies) runs before Slither; if
--skip-clean is kept for local dev, document that explicitly and add a CI guard
that runs at least one clean Slither invocation to prevent missed warnings.
In @substreams/Dockerfile:
- Around line 20-33: The image Dockerfile lacks a HEALTHCHECK, so add a
HEALTHCHECK instruction after CMD/ENTRYPOINT to let orchestrators probe health;
implement it to run the service's native health endpoint or a lightweight check
against the running binary (substreams-sink-sql) — e.g., invoke the service's
HTTP/GRPC/CLI health probe or a simple TCP check to the port it listens on, set
sensible intervals/timeouts/retries, and ensure the check runs as the substreams
user so it reflects real runtime conditions.
- Line 13: The Dockerfile currently fetches the "latest" Substreams release via
the GitHub API in the RUN curl line which makes builds non-deterministic; update
the RUN command to pin a specific release by introducing a build ARG (e.g., ARG
SUBSTREAMS_VERSION) with a sensible default or hardcode a tag (e.g., vX.Y.Z) and
use it in the download URL instead of querying /releases/latest (replace the
current curl -s https://api.github.com/... logic with a direct URL that includes
$SUBSTREAMS_VERSION and the architecture logic (uname -m | sed
's/aarch64/arm64/') so the image pulls a fixed linux_$arch tarball).
- Line 13: The Dockerfile RUN step downloads and pipes the substreams archive
directly into tar without verifying integrity; change it to download the release
asset to a file (e.g., /tmp/substreams.tar.gz) using the same jq logic used to
get the browser_download_url, also fetch the corresponding checksum (either the
checksums.txt asset or the .sha256 file for the platform), verify the archive
with sha256sum (or sha256sum -c against checksums.txt) and fail the build on
mismatch, and only after successful verification extract the archive with tar
zxf; update the RUN that currently contains the piped curl/tar to perform:
resolve URLs with the existing jq expression, curl -L -o <file>, curl -L -o
<checksum_file>, verify with sha256sum, and then tar zxf the verified file.
- Around line 32-33: The Dockerfile currently hardcodes a development DB URL in
CMD which will fail outside Docker Desktop; remove that hardcoded CMD line and
either (a) delete CMD so the image requires the connection string at runtime
(keep ENTRYPOINT ["dumb-init", "/app/substreams-sink-sql", "run"]), or (b)
replace the hardcoded value with an environment-driven value by using an ENV/ARG
and a shell-form CMD that references DATABASE_URL (or similar) so production
must set DATABASE_URL; also update docs to state DATABASE_URL is required in
production.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (9)
.dockerignore.github/workflows/docs.yaml.github/workflows/mobile-expo.yaml.github/workflows/server-build.yaml.github/workflows/substreams-build.yaml.github/workflows/test.yaml.github/workflows/version.yamlcontracts/package.jsonsubstreams/Dockerfile
🧰 Additional context used
🧠 Learnings (12)
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use `nx` for running tasks (build, lint, test, e2e) instead of underlying tooling directly
Applied to files:
.github/workflows/mobile-expo.yaml.github/workflows/version.yaml.github/workflows/docs.yamlcontracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Install dependencies using `pnpm install`
Applied to files:
.github/workflows/version.yaml.github/workflows/docs.yaml
📚 Learning: 2026-01-08T19:45:38.852Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .github/workflows/substreams-build.yaml:2-2
Timestamp: 2026-01-08T19:45:38.852Z
Learning: In the exactly/exa repository, prefer concise inline YAML syntax (e.g., `on: { push: { tags: ["pattern"] }, workflow_dispatch: {} }`) over expanded multi-line format in GitHub workflow files, even if it triggers YAMLlint warnings about formatting.
Applied to files:
.github/workflows/substreams-build.yaml.github/workflows/docs.yaml
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Applies to {src/generated/**,server/generated/**,contracts/broadcast/**,contracts/cache/**} : Do not manually edit files in generated directories: `src/generated/`, `server/generated/`, `contracts/broadcast/`, and `contracts/cache/`
Applied to files:
.dockerignore
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run contract tests using `pnpm --filter plugin test` and check formatting using `pnpm --filter plugin test:fmt`
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run all tests using `pnpm test`, which includes TypeScript compilation, ESLint, spell checking, markdown linting, and contract tests
Applied to files:
contracts/package.json
📚 Learning: 2026-01-08T19:46:37.829Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: contracts/test/mocks/BobExecute.s.sol:61-71
Timestamp: 2026-01-08T19:46:37.829Z
Learning: Natspec documentation requirements do not apply to test/mock/dev scripts (files in contracts/test/ or with .s.sol extension). Only production contract code in contracts/src/ requires complete natspec comments.
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to contracts/**/*.test.sol : Smart contracts testing: fuzzing required for inputs and gas snapshots mandatory
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Use fuzz testing (testFuzz_...) extensively for functions with numerical or address inputs to cover a wide range of scenarios
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Rely on `pnpm` scripts for all tasks; all setup, testing, and generation must be automated
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Test files must end with .t.sol and mirror the name of the contract they test (e.g., ExaPlugin.sol -> ExaPlugin.t.sol)
Applied to files:
contracts/package.json
🪛 Checkov (3.2.334)
substreams/Dockerfile
[low] 1-33: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
🪛 YAMLlint (1.37.1)
.github/workflows/substreams-build.yaml
[warning] 2-2: truthy value should be one of [false, true]
(truthy)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
.github/workflows/docs.yaml
[error] 14-14: too many spaces inside braces
(braces)
[error] 14-14: too many spaces inside braces
(braces)
🔇 Additional comments (11)
.github/workflows/version.yaml (1)
27-27: LGTM! Cache path updated correctly.The NX cache path change from
node_modules/.cache/nxto.nx/cachealigns with the updates across other workflows in this PR..github/workflows/docs.yaml (2)
14-14: LGTM! Inline YAML format is acceptable.The
NX_DAEMON: falsesetting is appropriate for CI environments. The inline braces format triggers a YAMLlint warning, but based on learnings, the team prefers concise inline YAML syntax in workflow files.
33-33: LGTM! Cache path updated correctly.The NX cache path change aligns with updates across other workflows in this PR.
.github/workflows/mobile-expo.yaml (1)
46-46: LGTM! Cache path updated correctly.The NX cache path change aligns with updates across other workflows in this PR.
.github/workflows/test.yaml (2)
9-9: LGTM! NX daemon disabled for CI.Setting
NX_DAEMON: falseis appropriate for CI environments to ensure deterministic builds.
34-34: LGTM! Cache path updated correctly.The NX cache path change aligns with updates across other workflows in this PR.
contracts/package.json (2)
49-49: LGTM: Well-structured test orchestration with appropriate parallelism settings.The addition of
"parallelism": falseto the build target and all test targets is correct, as Forge-based tasks share artifacts inout/andcache/directories. Running these tasks in parallel could cause race conditions and build inconsistencies.The restructured NX targets provide:
- Clear dependency orchestration through the main
testtarget withnx:noopexecutor- Explicit outputs for each target enabling proper NX caching
- Granular test targets (
test:sizes,test:gas,test:slither,test:coverage) that can be run independently or togetherThis improves both CI performance (through better caching) and reliability (by preventing parallel execution conflicts).
Also applies to: 61-101
9-9: The removal oftest:alltarget is safe. No references to this target exist elsewhere in the codebase, CI workflows, or documentation, so the restructuring fromnx test:all contractstonx test contractswill not break any existing workflows..github/workflows/server-build.yaml (1)
36-41: LGTM! Improved build performance with explicit targeting and caching.The explicit
filespecification and GitHub Actions cache configuration will improve build reliability and performance. Themode=maxexports all layers for optimal cache reuse..dockerignore (1)
1-29: LGTM! Well-structured ignore patterns.The granular ignore patterns appropriately exclude build artifacts, generated files, and temporary content from the Docker context while preserving necessary files like
run-latest.jsonthrough negation..github/workflows/substreams-build.yaml (1)
1-40: LGTM! Well-structured workflow with proper caching and attestation.The workflow follows best practices with:
- GitHub Actions caching for faster builds
- Build provenance attestation for supply chain security
- Proper permissions scoped to the job
- Consistent structure with the server-build workflow
The inline YAML syntax aligns with the repository's preferred style.
co-authored-by: itofarina <farinalvaro@gmail.com>
There was a problem hiding this comment.
Actionable comments posted: 16
🤖 Fix all issues with AI agents
In @.changeset/breezy-planes-repeat.md:
- Line 5: The changeset summary currently begins with an emoji ("✨ map exa
accounts"); remove the emoji prefix so the summary is a lowercase,
imperative-present-tense sentence (e.g., change "✨ map exa accounts" to "map exa
accounts") and save the updated summary in the changeset file where that line
appears.
In @.changeset/rare-readers-tell.md:
- Around line 1-5: The changeset summary contains an emoji and doesn't follow
the required lowercase imperative style; edit the
.changeset/rare-readers-tell.md summary line (currently "✨ store exa accounts")
to remove the emoji and use a lowercase imperative sentence such as "store exa
accounts" (no emoji, no trailing punctuation) so it conforms to the repository's
.changeset formatting rules.
In @.changeset/red-toys-pump.md:
- Around line 1-5: Update the changeset summary in ".changeset/red-toys-pump.md"
to be a lowercase imperative sentence with no emoji: replace "✨ map exa plugins"
with a concise imperative like "map exa plugins" (all lowercase, no emoji or
punctuation beyond the sentence itself) so the top-level summary follows the
repository changeset guideline.
In @.changeset/violet-poems-heal.md:
- Around line 1-5: Replace the changeset summary string "✨ insert exa plugins
into database" with a lowercase, imperative sentence without emoji or extra
punctuation — e.g., change it to "insert exa plugins into database" so the
summary follows the required lowercase imperative format.
In @.github/workflows/test.yaml:
- Around line 38-42: Replace the current unpinned, unauthenticated download loop
with a robust, pinned-and-verified implementation: add shell safety flags (set
-euo pipefail), introduce environment variables for each release tag (e.g.,
FIREHOSE_VERSION, SUBSTREAMS_VERSION, SUBSTREAMS_SINK_SQL_VERSION) and use those
tags when querying the GitHub Releases API, include the Authorization header
with the GITHUB_TOKEN when calling the API, restrict asset selection to only
.tar.gz assets before downloading, verify each downloaded archive against a
pinned checksum (or .sha256 file) prior to extraction, append "$HOME/.local/bin"
to $GITHUB_PATH so extracted binaries are on PATH, and ensure failures are
detected and exit non‑zero; locate and update the loop that iterates over the
three repos and the curl/tar sequence to implement these changes.
In @cspell.json:
- Around line 3-12: The cspell configuration's ignorePaths only includes
"substreams/target" but should also ignore the other generated substreams
directories listed in .gitignore/.dockerignore; update the "ignorePaths" array
in cspell.json to add "substreams/abi", "substreams/firehose-data",
"substreams/src/contracts", and "substreams/src/proto" so cspell won't run on
generated code before builds or codegen tasks.
In @server/database/schema.ts:
- Around line 69-99: The repo is missing migration files for the new substreams
schema defined by substreams and its tables cursors, substreamsHistory, blocks,
and exaPlugins; generate and add the migration files by running the drizzle-kit
migration generator (e.g., run drizzle-kit generate against the current schema),
commit the created migrations into a migrations directory, and ensure the
generated migration contains the DDL for those tables so future
deploys/migrations reflect the changes.
In @server/script/firehose.ts:
- Around line 100-104: The timestamp parsing loop that calls tsCursor.readTag()
assumes fields 1 (seconds) and 2 (nanoseconds) are varints without validating
tag.wireType, so update the loop in the function that reads timestamps (the for
loop using tsCursor.readTag()) to only call tsCursor.readVarint() for
fieldNumber 1 or 2 when tag.wireType is the varint wire type (e.g., 0 or your
WireType.VARINT enum); otherwise call tsCursor.skip(tag.wireType) so malformed
or changed schemas won’t be mis-parsed.
In @substreams/.gitignore:
- Around line 1-8: The .gitignore entries are currently unanchored and can match
anywhere under substreams; update the patterns to anchored top-level paths
(e.g., change abi, firehose-data, target, src/contracts, src/proto to /abi/,
/firehose-data/, /target/, /src/contracts/, /src/proto/) so they only ignore
those root folders, and confirm that src/proto and src/contracts are generated
artifacts (not source-of-truth) before ignoring them; keep the global file
patterns (*.log, *.spkg) as-is.
In @substreams/build.rs:
- Around line 61-67: The current code uses assert! and expect("formatting
failed") around Command::new("rustfmt") which is inconsistent with Result-based
error handling; replace that block so you call
Command::new("rustfmt").args(...).status()? into a variable (e.g., let status =
Command::new("rustfmt").args(...).status()?;) and then check status.success()
and return an Err with a clear diagnostic (or propagate a context-wrapped error)
if it is false instead of panicking; update the enclosing function signature to
return a Result (e.g., Result<(), Box<dyn std::error::Error>> or anyhow::Result)
if needed so the failure is surfaced consistently.
- Around line 50-59: The current Command::new(...).arg("protogen")...
.status().expect(...); wrapped in assert! is inconsistent and panics; replace
this with Result-style error propagation: call
Command::new(...).arg("protogen")... .status()? to capture the ExitStatus (or
.status().map_err(|e| format!(...))? if not using ?), then check exit.success()
and if false return an Err with a clear context string (e.g.,
format!("substreams protogen failed with status: {}", exit)), avoiding assert!
and .expect(); reference the existing Command::new(...).arg("protogen")...
.env(...) invocation and the uses of .status(), .expect(), and assert! when
making the change.
In @substreams/Dockerfile:
- Line 13: The Dockerfile currently downloads the "latest" substreams CLI which
makes builds non-reproducible; change the RUN that calls the GitHub releases API
to use a fixed, pinable version (e.g., introduce an ARG like SUBSTREAMS_VERSION
and use that tag in the API/query instead of "latest" or hardcode a specific
release tag) so the curl/tar step always fetches the same release; update the
RUN line that builds the URL (the curl -s
https://api.github.com/repos/streamingfast/substreams/releases/...) to reference
that pinned version variable or explicit tag and document the ARG default value
at the top of the Dockerfile.
In @substreams/package.json:
- Around line 44-64: Add an explicit nx target named "test:protolint" to match
the existing targets (so the top-level "test" target's dependsOn entry has a
concrete target), declare any dependencies similar to "test:clippy" (e.g.,
"prepare", "^prepare", or other required setup like "rustup") and set an
appropriate executor (e.g., "nx:run-commands" or the npm script) so the target
is defined consistently alongside "test:clippy" and "test:fmt"; update the
package.json targets section to include this "test:protolint" entry referencing
the same conventions used for the other test targets.
In @substreams/proto/exa.proto:
- Around line 1-17: Buf linter complains because the proto package is declared
as "package exa" but the file lives outside an exa directory; either move this
proto so its module path matches the package (e.g., place the file under an exa
directory so package exa aligns with the filesystem) or add/update buf.yaml to
disable the PACKAGE_DIRECTORY_MATCH rule; update references to the messages
(Accounts, Plugins, Plugins.Plugin) accordingly if you move the file so imports
remain correct.
In @substreams/rust-toolchain.toml:
- Around line 1-4: The toolchain is pinned to nightly via the line channel =
"nightly"; update it to a stable channel (e.g., channel = "stable" or channel =
"1.85") to avoid unnecessary nightly usage while keeping the components =
["clippy", "rustfmt"] and targets = ["wasm32-unknown-unknown"] unchanged; locate
the [toolchain] block and replace only the channel = "nightly" entry with the
chosen stable channel string.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsubstreams/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (39)
.changeset/breezy-planes-repeat.md.changeset/rare-readers-tell.md.changeset/red-toys-pump.md.changeset/violet-poems-heal.md.dockerignore.github/matcher.json.github/workflows/docs.yaml.github/workflows/mobile-expo.yaml.github/workflows/server-build.yaml.github/workflows/substreams-build.yaml.github/workflows/test.yaml.github/workflows/version.yaml.vscode/extensions.jsoncommon/package.jsoncontracts/package.jsoncontracts/test/mocks/BobExecute.s.solcspell.jsondocs/package.jsonpnpm-workspace.yamlserver/Dockerfileserver/database/schema.tsserver/drizzle.config.tsserver/package.jsonserver/script/firehose.tsserver/test/anvil.tsserver/test/database.tsserver/test/e2e.tssubstreams/.cargo/config.tomlsubstreams/.gitignoresubstreams/Cargo.tomlsubstreams/Dockerfilesubstreams/buf.gen.yamlsubstreams/build.rssubstreams/package.jsonsubstreams/proto/exa.protosubstreams/rust-toolchain.tomlsubstreams/rustfmt.tomlsubstreams/src/lib.rssubstreams/substreams.yaml
🧰 Additional context used
📓 Path-based instructions (11)
**/.changeset/*.md
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use a lowercase sentence in the imperative present tense for changeset summaries
Files:
.changeset/violet-poems-heal.md.changeset/red-toys-pump.md.changeset/rare-readers-tell.md.changeset/breezy-planes-repeat.md
server/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/**/*.ts: Usec.varobject to pass strongly-typed data between Hono middleware and route handlers; do not usec.set
All request validation (headers, body, params) must be handled by@hono/valibot-validatormiddleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwingnew HTTPException()for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLintplugin:n/recommendedconfiguration
Enforce Drizzle ORM best practices using ESLintplugin:drizzle/allconfiguration, including requiringwhereclauses forupdateanddeleteoperations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not accessprocess.envdirectly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; useasync/awaitcorrectly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Files:
server/drizzle.config.tsserver/test/e2e.tsserver/test/database.tsserver/test/anvil.tsserver/script/firehose.tsserver/database/schema.ts
**/*.{js,ts,tsx,jsx,sol}
📄 CodeRabbit inference engine (AGENTS.md)
Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No
anytype.
Files:
server/drizzle.config.tsserver/test/e2e.tsserver/test/database.tsserver/test/anvil.tsserver/script/firehose.tscontracts/test/mocks/BobExecute.s.solserver/database/schema.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Omit redundant type names in variable declarations - let the type system explain itself
**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas usingtype User = v.Input<typeof UserSchema>instead of manually defining interfaces
Files:
server/drizzle.config.tsserver/test/e2e.tsserver/test/database.tsserver/test/anvil.tsserver/script/firehose.tsserver/database/schema.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Preferconstfor all variable declarations by default; only useletif the variable's value will be reassigned
Declare each variable on its own line with its ownconstorletkeyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always useimport type { ... }for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), andfor...ofloops over traditional syntax
Do not use abbreviations or cryptic names; write out full words likeerror,parameters,requestinstead oferr,params,req
UseNumber.parseInt()instead of the globalparseInt()function when parsing numbers
All classes called withnewmust use PascalCase
UseBuffer.from(),Buffer.alloc(), orBuffer.allocUnsafe()instead of the deprecatednew Buffer()
Use@ts-expect-errorinstead of@ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like-or:
Do not include the type in a variable's name; let the static type system do its job (e.g., useconst user: Usernotconst userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...
Files:
server/drizzle.config.tsserver/test/e2e.tsserver/test/database.tsserver/test/anvil.tsserver/script/firehose.tsserver/database/schema.ts
server/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - usec.var.db
Files:
server/drizzle.config.tsserver/test/e2e.tsserver/test/database.tsserver/test/anvil.tsserver/script/firehose.tsserver/database/schema.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
For files with a single
defaultexport, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word
Files:
server/drizzle.config.tsserver/test/e2e.tsserver/test/database.tsserver/test/anvil.tsserver/script/firehose.tsserver/database/schema.ts
**/+(app|src)/**
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
Use kebab-case for directory names and public Expo router routes
Files:
substreams/src/lib.rs
contracts/**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/contracts.mdc)
contracts/**/*.sol: Import order must be sorted alphabetically and grouped by type (openzeppelin, solady, internal, etc.), enforced by forge fmt
All functions and state variables must have explicit visibility modifiers (public, private, internal, external)
Use custom errors instead of require() statements with string messages; require() with messages is forbidden
console.log is forbidden in contract code
All external and public functions that modify state must be protected by a reentrancy guard, unless explicitly proven unnecessary
Use modifiers like onlyOwner or function-level checks for privileged functions; default to strictest access level (private > internal > external > public)
Use named parameters in mappings for clarity (e.g., mapping(address user => uint256 balance))
All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Follow standard Solidity import order: external libraries (openzeppelin, solady) first, then project-internal contracts
All public and external functions and state variables must have complete natspec comments (@notice, @dev, @param, @return)
Use custom errors instead of require statements with string messages for better gas efficiency and error information
Internal and private variables should be prefixed with an underscore (_); function arguments should not be prefixed
Use constant keyword for true constants; use immutable for configurable values like contract addresses
Minimize writes to storage; read data into memory or calldata whenever possible for gas optimization
Use smallest integer size that is safe for the variable (e.g., uint32 for timestamps) and be aware of struct packing for gas efficiency
Prefer using the solady library over openzeppelin where possible, as it is highly optimized for gas
Correctly label functions as view or pure when they do not modify state
contracts/**/*.sol: Smart contracts: use custom errorserror MyError();instead of s...
Files:
contracts/test/mocks/BobExecute.s.sol
**/*.sol
📄 CodeRabbit inference engine (.cursor/rules/style.mdc)
**/*.sol: Use camelCase for Solidity variables and functions
Use PascalCase for Solidity contracts and structs
Files:
contracts/test/mocks/BobExecute.s.sol
server/database/schema.ts
📄 CodeRabbit inference engine (.cursor/rules/server.mdc)
server/database/schema.ts: Database schema must be defined inserver/database/schema.tsas the single source of truth for database structure
Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Files:
server/database/schema.ts
🧠 Learnings (39)
📚 Learning: 2026-01-08T19:45:38.852Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .github/workflows/substreams-build.yaml:2-2
Timestamp: 2026-01-08T19:45:38.852Z
Learning: In the exactly/exa repository, prefer concise inline YAML syntax (e.g., `on: { push: { tags: ["pattern"] }, workflow_dispatch: {} }`) over expanded multi-line format in GitHub workflow files, even if it triggers YAMLlint warnings about formatting.
Applied to files:
.github/workflows/docs.yaml.github/workflows/substreams-build.yaml
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use `nx` for running tasks (build, lint, test, e2e) instead of underlying tooling directly
Applied to files:
.github/workflows/docs.yaml.github/workflows/mobile-expo.yaml.github/workflows/version.yamlcontracts/package.jsonsubstreams/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Install dependencies using `pnpm install`
Applied to files:
.github/workflows/docs.yaml.github/workflows/version.yaml
📚 Learning: 2026-01-08T20:26:33.396Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .vscode/extensions.json:6-6
Timestamp: 2026-01-08T20:26:33.396Z
Learning: For validating VS Code extension IDs in this repository, check both the official VS Code Marketplace (marketplace.visualstudio.com) and the Open VSX registry (open-vsx.org) before flagging an extension as invalid. Open VSX is a widely-used alternative registry, so a valid ID may appear in either registry. If an ID is not present in both registries, flag it as invalid. Apply this check specifically when validating the IDs listed in .vscode/extensions.json.
Applied to files:
.vscode/extensions.json
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.
Applied to files:
.changeset/violet-poems-heal.md.changeset/red-toys-pump.md.changeset/rare-readers-tell.md.changeset/breezy-planes-repeat.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries
Applied to files:
.changeset/violet-poems-heal.md.changeset/rare-readers-tell.md.changeset/breezy-planes-repeat.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Select the most precise gitmoji that best communicates the intent of the change (e.g., ✨ for new feature, 🐛 for bug fix, ♻️ for refactoring, ⚡️ for performance improvement, 📝 for documentation, 🚀 for deployment)
Applied to files:
.changeset/violet-poems-heal.md.changeset/red-toys-pump.md.changeset/rare-readers-tell.md.changeset/breezy-planes-repeat.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commits must start with a single gitmoji from the official gitmoji list; this is not optional and the emoji visually communicates the intent of the change
Applied to files:
.changeset/violet-poems-heal.md.changeset/breezy-planes-repeat.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Do not use conventional commit prefixes like `feat:`, `fix:`, or `docs(...)`; do not use title case or sentence case in the commit subject line; do not create commits without a scope
Applied to files:
.changeset/violet-poems-heal.md.changeset/breezy-planes-repeat.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commit messages must strictly adhere to the format: `<emoji> <scope>: <message>` where emoji is a gitmoji unicode character, scope is a mandatory short name (app, server, contracts, docs, common, dependencies, github, config, or repo), and message is a short lowercase description
Applied to files:
.changeset/violet-poems-heal.md.changeset/breezy-planes-repeat.md
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Define relations explicitly in the schema file to enable Drizzle to generate typed query results including related entities
Applied to files:
server/drizzle.config.tsserver/test/database.tssubstreams/substreams.yamlserver/database/schema.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to server/**/*.{ts,tsx} : Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use `c.var.db`
Applied to files:
server/drizzle.config.tsserver/test/database.tssubstreams/substreams.yamlserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/schema.ts : Database schema must be defined in `server/database/schema.ts` as the single source of truth for database structure
Applied to files:
server/drizzle.config.tssubstreams/substreams.yamlserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/database/migrations/** : All schema changes must be accompanied by a migration file generated by `drizzle-kit`; never edit the database schema manually
Applied to files:
server/drizzle.config.tsserver/test/database.tssubstreams/substreams.yamlserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Drizzle ORM best practices using ESLint `plugin:drizzle/all` configuration, including requiring `where` clauses for `update` and `delete` operations
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
Applied to files:
server/drizzle.config.tsserver/test/database.tsserver/database/schema.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words from names like `data`, `state`, `manager`, `engine`, `object`, `entity`, and `instance`
Applied to files:
cspell.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation
Applied to files:
cspell.jsonserver/script/firehose.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services
Applied to files:
server/test/e2e.tscontracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All public and external functions and state variables must have complete natspec comments (notice, dev, param, return)
Applied to files:
substreams/proto/exa.protocontracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop
Applied to files:
server/test/database.tsserver/script/firehose.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Server (exactly/server) uses Hono (framework), Node.js, Drizzle ORM (database), and PostgreSQL
Applied to files:
server/test/database.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Applies to {src/generated/**,server/generated/**,contracts/broadcast/**,contracts/cache/**} : Do not manually edit files in generated directories: `src/generated/`, `server/generated/`, `contracts/broadcast/`, and `contracts/cache/`
Applied to files:
common/package.jsonsubstreams/build.rs.dockerignore
📚 Learning: 2026-01-08T19:54:26.885Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: substreams/rust-toolchain.toml:1-4
Timestamp: 2026-01-08T19:54:26.885Z
Learning: Rust edition 2024 was released as stable on February 20, 2025 with Rust 1.85.0 and does not require nightly channel.
Applied to files:
substreams/Cargo.tomlsubstreams/rust-toolchain.toml
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : Use custom errors instead of require statements with string messages for better gas efficiency and error information
Applied to files:
substreams/build.rs
📚 Learning: 2026-01-08T19:54:10.673Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: substreams/src/lib.rs:54-71
Timestamp: 2026-01-08T19:54:10.673Z
Learning: In substreams/src/lib.rs in the map_exa_plugins function, the deduplication logic using `!seen.insert(address.to_vec())` is intentional. The first plugin installed for a new account is an owner plugin (not an exa plugin), so it must be filtered out. The negation ensures the first plugin is skipped and subsequent plugins (the actual exa plugins) are kept.
Applied to files:
substreams/src/lib.rs
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Do not use abbreviations or cryptic names; write out full words like `error`, `parameters`, `request` instead of `err`, `params`, `req`
Applied to files:
server/script/firehose.ts
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : All code must pass pnpm --filter plugin test:slither and pnpm --filter plugin test:solhint with zero warnings; do not ignore warnings
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run contract tests using `pnpm --filter plugin test` and check formatting using `pnpm --filter plugin test:fmt`
Applied to files:
contracts/package.json
📚 Learning: 2026-01-08T19:46:37.829Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: contracts/test/mocks/BobExecute.s.sol:61-71
Timestamp: 2026-01-08T19:46:37.829Z
Learning: Natspec documentation requirements do not apply to test/mock/dev scripts (files in contracts/test/ or with .s.sol extension). Only production contract code in contracts/src/ requires complete natspec comments.
Applied to files:
contracts/package.jsoncontracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Run all tests using `pnpm test`, which includes TypeScript compilation, ESLint, spell checking, markdown linting, and contract tests
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to contracts/**/*.test.sol : Smart contracts testing: fuzzing required for inputs and gas snapshots mandatory
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Use fuzz testing (testFuzz_...) extensively for functions with numerical or address inputs to cover a wide range of scenarios
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Rely on `pnpm` scripts for all tasks; all setup, testing, and generation must be automated
Applied to files:
contracts/package.json
📚 Learning: 2026-01-09T15:14:32.535Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: contracts/package.json:13-13
Timestamp: 2026-01-09T15:14:32.535Z
Learning: In Slither with Foundry, the `--skip-clean` flag only skips an initial cleanup step but still runs `forge build --force`, ensuring fresh build artifacts. There's no risk of stale artifacts causing false negatives.
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Test files must end with .t.sol and mirror the name of the contract they test (e.g., ExaPlugin.sol -> ExaPlugin.t.sol)
Applied to files:
contracts/package.json
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.t.sol : Use vm.prank to simulate calls from different addresses; avoid changing msg.sender through other means
Applied to files:
contracts/test/mocks/BobExecute.s.sol
📚 Learning: 2025-12-23T19:56:43.683Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/contracts.mdc:0-0
Timestamp: 2025-12-23T19:56:43.683Z
Learning: Applies to contracts/**/*.sol : Use smallest integer size that is safe for the variable (e.g., uint32 for timestamps) and be aware of struct packing for gas efficiency
Applied to files:
server/database/schema.ts
🧬 Code graph analysis (2)
server/test/database.ts (1)
server/database/schema.ts (1)
substreams(69-69)
substreams/src/lib.rs (1)
server/database/schema.ts (1)
substreams(69-69)
🪛 Buf (1.62.1)
substreams/proto/exa.proto
3-3: Files with package "exa" must be within a directory "exa" relative to root but were in directory "substreams/proto".
(PACKAGE_DIRECTORY_MATCH)
🪛 Checkov (3.2.334)
substreams/Dockerfile
[low] 1-33: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
🪛 YAMLlint (1.37.1)
.github/workflows/docs.yaml
[error] 14-14: too many spaces inside braces
(braces)
[error] 14-14: too many spaces inside braces
(braces)
.github/workflows/substreams-build.yaml
[warning] 2-2: truthy value should be one of [false, true]
(truthy)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 2-2: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 6-6: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 11-11: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
[error] 25-25: too many spaces inside braces
(braces)
🔇 Additional comments (48)
.vscode/extensions.json (2)
6-6: Verifydrblury.protobuf-vscpublisher casing + Open VSX availability (if you support VSCodium). (marketplace.visualstudio.com)Marketplace lists this as
DrBlury.protobuf-vsc; if any tooling treats IDs as case-sensitive, the current lower-case form could fail to resolve. Based on learnings, also check Open VSX before concluding it’s invalid/unavailable.Proposed tweak (only if casing is required)
- "drblury.protobuf-vsc", + "DrBlury.protobuf-vsc",
16-16:rust-lang.rust-analyzeris a solid recommendation for the new Rust surface area. (marketplace.visualstudio.com)No concerns with the identifier.
substreams/.cargo/config.toml (1)
1-2: LGTM!Standard Cargo configuration for WASM builds. The
wasm32-unknown-unknowntarget is appropriate for substreams WASM modules.pnpm-workspace.yaml (1)
7-7: LGTM!Correctly adds the substreams package to the pnpm workspace, aligning with the PR's objective to introduce initial substreams implementation.
.github/workflows/docs.yaml (2)
14-14: LGTM!The inline YAML format aligns with the team's preference for concise syntax in workflow files. The YAMLlint warning can be safely ignored.
Based on learnings, concise inline YAML syntax is preferred in this repository even if it triggers formatting warnings.
33-33: LGTM!Cache path update from
node_modules/.cache/nxto.nx/cachecorrectly aligns with the broader NX cache location changes across workflows in this PR..github/workflows/version.yaml (1)
27-27: LGTM!Cache path update correctly aligns with the NX cache location changes applied consistently across multiple workflows in this PR.
.github/workflows/mobile-expo.yaml (1)
44-48: LGTM: Nx cache path updated to.nx/cache.This aligns with the newer Nx default cache directory and should improve cache portability across installs.
server/drizzle.config.ts (1)
8-9:schemaFilteris fully supported in drizzle-kit 0.31.8 and the configuration is correctly implemented. TheschemaFilter: ["public", "substreams"]explicitly includes only those schemas during introspection and migrations—it does not hide required tables; it intentionally filters to specified schemas. No changes needed.substreams/rustfmt.toml (1)
1-6: All rustfmt options in this configuration are valid and supported by the nightly toolchain. Thetab_spaces = 2setting is intentional (the default is 4 spaces), representing a deliberate project choice for 2-space indentation. No action required..github/workflows/server-build.yaml (2)
40-41: GHA cache enablement looks good; confirm it is scoped as intended across environments.
32-41: Build context configuration is already properly safeguarded.The root
.dockerignoreis present and well-maintained, aggressively excluding build artifacts (dist, cache, build, generated), dependencies (node_modules), secrets (.env), and development files (.log, .md, .tsbuildinfo). The multi-stage Dockerfile further minimizes risk by keeping only production dependencies in the final image. Thecontext: .setup is safe as-is.server/test/e2e.ts (1)
7-7: Type-only import is fine forimportOriginal<typeof sentry>()typing..github/workflows/test.yaml (1)
8-36: NX daemon disable + cache relocation look good for CI stability.docs/package.json (1)
31-45: No changes needed. ThedependentTasksOutputFiles: "server/generated/openapi.json"path is correct and properly reflects the workspace-relative output location of the server package'sgenerate:openapitarget.common/package.json (1)
44-50: LGTM!The addition of
dependentTasksOutputFilesentries correctly establishes dependencies on contract build outputs (contracts/out/**/*.json) and broadcast artifacts (contracts/broadcast/*/31337/*.json). This ensures the wagmi generation target properly invalidates its cache when upstream contract artifacts change.server/test/database.ts (2)
49-70: Well-structured process management with proper cancellation.The use of
AbortControllerwithcancelSignalandforceKillAfterDelayis appropriate for managing long-running external processes. Error handling correctly ignores abort-related errors while propagating unexpected failures.
90-93: Clean teardown with graceful process termination.Using
Promise.allSettledensures all cleanup operations complete regardless of individual failures, which is the correct pattern for teardown functions.contracts/test/mocks/BobExecute.s.sol (2)
39-39: LGTM!Using
getAddressinstead ofcreateAccountis appropriate here sincesetUponly needs to derive the account address for labeling and testing, not actually deploy it.
61-71: LGTM!The
BatchDeployhelper contract efficiently creates multiple test accounts in a single transaction. The sequential address generation (420 + i) provides deterministic test addresses. Based on learnings, natspec documentation is not required for test/mock contracts.server/test/anvil.ts (1)
159-160: LGTM!Adding
--tc BobExecuteScriptcorrectly targets only the intended test contract, which is necessary now that the file contains the additionalBatchDeploycontract.server/script/firehose.ts (2)
135-135: Verify the output format useslibNumtwice intentionally.The output duplicates
libNumin positions 5 and 6:FIRE BLOCK ${num} ${hash} ${libNum} ${parentHash} ${libNum} ${timestamp} ${newPayload}The input has 7 tokens with presumably different values at positions 5 and 6. Please confirm this duplication is the intended protocol transformation.
4-65: Well-implemented minimal protobuf cursor.The
ProtoCursorclass correctly handles varint decoding with proper overflow protection and supports the common wire types (0, 1, 2, 5). The implementation avoids heavy protobuf library dependencies for this lightweight processing task.contracts/package.json (3)
61-71: LGTM!The composite
testtarget withexecutor: "nx:noop"properly orchestrates all test subtasks throughdependsOn. This provides granular caching while maintaining a single entry point for running all tests.
72-92: LGTM!Setting
parallelism: falseon forge-based targets prevents concurrent executions that could cause file system conflicts with sharedoutandcachedirectories. The explicitoutputsdeclarations enable proper NX caching.
13-13: LGTM!The
--skip-cleanflag is appropriate here. Based on learnings, Slither with Foundry still runsforge build --forceinternally, ensuring fresh artifacts without risk of stale data.server/database/schema.ts (2)
35-35: LGTM!The updated destructured syntax
({ account }) => [...]is cleaner and aligns with the pattern used in the new tables. This is a minor stylistic improvement with no functional change.
87-90: Consider using appropriate integer types for block timestamps.Per coding guidelines, the smallest safe integer size should be used. Unix timestamps in seconds fit in
integer(32-bit), but iftimestampstores nanoseconds (as suggested by the firehose script'sseconds * 1_000_000_000n + nanoseconds), thenbigintis correct. Please confirm the timestamp unit.substreams/buf.gen.yaml (1)
1-8: LGTM!The Buf code-generation configuration is well-structured with pinned plugin versions for reproducibility. The output directory
src/protoaligns with the Rust code that consumes these generated types.server/package.json (2)
64-64: LGTM!Adding
@exactly/substreamsas a workspace dev dependency appropriately integrates the new substreams package for server-side tooling and build processes.
143-146: LGTM!Adding
dependentTasksOutputFilesfor contract build artifacts ensures proper NX cache invalidation when contract outputs change, establishing the correct dependency chain for thegenerate:broadcaststarget.substreams/Dockerfile (1)
20-33: LGTM!The runtime stage follows security best practices: non-root user (
substreams), proper signal handling withdumb-init, and minimal runtime image. The static analysis hint about missing HEALTHCHECK is acceptable for a sink container that connects to external Postgres—health is better determined by the orchestration layer.substreams/Cargo.toml (1)
1-35: LGTM!The Cargo manifest is well-configured for a substreams WASM library:
- Edition 2024 is appropriately used (stable since Rust 1.85.0)
cdylibcrate type is correct for WASM output- Release profile optimizes for size (
opt-level = 's', LTO, strip debuginfo), which is ideal for WASM deployment.github/workflows/substreams-build.yaml (1)
1-40: LGTM!The workflow is well-structured with:
- Appropriate permissions scoped for packages, attestations, and id-token
- Comprehensive Docker tag patterns for semantic versioning
- GHA caching for build performance
- Build provenance attestation for supply chain security
The YAMLlint warnings about inline brace syntax are intentional per repository conventions that prefer concise inline YAML syntax. Based on learnings, this style is preferred in the exactly/exa repository.
substreams/package.json (1)
1-66: LGTM! Well-structured nx configuration.The package manifest properly integrates with the nx workspace. The build target correctly specifies inputs (including
CHAIN_IDenv and contract artifacts) and outputs, and follows the learning to usenxfor running tasks via"test": "nx test substreams".substreams/build.rs (1)
13-48: LGTM! Well-structured codegen workflow.The ABI extraction and Rust binding generation flow is well-implemented with proper error context via
with_context. The generated module file correctly applies#[expect(clippy::style, clippy::complexity)]to suppress expected lints on generated code.substreams/substreams.yaml (2)
54-82: Verify hardcoded factory addresses match deployed contracts.The factory addresses are hardcoded per network. Please ensure these addresses are correct and consider adding a comment or documentation reference for each address to aid future maintenance.
1-52: LGTM! Well-structured Substreams manifest.The module pipeline (
map_exa_accounts→store_exa_accounts→map_exa_plugins→db_out) correctly chains data flow. The sink configuration properly references the server schema and the external SPKG imports are version-pinned.substreams/src/lib.rs (5)
53-71: LGTM! Deduplication logic is correct and intentional.The
!seen.insert(address.to_vec())logic correctly skips the first plugin (the owner plugin) for new accounts and keeps subsequent exa plugins. The?operator onget_last()correctly short-circuits in thefilter_mapcontext when the account isn't in the store. Based on learnings, this pattern is intentional.
27-44: LGTM! Clean event filtering implementation.The factory address decoding and event filtering chain is well-structured. The use of
try_for_eachsemantics viacollect::<Result<Vec<_>, _>>()ensures proper error propagation from hex decoding.
46-51: LGTM!The store handler correctly persists a flag for each account using the hex-encoded address as the key.
73-88: LGTM! Database output handler.The
db_outhandler correctly upserts plugin rows and conditionally creates a blocks entry when there are changes. The use ofTablesAPI aligns with the substreams-sink-database-changes pattern.
77-85: Update table names to include schema qualification for substreams sink compatibility.The code references
"exa_plugins"(line 78) and"blocks"(line 84) without schema qualification. However, the server schema (server/database/schema.ts) defines both tables under thesubstreamspgSchema, anddrizzle-kit exportgenerates schema-qualified SQL (e.g.,CREATE TABLE "substreams"."exa_plugins"). The substreams-sink-sql sink uses the table name string directly, so plain table names will not match the schema-qualified tables in the database.Update the table references to:
"substreams.exa_plugins"(line 78)"substreams.blocks"(line 84)⛔ Skipped due to learnings
Learnt from: cruzdanilo Repo: exactly/exa PR: 627 File: substreams/src/lib.rs:54-71 Timestamp: 2026-01-08T19:54:10.673Z Learning: In substreams/src/lib.rs in the map_exa_plugins function, the deduplication logic using `!seen.insert(address.to_vec())` is intentional. The first plugin installed for a new account is an owner plugin (not an exa plugin), so it must be filtered out. The negation ensures the first plugin is skipped and subsequent plugins (the actual exa plugins) are kept.Learnt from: CR Repo: exactly/exa PR: 0 File: AGENTS.md:0-0 Timestamp: 2025-12-23T19:58:16.574Z Learning: Applies to server/**/*.{ts,tsx} : Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use `c.var.db`cspell.json (1)
14-165: New Rust/Substreams dictionary entries look good.
The added terms match the introduced toolchain and should reduce false positives..dockerignore (2)
6-10: Negation forrun-latest.jsonis correctly ordered.
!**/contracts/broadcast/**/run-latest.jsoncomes after the**/*.jsonignore, so it should be included as intended.
6-25: No action needed — the excluded server artifacts are all safely generated during the Docker build.The entries
**/server/app,**/server/drizzle, and**/server/openapi.jsonare safe to ignore.server/appis generated fromdistduring the build (line 23 of Dockerfile),server/drizzleis a cache directory, andserver/openapi.jsondoes not exist as a tracked file (the actual generated file isserver/generated/openapi.json). None are pre-existing requirements for the Docker build..github/matcher.json (2)
81-92:rust-formattermatcher seems reasonable; please confirm output format matches your formatter invocation.
If the tool printsDiff in <file> at line <n>:(as your regex expects), this will annotate PRs nicely.
64-92: The severity concern is not a real issue; the Windows path limitation doesn't apply to this project.The rustc compiler outputs
warning:anderror:in lowercase in human-readable format, so the regex captures will always be lowercase—GitHub Actions receives the correct normalized values it expects. The regex defensively allows capitalized variants, but they never occur in actual rustc output.The Windows path issue is technically valid (
:in filenames likeC:\path), but this project has no Windows CI workflows and is Unix-focused. The proposed.+?fix doesn't fundamentally solve Windows path ambiguity anyway; the pattern is inherently brittle for paths containing colons.The rust-formatter matcher is correct as written.
this is the first step towards #585. it purposefully lets out the final deployment and usage in the server for later, in a following pr.
Summary by CodeRabbit
New Features
Database
Infrastructure
Developer Tools
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.