Skip to content

Conversation

@tk-o
Copy link
Contributor

@tk-o tk-o commented Dec 5, 2025

Suggested review order: commit-by-commit.

This PR introduces new Name Tokens API route.

Example: `finalroll.eth` (minted, wrapped)
Example request:
GET /api/name-tokens?name=finalroll.eth
GET /api/name-tokens?domainId=0x57bda742b7d14316ce9f4f90a4a6035b741057e90ffa1d078ac6e24b1c1028df

Example response:

{
  "responseCode": "ok",
  "registeredNameTokens": {
    "domainId": "0x57bda742b7d14316ce9f4f90a4a6035b741057e90ffa1d078ac6e24b1c1028df",
    "name": "finalroll.eth",
    "tokens": [
      {
        "domainAsset": {
          "assetId": {
            "assetNamespace": "erc721",
            "contract": {
              "chainId": 1,
              "address": "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85"
            },
            "tokenId": "0x5dc22117a784fc74bc3d4df34423ac78eabc534a3d233be07950b82fa68fcac7"
          },
          "domainId": "0x57bda742b7d14316ce9f4f90a4a6035b741057e90ffa1d078ac6e24b1c1028df"
        },
        "ownership": {
          "ownershipType": "proxy",
          "owner": {
            "chainId": 1,
            "address": "0xd4416b13d2b3a9abae7acd5d6c2bbdbe25686401"
          }
        },
        "mintStatus": "minted"
      },
      {
        "domainAsset": {
          "assetId": {
            "assetNamespace": "erc1155",
            "contract": {
              "chainId": 1,
              "address": "0xd4416b13d2b3a9abae7acd5d6c2bbdbe25686401"
            },
            "tokenId": "0x57bda742b7d14316ce9f4f90a4a6035b741057e90ffa1d078ac6e24b1c1028df"
          },
          "domainId": "0x57bda742b7d14316ce9f4f90a4a6035b741057e90ffa1d078ac6e24b1c1028df"
        },
        "ownership": {
          "ownershipType": "effective",
          "owner": {
            "chainId": 1,
            "address": "0x27c9b34eb43523447d3e1bcf26f009d814522687"
          }
        },
        "mintStatus": "minted"
      }
    ],
    "expiresAt": 1796731799,
    "accurateAsOf": 1765280254
  }
}

Example: `tko.eth` (unwrapped, expired)
Example request:
GET /api/name-tokens?name=tko.eth
GET /api/name-tokens?domainId=0xaf20e732cb67b5895d3a9516990611765a4ee138ebdadb60410539beb1dc42bb

Example response:

{
  "responseCode": "ok",
  "registeredNameTokens": {
    "domainId": "0xaf20e732cb67b5895d3a9516990611765a4ee138ebdadb60410539beb1dc42bb",
    "name": "tko.eth",
    "tokens": [
      {
        "domainAsset": {
          "assetId": {
            "assetNamespace": "erc721",
            "contract": {
              "chainId": 1,
              "address": "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85"
            },
            "tokenId": "0x2376b3952dc704bc0cbebbdab63e4b510d63a1234f5910f12362f5b34bdcc704"
          },
          "domainId": "0xaf20e732cb67b5895d3a9516990611765a4ee138ebdadb60410539beb1dc42bb"
        },
        "ownership": {
          "ownershipType": "effective",
          "owner": {
            "chainId": 1,
            "address": "0xd0ed779af7e3063c9121590c1f6a90396a22b824"
          }
        },
        "mintStatus": "minted"
      }
    ],
    "expiresAt": 1758748175,
    "accurateAsOf": 1765280339
  }
}

Builds on top of #1357

TODOs

Followup on

  • When there were no tokens found for the requested name, the response shoul be a 200 success where we just return an empty array of name tokens.
  • Think of better name (if any) for RegisteredNameTokens entity that's returned by Name Tokens API.
  • Set explicit order of name tokens in the response.
  • Handle the edge-case found by @lightwalker-eth in ENS app.

@vercel
Copy link

vercel bot commented Dec 5, 2025

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

Project Deployment Preview Comments Updated (UTC)
admin.ensnode.io Ready Ready Preview Comment Dec 10, 2025 5:29pm
ensnode.io Ready Ready Preview Comment Dec 10, 2025 5:29pm
ensrainbow.io Ready Ready Preview Comment Dec 10, 2025 5:29pm

@changeset-bot
Copy link

changeset-bot bot commented Dec 5, 2025

🦋 Changeset detected

Latest commit: b5d0cdc

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

This PR includes changesets to release 14 packages
Name Type
@ensnode/ensnode-react Minor
ensapi Minor
@ensnode/ensnode-sdk Minor
ensindexer Minor
ensadmin Minor
ensrainbow Minor
@ensnode/ensrainbow-sdk Minor
@ensnode/datasources Minor
@ensnode/ponder-metadata Minor
@ensnode/ensnode-schema Minor
@ensnode/ponder-subgraph Minor
@ensnode/shared-configs Minor
@docs/ensnode Minor
@docs/ensrainbow Minor

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

Also, fixes deserialization bug.
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.

@tk-o Really happy to see this! Shared some suggestions and feedback. Appreciate your efforts to help make this API amazing 🫡

tk-o added 8 commits December 8, 2025 10:37
simplify name tokens api data model
Add `domainId` request param for Name Tokens API route
Update `NameTokensResponseError` variant types.
define request schema
Update code docs for `RegisteredNameTokens`
Introduce Name Token ownership concept
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.

@tk-o Hey great job! 🚀 Shared feedback please take the lead to merge this once ready 👍

// Return 404 response with error code for unknown name context when
// the no name tokens were found for the domain ID associated with
// the requested name.
if (!registeredNameTokens) {
Copy link
Member

Choose a reason for hiding this comment

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

I don't believe this should be a 404 error case.

Shouldn't it be a 200 success where we just return an empty array of name tokens?


return useQuery({
...queryOptions,
refetchInterval: false, // no refetching - assume data is immutable until a full page refresh
Copy link
Member

Choose a reason for hiding this comment

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

I thought we had a different strategy for this idea on other hooks we've implemented?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lightwalker-eth, your previous feedback on that setting param:

In packages/ensnode-react/src/hooks/useNameTokens.ts:

+    refetchInterval: 60 * 1000, // 60 seconds - latest Name Tokens change infrequently

No, the results do not change frequently.

I would say it should be assumed to be effectively immutable until a full page refresh.

refetchInterval: false guarantees there query is immutable until the full page refresh.

Copy link
Member

Choose a reason for hiding this comment

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

@tk-o I was referring to ASSUME_IMMUTABLE_QUERY that we built. I assume that's what we want to use here?

* c) Is no longer wrapped by the NameWrapper, and is also no longer
* minted by the BaseRegistrar (both tokens now burned by sending to
* the null address).
*/
Copy link
Member

Choose a reason for hiding this comment

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

There's another edge case to consider which is defined here: https://namehash.slack.com/archives/C08BMPFUUDD/p1765288628066849

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cross-posting for clarity:

We may want to track independent expiresAt values for each NameToken. This can help us with goals including:

  • The specific case we're discussing here, where we need the ability to represent that a particular NameToken 's registration lifecycle has expired, even though the token itself is still technically minted.
  • The additional case where the expiration time of a name in the NameWrapper can go out of sync with the expiration time of a name in the BaseRegistrar.

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.

@tk-o Looks great, I'm happy to merge this. Please take the lead to merge this and then we can follow up on comments in another PR 👍

if (request.name !== undefined) {
const { name } = request;

// return 404 when the requested name was the ENS Root
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// return 404 when the requested name was the ENS Root
// return 404 when the requested name was the ENS Root as otherwise
// the call below to `getParentNameFQDN` will throw an error

*
* The latest Registration Lifecycle for a node referenced in
* `token.domainAsset.domainId`.
* The latest Registration Lifecycle for a node referenced in `domainId`.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* The latest Registration Lifecycle for a node referenced in `domainId`.
* The latest Registration Lifecycle for the node referenced in `domainId`.

*/
export const NameTokenOwnershipTypes = {
/**
* Name Token is owned by NameWrapper account.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* Name Token is owned by NameWrapper account.
* The owner of this Name Token is a NameWrapper account.

/**
* Name Token is owned fully onchain.
*
* This ownership type can only apply to direct subnames of `.eth`
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* This ownership type can only apply to direct subnames of `.eth`
* Currently, this ownership type only applies to direct subnames of `.eth`


return useQuery({
...queryOptions,
refetchInterval: false, // no refetching - assume data is immutable until a full page refresh
Copy link
Member

Choose a reason for hiding this comment

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

@tk-o I was referring to ASSUME_IMMUTABLE_QUERY that we built. I assume that's what we want to use here?

Comment on lines 62 to 65
/**
* Name Tokens for a registered name.
*/
export interface RegisteredNameTokens {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe then it's just a NameTokensResponse?

* Find all Name Tokens for the registered domain by domain ID,
* accurate as of `accurateAsOf`.
*
* @returns {RegisteredNameTokens} if some tokens associated with
Copy link
Member

Choose a reason for hiding this comment

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

When we action the follow-ups on this PR, Suggest we change all this "NameTokensNotIndexed" stuff to the more simple idea: "NameTokensUnknown"

/**
* Name Tokens for the requested name.
*/
nameTokens: RegisteredNameToken[];
Copy link
Member

Choose a reason for hiding this comment

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

Appreciate that complexity. We should be able to improve this once we transition the expiresAt field into being a field for each NameToken in the response.

Comment on lines +98 to +99
* - Order of name tokens follows the order of onchain events that were
* indexed when a token was minted, or burned.
Copy link
Member

Choose a reason for hiding this comment

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

@tk-o The more I think about it, the more I think we should just simplify this and say that the order of tokens returned in the response is undefined and not add any sort rules to it. There's no special need or goal for sort rules here.

@tk-o tk-o merged commit f64dad6 into main Dec 10, 2025
10 checks passed
@tk-o tk-o deleted the feat/name-tokens-api-endpoint branch December 10, 2025 19:59
@github-actions github-actions bot mentioned this pull request Dec 10, 2025
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