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 records must follow this format:
ENS1 dnstxt.ens.eth <context>
Magic prefix identifying ENS data
The resolver address (must resolve to DNSTXTResolver)
Space-separated key=value pairs with the actual data
Supported Record Types
Text Records
Addresses
Content Hash
Public Key
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.
Examples: a[60]=0x1234567890123456789012345678901234567890
a[e0]=0x... # Default EVM address
a[e59144]=0x... # Linea address
a[0]=0x00... # Bitcoin address
a[60] - Ethereum mainnet (coin type 60)
a[e<chain>] - EVM chain by chain ID
a[e0] - Default EVM address (fallback)
Example: c=0xe3010170122029f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f
Use ENSIP-7 content hash encoding (IPFS, Swarm, etc.)
Example: xy=0x1234...5678 # 64 bytes (32-byte x + 32-byte y)
Must be exactly 64 bytes (SECP256k1 public key coordinates).
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 )
}
DNS-encoded name (unused, resolver uses context)
Encoded resolver profile query
The TXT record context string
Supported Profile Queries
Function Selector Returns 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.
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.com → alice.nick.base.eth
Query: bob.nick.com → bob.nick.base.eth
ENS1 dnsalias.ens.eth <newName>
Completely replaces the queried name. Example: notdot.net. IN TXT "ENS1 dnsalias.ens.eth nick.eth"
Resolution:
Query: notdot.net → nick.eth
Constructor
constructor (
IRegistry rootRegistry ,
IGatewayProvider batchGatewayProvider
)
Root ENS v2 registry for resolution
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:
Checks if a name has a V1 resolver
If not, queries DNSSEC oracle for TXT records
Verifies DNSSEC signatures
Parses the TXT record to find resolver and context
Calls the specified resolver with the context
Constructor
constructor (
ENS ensRegistryV1 ,
address dnsTLDResolverV1 ,
IRegistry rootRegistry ,
DNSSEC dnssecOracle ,
IGatewayProvider oracleGatewayProvider ,
IGatewayProvider batchGatewayProvider
)
ENS v1 registry for fallback
DNSSEC oracle for verification
Gateway for DNSSEC queries
Gateway for batch resolver calls
Resolution Process
Check V1 Registry
First checks if a resolver exists in ENS v1
Query DNSSEC Oracle
If no V1 resolver, triggers CCIP-Read to fetch TXT records
Verify Records
Verifies DNSSEC signatures using the oracle
Parse TXT Record
Extracts resolver address and context from TXT record
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
)
Parsed resolver address, or zero if invalid
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
)
Example: Checking Resolution Type
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
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
Unquoted Value
Quoted Value
Escaped Quotes
With Arguments
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
Batch Queries with Multicall
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
Feature DNSTXTResolver DNSAliasResolver DNSTLDResolver Purpose Parse data from TXT Redirect to ENS Orchestrate DNSSEC Stores Data No (reads from context) No (reads from context) No (fetches from DNS) CCIP-Read No Yes Yes DNSSEC Verification No No Yes Typical Use Direct DNS data DNS → ENS bridge Entry point
DNSSEC Oracle Learn about DNSSEC verification
CCIP-Read Understand off-chain data fetching
ENS Resolution General resolution documentation