Skip to content

feat(ensapi): domain profile parsing in Omnigraph#2240

Merged
sevenzing merged 22 commits into
mainfrom
ll/domain-profile-omnigraph
Jun 3, 2026
Merged

feat(ensapi): domain profile parsing in Omnigraph#2240
sevenzing merged 22 commits into
mainfrom
ll/domain-profile-omnigraph

Conversation

@sevenzing
Copy link
Copy Markdown
Member

Lite PR

Tip: Review docs on the ENSNode PR process

Summary

  • What changed (1-3 bullets, no essays).

Why

  • Why this change exists. Link to related GitHub issues where relevant.

Testing

  • How this was tested.
  • If you didn't test it, say why.

Notes for Reviewer (Optional)

  • Anything non-obvious or worth a heads-up.

Pre-Review Checklist (Blocking)

  • This PR does not introduce significant changes and is low-risk to review quickly.
  • Relevant changesets are included (or are not required)

@sevenzing sevenzing requested a review from a team as a code owner June 2, 2026 12:16
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 2, 2026

🦋 Changeset detected

Latest commit: a07ef62

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

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Jun 2, 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 Jun 3, 2026 11:27am
enskit-react-example.ensnode.io Ready Ready Preview, Comment Jun 3, 2026 11:27am
ensnode.io Ready Ready Preview, Comment Jun 3, 2026 11:27am
ensrainbow.io Ready Ready Preview, Comment Jun 3, 2026 11:27am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements Domain.resolve.profile and PrimaryNameRecord.resolve.profile endpoints by introducing profile field interpreters, selection-driven resolver merging, and updating the GraphQL schema to expose semantic profile data (descriptions, social handles, cryptocurrency addresses, and image URLs).

Changes

Profile resolution implementation

Layer / File(s) Summary
SDK types and scalar support
packages/enssdk/src/lib/types/addresses.ts, packages/enssdk/src/lib/types/email.ts, packages/enssdk/src/lib/types/index.ts, packages/enssdk/src/omnigraph/graphql.ts, packages/enssdk/src/lib/ens-metadata-service.ts, packages/enssdk/src/lib/index.ts, packages/ensnode-sdk/src/shared/zod-schemas.ts, apps/ensapi/src/omnigraph-api/schema/scalars.ts, apps/ensapi/src/omnigraph-api/builder.ts
New address type aliases (Bitcoin, Litecoin, Solana, etc.) and Email type defined in enssdk; wired into omnigraph scalar mappings; Email schema validator added; ENS metadata service helper added for avatar/header image URLs; GraphQL scalars registered for all new types.
Profile field interpreters
apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/types.ts, apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/texts.ts, apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/images.ts, apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/addresses.ts, apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/social.ts, apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/test-helpers.ts, apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/*.test.ts, apps/ensapi/src/omnigraph-api/lib/resolution/profile/profile-descriptions.ts, apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/index.ts
ProfileFieldInterpreter interface defines selection requirements and interpret method; text interpreters extract description/email/website from records; image interpreters parse URLs or fall back to metadata service; address interpreters decode and encode multi-coin addresses; social interpreters normalize GitHub/Twitter/Telegram/LinkedIn/Keybase handles and URLs; comprehensive test coverage for all interpreter variants.
Selection building and merging
apps/ensapi/src/omnigraph-api/lib/resolution/profile/build-profile-selection.ts, apps/ensapi/src/omnigraph-api/lib/resolution/profile/build-profile-selection.test.ts, apps/ensapi/src/omnigraph-api/lib/resolution/records-selection.ts, apps/ensapi/src/omnigraph-api/lib/resolution/records-selection.test.ts
buildProfileSelectionFromResolveContainerInfo extracts requested profile fields from GraphQL query and merges interpreter selections; mergeRecordsSelections combines records and profile selections while deduplicating texts/addresses arrays and OR-ing abi bitmasks; full test coverage for all selection merge scenarios.
GraphQL schema wiring
apps/ensapi/src/omnigraph-api/lib/resolution/records-profile-model.ts, apps/ensapi/src/omnigraph-api/schema/records.ts, apps/ensapi/src/omnigraph-api/schema/forward-resolve.ts, apps/ensapi/src/omnigraph-api/schema/profile.ts, apps/ensapi/src/omnigraph-api/schema/domain.ts, apps/ensapi/src/omnigraph-api/schema/primary-name-record.ts
ResolvedRecordsModel restructured with explicit name and records fields; forward resolve exposes result field (previously records); profile schema adds header field, expands addresses/socials to multiple chains/platforms, wires interpreters for all fields; Domain.resolve and PrimaryNameRecord.resolve merge selections and call resolveForward with combined profile+records selection.
Devnet fixtures and record seeding
packages/integration-test-env/src/devnet/fixtures.ts, packages/integration-test-env/src/devnet/index.ts, packages/integration-test-env/src/seed/index.ts, packages/integration-test-env/src/seed/resolver-records.ts, packages/integration-test-env/package.json, packages/datasources/src/devnet/constants.ts
Test.eth text records and multi-coin raw addresses defined in fixtures; resolver record seeding iterates fixtures to populate text records and multicoin addresses; devnet package exports fixtures and accounts via ./devnet entry point; integration-test-env added to ensapi and ensnode-sdk dependencies.
Integration test updates
apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts, apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts, apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts, apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts, apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts, apps/ensapi/src/omnigraph-api/schema/resolution.integration.test.ts
Devnet account imports migrated to integration-test-env; resolve-records test expectations updated to use fixture-driven text records and raw addresses; domain profile test updated with new multi-field profile shape (httpUrl, multi-coin addresses, expanded socials); account profile test added to verify primaryName.resolve.profile.
Example queries and component migration
packages/ensnode-sdk/src/omnigraph-api/example-queries.ts, packages/namehash-ui/src/components/identity/EnsAvatar.tsx, packages/namehash-ui/src/index.ts, packages/namehash-ui/src/utils/ensMetadata.ts
Domain-profile example query added; EnsAvatar component migrated to use enssdk's centralized getEnsMetadataServiceImageUrl; local avatar URL helper removed from exports.
Documentation and changeset
apps/ensapi/src/omnigraph-api/lib/resolution/profile/README.md, .changeset/domain-profile-omnigraph.md
Profile resolution architecture documented with interpreter selection roadmap mapping record names to ENSIPs and interpreter identifiers; changeset records ensapi patch for Domain.resolve.profile and PrimaryNameRecord.resolve.profile addition.
Cache and dependencies
packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts, apps/ensapi/package.json, packages/ensnode-sdk/package.json
ProfileHeader marked as embedded data in graphcache; integration-test-env added as devDependency to ensapi and ensnode-sdk.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • namehash/ensnode#1974: Initial selection-driven forward resolution and Domain.records work that this PR extends with profile field merging.
  • namehash/ensnode#1908: Extended GraphQL scalar type support for multiple coin address types in omnigraph schema builder.
  • namehash/ensnode#2236: Related resolved-records model refactoring and embedding changes to the schema layer.

Poem

🐰 wiggles nose

Profile fields now resolve in one sweet leap,
Interpreters merge what the clients reap,
Socials and coins from the records flow,
Omnigraph profiles steal the show! 🎉

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ll/domain-profile-omnigraph

Comment thread apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/social.ts Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 2, 2026

Greptile Summary

This PR wires up the previously stubbed-out profile field in the ENS Omnigraph API, replacing PREVIEW no-op resolvers with real record fetching and interpretation for descriptions, images (avatar/header), website, email, social handles (GitHub, Twitter/X, Telegram, LinkedIn, Keybase), and multi-coin addresses (ETH, BTC, SOL, and eight others). It also refactors ResolvedRecordsModel to nest resolved records under a .records key (separating them from the canonical name identity), promotes the profile field from dev-only to production, and introduces a buildProfileSelectionFromResolveContainerInfo helper that introspects the incoming GraphQL query to merge only the necessary record keys into the upstream resolver call.

  • Profile interpreters: Each profile field is backed by a singleton ProfileFieldInterpreter that declares its required record selection and derives its output from the shared ResolvedRecordsModel, keeping all ENS resolution in one round-trip per request.
  • Merged selection building: buildRecordsSelectionFromResolveContainerInfo and buildProfileSelectionFromResolveContainerInfo are merged via mergeRecordsSelections (with deduplication) so that querying records and profile simultaneously still triggers only a single forward-resolution call.
  • New GraphQL scalars: Nine coin-address scalars and an Email scalar are added, each with parseValue validation via address-encoder's decode round-trip or Zod's email schema.

Confidence Score: 5/5

Safe to merge — the profile field is fully wired with good test coverage across unit, integration, and edge-case scenarios; the model refactor is applied consistently across all access sites.

The core data flow (selection building → merged forward resolution → interpreter-per-field) is correct and well-tested. The ResolvedRecordsModel refactor touching records.ts, domain.ts, and primary-name-record.ts is applied uniformly with no missed access sites. Social and address interpreters handle the full range of input shapes and include comprehensive test coverage. The only findings are two style-level redundancies.

No files require special attention; profile.ts has a minor website double-interpretation inconsistency noted in the review.

Important Files Changed

Filename Overview
apps/ensapi/src/omnigraph-api/schema/profile.ts Profile fields are now fully wired to their respective interpreters; minor inconsistency in website resolver calls interpret() twice vs. avatar/header's single-call pattern.
apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/social.ts Well-structured social handle parser; handles bare handles, @-prefix, full URLs, scheme-less URLs, and trailing slashes correctly with pattern validation per platform.
apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/addresses.ts Address interpreter correctly decodes hex-encoded ENSIP-9 bytes and re-encodes them using the appropriate coin coder, with proper null guards for zero bytes and empty hex.
apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/images.ts Image interpreter correctly falls back to ENS Metadata Service for non-HTTP URIs; rawValue guard in interpretProfileImageHttpUrl is redundant since the caller always passes a truthy string.
apps/ensapi/src/omnigraph-api/lib/resolution/profile/build-profile-selection.ts Selection builder correctly walks profile sub-fields (including inline fragments and named fragments) and merges per-interpreter selections without double-fetching records.
apps/ensapi/src/omnigraph-api/lib/resolution/records-selection.ts New mergeRecordsSelections correctly unions texts/addresses/interfaces and OR-es abi bitmasks; well-tested including deduplication via uniq.
apps/ensapi/src/omnigraph-api/lib/resolution/records-profile-model.ts Model refactored to nest records under .records and rename id to name, with all access sites in records.ts updated consistently.
apps/ensapi/src/omnigraph-api/schema/forward-resolve.ts profile field promoted from dev-only to production; records and profile both resolve from parent.result which carries merged resolved records.
apps/ensapi/src/omnigraph-api/schema/scalars.ts Nine new coin address scalars and Email scalar added, each with proper serialize/parseValue validation via address-encoder's decode round-trip check.
packages/enssdk/src/lib/ens-metadata-service.ts URL construction correctly guards against protocol-relative and scheme-containing names to prevent URL authority hijacking; only mainnet/sepolia are supported.
packages/integration-test-env/src/devnet/fixtures.ts New centralized fixtures file derives raw coin addresses from the address-encoder for seeding and test assertions, replacing hardcoded byte values.

Sequence Diagram

sequenceDiagram
    participant Client
    participant DomainResolve as Domain.resolve resolver
    participant SelBuilder as buildRecordsSelection<br/>+ buildProfileSelection
    participant Merge as mergeRecordsSelections
    participant ENS as resolveForward (ENS protocol)
    participant Profile as DomainProfile resolvers
    participant Records as ResolvedRecords resolvers

    Client->>DomainResolve: "{ resolve { records { texts(...) } profile { description avatar { httpUrl } socials { github { handle } } } } }"
    DomainResolve->>SelBuilder: buildRecordsSelectionFromResolveContainerInfo(info)
    SelBuilder-->>DomainResolve: "{ texts: [...requested keys] }"
    DomainResolve->>SelBuilder: buildProfileSelectionFromResolveContainerInfo(info)
    SelBuilder-->>DomainResolve: "{ texts: [description,avatar,com.github,vnd.github] }"
    DomainResolve->>Merge: mergeRecordsSelections(recordsSel, profileSel)
    Merge-->>DomainResolve: merged selection (deduped)
    DomainResolve->>ENS: resolveForward(name, mergedSelection)
    ENS-->>DomainResolve: ResolverRecordsResponseBase
    DomainResolve-->>DomainResolve: toResolvedRecordsModel(name, response) → ResolvedRecordsModel
    DomainResolve->>Records: parent.result (ResolvedRecordsModel)
    Records-->>Client: texts, addresses, etc.
    DomainResolve->>Profile: parent.result (ResolvedRecordsModel)
    Profile->>Profile: ProfileDescriptionInterpreter.interpret(model)
    Profile->>Profile: ProfileAvatarInterpreter.interpret(model)
    Profile->>Profile: SocialGithubInterpreter.interpret(model)
    Profile-->>Client: description, avatar.httpUrl, socials.github.handle
Loading

Reviews (7): Last reviewed commit: "Merge branch 'main' into ll/domain-profi..." | Re-trigger Greptile

Comment thread apps/ensapi/src/omnigraph-api/schema/domain.ts Outdated
Comment thread apps/ensapi/src/omnigraph-api/schema/primary-name-record.ts Outdated
Comment thread apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/social.ts Outdated
@vercel vercel Bot temporarily deployed to Preview – ensnode.io June 2, 2026 12:26 Inactive
@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io June 2, 2026 12:26 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io June 2, 2026 12:26 Inactive
Copy link
Copy Markdown
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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/ensapi/src/omnigraph-api/lib/resolution/profile/build-profile-selection.test.ts`:
- Around line 119-148: The current test named "builds selection from inline
fragments within profile selection" actually uses a named fragment spread
(...ProfileFields) and doesn't cover the InlineFragment branch in
buildProfileSelectionFromResolveContainerInfo; add a new test case that
constructs a GraphQL operation with an actual inline fragment (e.g. `{ resolve {
profile { ... on DomainProfile { description avatar { httpUrl } } } } }`), build
the same GraphQLResolveInfo (fieldNodes = [resolveField], fragments = {},
returnType = DomainResolveType, variableValues = {}), and assert the expected
selection (texts: ["description","avatar"]) to exercise the InlineFragment
handling in buildProfileSelectionFromResolveContainerInfo; optionally rename the
existing test to mention "named fragment spread" if you prefer clarity.

In `@apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/images.ts`:
- Around line 29-35: The parser currently treats whitespace-only image records
as populated; change the parse function in images.ts so the fetched value is
trimmed before any checks: compute a trimmedRaw from
records.texts?.[record]?.trim() (or trim immediately when assigning raw), use
trimmedRaw for the empty-string/null guard, and pass trimmedRaw into
parseDirectImageHttpUrl and interpretProfileImageHttpUrl so whitespace-only
values are treated as empty and do not produce a metadata-service URL.

In `@apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/social.ts`:
- Around line 53-63: The parser currently accepts any matching hostname and
extracts a handle without validating that the incoming URL's pathname is inside
the configured base path; update the logic in the Social... parser (where
variables toParse, url, hostnames, baseUrl, pathOffset, handle, httpUrl are
used) to first parse baseUrl (baseUrlParsed) and ensure url.pathname begins with
baseUrlParsed.pathname (compare segments or prefix, normalizing leading/trailing
slashes) before slicing segments and assigning handle; also ensure query and
fragment are removed (ignore url.search and url.hash) when producing httpUrl and
when computing the handle so only the validated base path + handle segments are
accepted.

In `@apps/ensapi/src/omnigraph-api/lib/resolution/profile/README.md`:
- Line 7: The README's fenced code block containing the directory-tree entry
"profile/" is missing a language tag (markdownlint MD040); update that fenced
block by adding a language identifier such as "text" (i.e., change the opening
fence from ``` to ```text) so the linter is satisfied while preserving the block
content.

In `@apps/ensapi/src/omnigraph-api/schema/domain.ts`:
- Around line 207-213: The ternary guarding mergedSelection is redundant because
the earlier early-return ensures name is truthy and normalized; replace the
conditional expression with a direct assignment to mergedSelection by calling
mergeRecordsSelections(buildRecordsSelectionFromResolveContainerInfo(info),
buildProfileSelectionFromResolveContainerInfo(info)) and remove the `name &&
isNormalizedName(name) ? ... : null` check so mergedSelection is always the
merged result in this code path.

In `@apps/ensapi/src/omnigraph-api/schema/primary-name-record.ts`:
- Around line 69-79: The ternary guarding mergedSelection with "name &&
isNormalizedName(name)" is redundant because earlier logic already guarantees
name is truthy and normalized; replace the conditional with a direct assignment:
call mergeRecordsSelections(buildRecordsSelectionFromResolveContainerInfo(info),
buildProfileSelectionFromResolveContainerInfo(info)) and remove the null branch
and subsequent if (!mergedSelection) early return that treats records as null;
ensure mergedSelection is used as before and delete the dead code paths
referencing the null case.

In `@packages/ensnode-sdk/src/shared/zod-schemas.ts`:
- Around line 154-167: The JSDoc for parsing an EVM address was accidentally
left above makeEmailSchema, so move the misplaced comment block that starts
"Parses a serialized representation of an EVM address into a {`@link`
NormalizedAddress}." so it immediately precedes makeNormalizedAddressSchema;
ensure the Email comment ("Parses a string into a validated {`@link` Email}
(trimmed).") stays above makeEmailSchema and that both functions
(makeEmailSchema and makeNormalizedAddressSchema) have their correct JSDoc
directly above them.

In `@packages/enssdk/src/lib/ens-metadata-service.ts`:
- Around line 32-41: The getEnsMetadataServiceImageUrl function currently calls
new URL(name, base) which allows an absolute or protocol-relative name to
override metadata. Add a defensive check at the start of
getEnsMetadataServiceImageUrl to reject names that begin with '//' or match a
URI scheme pattern like /^[a-zA-Z][a-zA-Z0-9+.-]*:/ (for example return null)
before calling namespaceIdToMetadataNetwork and new URL(...), so only
relative/name-only inputs produce URLs; reference getEnsMetadataServiceImageUrl,
namespaceIdToMetadataNetwork, and EnsMetadataImageRecord to locate the code to
change.
🪄 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: 9d9d3e7c-9db0-472b-8726-b6da7fc5b906

📥 Commits

Reviewing files that changed from the base of the PR and between 303628b and 7d8681c.

⛔ Files ignored due to path filters (3)
  • packages/enssdk/src/omnigraph/generated/introspection.ts is excluded by !**/generated/**
  • packages/enssdk/src/omnigraph/generated/schema.graphql is excluded by !**/generated/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (42)
  • .changeset/domain-profile-omnigraph.md
  • apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts
  • apps/ensapi/src/omnigraph-api/builder.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/README.md
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/build-profile-selection.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/build-profile-selection.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/addresses.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/addresses.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/images.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/images.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/index.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/social.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/social.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/test-helpers.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/texts.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/texts.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/types.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/records-selection.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/records-selection.ts
  • apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts
  • apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts
  • apps/ensapi/src/omnigraph-api/schema/domain.ts
  • apps/ensapi/src/omnigraph-api/schema/forward-resolve.ts
  • apps/ensapi/src/omnigraph-api/schema/primary-name-record.ts
  • apps/ensapi/src/omnigraph-api/schema/profile.ts
  • apps/ensapi/src/omnigraph-api/schema/scalars.ts
  • packages/datasources/package.json
  • packages/datasources/src/devnet/constants.ts
  • packages/enskit/src/react/omnigraph/_lib/cache-exchange.ts
  • packages/ensnode-sdk/src/omnigraph-api/example-queries.ts
  • packages/ensnode-sdk/src/shared/zod-schemas.ts
  • packages/enssdk/src/lib/ens-metadata-service.test.ts
  • packages/enssdk/src/lib/ens-metadata-service.ts
  • packages/enssdk/src/lib/index.ts
  • packages/enssdk/src/lib/types/addresses.ts
  • packages/enssdk/src/lib/types/email.ts
  • packages/enssdk/src/lib/types/index.ts
  • packages/enssdk/src/omnigraph/graphql.ts
  • packages/integration-test-env/src/seed/resolver-records.ts
  • packages/namehash-ui/src/components/identity/EnsAvatar.tsx
  • packages/namehash-ui/src/index.ts
  • packages/namehash-ui/src/utils/ensMetadata.ts
💤 Files with no reviewable changes (1)
  • packages/namehash-ui/src/utils/ensMetadata.ts

Comment thread apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/images.ts Outdated
Comment thread apps/ensapi/src/omnigraph-api/lib/resolution/profile/README.md
Comment thread apps/ensapi/src/omnigraph-api/schema/domain.ts Outdated
Comment thread apps/ensapi/src/omnigraph-api/schema/primary-name-record.ts Outdated
Comment thread packages/ensnode-sdk/src/shared/zod-schemas.ts Outdated
Comment thread packages/enssdk/src/lib/ens-metadata-service.ts
Copy link
Copy Markdown
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.

Caution

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

⚠️ Outside diff range comments (2)
packages/enssdk/src/lib/ens-metadata-service.ts (1)

39-44: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Reject root-relative names (name starting with /) before building the metadata URL.

name.startsWith("//") and URI_SCHEME_PATTERN block protocol-relative URLs and custom schemes, but new URL("/foo", base) still drops the https://metadata.ens.domains/${network}/${record}/ prefix and builds a different metadata path. Guard leading / (and extend the malicious-input test set to include "/foo").

🛠 Proposed fix
-  if (name.startsWith("//") || URI_SCHEME_PATTERN.test(name)) return null;
+  if (name.startsWith("/") || URI_SCHEME_PATTERN.test(name)) return null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/enssdk/src/lib/ens-metadata-service.ts` around lines 39 - 44, The
function that builds ENS metadata URLs currently only rejects protocol-relative
URLs via name.startsWith("//") but still allows root-relative names like "/foo"
which cause new URL(name, base) to ignore the base; change the guard to reject
any name that starts with "/" (e.g., use name.startsWith("/") instead of
name.startsWith("//")) alongside the existing URI_SCHEME_PATTERN check, so the
block becomes: reject leading-slash inputs and scheme-matching inputs before
calling namespaceIdToMetadataNetwork and new URL(...); also update the
malicious-input test set to include "/foo" to cover this case.
apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts (1)

700-729: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Keep DomainProfileResult aligned with the expanded query.

DomainProfileResult still only types avatar, addresses.ethereum, and socials.github, but the query/assertion now depend on header, website, email, bitcoin, litecoin, solana, twitter, and telegram too. That weakens the compile-time contract for this test and makes future schema drift in those new fields easier to miss.

♻️ Suggested shape update
   type DomainProfileResult = {
     domain: {
       resolve: {
         profile: {
           description: string | null;
           avatar: { httpUrl: UrlString | null } | null;
-          addresses: { ethereum: NormalizedAddress | null } | null;
-          socials: { github: { handle: string; httpUrl: UrlString } | null } | null;
+          header: { httpUrl: UrlString | null } | null;
+          website: { httpUrl: UrlString | null } | null;
+          email: string | null;
+          addresses: {
+            ethereum: NormalizedAddress | null;
+            bitcoin: string | null;
+            litecoin: string | null;
+            solana: string | null;
+          } | null;
+          socials: {
+            github: { handle: string; httpUrl: UrlString } | null;
+            twitter: { handle: string; httpUrl: UrlString } | null;
+            telegram: { handle: string; httpUrl: UrlString } | null;
+          } | null;
         } | null;
       };
     };
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts` around lines
700 - 729, The DomainProfileResult TypeScript type is out of sync with the
expanded GraphQL query DomainProfile; update the DomainProfileResult type (the
domain.resolve.profile shape) to include header and website as { httpUrl:
UrlString | null }, email as string | null, addresses to include bitcoin,
litecoin, solana (each NormalizedAddress | null), and socials to include twitter
and telegram with the same { httpUrl: UrlString; handle: string } | null shape
as github so the test's compile-time contract matches the query.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts`:
- Around line 700-729: The DomainProfileResult TypeScript type is out of sync
with the expanded GraphQL query DomainProfile; update the DomainProfileResult
type (the domain.resolve.profile shape) to include header and website as {
httpUrl: UrlString | null }, email as string | null, addresses to include
bitcoin, litecoin, solana (each NormalizedAddress | null), and socials to
include twitter and telegram with the same { httpUrl: UrlString; handle: string
} | null shape as github so the test's compile-time contract matches the query.

In `@packages/enssdk/src/lib/ens-metadata-service.ts`:
- Around line 39-44: The function that builds ENS metadata URLs currently only
rejects protocol-relative URLs via name.startsWith("//") but still allows
root-relative names like "/foo" which cause new URL(name, base) to ignore the
base; change the guard to reject any name that starts with "/" (e.g., use
name.startsWith("/") instead of name.startsWith("//")) alongside the existing
URI_SCHEME_PATTERN check, so the block becomes: reject leading-slash inputs and
scheme-matching inputs before calling namespaceIdToMetadataNetwork and new
URL(...); also update the malicious-input test set to include "/foo" to cover
this case.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d8bdb6bc-f163-4550-9fd7-3f5a58f183b3

📥 Commits

Reviewing files that changed from the base of the PR and between 7d8681c and 8915213.

📒 Files selected for processing (24)
  • .changeset/domain-profile-omnigraph.md
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/README.md
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/build-profile-selection.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/addresses.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/addresses.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/images.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/social.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/test-helpers.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/texts.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/types.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/records-profile-model.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/records-selection.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/records-selection.ts
  • apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts
  • apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts
  • apps/ensapi/src/omnigraph-api/schema/domain.ts
  • apps/ensapi/src/omnigraph-api/schema/forward-resolve.ts
  • apps/ensapi/src/omnigraph-api/schema/primary-name-record.ts
  • apps/ensapi/src/omnigraph-api/schema/records.ts
  • apps/ensapi/src/omnigraph-api/schema/scalars.ts
  • packages/ensnode-sdk/src/shared/zod-schemas.ts
  • packages/enssdk/src/lib/ens-metadata-service.test.ts
  • packages/enssdk/src/lib/ens-metadata-service.ts
  • packages/enssdk/src/lib/types/email.ts

@vercel vercel Bot temporarily deployed to Preview – admin.ensnode.io June 2, 2026 16:48 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensrainbow.io June 2, 2026 16:48 Inactive
@vercel vercel Bot temporarily deployed to Preview – ensnode.io June 2, 2026 16:48 Inactive
Copy link
Copy Markdown
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.

@sevenzing Looks amazing! 🚀 🚀 Great work! Shared a few suggestions. Please take the lead to merge when ready 👍

Comment thread packages/enssdk/src/omnigraph/generated/schema.graphql
Comment thread packages/enssdk/src/lib/types/email.ts
Comment thread packages/datasources/src/devnet/constants.ts Outdated
Comment thread packages/datasources/package.json Outdated
Comment thread apps/ensapi/src/omnigraph-api/schema/profile.ts Outdated
Comment thread apps/ensapi/src/omnigraph-api/schema/scalars.ts
Comment thread apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/texts.ts Outdated
Comment thread apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/social.test.ts Outdated
Comment thread apps/ensapi/src/omnigraph-api/lib/resolution/profile/parsers/images.ts Outdated
Copy link
Copy Markdown
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.

♻️ Duplicate comments (1)
apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/social.ts (1)

52-65: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Base path validation is still missing.

The parser accepts any URL with a matching hostname, even when the pathname is outside the configured base path. For example, SocialLinkedInParser (baseUrl https://www.linkedin.com/in) would accept https://linkedin.com/company/namehash and incorrectly extract handle namehash because only the hostname is validated.

🛡️ Suggested fix to validate base path
  try {
    const url = new URL(toParse);
    if (hostnames.includes(url.hostname)) {
+      const baseUrlParsed = new URL(baseUrl);
+      const baseSegments = baseUrlParsed.pathname.split("/").filter((s) => s.length > 0);
      const segments = url.pathname.split("/").filter((s) => s.length > 0);
+      const matchesBasePath = baseSegments.every(
+        (segment, index) => segments[index] === segment,
+      );
+      if (!matchesBasePath) return null;
+
-      handle = segments.slice(pathOffset).join("/");
-      handle = handle === "" ? null : handle;
+      handle = segments.slice(pathOffset).join("/") || null;

      if (handle) {
-        const baseUrlParsed = new URL(baseUrl);
        url.host = baseUrlParsed.host;
        url.protocol = baseUrlParsed.protocol;
        url.pathname = url.pathname.endsWith("/") ? url.pathname.slice(0, -1) : url.pathname;
        httpUrl = url.toString();
      }
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/social.ts`
around lines 52 - 65, The parser currently only checks hostname and then slices
pathname using pathOffset, which lets URLs outside the configured base path
(baseUrl) pass; update the block inside the hostnames check to parse baseUrl
into baseUrlParsed and validate that url.pathname starts with
baseUrlParsed.pathname (normalize trailing slashes) or that the pathname
segments before pathOffset match the base path segments before extracting
handle; only proceed to set handle, url.host/protocol/pathname and httpUrl when
the base path matches. Use the existing variables baseUrl, baseUrlParsed,
pathOffset, url.pathname, handle and httpUrl to implement this validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/social.ts`:
- Around line 52-65: The parser currently only checks hostname and then slices
pathname using pathOffset, which lets URLs outside the configured base path
(baseUrl) pass; update the block inside the hostnames check to parse baseUrl
into baseUrlParsed and validate that url.pathname starts with
baseUrlParsed.pathname (normalize trailing slashes) or that the pathname
segments before pathOffset match the base path segments before extracting
handle; only proceed to set handle, url.host/protocol/pathname and httpUrl when
the base path matches. Use the existing variables baseUrl, baseUrlParsed,
pathOffset, url.pathname, handle and httpUrl to implement this validation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 402837a4-1c63-4876-95fd-a567477f0f81

📥 Commits

Reviewing files that changed from the base of the PR and between 8915213 and a07ef62.

⛔ Files ignored due to path filters (2)
  • packages/enssdk/src/omnigraph/generated/schema.graphql is excluded by !**/generated/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (35)
  • apps/ensapi/package.json
  • apps/ensapi/src/handlers/api/resolution/resolve-primary-name.integration.test.ts
  • apps/ensapi/src/handlers/api/resolution/resolve-primary-names.integration.test.ts
  • apps/ensapi/src/handlers/api/resolution/resolve-records.integration.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/README.md
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/build-profile-selection.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/addresses.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/addresses.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/images.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/images.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/index.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/social.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/social.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/test-helpers.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/texts.test.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/texts.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/types.ts
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/profile-descriptions.ts
  • apps/ensapi/src/omnigraph-api/schema/account.integration.test.ts
  • apps/ensapi/src/omnigraph-api/schema/domain.integration.test.ts
  • apps/ensapi/src/omnigraph-api/schema/forward-resolve.ts
  • apps/ensapi/src/omnigraph-api/schema/profile.ts
  • apps/ensapi/src/omnigraph-api/schema/resolution.integration.test.ts
  • packages/datasources/src/devnet/constants.ts
  • packages/ensnode-sdk/package.json
  • packages/ensnode-sdk/src/omnigraph-api/example-queries.ts
  • packages/enssdk/src/lib/ens-metadata-service.test.ts
  • packages/enssdk/src/lib/ens-metadata-service.ts
  • packages/integration-test-env/package.json
  • packages/integration-test-env/src/devnet/fixtures.ts
  • packages/integration-test-env/src/devnet/index.ts
  • packages/integration-test-env/src/seed/index.ts
  • packages/integration-test-env/src/seed/resolver-records.ts
  • packages/namehash-ui/src/components/identity/EnsAvatar.tsx
  • packages/namehash-ui/src/index.ts
💤 Files with no reviewable changes (2)
  • apps/ensapi/src/omnigraph-api/lib/resolution/profile/interpreters/test-helpers.ts
  • packages/namehash-ui/src/index.ts

@sevenzing
Copy link
Copy Markdown
Member Author

@greptile review

@sevenzing sevenzing merged commit 7d23ee9 into main Jun 3, 2026
19 checks passed
@sevenzing sevenzing deleted the ll/domain-profile-omnigraph branch June 3, 2026 11:42
@github-actions github-actions Bot mentioned this pull request Jun 3, 2026
Copy link
Copy Markdown
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.

@sevenzing This is amazing 🚀 🚀 ! Big milestone for us! Great job!

Sharing a few very small nits that will be nice to optimize in a small new PR before we release this. Cheers!

email: Email

"""
Interpreted header metadata. Returns null when the raw header record is unset or empty.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Interpreted header metadata. Returns null when the raw header record is unset or empty.
The interpreted header image on the profile of the ENS name. Returns null when the raw header record is unset or empty.

addresses: ProfileAddresses

"""
Interpreted avatar metadata. Returns null when the raw avatar record is unset or empty.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
Interpreted avatar metadata. Returns null when the raw avatar record is unset or empty.
The interpreted avatar image on the profile of the ENS name. Returns null when the raw avatar record is unset or empty.

"""An interpreted profile for a name."""
"""The interpreted profile of an ENS name."""
type DomainProfile {
"""The interpreted addresses on the profile of an ENS name."""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
"""The interpreted addresses on the profile of an ENS name."""
"""The interpreted addresses on the profile of the ENS name."""


"""The profile description, or null when unset."""
"""
The interpreted description on the profile of an ENS name, or null when unset.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
The interpreted description on the profile of an ENS name, or null when unset.
The interpreted description on the profile of the ENS name, or null when unset.


"""The contact email address, or null when unset or invalid."""
"""
The interpreted email address on the profile of an ENS name, or null when unset or invalid.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
The interpreted email address on the profile of an ENS name, or null when unset or invalid.
The interpreted email address on the profile of the ENS name, or null when unset or invalid.

"""
header: ProfileHeader

"""The interpreted social accounts on the profile of an ENS name."""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
"""The interpreted social accounts on the profile of an ENS name."""
"""The interpreted social accounts on the profile of the ENS name."""

"""The interpreted social accounts on the profile of an ENS name."""
socials: ProfileSocials

"""The interpreted website on the profile of an ENS name."""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
"""The interpreted website on the profile of an ENS name."""
"""The interpreted website on the profile of the ENS name."""

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.

3 participants