Skip to content

chore: upgrade to ENSv2 (Universal Resolver)#49

Open
Dhaiwat10 wants to merge 2 commits into
wealdtech:masterfrom
Dhaiwat10:ensv2-upgrade
Open

chore: upgrade to ENSv2 (Universal Resolver)#49
Dhaiwat10 wants to merge 2 commits into
wealdtech:masterfrom
Dhaiwat10:ensv2-upgrade

Conversation

@Dhaiwat10
Copy link
Copy Markdown

@Dhaiwat10 Dhaiwat10 commented May 5, 2026

Hey! I'm Dhaiwat from DevRel at ENS Labs, and I'm working with library maintainers across the ecosystem to get them ready for ENSv2. Closes #38 (Support CCIP Read and ENSIP-10).

This PR routes ENS resolution through the Universal Resolver (ENSv2) instead of the legacy ENS registry. ENSIP-10 wildcard resolution and EIP-3668 CCIP-Read both fall out of this routing.

  • Route Resolve and ReverseResolve through the Universal Resolver at 0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe. Without this, names with offchain (CCIP-Read) or L2 records — including the wildcard ENSv2 names rolling out now — silently return 0x0 or fail because go-ens was walking the legacy registry directly instead of the UR proxy.
  • Add ccipread package implementing the ERC-3668 OffchainLookup flow: decodes the revert via rpc.DataError, queries gateway URLs (GET when the URL contains {data}/{sender}, POST otherwise; 4xx aborts and 5xx falls through to the next URL), and re-invokes the callback selector. Bounded to 4 hops, matching viem/ethers.
  • Add abigen bindings for the Universal Resolver under contracts/universalresolver/ (ABI from ens-contracts staging).
  • Add UniversalResolver wrapper at the package root (Resolve, ResolveAddress, Reverse) plus a DNSEncode helper for the bytes name argument.
  • Decode the UR's typed custom errors into typed Go errors with friendly messages: ResolverNotFoundError (Error → "unregistered name", wraps ErrUnregistered), EmptyAddressError (wraps ErrNoAddress), ResolverNotContractError, UnsupportedResolverProfileError, ResolverErrorError, HTTPGatewayError, ReverseAddressMismatchError. Existing strings like "unregistered name" are preserved so most downstream error checks keep working.
  • Tests: integration tests against ur.integration-tests.eth (UR sentinel) and test.offchaindemo.eth (CCIP-Read end-to-end), plus 6 unit tests for the error decoder. RPC defaults to https://ethereum.publicnode.com and is overridable via GO_ENS_TEST_RPC. Three pre-existing tests had stale assertions updated.
  • No API changes. The legacy Resolver/Registry/ReverseResolver/Name types keep all their methods — write paths (SetAddress, SetText, SetContenthash, etc.) still target specific resolver contracts via the registry. Resolver.Address() / Name.Address(coinType) still walk the legacy path because they're paired with write methods on the same struct; happy to migrate those reads through UR too in a follow-up if you want full coverage.

Verification

A/B test against the upstream @latest version vs this branch:

go-ens version ens.Resolve(client, "ur.integration-tests.eth")
wealdtech/go-ens/v3@latest 0x1111111111111111111111111111111111111111 (legacy v1)
this branch 0x2222222222222222222222222222222222222222 (ENSv2 ✅)

To verify the upgrade, resolve ur.integration-tests.eth: it should return 0x2222222222222222222222222222222222222222 via the Universal Resolver. Legacy resolution returns 0x1111111111111111111111111111111111111111, so seeing the latter means the upgrade did not take effect.

You can read more about ENSv2 readiness here: https://docs.ens.domains/web/ensv2-readiness

Route the package-level Resolve and ReverseResolve through the ENS
Universal Resolver proxy at 0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe
instead of walking the legacy registry. CCIP-Read (ERC-3668) is followed
transparently via a new ccipread package, so offchain and L2 names now
resolve correctly.

Legacy Resolver/Registry/ReverseResolver/Name types keep their existing
behaviour so write paths (SetAddress, SetText, etc.) are unaffected and
no public function signatures change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread resolver_test.go Outdated
func TestResolveEthereum(t *testing.T) {
expected := "de0b295669a9fd93d5f28d9ec85e40f4cb697bae"
actual, err := Resolve(client, "ethereum.eth")
func TestResolveVitalik(t *testing.T) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Do not test for vitalik.eth, he might change his address and this test will break than. Better to use any of the resolution-tests: https://github.com/ensdomains/resolution-tests/blob/main/test-cases.json

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point, agreed vitalik.eth is fragile. I've removed TestResolveVitalik entirely in e68d7b4 rather than adding yet another fixture: forward-resolution coverage already lives in universalresolver_test.go against the canonical ur.integration-tests.eth and test.offchaindemo.eth. The package-level Resolve is just a thin wrapper around UniversalResolver.ResolveAddress, so duplicating the assertion here didn't add much.

Comment thread tokenid_test.go Outdated
name: "Invalid ENS domain",
expected: "",
input: "foo.bar",
input: "sirnotappearinginthisregistry.eth",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What if someone registers this name?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fair concern. I switched to a longer descriptive label in e68d7b4 (this-name-is-not-registered-go-ens-test-fixture-do-not-register.eth) and added a comment flagging the registration-risk caveat. Worth noting: any .eth name is technically registerable, so this just makes a collision extremely less likely rather than impossible.

- Drop TestResolveVitalik from resolver_test.go: vitalik.eth's address
  could change, and the rename was unnecessary churn since broader
  Resolve coverage already lives in universalresolver_test.go.
- Replace sirnotappearinginthisregistry.eth in tokenid_test.go with a
  longer, descriptive label that's much less likely to be registered;
  add a comment noting the registration-risk caveat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

Support CCIP Read and ENSIP-10

2 participants