ENSNode API Querying Best Practices
It may be helpful to refer to the Terminology guide when reading this document.
Stable Name Identification
Section titled “Stable Name Identification”When querying the ENSNode API for specific names or sets of names, it’s crucial to understand that the representation of labels (both known and unknown) should not generally be assumed to be immutable identifiers. Here’s why:
Label Mutability
Section titled “Label Mutability”- ENSNode indexes all onchain events where a subname is created in the ENS Registry. When these events are indexed, the labelhash of the subname is always known, however sometimes the label of the subname is unknown (strictly from indexed onchain data). When this happens ENSNode attempts to lookup the label for the labelhash through an attached ENSRainbow server. If this lookup succeeds, ENSNode will represent the subname using its true label. If this lookup fails, some label to represent the subname is still required. Therefore, ENSNode will represent the “unknown label” using its labelhash in the format
[labelhash]
. - Changes in the set of healable labels maintained by an ENSRainbow instance can modify the resulting indexed state in attached ENSNode instances. For example, if at “time 1” ENSRainbow does not have knowledge to heal label X, but at “time 2” it does (from the perspective of an ENSNode client) a label represented as “unknown” at “time 1” could transition to become known at “time 2”. Each ENSNode instance should ensure it is attached to an ENSRainbow instance that only grows its set of healable labels across time, such that from the perspective of an ENSNode client a “known label” should never transition back to its “unknown” representation. However, if an ENSNode instance is improperly operated, such a situation could occur.
ENS Normalization Standard Changes
Section titled “ENS Normalization Standard Changes”The ENSIP-15: ENS Name Normalization Standard may change across time such that the set of normalizable names grows (thankfully it should never shrink). For example, consider a new Unicode release that standardizes new emoji. The ENS Normalize standard may subsequently change to expand support for those new emoji.
Therefore, always use the node of a name (calculated by the namehash of the name) as the stable identifier when querying the ENSNode API. The node of a name is immutable across time and works for all names, even if they are unknown, unnormalized, or subgraph-unindexable.
Recommended Query Patterns
Section titled “Recommended Query Patterns”There are two distinct patterns for querying names, depending on the source of the name:
Pattern 1: Names from User Input / Offchain Data
Section titled “Pattern 1: Names from User Input / Offchain Data”When querying ENSNode for names that originate from user input (e.g., search fields, user-entered addresses) or offchain data (e.g. traditional data sources), always apply the following procedure within your app:
- Normalize the name according to ENSIP-15.
- Calculate the
node
for the normalized name using thenamehash
function. - Query the
id
field of domains in ENSNode usingnode
calculated in the previous step, rather than the name itself (for backwards compatibility with the ENS Subgraph, the field for thenode
of the name is actually theid
field).
Example:
First, let’s prepare the name for querying by normalizing it and calculating its node:
import { namehash, normalize } from "viem/ens";
// 1. Normalize the user input according to ENSIP-15const userInput = "Vitalik.eth";const normalizedName = normalize(userInput);
// 2. Calculate the node from the normalized nameconst node = namehash(normalizedName);
Now use this node to query the domain id in the ENSNode API:
{ domain( id: "0xee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835" ) { id name labelName labelhash createdAt }}
The query will return the domain information:
{ "data": { "domain": { "createdAt": "1497775154", "id": "0xee6c4522aab0003e8d14cd40a6af439055fd2577951148c14b6cea9a53475835", "labelName": "vitalik", "labelhash": "0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc", "name": "vitalik.eth" } }}
Here’s a complete example showing how to put it all together using a GraphQL client:
import { namehash, normalize } from "viem/ens";import { createClient } from "graphql-request";
async function queryENSName(userInput: string) { // 1. Normalize the user input const normalizedName = normalize(userInput);
// 2. Calculate the node const node = namehash(normalizedName);
// 3. Set up the GraphQL client const client = createClient({ url: "YOUR_ENSNODE_ENDPOINT", });
// 4. Define the query const query = ` query GetDomain($id: ID!) { domain(id: $id) { id name labelName labelhash createdAt } } `;
// 5. Execute the query const result = await client.request(query, { id: node }); return result;}
Pattern 2: Names from Onchain Data
Section titled “Pattern 2: Names from Onchain Data”When querying for name values sourced directly from onchain data (e.g., ENS NFTs, contract events), you must:
- Skip any normalization step - the name value passed to namehash must be exactly as it appears onchain, even if unnnormalized.
- Calculate the node by taking the namehash of the onchain name (without any normalization). Be warned however that unnormalized labels may contain ”.” characters within the label value which can confuse namehash if special precautions are not taken.
- Query the domain id in the ENSNode API using the node of the name
This pattern is crucial when dealing with unnormalized names that exist onchain. For example, if while examining onchain data you see a registration for “EXAMPLE.eth” (note the uppercase unnormalized characters), attempting to normalize this name in the process of querying ENSNode for additional information about it would result in looking up details for a different node in the ENS Registry (in this case the node for “example.eth” rather than “EXAMPLE.eth”).
The query structure in Pattern 2 remains the same as Pattern 1, except the normalization step is skipped to ensure the node that you query data about is the intended node.
Unnormalized Labels
Section titled “Unnormalized Labels”ENSNode’s handling of unnormalized labels is controlled by the SUBGRAPH_COMPAT
configuration option:
SUBGRAPH_COMPAT=true
allows unnormalized labels to be returned as Subgraph Interpreted Labels (required for full ENS Subgraph compatibility)SUBGRAPH_COMPAT=false
(default) encodes unnormalized labels as Interpreted Labels, improving security
For more details, see Subgraph Compatibility.
When SUBGRAPH_COMPAT=true
Section titled “When SUBGRAPH_COMPAT=true”ENSNode may return unnormalized labels as Subgraph Interpreted Labels associated with indexed names. It is important to note that ENSNode clients should never attempt to normalize labels returned by ENSNode. This is because when ENSNode returns an unnormalized label, that label is associated with a specific node that has been indexed. Normalizing an unnormalized label in this context would represent a different node.
There are effectively two distinct query patterns to consider:
- User input / offchain data → ENSNode data: Normalization is appropriate and necessary
- Onchain data → ENSNode data: Normalization should NOT be performed
An ENSNode client is permitted to validate that all labels returned by ENSNode are in normalized form, and to reject any names with unnormalized labels from further processing. However, the key principle is that an ENSNode client should never normalize returned labels, as normalization transforms the label and therefore also the node associated with the name the label is contained within.
When SUBGRAPH_COMPAT=false
(default)
Section titled “When SUBGRAPH_COMPAT=false (default)”By default, ENSNode is configured to use Interpreted Labels instead of Subgraph Interpreted Labels, which helps avoid edge cases related to null bytes, full-stop characters (periods), or exotic unicode characters. When names are returned from any of the ENSNode APIs, including the Subgraph-compatible GraphQL API, names will be Interpreted Names
Calculating the Node for Names that contain Encoded LabelHashes
Section titled “Calculating the Node for Names that contain Encoded LabelHashes”According to ENSIP-1, the namehash algorithm makes no special consideration for Encoded LabelHashes, and therefore interprets Encoded-LabelHash-looking strings as Literal Label values. Due to this behavior, we recommend using an “Encoded-LabelHash-aware” namehash algorithm implementation such as the viem namehash implementation.
Unknown Label Representation
Section titled “Unknown Label Representation”The following is relevant when SUBGRAPH_COMPAT=false
(default) and ENSNode is using Interpreted Labels for handling unknown labels.
When an Unknown or subgraph-unindexable
label is encountered, ENSNode represents it as an Encoded LabelHash in the format [{labelhash}]
, where {labelhash}
is the labelhash of the label in question. This representation creates an interesting edge case that must be handled carefully:
Consider an unnormalized label that literally looks like [24695ee963d29f0f52edfdea1e830d2fcfc9052d5ba70b194bddd0afbbc89765]
. Because this label contains square brackets (subgraph-unindexable
characters), it will be represented as the unknown label:
[80968d00b78a91f47b233eaa213576293d16dadcbbdceb257bca94b08451ba7f]
Therefore, this represents the subgraph-unindexable
label as an Encoded LabelHash, encoding the labelhash of the original unnormalized label (including its square brackets) in square brackets. This demonstrates why square brackets are considered subgraph-unindexable
- they create ambiguity between literal labels and the representation of Encoded LabelHashes.
When ENSNode encounters an subgraph-unindexable
label, it will represent it as an Encoded LabelHash even if the actual label data is available.
For more detailed information about subgraph-unindexable
labels and their handling, please refer to the ENSNode SDK implementation.