[Draft] ENSIP-##: Off-chain Name Meta-Resolution

Current draft here: ENSIP-##: Off-chain Name Meta-Resolution


There is no standard way today to retrieve the following information for off-chain names:

  • List of existing subnames
  • List of resolvable text record keys
  • List of resolvable coin types

For on-chain names, clients query the above information from the ENS Subgraph, like:

domain {
    subdomains {
    resolver {

This means that client apps/websites (including the official ENS Manager App) cannot display subnames, or know in advance which text records / coin types a name has set.

Of course, a client could just guess, like just multicall-resolve the most common coin types and text records. But if the goal is to resolve and display all currently set records, then that approach won’t work, especially for arbitrary text records.


A new resolver profile (interface), and/or a standard text record, that resolvers can use to provide this “meta-resolution” information directly to clients:

  • List of existing subnames
  • List of resolvable text record keys
  • List of resolvable coin types
  • Other things? (Future-proofing?)

For example, an interface like this:

interface IMetaResolver {
    // @notice Returns the list of labels of existing subnames for an ENS name
    // @param node A nodehash for an ENS name
    // @return The array of labels for existing subnames
    function subLabels(bytes32 node) external view returns (string[] subLabels);

    // @notice Returns the list of resolvable text record keys for an ENS name
    // @param node A nodehash for an ENS name
    // @return The array of text record keys
    function texts(bytes32 node) external view returns (string[] texts);

    // @notice Returns the list of resolvable coin types for an ENS name
    // @param node A nodehash for an ENS name
    // @return The array of coin types
    function coinTypes(bytes32 node) external view returns (uint256[] coinTypes);

    // @notice Returns the text meta-information associated with a key for an ENS name
    // @param node A nodehash for an ENS name
    // @return The text meta-information
    function metaText(bytes32 node, string key) external view returns (string value);

Clients can then check whether a resolver supports that interface, and if so, call one or more of the above methods to query that data.


  • I want to display all text records for validator.nfty.eth
  • Call texts(nodehash('validator.nfty.eth'))
    • Uses CCIP-read off-chain resolution
    • Returns dm, email, twitter, etc…
  • Multicall the resolver to resolve those text records


  • I want to display all *.cb.id subnames
  • Call subLabels(nodehash('cb.id'))
    • Uses CCIP-read off-chain resolution
    • Returns the list of subnames, like validator, serenaefansubs, etc.

Rationale / Backwards Compatibility:

Another option is to use a particular text record for this, like eth.ens.meta. The value of which would be a JSON Object, conforming to a particular specification (in the draft here).

Currently, I have both the interface and the text record as options in the ENSIP. But this can certainly be simplified, if we think only one or the other would be better. The strongly-typed interface is nicer, but on the other hand a text record would allow even pre-existing resolvers to start using this standard.

The other bit I’m not sure on is the metaText (or additional properties in the JSON Object). The idea is to future-proof the standard, since we don’t know what additional pieces of “meta-resolution” information might be needed in the future. But this could be removed as well if we think it isn’t needed.

Anyway, feedback welcome, feel free to beat it up: ENSIP-##: Off-chain Name Meta-Resolution

Thanks to the Nftychat team for giving me the idea, as I was assisting with their *.nfty.eth off-chain names in a support ticket, and explaining why not all records (like their custom records) are showing up in the ENS Manager App.


Hi @serenae

We are currently working on ENSIP-14 - CCIP metadata by jefflau · Pull Request #116 · ensdomains/docs · GitHub that is compatible to what you suggested. The main difference is to make more use of GraphQL instead of trying to return everything via functions.


:pray: @matoken.eth we’re also working on fully dweb/off-chain based records manager using IPNS+IPFS & IPNS+IPLD (dag-json/dag-cbor) for signed records storage. We already have full ENS records data indexed in as ccip-read compatible json, so it’s easy to add @serenae’s ENSIP-16 meta records in same storage before sealing latest records with IPNS version data.

Our ENSIP-xx docs isn’t up to date with latest resolver/clients codes & we’re still finalizing details for indexer, IPFS/IPLD pinning and IPNS republishers services/APis with full IPFS/Helia client support in-browser and local IPFS node.

Resolver : GitHub - namesys-eth/ccip2-eth-resolver: CCIP2.ETH Resolver Contract
Client : https://ccip2.eth.limo
GitHub - namesys-eth/ccip2-eth-client: CCIP2.ETH : Off-Chain ENS Records Manager

I ?think that should return (string[] keys, string[] values), or as struct {key : value}
eg, avatar : eip155:1:0x<nft>/<id>

I’ve few more suggestions.

a) replace metaText(node, key) function with text(node, "eth.ens.meta") records, and exclude “eth.ens.meta” or any “meta” records’ keys from texts(node) function’s return value.

b) Add extra helper function to support domains with >100~0s of subs, e.g., subLabelsByIndex(bytes32 node, uint _start, uint _num) external view returns (string[] subLabels, uint256 totalSubs), where _start is starting index, _num is total sub labels to return in this request. It’s up to sub domain manager/resolvers to keep track of their sub domains and how they prefer to index/sorting them.

@0xc0de4c0ffee isn’t your specification more about allowing to store records on IPNS than more generic metadata API that covers all offchain resolution scenario?

That’s correct, I’m making sure if our off-chain records manager is compatible with this metadata resolution ENSIP-XX as we’ve full ENS records for *.domain.eth “indexed” in same storage pointer but we’ve NO on-chain events to trigger L2/mainnet indexers.

I see it’s only a list of text record’s keys so we’ve to make more individual requests to read actual values.

@serenae I’ve a related question from NFT 'Showcase' ENS record and record standardization - #33 by 0xc0de4c0ffee
if we should prefix json string stored in text records as “data:application/json,<…json-string>”?

Cool, this makes more sense than my idea then!

So the owner of the ENS name would need to host a GraphQL endpoint (possibly on the same gateway server). And also deploy their own subgraph if they want/need to actually use TheGraph for it (though I don’t see why they would need to).

I see that Resolver still needs to be defined in this spec. Domain is explicitly defined here, but not Resolver.

Also, I see a subdomains field, but it’d be useful to have subdomainCount too like the subgraph has (for pagination purposes).

Yeah it’s probably a good idea to make it a URI, so you can use a direct data: URI if you need to. Theoretically it could be any other scheme too, like a gateway server.

You don’t necessarily have to have on chain event, as long as your solution has a capability to list name of domains/subdomains

They can have their own, but we are trying to come up with a solution where we can just index all existing resolvers on the specific chain

Gotcha, so in that case, the owner of the ENS name would need to somehow periodically post updates to the “canonical ENS Labs” service?

Not quite.

If the name is set on some sort of L1, we can have a canonical graph indexer indexing any resolver events by decoding the eventlog (apparently our l1 subgraph does the same way rather than tracking all resolver contract address). If the name is set on somewhere we don’t operate (or they want to have their own), then they have to set the graphendpoint by themselves

1 Like
  • Would subLabels() be compatible with ExtendedResolver? — I think your example implies yes.

  • subLabelCount()/numberOfSubLabels() ?

  • Should subLabels() be sliced/paginated? subLabels(uint256 offset, uint256 size) where offset must be in-bound and size > 0 and returns less than size labels if end of list. — Looks like @ 0xc0de4c0ffee already mentioned this.

I like the solution that the ENS Labs team was working on a few months ago actually: https://github.com/ensdomains/docs/pull/116

Basically the client is still using a GraphQL endpoint (just like the subgraph), so I think it gels well. But you’re right, there are no notes about pagination. That would probably be recommended for any such implementation.

Also, maybe a subdomainCount should be added to the schema too, just like there is one in the ENS subgraph schema.