Skip to content

ENSv2 Plugin Events#1744

Merged
shrugs merged 32 commits intomainfrom
feat/ensv2-events
Mar 12, 2026
Merged

ENSv2 Plugin Events#1744
shrugs merged 32 commits intomainfrom
feat/ensv2-events

Conversation

@shrugs
Copy link
Collaborator

@shrugs shrugs commented Mar 9, 2026

closes #1674

I was trying to debug some event decoding issues, which necessitated making sure that the devnet and my ABIs were in sync with each other, so this also includes an update to the commit of devnet that we're integration testing against.

Reviewer Focus (Read This First)

the main areas to review:

  1. event-db-helpers expansionensureDomainEvent, ensureResolverEvent, ensurePermissionsEvent now push events to the appropriate join tables
  2. find-events resolver and where filters — new cursor-based pagination for events with topic0_in, timestamp_gte/lte, from filters. the through: { table, scope } pattern for narrowing via join tables
  3. update ens-test-env to latest devnet — large surface area of ABI updates. these are built and then copied from the ens contract repos

Problem & Motivation

  • ens alpha apps want event history for domains and resolvers
  • Registration.start was previously removed from the GraphQL API in favor of joining through registration.event.timestamp, making queries slower and more complex
  • ens-test-env datasource was out of date

What Changed (Concrete)

  1. event history tracking — all ensv2 handlers now push events to domain/resolver/permissions join tables. new handlers for ENSv1Registry (NewTTL, NewResolver) and shared Resolver events.
  2. Registration.start materialized — stored on the registration row, exposed in GraphQL, used directly for ordering (removes event table join from with-ordering-metadata).
  3. events API — Account.events field added. where filters (topic0_in, timestamp_gte/lte, from) on all *.events connections. resolveFindEvents queries event table directly, optionally narrowed via join table.
  4. schema/infra — event table expanded (topic0, topics, data, from/to), cursor utilities consolidated, Event GraphQL type completed.
  5. ens-test-env update — ABI updates across contracts, new UniversalResolverV2

Design & Planning

  • event tracking follows join-table pattern
  • resolveFindEvents design iterated through: join-table-first → scope-first → always-query-event-table with optional join. final design keeps the query plan simple and avoids conditional FROM clause complexity.
  • where filter design follows the find-domains pattern (input types → internal filter shape → SQL conditions)

Self-Review

  • Bugs caught: none significant
  • Logic simplified: removed unnecessary event table join from with-ordering-metadata after materializing registration.start
  • Naming / terminology improved: through: { table, scope } reads clearly at callsites
  • Dead or unnecessary code removed: old cursors.ts in schema/ replaced by shared cursors lib

Downstream & Consumer Impact

  • Public APIs affected:
    • Registration.start field added (non-breaking, new field)
    • Account.events connection added (non-breaking, new field)
    • Domain.events, Resolver.events, Permissions.events now accept where arg (non-breaking, optional)
    • Event type gains blockNumber, transactionIndex, to, topics, data fields (non-breaking)
    • new input types: EventsWhereInput, AccountEventsWhereInput
  • Docs updated: changeset added

Testing Evidence

  • Testing performed: manual devnet integration testing
  • Known gaps: no integration tests for event pagination or where filters yet
  • What reviewers have to reason about manually: correctness of SQL composition in resolveFindEvents

Risk Analysis

  • Risk areas: could run into issues at runtime with the find-events module since it's not fully integration tested
  • Mitigations: ensv2 plugin is alpha
  • Named owner if this causes problems: @shrugs

Pre-Review Checklist (Blocking)

  • I reviewed every line of this diff and understand it end-to-end
  • I'm prepared to defend this PR line-by-line in review
  • I'm comfortable being the on-call owner for this change
  • Relevant changesets are included (or explicitly not required)

Copilot AI review requested due to automatic review settings March 9, 2026 21:38
@vercel
Copy link
Contributor

vercel bot commented Mar 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
admin.ensnode.io Ready Ready Preview, Comment Mar 12, 2026 9:04pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
ensnode.io Skipped Skipped Mar 12, 2026 9:04pm
ensrainbow.io Skipped Skipped Mar 12, 2026 9:04pm

@changeset-bot
Copy link

changeset-bot bot commented Mar 9, 2026

🦋 Changeset detected

Latest commit: a42ea5b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 19 packages
Name Type
ensapi Major
ensindexer Major
ensadmin Major
ensrainbow Major
fallback-ensapi Major
@ensnode/datasources Major
@ensnode/ensrainbow-sdk Major
@ensnode/ensnode-schema Major
@ensnode/ensnode-react Major
@ensnode/ensnode-sdk Major
@ensnode/ponder-sdk Major
@ensnode/ponder-subgraph Major
@ensnode/shared-configs Major
@docs/ensnode Major
@docs/ensrainbow Major
@docs/mintlify Major
@namehash/ens-referrals Major
@namehash/namehash-ui Major
@ensnode/integration-test-env Patch

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Centralizes cursor encoding, adds paginated Event queries and filters, extends Event schema/DB and registration.start, wires event recording across indexer handlers, updates GraphQL schema/resolvers/tests, and applies large ABI/type polishing and datasource config changes.

Changes

Cohort / File(s) Summary
Cursor & Connection
apps/ensapi/src/graphql-api/lib/cursors.ts, apps/ensapi/src/graphql-api/lib/connection-helpers.ts, apps/ensapi/src/graphql-api/lib/find-domains/domain-cursor.ts, apps/ensapi/src/graphql-api/lib/find-domains/domain-cursor.test.ts, apps/ensapi/src/graphql-api/schema/cursors.ts
Add shared cursors util (superjson + base64); switch domain cursor to delegate to it; remove legacy schema cursors export; update imports/tests.
Event resolver & GraphQL schema
apps/ensapi/src/graphql-api/lib/find-events/find-events-resolver.ts, apps/ensapi/src/graphql-api/schema/event.ts, apps/ensapi/src/graphql-api/schema/domain.ts, apps/ensapi/src/graphql-api/schema/resolver.ts, apps/ensapi/src/graphql-api/schema/account.ts, apps/ensapi/src/graphql-api/schema/permissions.ts, apps/ensapi/src/graphql-api/schema/registration.ts
Introduce resolveFindEvents with where filters and optional through-join scoping; add Event fields (blockNumber, transactionIndex, to, topics, data); new EventsWhere/AccountEventsWhere inputs; add events connections on Domain/Resolver/Account/Permissions; add Registration.start field.
Find-domains defaults & ordering
apps/ensapi/src/graphql-api/schema/constants.ts, apps/ensapi/src/graphql-api/lib/find-domains/find-domains-resolver.ts, apps/ensapi/src/graphql-api/lib/find-domains/layers/with-ordering-metadata.ts
Add PAGINATION_DEFAULT_PAGE_SIZE/MAX_SIZE; include defaultSize/maxSize/args in cursor payloads; ordering metadata now derives registrationTimestamp from registration.start.
Indexer: DB helpers & types
apps/ensindexer/src/lib/ensv2/event-db-helpers.ts, apps/ensindexer/src/lib/get-this-account-id.ts, apps/ensindexer/src/lib/ponder-helpers.ts, apps/ensindexer/src/lib/subgraph/db-helpers.ts, apps/ensindexer/src/lib/heal-addr-reverse-subname-label.ts
Introduce LogEventBase and FilterLogEvents; ensureEvent now uses LogEventBase and enforces topics; add ensureDomainEvent/ensureResolverEvent/ensurePermissionsEvent; add contract-receipt healing branch and populate extra event fields.
Indexer: handlers & wiring
apps/ensindexer/src/plugins/ensv2/handlers/shared/Resolver.ts, apps/ensindexer/src/plugins/ensv2/event-handlers.ts, apps/ensindexer/src/plugins/ensv2/plugin.ts, multiple apps/ensindexer/src/plugins/ensv2/handlers/**
Register shared Resolver handlers; call ensureDomainEvent/ensureResolverEvent/ensurePermissionsEvent across many ENSv1/ENSv2 handlers; capture registration.start when inserting registrations.
Schema / DB tables
packages/ensnode-schema/src/schemas/ensv2.schema.ts
Extend events table with blockNumber, transactionIndex, to, topic0, topics, data and add indices; add join tables domainEvent/resolverEvent/permissionsEvent; add registration.start column.
Tests & test utilities
apps/ensapi/src/test/integration/**, apps/ensapi/src/graphql-api/schema/*.integration.test.ts, apps/ensapi/src/test/integration/find-events/*, apps/ensapi/src/test/integration/graphql-utils.ts
Add event pagination queries/fragments, testEventPagination suite and collectors (collectForward/collectBackward); add integration tests for Domain/Resolver/Account/Permissions events and update domain pagination tests to use registration.start.
Datasource / ABIs / configs
packages/datasources/src/abis/**, packages/datasources/src/ens-test-env.ts, packages/datasources/src/{mainnet,sepolia,sepolia-v2}.ts, packages/datasources/src/index.ts
Large ABI additions/updates (UniversalResolverV1/V2, ETHRegistrar, Registry, BaseRegistrar, NameWrapper, etc.); apply as const satisfies Abi across many ABI modules; add/adjust resolver ABIs and devnet addresses; export unified UniversalResolverABI.
SDK examples & changesets
packages/ensnode-sdk/src/graphql-api/example-queries.ts, .changeset/*, packages/integration-test-env/*
Add example queries for events; update changesets documenting Registration.start and events filters; update devnet image tag.
Minor import/path moves
assorted files under apps/ensapi/src and tests
Adjust import paths for moved modules (pagination helpers, cursor util) and update tests accordingly.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(0,128,0,0.5)
    participant Client as GraphQL Client
    end
    rect rgba(0,0,255,0.5)
    participant Resolver as resolveFindEvents
    end
    rect rgba(128,0,128,0.5)
    participant DB as Database
    end
    rect rgba(255,165,0,0.5)
    participant Cursor as cursors
    end

    Client->>Resolver: Query <entity>.events(args, pagination)
    Resolver->>Cursor: decode(after|before)
    Cursor-->>Resolver: decoded cursor state
    Resolver->>DB: SELECT events with where/through/join, ORDER BY composite key, LIMIT+1
    DB-->>Resolver: rows
    Resolver->>Cursor: encode(nextCursor)
    Cursor-->>Resolver: cursor string
    Resolver-->>Client: Connection { edges, pageInfo }
Loading
sequenceDiagram
    rect rgba(0,128,0,0.5)
    participant Indexer as Indexer Handler
    end
    rect rgba(255,0,0,0.5)
    participant EventDB as event-db-helpers
    end
    rect rgba(128,128,0,0.5)
    participant Events as events Table
    end
    rect rgba(0,0,255,0.5)
    participant Join as domainEvent/resolverEvent/permissionsEvent
    end

    Indexer->>EventDB: ensureDomainEvent(context, event, id)
    EventDB->>Events: ensureEvent(context, event) (insert/upsert)
    Events-->>EventDB: event.id
    EventDB->>Join: insert (ownerId, eventId)
    Join-->>EventDB: link created
    EventDB-->>Indexer: acknowledged
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

ensanalytics

Poem

🐰
I nibble bytes and hop through logs,
encoding cursors under clover clogs.
Events unfurl in tidy rows,
from domain burrows to resolver meadows.
Hooray — the indexer’s garden grows!

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/ensv2-events

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class ENSv2 “Event” storage + GraphQL exposure so Domains/Resolvers/Registries can surface an on-chain audit trail via cursor pagination, aligning with the long-term “history” needs described in #1674.

Changes:

  • Expanded the events table to store full log metadata (block/tx indices, topics, data) and added join tables (domain_events, resolver_events, registry_events).
  • Added ENSIndexer helpers/handlers to persist events and attach ENSv1 registry events to Domain history.
  • Added GraphQL *.events connections plus new keyset cursor utilities (superjson base64 cursors) and pagination plumbing.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/ensnode-schema/src/schemas/ensv2.schema.ts Expands event storage and adds join tables for entity↔event relationships.
packages/datasources/src/abis/ensv2/Registry.ts Updates ENSv2 Registry ABI event/function shapes (sender/indexed fields, new events).
packages/datasources/src/abis/ensv2/EnhancedAccessControl.ts ABI param rename (rolesBitmaproleBitmap).
packages/datasources/src/abis/ensv2/ETHRegistrar.ts Large ABI refresh (constructor, errors, events, view/mutating fns).
apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts Adds event-to-domain-history wiring and indexes additional ENSv1 registry events.
apps/ensindexer/src/lib/ensv2/event-db-helpers.ts Implements ensureEvent (richer metadata) + ensureDomainEvent.
apps/ensapi/src/graphql-api/schema/event.ts Exposes new Event fields (blockNumber, txIndex, to, topics, data).
apps/ensapi/src/graphql-api/schema/domain.ts Adds Domain.events connection.
apps/ensapi/src/graphql-api/schema/resolver.ts Adds Resolver.events connection.
apps/ensapi/src/graphql-api/schema/registry.ts Adds Registry.events connection.
apps/ensapi/src/graphql-api/lib/find-events/find-events-resolver.ts New reusable resolver for paginated event connections via join tables.
apps/ensapi/src/graphql-api/lib/find-events/event-cursor.ts Defines composite event cursor encode/decode.
apps/ensapi/src/graphql-api/lib/cursors.ts Centralizes base64(superjson) cursor encoding/decoding.
apps/ensapi/src/graphql-api/schema/constants.ts Moves cursor import + centralizes default/max page sizes.
apps/ensapi/src/graphql-api/lib/connection-helpers.ts Updates to use new shared cursors util.
apps/ensapi/src/graphql-api/lib/find-domains/find-domains-resolver.ts Switches domain pagination to shared page-size constants + DomainCursors.
apps/ensapi/src/graphql-api/lib/find-domains/domain-cursor.ts Uses shared cursors util; renames helper to DomainCursors.
apps/ensapi/src/graphql-api/lib/find-domains/domain-cursor.test.ts Updates tests to use DomainCursors.
apps/ensapi/src/graphql-api/schema/cursors.ts Removes old cursor helper (now replaced by lib/cursors).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensapi/src/graphql-api/lib/cursors.ts`:
- Around line 14-17: The thrown Error message in the cursor decoding catch block
is too specific ("failed to decode event cursor") for a shared utility; update
the message to be generic (e.g., "Invalid cursor: failed to decode cursor. The
cursor may be malformed or from an incompatible query.") in the catch inside the
cursor decoding function (the catch that currently throws the "Invalid cursor:
failed to decode event cursor..." error – locate the cursor decode utility,
e.g., decodeCursor/parseCursor or the catch in cursors.ts) so it no longer
references "event".

In `@apps/ensapi/src/graphql-api/lib/find-events/event-cursor.ts`:
- Around line 17-19: EventCursors.decode currently returns
cursors.decode<EventCursor>(cursor) without runtime validation; mirror the
pattern used in DomainCursors.decode by wrapping the decoded value in a
validation/guard and throwing a clear error if it doesn't match the EventCursor
shape. Update the EventCursors object (specifically the decode implementation)
to call cursors.decode, validate the result against the EventCursor structure
(or use an existing type guard/validator), and throw a descriptive error when
validation fails, similar to the TODO noted in domain-cursor.ts and
DomainCursors.decode.

In `@apps/ensapi/src/graphql-api/schema/domain.ts`:
- Around line 247-257: Domain.events currently lacks where/order args (left as
TODO); add the same connection args used by the registrations connection and
pass them into resolveFindEvents. Update the Domain.events field to accept
filtering/sorting args (e.g., where, order, first/after) matching the connection
input types used by registrations, ensure the resolver call
resolveFindEvents(schema.domainEvent, eq(schema.domainEvent.domainId,
parent.id), args) forwards those args unchanged, and add/update unit tests for
events filtering/ordering and schema types for EventRef to reflect the new args.

In `@apps/ensindexer/src/lib/ensv2/event-db-helpers.ts`:
- Around line 28-33: The current invariant throws an Error when event.log.topics
contains null, which is blocking CI; change this to log a non-fatal warning and
skip processing that log instead of throwing so indexing continues. Replace the
throw in the block that checks event.log.topics.some(topic => topic === null)
with a warning call (e.g., processLogger.warn or a module logger) that includes
the same message/context and the toJson(event.log.topics) payload, then
return/continue from the enclosing function to skip that malformed event;
alternatively only throw in a dev-only branch (NODE_ENV==='test' or an explicit
feature flag) if you must preserve the invariant in local dev. Ensure you update
all references to event.log.topics and keep the descriptive message for
debugging.
- Around line 67-69: The insert in ensureDomainEvent is not idempotent and will
fail on duplicate (domainId,eventId); update ensureDomainEvent to use the same
dedup pattern as ensureEvent by performing the insert into schema.domainEvent
with conflict handling (e.g., add an onConflictDoNothing() / equivalent on the
context.db.insert call) so repeated calls for the same domainId and eventId are
no-ops and do not throw.

In `@apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts`:
- Around line 131-159: Both handleNewTTL and handleNewResolver call
makeENSv1DomainId(node) and persist history via ensureDomainEvent even when node
=== ROOT_NODE; add an early return to skip handling when node equals ROOT_NODE
to avoid creating history for an unmodeled root domain. Specifically, in both
functions (handleNewTTL and handleNewResolver) check if event.args.node ===
ROOT_NODE (or compare node to ROOT_NODE) and return immediately before computing
domainId or calling ensureDomainEvent.

In `@packages/ensnode-schema/src/schemas/ensv2.schema.ts`:
- Around line 132-149: The join tables domainEvent, resolverEvent, and
registryEvent currently only define composite primary keys (entityId, eventId),
which orders the index by entity first and hurts performance for joins on
eventId; add a secondary index on eventId for each join table by updating the
onchainTable definitions (domainEvent, resolverEvent, registryEvent) to include
an index referencing t.eventId (e.g., add an index/secondaryIndex entry using
t.eventId) so that joins like .innerJoin(schema.event, eq(joinTable.eventId,
schema.event.id)) can use an eventId-prefixed index.
- Around line 83-90: The doc comment describing the Events table is cut off
("These join tables may store additional"); update the comment in
ensv2.schema.ts (the block describing Events, DomainEvent, ResolverEvent,
Registration, Renewal) to complete that sentence—e.g., state that join tables
may store additional relationship metadata (such as event-specific fields,
timestamps, blockNumber/transactionHash, or link attributes) so readers
understand what extra data DomainEvent/ResolverEvent can hold; keep the rest of
the paragraph unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 96533aea-2bf7-4f52-bd5c-7bc1feb37f7d

📥 Commits

Reviewing files that changed from the base of the PR and between 8aa8326 and 38ab526.

📒 Files selected for processing (19)
  • apps/ensapi/src/graphql-api/lib/connection-helpers.ts
  • apps/ensapi/src/graphql-api/lib/cursors.ts
  • apps/ensapi/src/graphql-api/lib/find-domains/domain-cursor.test.ts
  • apps/ensapi/src/graphql-api/lib/find-domains/domain-cursor.ts
  • apps/ensapi/src/graphql-api/lib/find-domains/find-domains-resolver.ts
  • apps/ensapi/src/graphql-api/lib/find-events/event-cursor.ts
  • apps/ensapi/src/graphql-api/lib/find-events/find-events-resolver.ts
  • apps/ensapi/src/graphql-api/schema/constants.ts
  • apps/ensapi/src/graphql-api/schema/cursors.ts
  • apps/ensapi/src/graphql-api/schema/domain.ts
  • apps/ensapi/src/graphql-api/schema/event.ts
  • apps/ensapi/src/graphql-api/schema/registry.ts
  • apps/ensapi/src/graphql-api/schema/resolver.ts
  • apps/ensindexer/src/lib/ensv2/event-db-helpers.ts
  • apps/ensindexer/src/plugins/ensv2/handlers/ensv1/ENSv1Registry.ts
  • packages/datasources/src/abis/ensv2/ETHRegistrar.ts
  • packages/datasources/src/abis/ensv2/EnhancedAccessControl.ts
  • packages/datasources/src/abis/ensv2/Registry.ts
  • packages/ensnode-schema/src/schemas/ensv2.schema.ts
💤 Files with no reviewable changes (1)
  • apps/ensapi/src/graphql-api/schema/cursors.ts

Copilot AI review requested due to automatic review settings March 10, 2026 21:36
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 83 out of 83 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/ensapi/src/test/integration/find-domains/domain-pagination-queries.ts (1)

14-34: ⚠️ Potential issue | 🟠 Major

Fetch registration.start in the shared domain-pagination fixture.

This PR moves ordering to the materialized Registration.start, but the shared fragment/type still only carries registration.event.timestamp. That means the domain pagination suites are not asserting the field the resolver now orders by, so a broken start backfill or stale ORDER BY can still pass.

Suggested update
 const PaginatedDomainFragment = gql`
   fragment PaginatedDomainFragment on Domain {
     id
     name
     label { interpreted }
     registration {
+      start
       expiry
-      event { timestamp }
     }
   }
 `;

 export type PaginatedDomainResult = {
   id: string;
   name: Name | null;
   label: { interpreted: InterpretedLabel };
   registration: {
+    start: string;
     expiry: string | null;
-    event: { timestamp: string };
   } | null;
 };

Then update apps/ensapi/src/test/integration/find-domains/test-domain-pagination.ts to compare registration.start for the START-based order assertions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/test/integration/find-domains/domain-pagination-queries.ts`
around lines 14 - 34, The shared GraphQL fragment PaginatedDomainFragment and
the TypeScript type PaginatedDomainResult must include registration.start so
tests can assert the ORDER BY key; update PaginatedDomainFragment to fetch
registration { start expiry event { timestamp } } and extend
PaginatedDomainResult.registration to include start: string | null, then update
the domain pagination test assertions to compare registration.start (instead of
or in addition to event.timestamp) for START-based ordering checks.
♻️ Duplicate comments (3)
packages/ensnode-sdk/src/graphql-api/example-queries.ts (2)

347-352: ⚠️ Potential issue | 🟠 Major

SepoliaV2 resolver lookup should use non-throwing pattern.

The getDatasourceContract call throws during module evaluation if ReverseResolverRoot.DefaultPublicResolver5 is absent for SepoliaV2. This breaks the entire examples module even if the user never selects SepoliaV2. Other SepoliaV2 lookups in this file (lines 6-16) use maybeGetDatasourceContract defensively.

Suggested fix

Add a non-throwing lookup at the top with other datasource constants:

const SEPOLIA_V2_DEFAULT_PUBLIC_RESOLVER = maybeGetDatasourceContract(
  ENSNamespaceIds.SepoliaV2,
  DatasourceNames.ReverseResolverRoot,
  "DefaultPublicResolver5",
);

Then conditionally include the SepoliaV2 entry:

-      [ENSNamespaceIds.SepoliaV2]: {
-        resolver: getDatasourceContract(
-          ENSNamespaceIds.SepoliaV2,
-          DatasourceNames.ReverseResolverRoot,
-          "DefaultPublicResolver5",
-        ),
-      },
+      ...(SEPOLIA_V2_DEFAULT_PUBLIC_RESOLVER
+        ? { [ENSNamespaceIds.SepoliaV2]: { resolver: SEPOLIA_V2_DEFAULT_PUBLIC_RESOLVER } }
+        : {}),

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ensnode-sdk/src/graphql-api/example-queries.ts` around lines 347 -
352, The SepoliaV2 entry currently calls getDatasourceContract which throws
during module evaluation if ReverseResolverRoot.DefaultPublicResolver5 is
missing; change it to use a non-throwing lookup by adding a top-level constant
using maybeGetDatasourceContract for ENSNamespaceIds.SepoliaV2,
DatasourceNames.ReverseResolverRoot, "DefaultPublicResolver5" (e.g.
SEPOLIA_V2_DEFAULT_PUBLIC_RESOLVER) and then update the map entry that currently
calls getDatasourceContract to conditionally include the SepoliaV2 entry only if
that constant is defined, leaving other entries unchanged.

141-164: 🧹 Nitpick | 🔵 Trivial

Demonstrate cursor pagination and where filters in the events query.

Per PR objectives, the events API supports cursor-based pagination and where filters (topic0_in, timestamp_gte, timestamp_lte, from). This example query uses the bare events connection, which misses the opportunity to showcase these new capabilities in the playground.

Suggested enhancement
-query DomainEvents($name: Name!) {
+query DomainEvents($name: Name!, $first: Int, $after: String, $where: DomainEventsWhereInput) {
   domain(by: {name: $name}) {
-    events {
+    events(first: $first, after: $after, where: $where) {
       totalCount
+      pageInfo { hasNextPage endCursor }
       edges {
         node {
           from
           to
           topics
           data
           timestamp
           transactionHash
         }
       }
     }
   }
 }`,
-    variables: { default: { name: "newowner.eth" } },
+    variables: { default: { name: "newowner.eth", first: 10 } },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ensnode-sdk/src/graphql-api/example-queries.ts` around lines 141 -
164, The DomainEvents example should demonstrate cursor pagination and where
filters: update the GraphQL query named DomainEvents to call the events
connection with arguments (e.g., events(first: $first, after: $after, where:
$where)) and return pageInfo (endCursor, hasNextPage) and edges.node as before;
add variables for first, after, and where (where can include topic0_in,
timestamp_gte, timestamp_lte, from) and update the example's variables object
(currently { default: { name: "newowner.eth" } }) to include these new variables
so the playground shows paginated results and filtering; reference the
DomainEvents query and the events field when making these changes.
apps/ensindexer/src/lib/ensv2/event-db-helpers.ts (1)

69-96: 🧹 Nitpick | 🔵 Trivial

Consider returning eventId from the relation helpers.

This was noted in a previous review and remains valid. All three helpers compute eventId but discard it. Returning it would allow callers to avoid a second call to ensureEvent when they need the ID.

♻️ Suggested pattern
-export async function ensureDomainEvent(context: Context, event: LogEventBase, domainId: DomainId) {
+export async function ensureDomainEvent(context: Context, event: LogEventBase, domainId: DomainId): Promise<string> {
   const eventId = await ensureEvent(context, event);
   await context.db.insert(schema.domainEvent).values({ domainId, eventId }).onConflictDoNothing();
+  return eventId;
 }

Apply the same pattern to ensureResolverEvent and ensurePermissionsEvent.

,

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensindexer/src/lib/ensv2/event-db-helpers.ts` around lines 69 - 96, The
three helper functions ensureDomainEvent, ensureResolverEvent, and
ensurePermissionsEvent compute eventId via ensureEvent but don't return it;
change each to return the computed eventId after performing the insert (i.e.,
call const eventId = await ensureEvent(...) then await
context.db.insert(...).onConflictDoNothing(); and finally return eventId) so
callers can reuse the id without calling ensureEvent again.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensapi/src/graphql-api/schema/permissions.integration.test.ts`:
- Around line 31-64: The test currently only checks events.length > 0; update
the test that uses PermissionsEvents (and the pagination helper call) to also
iterate the flattened events array and assert each node is scoped to
V2_ETH_REGISTRY by checking the event's permission target field (e.g.,
for/contract/account id) — reference the PermissionsEvents query, the
flattenConnection(result.permissions.events) result stored in events, and
V2_ETH_REGISTRY; add an expect inside the loop that compares the event node's
contract/for/account id to V2_ETH_REGISTRY so the test fails if unrelated/global
events are returned.

In `@apps/ensapi/src/graphql-api/schema/resolver.integration.test.ts`:
- Around line 32-64: Add a focused test that exercises Resolver.events filtering
by invoking the GraphQL query (use the existing ResolverEvents or create a
ResolverEventsWithWhere variant) against SOME_OWNED_RESOLVER with an
EventsWhereInput (e.g., topic0_in or timestamp_gte/timestamp_lte or from) and
assert the returned flattened events respect the filter (non-empty when expected
and/or that each event matches the where criteria). Hook this test into the same
describe block as the other Resolver.tests and/or add a parallel
pagination-filtered test similar to how ResolverEventsPaginated and
testEventPagination are used so regressions in topic0_in, timestamp_gte/lte, or
from wiring are caught.

In `@apps/ensapi/src/test/integration/find-domains/test-domain-pagination.ts`:
- Around line 105-127: The ordering oracle still uses REGISTRATION_TIMESTAMP
derived from domain.registration?.event.timestamp; change it to use the
materialized Registration.start instead: update the getSortValue helper (and any
constant like REGISTRATION_TIMESTAMP) to return domain.registration?.start (or
the materialized Registration.start field) and ensure assertOrdering and the
test's selected fields fetch registration.start rather than
domain.registration.event.timestamp so comparisons validate the new materialized
key.

In `@apps/ensapi/src/test/integration/graphql-utils.ts`:
- Around line 42-58: The collector collectForward should detect cursor cycles
and require a non-null advancing cursor when pageInfo.hasNextPage is true:
update collectForward to maintain a Set of seen cursors, assert that when
page.pageInfo.hasNextPage is true the page.pageInfo.endCursor is non-null,
assert the new endCursor is not already in the seen set (to catch A->B->A
cycles), and add each seen cursor before advancing; apply the same guards to the
corresponding backward collector (the one around lines 63-81) using
pageInfo.startCursor and hasPreviousPage and the same seen-cursor check so the
helpers fail fast instead of hanging when paginators misbehave.

In `@apps/ensindexer/src/lib/heal-addr-reverse-subname-label.ts`:
- Around line 65-76: The current try/catch wraps both the RPC call and the local
healing logic so exceptions from maybeHealLabelByAddrReverseSubname are
swallowed; change it so only the RPC call is inside the try: await
context.client.getTransactionReceipt({ hash: event.transaction.hash }) should be
wrapped and assign the result to a local variable, the catch should only swallow
RPC errors, and then call maybeHealLabelByAddrReverseSubname(labelHash,
receipt.contractAddress) outside the try block so any exceptions from
maybeHealLabelByAddrReverseSubname propagate (or rethrow non-RPC errors instead
of swallowing them).

In `@apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts`:
- Around line 279-281: Move the call to ensureDomainEvent(context, event,
domainId) so it runs after the state mutations in this handler: remove the
current call placed before the conditional and place it at the end of the
conditional block (after the db.update(...) calls that mutate state). Ensure the
event logging happens after all updates complete (consistent with NameWrapped,
BaseRegistrar:NameRegistered, and handleTransfer patterns) so the call to
ensureDomainEvent uses the final state of the domain.

In `@packages/ensnode-sdk/src/graphql-api/example-queries.ts`:
- Around line 36-38: SOME_OWNED_RESOLVER is defined in lowercase; update its
value to the checksummed (EIP-55) address to match the other devnet constants
(e.g., DEVNET_DEPLOYER, DEVNET_OWNER). Replace the string for
SOME_OWNED_RESOLVER with the checksummed version (you can produce it via
ethers.utils.getAddress or any EIP-55 formatter) so the file uses consistent
address formatting.

---

Outside diff comments:
In `@apps/ensapi/src/test/integration/find-domains/domain-pagination-queries.ts`:
- Around line 14-34: The shared GraphQL fragment PaginatedDomainFragment and the
TypeScript type PaginatedDomainResult must include registration.start so tests
can assert the ORDER BY key; update PaginatedDomainFragment to fetch
registration { start expiry event { timestamp } } and extend
PaginatedDomainResult.registration to include start: string | null, then update
the domain pagination test assertions to compare registration.start (instead of
or in addition to event.timestamp) for START-based ordering checks.

---

Duplicate comments:
In `@apps/ensindexer/src/lib/ensv2/event-db-helpers.ts`:
- Around line 69-96: The three helper functions ensureDomainEvent,
ensureResolverEvent, and ensurePermissionsEvent compute eventId via ensureEvent
but don't return it; change each to return the computed eventId after performing
the insert (i.e., call const eventId = await ensureEvent(...) then await
context.db.insert(...).onConflictDoNothing(); and finally return eventId) so
callers can reuse the id without calling ensureEvent again.

In `@packages/ensnode-sdk/src/graphql-api/example-queries.ts`:
- Around line 347-352: The SepoliaV2 entry currently calls getDatasourceContract
which throws during module evaluation if
ReverseResolverRoot.DefaultPublicResolver5 is missing; change it to use a
non-throwing lookup by adding a top-level constant using
maybeGetDatasourceContract for ENSNamespaceIds.SepoliaV2,
DatasourceNames.ReverseResolverRoot, "DefaultPublicResolver5" (e.g.
SEPOLIA_V2_DEFAULT_PUBLIC_RESOLVER) and then update the map entry that currently
calls getDatasourceContract to conditionally include the SepoliaV2 entry only if
that constant is defined, leaving other entries unchanged.
- Around line 141-164: The DomainEvents example should demonstrate cursor
pagination and where filters: update the GraphQL query named DomainEvents to
call the events connection with arguments (e.g., events(first: $first, after:
$after, where: $where)) and return pageInfo (endCursor, hasNextPage) and
edges.node as before; add variables for first, after, and where (where can
include topic0_in, timestamp_gte, timestamp_lte, from) and update the example's
variables object (currently { default: { name: "newowner.eth" } }) to include
these new variables so the playground shows paginated results and filtering;
reference the DomainEvents query and the events field when making these changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5e113ec1-2ebb-4c7a-81ee-fc11009140af

📥 Commits

Reviewing files that changed from the base of the PR and between 11ede60 and 4f8dace.

📒 Files selected for processing (16)
  • apps/ensapi/src/graphql-api/schema/account.integration.test.ts
  • apps/ensapi/src/graphql-api/schema/domain.integration.test.ts
  • apps/ensapi/src/graphql-api/schema/permissions.integration.test.ts
  • apps/ensapi/src/graphql-api/schema/permissions.ts
  • apps/ensapi/src/graphql-api/schema/query.integration.test.ts
  • apps/ensapi/src/graphql-api/schema/registry.integration.test.ts
  • apps/ensapi/src/graphql-api/schema/resolver.integration.test.ts
  • apps/ensapi/src/test/integration/find-domains/domain-pagination-queries.ts
  • apps/ensapi/src/test/integration/find-domains/test-domain-pagination.ts
  • apps/ensapi/src/test/integration/find-events/event-pagination-queries.ts
  • apps/ensapi/src/test/integration/find-events/test-event-pagination.ts
  • apps/ensapi/src/test/integration/graphql-utils.ts
  • apps/ensindexer/src/lib/ensv2/event-db-helpers.ts
  • apps/ensindexer/src/lib/heal-addr-reverse-subname-label.ts
  • apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts
  • packages/ensnode-sdk/src/graphql-api/example-queries.ts

Comment on lines +42 to +58
export async function collectForward<T>(fetchPage: FetchPage<T>, pageSize: number): Promise<T[]> {
const all: T[] = [];
let after: string | undefined;

while (true) {
const page = await fetchPage({ first: pageSize, after });
all.push(...flattenConnection(page));

if (!page.pageInfo.hasNextPage) break;

const nextCursor = page.pageInfo.endCursor ?? undefined;
expect(nextCursor, "endCursor must advance when hasNextPage is true").not.toBe(after);
after = nextCursor;
}

return all;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard the shared collectors against cursor cycles and missing cursors.

These loops only check that the next cursor differs from the immediately previous one. A buggy paginator can still alternate cursors (A -> B -> A) or return hasNextPage/hasPreviousPage with a later-page null cursor, and this helper will hang instead of failing fast. Track seen cursors and require a non-null cursor whenever more pages are advertised.

Suggested hardening
 export async function collectForward<T>(fetchPage: FetchPage<T>, pageSize: number): Promise<T[]> {
   const all: T[] = [];
   let after: string | undefined;
+  const seenCursors = new Set<string>();

   while (true) {
     const page = await fetchPage({ first: pageSize, after });
     all.push(...flattenConnection(page));

     if (!page.pageInfo.hasNextPage) break;

-    const nextCursor = page.pageInfo.endCursor ?? undefined;
-    expect(nextCursor, "endCursor must advance when hasNextPage is true").not.toBe(after);
+    const nextCursor = page.pageInfo.endCursor;
+    if (nextCursor == null) {
+      throw new Error("endCursor must be present when hasNextPage is true");
+    }
+    expect(seenCursors.has(nextCursor), `cursor cycle detected: ${nextCursor}`).toBe(false);
+    seenCursors.add(nextCursor);
     after = nextCursor;
   }

   return all;
 }

 export async function collectBackward<T>(fetchPage: FetchPage<T>, pageSize: number): Promise<T[]> {
   const all: T[] = [];
   let before: string | undefined;
+  const seenCursors = new Set<string>();

   while (true) {
     const page = await fetchPage({ last: pageSize, before });
     all.unshift(...flattenConnection(page));

     if (!page.pageInfo.hasPreviousPage) break;

-    const nextCursor = page.pageInfo.startCursor ?? undefined;
-    expect(nextCursor, "startCursor must advance when hasPreviousPage is true").not.toBe(before);
+    const nextCursor = page.pageInfo.startCursor;
+    if (nextCursor == null) {
+      throw new Error("startCursor must be present when hasPreviousPage is true");
+    }
+    expect(seenCursors.has(nextCursor), `cursor cycle detected: ${nextCursor}`).toBe(false);
+    seenCursors.add(nextCursor);
     before = nextCursor;
   }

   return all;
 }

Also applies to: 63-81

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/test/integration/graphql-utils.ts` around lines 42 - 58, The
collector collectForward should detect cursor cycles and require a non-null
advancing cursor when pageInfo.hasNextPage is true: update collectForward to
maintain a Set of seen cursors, assert that when page.pageInfo.hasNextPage is
true the page.pageInfo.endCursor is non-null, assert the new endCursor is not
already in the seen set (to catch A->B->A cycles), and add each seen cursor
before advancing; apply the same guards to the corresponding backward collector
(the one around lines 63-81) using pageInfo.startCursor and hasPreviousPage and
the same seen-cursor check so the helpers fail fast instead of hanging when
paginators misbehave.

@lightwalker-eth
Copy link
Member

@greptile

Copy link
Member

@lightwalker-eth lightwalker-eth left a comment

Choose a reason for hiding this comment

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

@shrugs Great work! 💪

Copilot AI review requested due to automatic review settings March 12, 2026 19:13
@vercel vercel bot temporarily deployed to Preview – ensrainbow.io March 12, 2026 19:14 Inactive
@vercel vercel bot temporarily deployed to Preview – ensnode.io March 12, 2026 19:14 Inactive
@vercel vercel bot temporarily deployed to Preview – admin.ensnode.io March 12, 2026 19:14 Inactive
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 83 out of 83 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (3)
apps/ensindexer/src/lib/ensv2/event-db-helpers.ts (1)

69-96: 🧹 Nitpick | 🔵 Trivial

Consider returning eventId from relation helpers (optional).

These helpers compute eventId internally but discard it. Callers that also need the ID (e.g., ExpiryExtended handler which calls ensureDomainEvent then ensureEvent again for renewal) incur an extra round-trip.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensindexer/src/lib/ensv2/event-db-helpers.ts` around lines 69 - 96, The
relation helpers ensureDomainEvent, ensureResolverEvent, and
ensurePermissionsEvent compute eventId via ensureEvent but currently discard it,
causing callers to re-call ensureEvent and incur extra DB round-trips; update
each helper to return the computed eventId (preserve current DB inserts and
.onConflictDoNothing() behavior) and update callers (e.g., the ExpiryExtended
handler) to use the returned eventId instead of calling ensureEvent again to
avoid duplicate lookups.
apps/ensapi/src/graphql-api/schema/resolver.integration.test.ts (1)

21-66: ⚠️ Potential issue | 🟠 Major

Add at least one Resolver.events(where: ...) integration case.

This suite validates default and pagination paths but still doesn’t exercise filter wiring (topic0_in, timestamp_gte/lte, from) for Resolver events.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ensapi/src/graphql-api/schema/resolver.integration.test.ts` around lines
21 - 66, The tests cover default and pagination paths but lack an integration
case that exercises Resolver.events filters; add at least one test that calls
the ResolverEvents query with a where input (e.g., topic0_in,
timestamp_gte/timestamp_lte, from) and asserts the returned events are correctly
filtered. Locate the existing ResolverEvents/ResolverEventsPaginated test
scaffolding and add a new it/test case that sends variables including a where
object (using DEVNET_NAME_WITH_OWNED_RESOLVER for name) and validate using
flattenConnection or by checking node properties that only matching events are
returned; ensure the new test references Resolver.events and the same
EventFragment so it integrates with the current query shape.
packages/ensnode-sdk/src/graphql-api/example-queries.ts (1)

145-163: 🧹 Nitpick | 🔵 Trivial

Show pagination + where in the DomainEvents example query.

This example still demonstrates only the bare connection, so consumers don’t see how to use the newly added event filtering/pagination surface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ensnode-sdk/src/graphql-api/example-queries.ts` around lines 145 -
163, Update the DomainEvents example to show how to pass pagination and
filtering by adding variables and using them in the events connection; change
the query signature for DomainEvents to accept ($name: Name!, $first: Int,
$after: String, $where: EventWhere) and call events(first: $first, after:
$after, where: $where) so the response still returns totalCount and edges; also
update the variables object (the variables.default currently used for name) to
include sensible defaults for first/after/where (e.g., { name: "newowner.eth",
first: 50, after: null, where: { /* example filter */ } }) so consumers see both
pagination and where usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/ensapi/src/graphql-api/schema/account.integration.test.ts`:
- Line 131: Replace the placeholder describe.todo with real, executable
integration tests that exercise Account.events filtering via the
AccountEventsWhereInput: add a table-driven describe block that runs the GraphQL
query used elsewhere in this test suite (reuse the same test helper used in this
file) and asserts results for topic0_in, timestamp_gte, and timestamp_lte
combinations; for each case provide input where events should be returned and
where they should be excluded, using the existing fixtures/events from this test
file to verify correct inclusion/exclusion, and name the tests clearly (e.g.,
"filters by topic0_in" and "filters by timestamp_gte/timestamp_lte") so CI will
run them instead of leaving describe.todo.

In `@apps/ensapi/src/graphql-api/schema/domain.integration.test.ts`:
- Line 124: Replace the placeholder describe.todo("Domain.events filtering
(EventsWhereInput)") with real tests that exercise the GraphQL Domain.events
query using the EventsWhereInput filters; write tests that seed several Domain
event records (varying type, owner, timestamps, and metadata), then run GraphQL
queries against the Domain.events field with different EventsWhereInput
permutations (e.g., by event type, by owner, by createdAt range, and by metadata
key/value) and assert returned items and counts match expectations and
ordering/pagination semantics; reference the Domain.events resolver/field and
EventsWhereInput input type in your tests and reuse the test file's existing
test utilities to seed data and execute GraphQL queries.

In `@apps/ensapi/src/graphql-api/schema/permissions.integration.test.ts`:
- Line 71: Replace the placeholder describe.todo with real, executable tests
that verify Permissions.events filtering via the EventsWhereInput: write tests
that create sample events/fixtures, invoke the Permissions.events GraphQL query
(or resolver) with specific EventsWhereInput filter cases (e.g., by actor, verb,
object, date range) and assert the returned event list includes expected items
and excludes others; ensure you use the existing integration test helpers/client
used elsewhere in the suite to run the query and clean up fixtures.
- Line 57: The assertion compares event.address.toLowerCase() to
V2_ETH_REGISTRY.address but only lowercases the left side; update the test to
normalize both values (e.g., lowercase both) before comparing so the assertion
uses event.address.toLowerCase() === V2_ETH_REGISTRY.address.toLowerCase();
modify the expectation in the test that references event.address and
V2_ETH_REGISTRY.address accordingly.

---

Duplicate comments:
In `@apps/ensapi/src/graphql-api/schema/resolver.integration.test.ts`:
- Around line 21-66: The tests cover default and pagination paths but lack an
integration case that exercises Resolver.events filters; add at least one test
that calls the ResolverEvents query with a where input (e.g., topic0_in,
timestamp_gte/timestamp_lte, from) and asserts the returned events are correctly
filtered. Locate the existing ResolverEvents/ResolverEventsPaginated test
scaffolding and add a new it/test case that sends variables including a where
object (using DEVNET_NAME_WITH_OWNED_RESOLVER for name) and validate using
flattenConnection or by checking node properties that only matching events are
returned; ensure the new test references Resolver.events and the same
EventFragment so it integrates with the current query shape.

In `@apps/ensindexer/src/lib/ensv2/event-db-helpers.ts`:
- Around line 69-96: The relation helpers ensureDomainEvent,
ensureResolverEvent, and ensurePermissionsEvent compute eventId via ensureEvent
but currently discard it, causing callers to re-call ensureEvent and incur extra
DB round-trips; update each helper to return the computed eventId (preserve
current DB inserts and .onConflictDoNothing() behavior) and update callers
(e.g., the ExpiryExtended handler) to use the returned eventId instead of
calling ensureEvent again to avoid duplicate lookups.

In `@packages/ensnode-sdk/src/graphql-api/example-queries.ts`:
- Around line 145-163: Update the DomainEvents example to show how to pass
pagination and filtering by adding variables and using them in the events
connection; change the query signature for DomainEvents to accept ($name: Name!,
$first: Int, $after: String, $where: EventWhere) and call events(first: $first,
after: $after, where: $where) so the response still returns totalCount and
edges; also update the variables object (the variables.default currently used
for name) to include sensible defaults for first/after/where (e.g., { name:
"newowner.eth", first: 50, after: null, where: { /* example filter */ } }) so
consumers see both pagination and where usage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 43a5e37a-7a59-4a19-b414-9de08f3f72e9

📥 Commits

Reviewing files that changed from the base of the PR and between 4f8dace and a42ea5b.

📒 Files selected for processing (15)
  • apps/ensapi/src/graphql-api/lib/find-events/find-events-resolver.ts
  • apps/ensapi/src/graphql-api/schema/account.integration.test.ts
  • apps/ensapi/src/graphql-api/schema/account.ts
  • apps/ensapi/src/graphql-api/schema/domain.integration.test.ts
  • apps/ensapi/src/graphql-api/schema/domain.ts
  • apps/ensapi/src/graphql-api/schema/permissions.integration.test.ts
  • apps/ensapi/src/graphql-api/schema/permissions.ts
  • apps/ensapi/src/graphql-api/schema/registration.ts
  • apps/ensapi/src/graphql-api/schema/resolver.integration.test.ts
  • apps/ensapi/src/test/integration/find-domains/domain-pagination-queries.ts
  • apps/ensapi/src/test/integration/find-domains/test-domain-pagination.ts
  • apps/ensapi/src/test/integration/find-events/event-pagination-queries.ts
  • apps/ensindexer/src/lib/ensv2/event-db-helpers.ts
  • apps/ensindexer/src/plugins/ensv2/handlers/ensv1/NameWrapper.ts
  • packages/ensnode-sdk/src/graphql-api/example-queries.ts

@shrugs shrugs merged commit 9429530 into main Mar 12, 2026
16 of 17 checks passed
@shrugs shrugs deleted the feat/ensv2-events branch March 12, 2026 21:25
@github-actions github-actions bot mentioned this pull request Mar 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ENSv2: Domain/Resolver/Registration History

3 participants