Skip to content

fix(ensapi): delegate to UniversalResolver for unaccelerated forward resolution#2268

Merged
shrugs merged 2 commits into
mainfrom
fix/ur-sentinel
Jun 5, 2026
Merged

fix(ensapi): delegate to UniversalResolver for unaccelerated forward resolution#2268
shrugs merged 2 commits into
mainfrom
fix/ur-sentinel

Conversation

@shrugs
Copy link
Copy Markdown
Member

@shrugs shrugs commented Jun 5, 2026

Problem

ur.integration-test.eth (the ENS ENSv2-Readiness canary) resolved to the legacy sentinel 0x1111…1111 through ENSApi, while the on-chain UniversalResolver returns the ready sentinel 0x2222…2222.

Root cause: the canary's URTestResolver gates its IExtendedResolver support on msg.sender == UniversalResolver.implementation(). It reveals its extended shape (→ resolve()0x2222) only to the canonical UR implementation, and its legacy shape (→ addr()0x1111) to everyone else. ENSApi reconstructed resolution off-chain (findResolver + a direct call to the discovered resolver, gated on a full-gas supportsInterface), so it could never reproduce the UR's answer. This is not reproducible off-chain by design — not a gas/caller trick we can mimic.

Fix

Delegate to the on-chain UniversalResolver whenever ENSApi resolves on the ENS Root Chain and cannot satisfy the records purely from indexed data:

  • Non-accelerated requests delegate wholesale to the Root Chain UniversalResolver (unifying with the existing ENSv2 bailout).
  • Accelerated requests still serve indexed records (known static / ENSIP-19 reverse resolvers) from the index. For the remainder, they delegate to the Root Chain UniversalResolver when the active resolver is on the Root Chain, and resolve directly from the discovered resolver on shadow Registry chains (Basenames/Lineanames), where no UniversalResolver exists.

The shadow-Registry path is unchanged, so cross-chain ENSv1 resolution (navigate L1 → bridged resolver → L2 shadow Registry DRRs → resolve from the L2 resolver) is preserved.

Validation

pnpm typecheck, pnpm lint, and pnpm test --project ensapi (288/288) all pass.

Copilot AI review requested due to automatic review settings June 5, 2026 22:34
@shrugs shrugs requested a review from a team as a code owner June 5, 2026 22:34
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Jun 5, 2026

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

Project Deployment Actions Updated (UTC)
enskit-react-example.ensnode.io Ready Ready Preview, Comment Jun 5, 2026 11:03pm
3 Skipped Deployments
Project Deployment Actions Updated (UTC)
admin.ensnode.io Skipped Skipped Jun 5, 2026 11:03pm
ensnode.io Skipped Skipped Jun 5, 2026 11:03pm
ensrainbow.io Skipped Skipped Jun 5, 2026 11:03pm

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 5, 2026

🦋 Changeset detected

Latest commit: 62ae07d

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

This PR includes changesets to release 24 packages
Name Type
ensapi Patch
ensindexer Patch
ensadmin Patch
ensrainbow Patch
fallback-ensapi Patch
enssdk Patch
enscli Patch
enskit Patch
ensskills Patch
@ensnode/datasources Patch
@ensnode/ensrainbow-sdk Patch
@ensnode/ensdb-sdk Patch
@ensnode/ensnode-sdk Patch
@ensnode/integration-test-env Patch
@ensnode/ponder-sdk Patch
@ensnode/ponder-subgraph Patch
@ensnode/shared-configs Patch
@docs/ensnode Patch
@docs/ensrainbow Patch
@namehash/ens-referrals Patch
@namehash/namehash-ui Patch
@ensnode/ensindexer-perf-testing Patch
@ensnode/enskit-react-example Patch
@ensnode/enssdk-example 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
Copy Markdown
Contributor

coderabbitai Bot commented Jun 5, 2026

Too much diff to scan? Review this PR in Change Stack to start with the highest-impact changes.

Review Change Stack

Warning

Review limit reached

@shrugs, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 30 minutes and 36 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 76285ea7-97dc-4f97-b6b4-1fcef3ec8958

📥 Commits

Reviewing files that changed from the base of the PR and between ec41746 and 62ae07d.

📒 Files selected for processing (1)
  • apps/ensapi/src/lib/resolution/forward-resolution.ts
📝 Walkthrough

Walkthrough

Forward Resolution is refactored to centralize UniversalResolver delegation: a new helper function standardizes delegated calls, temporary ENSv2 bailout logic is replaced with unified conditional delegation, and final-stage resolution now routes to UniversalResolver on the ENS Root Chain or conditionally to the active resolver on shadow chains with ENSIP-10 compatibility checks.

Changes

UniversalResolver delegation centralization

Layer / File(s) Summary
Release documentation and import setup
.changeset/forward-resolution-delegate-universal-resolver.md, apps/ensapi/src/lib/resolution/forward-resolution.ts
Changeset documents that Forward Resolution now delegates unaccelerated requests to UniversalResolver including ENSv2-Readiness behavior. Imports are updated to add PublicClient and Operation type support.
UniversalResolver delegation helper
apps/ensapi/src/lib/resolution/forward-resolution.ts
New internal resolveViaUniversalResolver helper function executes forward-resolution operations by delegating to the UniversalResolver's ENSIP-10 resolve() method, wrapped in protocol tracing via withEnsProtocolStep.
ENSv2 namespace bailout refactor
apps/ensapi/src/lib/resolution/forward-resolution.ts
Temporary ENSv2 bailout block replaced with a unified delegation check that delegates to resolveViaUniversalResolver when acceleration is not attempted or when ENSv2Root datasource is present.
Final-stage resolution conditional routing
apps/ensapi/src/lib/resolution/forward-resolution.ts
Final resolution conditionally delegates to UniversalResolver on ENS Root Chain; on shadow chains it resolves via the active resolver after performing ENSIP-10 extended-resolver requirement checks and conditionally executing operations based on resolver compatibility. End-of-function comment updated.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • namehash/ensnode#2267: Both PRs modify apps/ensapi/src/lib/resolution/forward-resolution.ts to change how forward resolution delegates to the consolidated UniversalResolver proxy, especially in the ENSv2/unfinished-path execution flow and contract selection.
  • namehash/ensnode#1932: Both PRs modify apps/ensapi/src/lib/resolution/forward-resolution.ts—the retrieved PR adds an ENS_ROOT_NAME guard, while this PR refactors the delegation and execution paths for forward resolution.

Poem

🐰 Names now flow through one tunnel bright,
A Universal Resolver to set things right,
No more scattered bailouts, just one clean way,
Delegation unified—forward moves each day!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(ensapi): delegate to UniversalResolver for unaccelerated forward resolution' accurately summarizes the main change: delegating unaccelerated forward resolution to the UniversalResolver.
Description check ✅ Passed The PR description covers all required sections: Problem (with clear root cause analysis), Fix (detailed implementation approach), and Validation (test results). The description follows the template structure and provides comprehensive context.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ur-sentinel

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.

❤️ Share

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

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 5, 2026

Greptile Summary

This PR fixes forward resolution in ENSApi so that on-chain resolver behavior gated on msg.sender == UniversalResolver.implementation() (e.g. the ENSv2-readiness canary ur.integration-test.eth) is correctly reproduced by delegating to the Root Chain UniversalResolver rather than reconstructing resolution off-chain.

  • Non-accelerated requests (and ENSv2 namespaces) now take an early exit at step 0 by calling the new resolveViaUniversalResolver helper, eliminating the previous per-namespace ENSv2 bailout in favour of a single unconditional delegation path.
  • Accelerated requests still attempt to serve records from indexed data (ENSIP-19 reverse resolver, known static resolvers), and only fall through to resolveViaUniversalResolver when unresolved operations remain, replacing the previous isExtendedResolver probe + direct-resolver call at step 4–5.
  • The isExtendedResolver check and the requiresWildcardSupport && !extended early-return guard are removed; both concerns are now handled correctly by the on-chain UR.

Confidence Score: 5/5

Safe to merge. The change correctly unifies all non-indexed forward resolution through the on-chain UniversalResolver, which is the only path that can faithfully reproduce caller-sensitive resolver behavior.

The refactoring is well-scoped: resolveViaUniversalResolver is a thin wrapper around the existing executeOperations + withEnsProtocolStep machinery, the removed isExtendedResolver probe and requiresWildcardSupport guard are now subsumed by the UR itself, and the ENSv2 bailout is cleanly folded into the same branch. All 288 tests pass and there are no dangling imports or unused variables.

No files require special attention.

Important Files Changed

Filename Overview
apps/ensapi/src/lib/resolution/forward-resolution.ts Core resolution logic refactored to delegate all non-indexed resolution to the Root Chain UniversalResolver; removes the isExtendedResolver probe, the requiresWildcardSupport guard, and the per-namespace ENSv2 bailout in favour of a unified resolveViaUniversalResolver helper. No correctness issues found.
.changeset/forward-resolution-delegate-universal-resolver.md New changeset entry correctly marks the ensapi package as a patch release with an accurate description of the fix.

Sequence Diagram

sequenceDiagram
    participant Client
    participant resolveForward
    participant _resolveForward
    participant resolveViaUniversalResolver
    participant Acceleration
    participant RootChainUR as Root Chain UniversalResolver

    Client->>resolveForward: resolve(name, selection)
    resolveForward->>_resolveForward: "(name, selection, {registry: ENSRoot})"

    alt Non-accelerated OR ENSv2 namespace (Step 0)
        _resolveForward->>resolveViaUniversalResolver: (name, ops, rootChainClient)
        resolveViaUniversalResolver->>RootChainUR: ENSIP-10 resolve() via CCIP-Read
        RootChainUR-->>resolveViaUniversalResolver: resolved operations
        resolveViaUniversalResolver-->>_resolveForward: operations
        _resolveForward-->>Client: records response
    else Accelerated path
        _resolveForward->>_resolveForward: Step 1: findResolver
        alt Bridged Resolver (e.g. Basenames/Lineanames)
            _resolveForward->>_resolveForward: recurse with shadow registry
        end
        _resolveForward->>Acceleration: Step 3: ENSIP-19 / Static Resolver passes
        Acceleration-->>_resolveForward: partially/fully resolved ops
        alt All operations resolved
            _resolveForward-->>Client: records response (early return)
        else Remaining unresolved ops (Step 4)
            _resolveForward->>resolveViaUniversalResolver: (name, remaining ops, rootChainClient)
            resolveViaUniversalResolver->>RootChainUR: ENSIP-10 resolve() via CCIP-Read
            RootChainUR-->>resolveViaUniversalResolver: resolved operations
            resolveViaUniversalResolver-->>_resolveForward: operations
            _resolveForward-->>Client: records response
        end
    end
Loading

Reviews (3): Last reviewed commit: "fix: delegate all RPC fallback to Univer..." | Re-trigger Greptile

Comment thread apps/ensapi/src/lib/resolution/forward-resolution.ts
Copy link
Copy Markdown
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

This PR updates ENSApi forward resolution to match on-chain behavior by delegating unresolved (non-accelerated) resolution to the ENS Root Chain UniversalResolver, addressing cases where off-chain reconstruction cannot reproduce msg.sender-gated resolver behavior (e.g. the ENSv2-readiness canary).

Changes:

  • Added a resolveViaUniversalResolver() helper to execute remaining forward-resolution operations via the Root Chain UniversalResolver’s resolve().
  • Changed forward resolution flow so that when acceleration can’t be attempted (or ENSv2 indexing is present), resolution delegates wholesale to the UniversalResolver.
  • For accelerated requests, kept indexed acceleration for known patterns, then delegated remaining Root Chain operations to UniversalResolver while preserving direct-resolution behavior on shadow Registry chains.

Reviewed changes

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

File Description
apps/ensapi/src/lib/resolution/forward-resolution.ts Routes unaccelerated (and Root Chain remainder) forward resolution through the Root Chain UniversalResolver to ensure protocol-faithful results.
.changeset/forward-resolution-delegate-universal-resolver.md Adds a patch changeset documenting the behavioral change for forward resolution.

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

Comment thread apps/ensapi/src/lib/resolution/forward-resolution.ts
Comment thread apps/ensapi/src/lib/resolution/forward-resolution.ts Outdated
Comment thread apps/ensapi/src/lib/resolution/forward-resolution.ts Outdated
- step 4 always delegates to the root-chain UniversalResolver (correctly
  resolves shadow-Registry names via the original input instead of calling
  an L2 resolver through the root-chain client)
- coerce ENSv2 namespace check to boolean
- remove now-orphaned imports (isExtendedResolver, withSpanAsync, getENSRootChainId)
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io June 5, 2026 23:03 Inactive
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io June 5, 2026 23:03 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io June 5, 2026 23:03 Inactive
@shrugs
Copy link
Copy Markdown
Member Author

shrugs commented Jun 5, 2026

@greptile review

@shrugs shrugs merged commit ff75f79 into main Jun 5, 2026
20 checks passed
@shrugs shrugs deleted the fix/ur-sentinel branch June 5, 2026 23:08
@github-actions github-actions Bot mentioned this pull request Jun 5, 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.

2 participants