Introduction: Mapping Human-Readable Names to Machine Addresses
The Ethereum Name Service (ENS) transforms long, error-prone hexadecimal addresses (e.g., 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B) into human-readable names like alice.eth. While the ENS registry acts as a global database that stores which name belongs to which owner, the actual translation from name to address—and other records—is handled by a separate component: the ENS resolver contract.
If you have ever asked, “How does my wallet know that bob.eth points to 0x1234…?” the answer lies in the resolver contract. In this complete beginner’s guide, we will dissect what a resolver contract is, how it operates, the types of records it can hold, and why understanding it is critical for anyone interacting with ENS domains. By the end, you will be able to trace the full resolution pipeline and even configure your own resolver.
1. The Three-Layer Architecture of ENS
Before diving into resolvers, you must understand the three distinct smart contracts that make up the ENS system:
- ENS Registry – A single contract that stores the owner of each domain, the resolver address for that domain, and the Time-to-Live (TTL) for caching. It is the authoritative source for “who owns what.”
- Resolver Contract – A per-domain (or per-subdomain) contract that holds the actual mapping data, such as the Ethereum address, content hash (for IPFS), text records, and more. The registry points to the resolver.
- Registrar – The contract that governs domain registration rules, renewal, and expiration. The registrar ultimately owns the domain in the registry and assigns ownership to users.
Think of the registry as a phone book’s index (listing whose name is in the book) and the resolver as the specific page that lists the phone number, email, and address for that person. The resolver is where the real resolution happens.
2. What Exactly Is an ENS Resolver Contract?
An ENS resolver contract is a smart contract that implements the EIP-137 and later ENSIP (ENS Improvement Proposal) interfaces. It must provide at least a addr(bytes32 node) function, which returns the Ethereum address associated with a given name hash. However, modern resolvers support far more record types.
Key properties of a resolver contract:
- Permissionless – Anyone can deploy a resolver contract. There is no central approval.
- Upgradable – A domain owner can change the resolver address in the registry at any time, switching to a newer, more feature-rich resolver.
- Standardized Interface – ENS definies a set of function signatures (e.g.,
addr,name,setText) that all resolvers should support. This ensures wallets and dApps can resolve any ENS name without knowing the resolver’s internal logic.
When you set a resolver for your domain, you are effectively telling the registry: “To find all records for my-domain.eth, ask contract address X.” If you never set a resolver, the registry returns address zero, and no records can be resolved.
3. The Resolution Process (Step-by-Step)
Understanding resolution means following the exact calls made by a wallet or dApp. Here is the complete pipeline for resolving alice.eth to an Ethereum address:
- Name Hash – The application first hashes the name using
keccak256in a recursive process (namehash). Foralice.eth, the namehash is a unique 32-byte value. - Lookup in ENS Registry – The application calls
resolver(bytes32 node)on the registry contract, passing the namehash. The registry returns the resolver contract address (or zero if no resolver is set). - Call Resolver – The application then calls
addr(bytes32 node)on the resolver contract, using the same namehash. The resolver returns the Ethereum address. - Return to User – The wallet displays the address, or the dApp uses it for a transaction.
If the resolver does not implement the addr function, the call reverts. That is why every resolver must conform to the minimal interface. Most modern resolvers implement the Public Resolver (ENSIP-10) which supports multiple record types:
- Ethereum address –
addr(bytes32 node) - Contract ABI –
ABI(bytes32 node, uint256 kind) - Content hash –
contenthash(bytes32 node)(used for IPFS, Swarm, etc.) - Text records –
text(bytes32 node, string key)(e.g.,email,url,avatar) - Reverse lookup –
name(bytes32 node)(returns the ENS name for a given address)
A concrete example: if you look up vitalik.eth, the public resolver returns the address 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 via the addr() function. The same resolver will also return an IPFS content hash if one was set.
4. Public Resolver vs. Custom Resolver
The ENS team deploys a canonical Public Resolver contract that anyone can use for free (gas costs only). This resolver supports all common record types and follows the latest ENSIP specifications. For 99% of users, the public resolver is sufficient.
However, advanced users or applications may deploy a custom resolver to:
- Add proprietary records (e.g., a reputation score, a custom token balance).
- Implement automatic updates (e.g., a resolver that changes the address daily based on a price oracle).
- Enforce access controls (e.g., only certain addresses can update records).
- Optimize gas costs for high-frequency lookups (e.g., using a lazy resolution pattern).
For example, a DAO might deploy a custom resolver for its subdomains that automatically resolves to the current multisig signer set. The resolver contract reads from a governance-controlled list, not from static storage.
5. Reverse Resolution and the Reverse Resolver
A complementary concept is reverse resolution: given an Ethereum address, find its primary ENS name. This is handled by a separate reverse resolver contract under the special domain addr.reverse. The ENS registry owns a subdomain for each address (e.g., 0xabcd…addr.reverse). The resolver for that subdomain stores the name that the address owner wants to claim as primary.
When a wallet shows “alice.eth” next to a transaction, it is using reverse resolution:
- Compute the reverse node:
namehash(address + “.addr.reverse”). - Look up the resolver in the ENS registry.
- Call
name(bytes32 node)on that resolver. - If the returned name resolves back to the original address (forward resolution), the wallet displays it as verified.
This creates a trust chain: a verified reverse record ensures the address owner controls the displayed name. Reverse resolution is essential for user-friendly interfaces.
6. Practical Considerations for Beginners
6.1 How to Set a Resolver for Your Domain
When you register a .eth domain via an official registrar (like ENS Domains), the registrar automatically sets the public resolver for you. You can later change it from the ENS app’s “Details” page. Simply select a different resolver address (or deploy your own) and confirm the transaction. After that, any records you set will be stored in that resolver.
6.2 Gas Costs and Resolution
Each resolver lookup costs gas proportional to the number of storage reads. A simple addr() call on the public resolver costs about 20,000–30,000 gas. DApps can cache resolver addresses and records using the TTL from the registry to reduce on-chain queries.
6.3 Security and Trust
The resolver contract is trust-based. If you set a resolver you do not control, the resolver owner can change the address your domain resolves to. Always verify that a resolver’s code is audited and immutable. The ENS public resolver is immutable and trustless—once deployed, no one can alter it.
7. Common Pitfalls and Troubleshooting
- “No resolver set” – If you see this error, the domain owner has not assigned a resolver in the registry. Solution: set a resolver via the ENS manager.
- “Resolver does not support this function” – The resolver you are using lacks the
text()orcontenthash()function. Upgrade to a public resolver that supports ENSIP-10. - “Reverse lookup failed” – The address has not set a reverse record. The user must call
setName()on the reverse resolver. - “Stale TTL” – DApps may cache old resolver data. Clearing your browser cache or waiting for the TTL to expire can fix stale results.
8. Advanced: Custom Resolver Example
Suppose you want a resolver that returns a dynamic address based on the current block number. You would deploy a new contract implementing the ENSResolver interface. Here is a simplified Solidity snippet:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
interface IResolver {
function addr(bytes32 node) external view returns (address);
}
contract DynamicResolver is IResolver {
address public primary;
address public backup;
constructor(address _primary, address _backup) {
primary = _primary;
backup = _backup;
}
function addr(bytes32 node) external view returns (address) {
if (block.number % 100 == 0) {
return backup;
}
return primary;
}
}
Then you point your domain in the ENS registry to this new resolver address. Any resolution will now follow your custom logic.
9. Where to Learn More
To see the resolution process in action, complete with real transaction simulations, watch this ENS video tutorial. It walks through setting up a resolver, verifying records, and debugging common errors.
Once you understand resolvers, managing your domains becomes straightforward. For a practical guide on moving a domain from one wallet to another (which requires updating records in the resolver), you can learn how to transfer ENS domain safely, including the necessary resolver reconfiguration steps.
Conclusion
An ENS resolver contract is the silent workhorse behind every ENS name resolution. It separates the ownership (registry) from the data (records), enabling flexible, upgradable, and feature-rich name systems. Whether you are a beginner setting up your first .eth name or a developer building a custom naming system, understanding resolvers is essential. Remember: the registry tells you who to ask, but the resolver gives you what you need.
Now that you know the fundamental architecture, you can debug resolution failures, choose the right resolver for your use case, and even build your own if needed. Start with the public resolver—it handles 99% of use cases—and only move to custom resolvers when you have a specific, measurable reason. The ENS ecosystem is constantly evolving, but the resolver contract will remain a core primitive for the foreseeable future.