Universal Resolver
Overview
The Universal Resolver is a smart contract that simplifies the process of resolving ENS names. It's the recommended way to implement ENS resolution in modern libraries.
Adopting the universal resolver will make the transition to ENSv2 seamless.
Implementation Guide
The universal resolver is the only ENS smart contract that should to be hardcoded into an application for resolution. You can find the latest deployment addresses here.
This guide assumes that your application already supports CCIP Read.
Forward Resolution
To resolve one or more records for a name, use the resolve(bytes name, bytes data)
method which returns (bytes data, address resolver)
.
The name
argument is the DNS-encoded version of the name. Make sure to normalize the name first, as well! For example, given the name My.Name.eth
:
- Normalize:
My.Name.eth
->my.name.eth
- DNS Encode:
my.name.eth
->0x026d79046e616d650365746800
The data
argument is a single ABI-encoded call to the resolver for that name, or a multicall encoded via the following interface:
interface IMulticallable {
function multicall(bytes[] calldata data) external view returns (bytes[] memory results);
}
For example, if you want to resolve the ETH address and description text record for nick.eth
, your logic to generate the data
parameter would look something like this:
import { namehash, normalize } from 'viem/ens'
import { encodeFunctionData, parseAbi } from 'viem/utils'
const simpleResolverAbi = parseAbi([
'function addr(bytes32 node) view returns (address)',
'function text(bytes32 node, string key) view returns (string)',
])
const multicallAbi = parseAbi([
'function multicall(bytes[] data) returns (bytes[] results)',
])
const name = normalize('nick.eth')
const node = namehash(name)
const resolverCalls = [
{
abi: simpleResolverAbi,
functionName: 'addr',
args: [node],
},
{
abi: simpleResolverAbi,
functionName: 'text',
args: [node, 'description'],
},
] as const
const data = encodeFunctionData({
abi: multicallAbi,
functionName: 'multicall',
args: [resolverCalls.map((call) => encodeFunctionData(call))],
})
Learn about standard resolver methods for ABI-encoding that should be added to simpleResolverAbi
in production.
The response you'll get back from resolve
will have to be decoded against the multicall ABI, then further decoded against the base resolver ABI. This results in the ETH address and value of the description text record. The rest of the logic would look something like this:
import { createPublicClient, decodeFunctionResult, http, toHex } from 'viem'
import { mainnet } from 'viem/chains'
import { packetToBytes } from 'viem/ens'
// ...adding from the above example
const dnsEncodedName = toHex(packetToBytes(name))
const universalResolverAbi = parseAbi([
'error ResolverNotFound(bytes name)',
'error ResolverNotContract(bytes name, address resolver)',
'error UnsupportedResolverProfile(bytes4 selector)',
'error ResolverError(bytes errorData)',
'error ReverseAddressMismatch(string primary, bytes primaryAddress)',
'error HttpError(uint16 status, string message)',
'function resolve(bytes name, bytes data) view returns (bytes result, address resolver)',
'function reverse(bytes lookupAddress, uint256 coinType) view returns (string primary, address resolver, address reverseResolver)',
])
const client = createPublicClient({
chain: mainnet,
transport: http(),
})
const resolveRes = await client.readContract({
abi: universalResolverAbi,
address: '0xaBd80E8a13596fEeA40Fd26fD6a24c3fe76F05fB',
functionName: 'resolve',
args: [dnsEncodedName, data],
})
const decodedMulticall = decodeFunctionResult({
abi: multicallAbi,
functionName: 'multicall',
data: resolveRes[0],
})
const decodedRes = decodedMulticall.map((res, i) =>
decodeFunctionResult({
abi: simpleResolverAbi,
functionName: resolverCalls[i].functionName,
data: res,
})
)
// decodedRes[0] = "0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5"
// decodedRes[1] = "Lead developer of ENS & Ethereum Foundation alum. Certified rat tickler. he/him."
Reverse Resolution
To reverse-resolve an address to an ENS name (go from address to name), call the reverse (bytes lookupAddress, uint256 coinType)
method which returns (string primary, address resolver, address reverseResolver)
.
The lookupAddress
argument is the Ethereum address of the account you want to fetch the primary name for. The coinType
argument defines the chain you'd like to fetch the reverse record from. See Multichain Addresses for more information about the coinType
argument.
reverse
internally checks that the name forward resolves to the address you're looking up, so your implementation doesn't need to do any additional checks and should be quite straightforward. It might look something like this:
import { createPublicClient, http, parseAbi } from 'viem'
import { mainnet } from 'viem/chains'
const address = '0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5'
const universalResolverAbi = parseAbi([
'error ResolverNotFound(bytes name)',
'error ResolverNotContract(bytes name, address resolver)',
'error UnsupportedResolverProfile(bytes4 selector)',
'error ResolverError(bytes errorData)',
'error ReverseAddressMismatch(string primary, bytes primaryAddress)',
'error HttpError(uint16 status, string message)',
'function resolve(bytes name, bytes data) view returns (bytes result, address resolver)',
'function reverse(bytes lookupAddress, uint256 coinType) view returns (string primary, address resolver, address reverseResolver)',
])
const client = createPublicClient({
chain: mainnet,
transport: http(),
})
const [primaryName] = await client.readContract({
abi: universalResolverAbi,
address: '0xaBd80E8a13596fEeA40Fd26fD6a24c3fe76F05fB',
functionName: 'reverse',
args: [address, 60n],
})
// primaryName = "nick.eth"
Batch Gateways
The Universal Resolver utilizes a batch gateway to perform parallel EIP-3668 (CCIP-Read) requests and mitigate offchain server issues. CCIPBatcher.sol
is the recommended batch gateway client implementation. The protocol and server implementation is defined in ENSIP-21: Batch Gateway Offchain Lookup Protocol (BGOLP).
The latest batch gateways can be queried from the Universal Resolver via batchGateways()
which returns (string[] gateways)
.
-
External Batch Gateway
https://ccip-v3.ens.xyz
is a trustless service operated by ENS Labs which receives batch gateway requests, performs the supplied CCIP-Read requests in parallel, bundles up the responses or failures, and replies to the caller. This service acts like an open proxy. This mechanism functions in ALL clients that support CCIP-Read. -
Local Batch Gateway
x-batch-gateway:true
is a special-purpose URL which notifies ENSIP-21 aware clients that theOffchainLookup
is a BGOLP request and can be handled locally without using the External Batch Gateway service. The client follows the same process but the requests originate locally without an additional network hop to an external service. For unaware clients, the URL is ignored by the Client Lookup Protocol in EIP-3668.