Skip to main content

Overview

The DNS resolver suite enables ENS resolution using DNSSEC-verified DNS records. This allows traditional DNS domain owners to participate in the ENS ecosystem without importing their domains on-chain.
All three resolvers work together: DNSTLDResolver orchestrates the resolution, while DNSTXTResolver and DNSAliasResolver provide the actual data.

DNSTLDResolver

Top-level orchestrator and DNSSEC verifier

DNSTXTResolver

Parse data from TXT records

DNSAliasResolver

Redirect to ENS names

DNSTXTResolver

Resolves ENS records directly from data encoded in DNSSEC TXT records.

TXT Record Format

TXT records must follow this format:
ENS1 dnstxt.ens.eth <context>
ENS1
string
Magic prefix identifying ENS data
dnstxt.ens.eth
string
The resolver address (must resolve to DNSTXTResolver)
<context>
string
Space-separated key=value pairs with the actual data

Supported Record Types

t[key]=value
Examples:
t[age]=18
t[description]="Once upon a time, ..."
t[notice]="\"E N S!\""
Use quotes for values with spaces. Escape quotes inside quoted strings with backslash.

Complete TXT Record Examples

example.com.  IN  TXT  "ENS1 dnstxt.ens.eth a[60]=0x1234567890123456789012345678901234567890 t[com.twitter]=alice t[avatar]=https://example.com/avatar.png"
example.com.  IN  TXT  "ENS1 dnstxt.ens.eth a[60]=0xEthAddr a[0]=0xBitcoinAddr a[e137]=0xPolygonAddr a[e0]=0xDefaultEVMAddr"
example.com.  IN  TXT  "ENS1 dnstxt.ens.eth c=0xe3010170122029f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"
example.com.  IN  TXT  "ENS1 dnstxt.ens.eth t[description]='Alice\\'s ENS profile' t[url]='https://alice.example.com' t[email][email protected]"

Contract Interface

contract DNSTXTResolver is ERC165, IERC7996, IExtendedDNSResolver {
    function resolve(
        bytes calldata /* name */,
        bytes calldata data,
        bytes calldata context
    ) external view returns (bytes memory result)
}
name
bytes
DNS-encoded name (unused, resolver uses context)
data
bytes
Encoded resolver profile query
context
bytes
The TXT record context string

Supported Profile Queries

FunctionSelectorReturns
addr(bytes32)IAddrResolver.addr.selectorEthereum address
addr(bytes32,uint256)IAddressResolver.addr.selectorAddress for coin type
hasAddr(bytes32,uint256)IHasAddressResolver.hasAddr.selectorBoolean
text(bytes32,string)ITextResolver.text.selectorText value
contenthash(bytes32)IContentHashResolver.contenthash.selectorContent hash
pubkey(bytes32)IPubkeyResolver.pubkey.selectorPublic key x,y
multicall(bytes[])IMulticallable.multicall.selectorArray of results

Usage Example

// Context from TXT record
bytes memory context = "a[60]=0x1234567890123456789012345678901234567890 t[avatar]=https://example.com/avatar.png";

// Query for address
bytes memory query = abi.encodeCall(
    IAddrResolver.addr,
    (bytes32(0)) // node doesn't matter, data is in context
);

bytes memory result = dnsResolver.resolve("", query, context);
address ethAddr = abi.decode(result, (address));
// ethAddr == 0x1234567890123456789012345678901234567890

Errors

error UnsupportedResolverProfile(bytes4 selector);
error InvalidHexData(bytes data);
error InvalidDataLength(bytes data, uint256 expected);

DNSAliasResolver

Redirects DNS name resolution to an ENS name, enabling gasless off-chain DNS domains to use existing ENS infrastructure.

TXT Record Format

ENS1 <resolver-address> <context>
Two context formats are supported:
ENS1 dnsalias.ens.eth <oldSuffix> <newSuffix>
Replaces the matching suffix in the queried name.Example:
*.nick.com.  IN  TXT  "ENS1 dnsalias.ens.eth com base.eth"
Resolution:
  • Query: alice.nick.comalice.nick.base.eth
  • Query: bob.nick.combob.nick.base.eth

Constructor

constructor(
    IRegistry rootRegistry,
    IGatewayProvider batchGatewayProvider
)
rootRegistry
IRegistry
Root ENS v2 registry for resolution
batchGatewayProvider
IGatewayProvider
CCIP-Read gateway provider for batch operations

Resolution Function

function resolve(
    bytes calldata name,
    bytes calldata data,
    bytes calldata context
) external view returns (bytes memory)

Rewrite Logic

function rewriteNameWithContext(
    bytes calldata name,
    bytes calldata context
) public pure returns (bytes memory)

Example: Name Rewriting

// Rewrite rule: "com" -> "base.eth"
bytes memory name = dnsEncode("alice.nick.com");
bytes memory context = "com base.eth";

bytes memory newName = aliasResolver.rewriteNameWithContext(name, context);
// newName == dnsEncode("alice.nick.base.eth")
// Replace rule: "notdot.net" -> "nick.eth"
bytes memory name = dnsEncode("notdot.net");
bytes memory context = "nick.eth";

bytes memory newName = aliasResolver.rewriteNameWithContext(name, context);
// newName == dnsEncode("nick.eth")

Complete Flow

Errors

error NoSuffixMatch(bytes name, bytes suffix);

DNSTLDResolver

The top-level orchestrator that verifies DNSSEC records and delegates to appropriate resolvers.

Architecture

The DNSTLDResolver:
  1. Checks if a name has a V1 resolver
  2. If not, queries DNSSEC oracle for TXT records
  3. Verifies DNSSEC signatures
  4. Parses the TXT record to find resolver and context
  5. Calls the specified resolver with the context

Constructor

constructor(
    ENS ensRegistryV1,
    address dnsTLDResolverV1,
    IRegistry rootRegistry,
    DNSSEC dnssecOracle,
    IGatewayProvider oracleGatewayProvider,
    IGatewayProvider batchGatewayProvider
)
ensRegistryV1
ENS
ENS v1 registry for fallback
dnsTLDResolverV1
address
V1 DNS resolver address
rootRegistry
IRegistry
ENS v2 root registry
dnssecOracle
DNSSEC
DNSSEC oracle for verification
oracleGatewayProvider
IGatewayProvider
Gateway for DNSSEC queries
batchGatewayProvider
IGatewayProvider
Gateway for batch resolver calls

Resolution Process

1

Check V1 Registry

First checks if a resolver exists in ENS v1
2

Query DNSSEC Oracle

If no V1 resolver, triggers CCIP-Read to fetch TXT records
3

Verify Records

Verifies DNSSEC signatures using the oracle
4

Parse TXT Record

Extracts resolver address and context from TXT record
5

Call Resolver

Delegates to the specified resolver with context

Main Resolution Function

function resolve(
    bytes calldata name,
    bytes calldata data
) external view returns (bytes memory)
This function uses CCIP-Read (EIP-3668) and will revert with OffchainLookup to trigger off-chain data fetching.

DNSSEC Record Retrieval

function getDNSSECRecords(
    bytes calldata name
) external view returns (bytes[] memory)
Fetches and verifies all DNSSEC TXT records for a name.

Example: Fetching DNS Records

// Using ethers.js with CCIP-Read support
const records = await dnsTLDResolver.getDNSSECRecords(
  dnsEncode('example.com')
);

records.forEach(record => {
  console.log('TXT record:', Buffer.from(record).toString());
});

Parsing TXT Records

function parseDNSSECRecord(
    bytes memory txt
) public view returns (
    address resolver,
    bytes memory context
)
txt
bytes
The TXT record content
resolver
address
Parsed resolver address, or zero if invalid
context
bytes
Context data to pass to resolver

Parsing Examples

// Parse address literal
bytes memory txt = "ENS1 0x1234567890123456789012345678901234567890 context data";
(address resolver, bytes memory context) = dnsTLD.parseDNSSECRecord(txt);
// resolver = 0x1234567890123456789012345678901234567890
// context = "context data"
// Parse ENS name
bytes memory txt = "ENS1 dnstxt.ens.eth a[60]=0x...";
(address resolver, bytes memory context) = dnsTLD.parseDNSSECRecord(txt);
// resolver = address resolved from dnstxt.ens.eth
// context = "a[60]=0x..."

Composite Resolver Interface

function requiresOffchain(bytes calldata name) external view returns (bool)

function getResolver(bytes calldata name) external view returns (address, bool)

function verifierMetadata(bytes calldata name) external view returns (
    address verifier,
    string[] memory gateways
)
bool needsOffchain = dnsTLD.requiresOffchain(dnsEncode("example.com"));

if (needsOffchain) {
    // This name requires DNSSEC lookup
    (address verifier, string[] memory gateways) = 
        dnsTLD.verifierMetadata(dnsEncode("example.com"));
    
    // verifier = DNSSEC oracle address
    // gateways = array of CCIP-Read gateway URLs
}

Errors

error InvalidTXT();

Complete Integration Example

Here’s how all three resolvers work together:

DNS Setup

; Zone file for example.com
example.com.  IN  TXT  "ENS1 dnstxt.ens.eth a[60]=0x1234567890123456789012345678901234567890 t[com.twitter]=alice"

; DNSSEC signatures (managed by DNS provider)
example.com.  IN  RRSIG  TXT ...

Smart Contract Integration

import {DNSTLDResolver} from "./dns/DNSTLDResolver.sol";
import {IAddrResolver} from "@ens/contracts/resolvers/profiles/IAddrResolver.sol";

contract DNSIntegration {
    DNSTLDResolver public dnsResolver;
    
    constructor(DNSTLDResolver _dnsResolver) {
        dnsResolver = _dnsResolver;
    }
    
    // Resolve using CCIP-Read
    function resolveAddress(string memory domain) external view returns (address) {
        bytes memory name = dnsEncode(domain);
        bytes memory query = abi.encodeCall(
            IAddrResolver.addr,
            (bytes32(0)) // node computed by resolver
        );
        
        bytes memory result = dnsResolver.resolve(name, query);
        return abi.decode(result, (address));
    }
}

Frontend Integration

import { ethers } from 'ethers';

// Provider must support CCIP-Read (most modern providers do)
const provider = new ethers.JsonRpcProvider(RPC_URL);

const dnsTLD = new ethers.Contract(
  DNS_TLD_RESOLVER_ADDRESS,
  DNS_TLD_RESOLVER_ABI,
  provider
);

// Resolve example.com
const name = dnsEncode('example.com');
const query = dnsTLD.interface.encodeFunctionData('addr', [ethers.ZeroHash]);

try {
  const result = await dnsTLD.resolve(name, query);
  const address = ethers.AbiCoder.defaultAbiCoder().decode(['address'], result)[0];
  console.log('Resolved address:', address);
} catch (error) {
  if (error.code === 'CALL_EXCEPTION') {
    // CCIP-Read in progress
    console.log('Fetching off-chain data...');
  }
}

DNS Record Parsing

All resolvers use the DNSTXTParserLib library for parsing context data.

Parser Grammar

<records> ::= " "* <rr>* " "*
     <rr> ::= <r> | <r> <rr>
      <r> ::= <pk> | <kv>
     <pk> ::= <u> | <u> "[" <a> "]" <u>
     <kv> ::= <k> "=" <v>
      <k> ::= <u> | <u> "[" <a> "]"
      <v> ::= "'" <q> "'" | <u>
      <q> ::= <all octets except "'" unless preceded by "\">
      <u> ::= <all octets except " ">
      <a> ::= <all octets except "]">

Parsing Examples

DNSTXTParserLib.find("a=x b=y", "a=")
// Returns: "x"

Security Considerations

DNSSEC Trust: The entire system relies on DNSSEC security. Ensure the DNSSEC oracle is properly configured and trusted.
Gateway Trust: CCIP-Read gateways must be trusted to return correct DNSSEC proofs. Use multiple gateways when possible.
Resolver Trust: The TXT record specifies which resolver to use. This resolver must be trusted for the specific domain.
Caching: DNSSEC records can be cached according to their TTL. Implement appropriate caching strategies off-chain.

Gas Optimization

Use the multicall selector in your query to fetch multiple records in one CCIP-Read round trip.
Keep context data minimal. Each byte costs gas when processed.
Set a[e0] as a fallback instead of specifying every EVM chain.

Comparison Table

FeatureDNSTXTResolverDNSAliasResolverDNSTLDResolver
PurposeParse data from TXTRedirect to ENSOrchestrate DNSSEC
Stores DataNo (reads from context)No (reads from context)No (fetches from DNS)
CCIP-ReadNoYesYes
DNSSEC VerificationNoNoYes
Typical UseDirect DNS dataDNS → ENS bridgeEntry point

DNSSEC Oracle

Learn about DNSSEC verification

CCIP-Read

Understand off-chain data fetching

ENS Resolution

General resolution documentation

Build docs developers (and LLMs) love