feat: Migrate domain indexing to ENSNode (Omnigraph + subgraph-compat) for ENSv2 - #4
feat: Migrate domain indexing to ENSNode (Omnigraph + subgraph-compat) for ENSv2#4Quantumlyy wants to merge 2 commits into
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
ens-metadata-flarecloud | e798440 | Commit Preview URL | Jun 08 2026, 04:07 PM |
Greptile SummaryThis PR replaces The Graph subgraph client with a new
Confidence Score: 3/5The core ENSv1 lookup paths work correctly, but two defects in the ENSv2 paths could silently return wrong data or break downstream consumers. Two issues affect live ENSv2 behaviour: resolveNftIdentifiers silently returns NAME_WRAPPER_V2 as the contract when an ENSv2Domain from Omnigraph is missing tokenId or registry.contract.address, giving callers an incorrect NFT identity with no error signal. Separately, /queryNFT now advertises the ENSv2 registry contract address in its response, but the metadata endpoint's contractKind will reject that address with a 400 — breaking any consumer that uses the queryNFT response to construct a metadata request. src/services/ensnode.ts (resolveNftIdentifiers fallback) and src/services/domain.ts (contractKind missing ENSv2 registry support) Important Files Changed
Sequence DiagramsequenceDiagram
participant Client
participant MetadataRoute as /network/contract/tokenId
participant QueryNFTRoute as /queryNFT?name=
participant DomainSvc as domain.ts
participant ENSNode as ensnode.ts
participant Omnigraph as ENSNode /api/omnigraph
participant Subgraph as ENSNode /subgraph
Client->>QueryNFTRoute: "GET /queryNFT?name=foo.eth"
QueryNFTRoute->>ENSNode: queryDomainByName(name)
ENSNode->>Omnigraph: DomainByName query
Omnigraph-->>ENSNode: "ENSv1Domain | ENSv2Domain"
ENSNode-->>QueryNFTRoute: DomainRecord (protocolVersion, nftContract?)
QueryNFTRoute-->>Client: "{contract: ENSv2_registry, tokenId: nftTokenId}"
Client->>MetadataRoute: "GET /mainnet/{ENSv2_registry}/{nftTokenId}"
MetadataRoute->>DomainSvc: resolveDomain(contract)
DomainSvc->>DomainSvc: contractKind(ENSv2_registry) → null
DomainSvc-->>MetadataRoute: 400 unsupported contract
Client->>MetadataRoute: "GET /mainnet/NAME_WRAPPER_V2/{namehash}"
MetadataRoute->>DomainSvc: resolveDomain(NAME_WRAPPER_V2)
DomainSvc->>ENSNode: queryDomainByNamehash(namehash)
ENSNode->>Omnigraph: DomainById (ETH_REGISTRY_V1)
Omnigraph-->>ENSNode: null (miss)
ENSNode->>Omnigraph: DomainById (ETH_REGISTRY_V2)
Omnigraph-->>ENSNode: ENSv2Domain
ENSNode-->>DomainSvc: "DomainRecord{protocolVersion:ENSv2}"
DomainSvc-->>MetadataRoute: "{kind:v2, record}"
MetadataRoute-->>Client: "{version:3}"
Client->>MetadataRoute: "GET /mainnet/BASE_REGISTRAR_V1/{labelhash}"
MetadataRoute->>DomainSvc: resolveDomain(BASE_REGISTRAR_V1)
DomainSvc->>ENSNode: queryDomainByLabelhash(labelhash)
ENSNode->>Subgraph: DomainByLabelhash query
Subgraph-->>ENSNode: domains[]
ENSNode-->>DomainSvc: "DomainRecord{protocolVersion:ENSv1}"
DomainSvc-->>MetadataRoute: "{kind:v1, record}"
MetadataRoute-->>Client: "{version:1}"
Reviews (2): Last reviewed commit: "fix: address requested changes" | Re-trigger Greptile |
| fragment OmnigraphDomainFields on Domain { | ||
| __typename | ||
| ... on ENSv1Domain { |
There was a problem hiding this comment.
The
ENSv1Domain and ENSv2Domain inline fragments in OMNIGRAPH_DOMAIN_FIELDS are byte-for-byte identical. Duplicate fragment bodies increase query size and make future field additions error-prone (easy to update one and forget the other). A comment noting the intentional duplication would at least document the constraint.
| fragment OmnigraphDomainFields on Domain { | |
| __typename | |
| ... on ENSv1Domain { | |
| # ENSv1Domain and ENSv2Domain expose identical fields today; both inline | |
| # fragments must remain in sync if the schema diverges in the future. | |
| fragment OmnigraphDomainFields on Domain { | |
| __typename | |
| ... on ENSv1Domain { |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Pull request overview
This PR migrates ENS domain indexing from The Graph subgraph to ENSNode, using ENSNode’s Omnigraph GraphQL API for NameWrapper/name-based lookups (including ENSv2 support) while retaining a subgraph-compatible query shape for Base Registrar v1 labelhash lookups. It also updates configuration, documentation, and unit tests to reflect the new indexing backend and Holesky limitations.
Changes:
- Replace subgraph-based lookups with
src/services/ensnode.ts(Omnigraph + subgraph-compat hybrid), and removesrc/services/subgraph.ts. - Update env/config wiring (
Env,NetworkConfig,wrangler.toml, test envs) fromSUBGRAPH_URL_*+THE_GRAPH_API_KEYtoENSNODE_URL_*. - Extend metadata
versionsemantics to emit3for ENSv2 domains, and add/adjust unit tests and docs accordingly.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
wrangler.toml |
Replaces SUBGRAPH_URL_* vars with ENSNODE_URL_* vars (including empty Holesky default). |
vitest.config.ts |
Updates test env vars to use ENSNODE_URL_* instead of subgraph URLs. |
test/unit/routeCache.test.ts |
Updates route cache tests to mock ENSNode Omnigraph + subgraph-compat endpoints via fetch. |
test/unit/ensnode.test.ts |
Adds unit tests for ENSNode query helpers, Omnigraph mapping, and Holesky fast-fail behavior. |
src/storage/r2Cache.ts |
Updates internal comment to reference ENSNode instead of subgraph roundtrips. |
src/services/subgraph.ts |
Removes The Graph subgraph client implementation. |
src/services/ensnode.ts |
Adds ENSNode client (Omnigraph + subgraph-compat), Omnigraph mapping, and Holesky availability guard. |
src/services/domain.ts |
Repoints domain resolution to ENSNode-based query functions. |
src/routes/queryNFT.ts |
Changes /queryNFT to resolve domains by normalized name via Omnigraph instead of namehash-only subgraph lookup. |
src/routes/metadata.ts |
Emits metadata version: 3 for ENSv2 domains based on protocolVersion. |
src/lib/networks.ts |
Changes network config field from subgraphUrl to ensnodeUrl and wires it from env. |
src/env.ts |
Replaces SUBGRAPH_URL_*/THE_GRAPH_API_KEY with ENSNODE_URL_* env vars. |
src/constants.ts |
Adds ETH_REGISTRY_V1 constant used for Omnigraph DomainId construction. |
README.md |
Documents ENSNode indexing backend, Holesky behavior, and metadata version semantics. |
package.json |
Updates deploy-time variable descriptions to reflect ENSNODE_URL_* configuration. |
.dev.vars.example |
Removes The Graph API key example and clarifies local dev secrets. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
src/services/ensnode.tsmodule that talks to ENSNode hosted instances./queryNFTname lookups use the Omnigraph GraphQL API (/api/omnigraph), includingENSv1DomainandENSv2Domainfragments./subgraph) with the existingdomains(where: …)query shape.SUBGRAPH_URL_*andTHE_GRAPH_API_KEY. AddENSNODE_URL_MAINNET,ENSNODE_URL_SEPOLIA, andENSNODE_URL_HOLESKYinwrangler.toml[vars](defaults:https://api.alpha.ensnode.io,https://api.alpha-sepolia.ensnode.io, empty for Holesky). Network config insrc/lib/networks.tsnow exposesensnodeUrlper chain.GET /{network}/…metadata,/queryNFT) onholeskyreturn 503 withholesky_unsupportedand a link to hosted instances docs. Avatar/header resolution via RPC still works whenHOLESKY_RPC_URLis set.version:1= Base Registrar,2= NameWrapper (ENSv1),3= native ENSv2 (protocolVersion === "ENSv2"from Omnigraph)./queryNFT: resolves domains by normalized name via Omnigraph (queryDomainByName) instead of namehash-only subgraph lookup.src/services/subgraph.ts. Add unit coverage intest/unit/ensnode.test.ts; update route cache tests to mock Omnigraph + subgraph-compat responses.