# ENS Documentation
import { Button } from '../components/ui/Button'
## 🪲 Bug Bounty Program
The ENS bug bounty program rewards anyone who finds a bug in covered ENS smart contracts and ENS Labs assets. This page provides a brief overview of the program which is operated by Immunefi and ENS Labs.
[See the full program](https://immunefi.com/bug-bounty/ens)
### Bounties 💸
Reward sizes are guided by the rules below, but are in the end, determined at the sole discretion of the ENS Labs team.
#### Smart Contracts
* **Critical**: up to $250,000 USD
* **High**: up to $150,000 USD
* **Medium**: up to $100,000 USD
#### Websites and Applications
* **Critical**: up to $50,000 USD
* **High**: up to $20,000 USD
* **Medium**: up to $5,000 USD
* **Low**: up to $1,000 USD
The ENS Labs team reserves the right to adjust bounty amounts at any time in the future.
## Building with AI
ENS provides tools and resources for developers building with large language models (LLMs) and AI assistants. Whether you're using AI to help write code, building agentic applications, or integrating ENS into AI-powered products, these resources will help.
### Plain Text Documentation
LLMs work best with plain text content that has fewer formatting tokens. ENS hosts machine-readable versions of this documentation following the emerging [llms.txt standard](https://llmstxt.org/).
| File | Description |
| -------------------------------------------------------- | ------------------------------------------------ |
| [/llms.txt](https://docs.ens.domains/llms.txt) | Concise overview of ENS documentation with links |
| [/llms-full.txt](https://docs.ens.domains/llms-full.txt) | Complete documentation in plain text format |
You can provide these URLs to AI assistants or include them in your RAG (Retrieval-Augmented Generation) pipelines to give your AI tools up-to-date knowledge about ENS.
#### Example Usage
When working with an AI assistant, you can reference these files directly:
```
Please read https://docs.ens.domains/llms.txt to learn about ENS,
then help me integrate ENS name resolution into my application.
```
### Context7 MCP
[Context7](https://context7.com) provides a Model Context Protocol (MCP) server that gives your AI coding assistant access to up-to-date ENS documentation. Once installed, you can simply ask your AI to use Context7 when working on ENS integrations.
#### Installation
Install the Context7 MCP in your preferred AI coding tool:
:::code-group
```bash [Claude Code]
claude mcp add context7 -- npx -y @upstash/context7-mcp
```
```json [Cursor (~/.cursor/mcp.json)]
{
"mcpServers": {
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
}
}
}
```
```json [Windsurf]
{
"mcpServers": {
"context7": {
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"]
}
}
}
```
:::
#### Example Prompts
Once Context7 is connected, you can use prompts like:
```
Add ENS name resolution to this address input field. Use context7.
```
```
Show me how to fetch a user's avatar from their ENS name. Use context7 for ensdomains/docs.
```
```
Help me implement reverse resolution to show ENS names instead of addresses. Use context7.
```
The key is adding "use context7" to your prompt, which tells your AI assistant to fetch the latest ENS documentation before responding.
### AI Chat Assistant
Every page in this documentation includes an AI-powered chat assistant in the bottom right corner. Powered by [Cookbook](https://ai.cookbook.dev/), this assistant can:
* Answer questions about ENS concepts and implementation
* Help you navigate the documentation
* Provide code examples and explanations
* Assist with debugging ENS integrations
Click the chat icon in the bottom right corner of any page to get started.
### Community MCP Servers
The community has built additional MCP servers that may be useful for ENS and greater Ethereum ecosystem development:
* **[ETHID MCP](https://ethidentitykit.com/docs/ai-tools/ethid-mcp)** - Tools for working with ENS and EFP
* **[Ethereum MCP](https://github.com/gskril/ethereum-mcp)** - General-purpose EVM tools including ENS resolution, ABI parsing, and more
* **[ENS MCP by Namespace](https://github.com/thenamespace/ens-mcp)** - Lets AI agents query ENS names, subnames, ownership, profiles, pricing, availability, and history.
These are independently maintained by community members. Check their documentation for installation instructions and available features.
### Tips for AI-Assisted Development
When building ENS integrations with AI assistance:
1. **Use the full docs** - For comprehensive context, use `/llms-full.txt` in your prompts
2. **Specify your stack** - Mention which library you're using ([viem](https://viem.sh), [ethers.js](https://docs.ethers.org/), [ENSjs](https://github.com/ensdomains/ensjs)) for more relevant code examples
3. **Ensure ENSv2 readiness** - Point your AI to the [ENSv2 readiness guide](/web/ensv2-readiness) to make sure your integration is compatible
### Get Help
For human support, join the [ENS Developers Telegram group](https://t.me/+aLmF83si62ZhOGNh).
### See Also
* [Getting Started with ENS](/web) - Introduction to integrating ENS
* [Preparing for ENSv2](/web/ensv2-readiness) - Ensure your app works with ENSv2
* [Tools & Libraries](/web/libraries) - SDKs and libraries for ENS development
## 📝 Changelog
This page contains a list of changes and events that happened to the ENS protocol & ecosystem.
### Dentity Announcement
On August 21st, 2024 the ENS Labs team announced a new integration with Dentity, an independent identity provider that allows users to verify information and share it on their ENS profile.0
This integration leverages a draft ENSIP that allows for W3C Verifiable Credentials to be stored inside ENS profiles.
### ENSv2 Announcement
On March 28th, 2024 the ENS Labs team announced our plans and roadmap for scaling ENS to the entire internet and beyond.
This involves migrating .eth registrations to a brand new system, in addition to improving support for existing L2 solutions.
You can read more [on our blog](https://blog.ens.domains/post/ensv2), [on X](https://twitter.com/ensdomains/status/1795440186513576318), and [the forums](https://discuss.ens.domains/t/technical-feedback-thread-for-ensv2/19233).
## FAQ
### Which wallets and dApps support ENS?
ENS is supported by a wide range of wallets and dApps, some notable ones can be found on the [integrations page](https://ens.domains/).
This page is currently under construction however a link to add yourself will be put here soon.
### Can I hold my name with one address, and point it at the other?
Yes, you can hold your name with one address and point it at another.
Simply visit the [ENS Manager App](https://ens.app/) and update the appropriate address record (by chain) for your name to point to the address you wish.
### Once I own a name, can I create my own subdomains?
Yes. You can create whatever subdomains you wish and assign ownership of them to other people if you desire. You can even set up your own registrar for your domain.
Some resolvers might provide even more advanced features, read more [about Resolvers](/resolvers/quickstart).
### Can I change the address my name points to after I've bought it?
Yes, you can update the addresses and other resources pointed to by your name at any time.
To update your name checkout the [ENS Manager App](https://ens.app/).
### ETH Registration
#### Why are names registered as hashes?
Hashes provide a fixed length identifier that can easily be passed around between contracts with fixed overhead and no issues passing around variable-length strings.
Read more about [labelhash, namehash, and encodings](/resolution/names).
#### What characters are supported?
ENS names are generally encoded using UTS-46.
This means there is partial support for Unicode characters, including emoji.
However technically possible to register any name, names that are not valid UTS-46 will not be resolvable by most resolvers.
Therefore it is generally recommended for apps that implement registration to limit the characters that can be registered to ensure a smooth experience.
To read more about supported characters [name normalization](/resolution/names).
#### What does it cost to register a .eth domain?
Currently, registration costs are set at the following prices:
* 5+ character .eth names: $5 in ETH per year.
* 4 character .eth names: $160 in ETH per year.
* 3 character .eth names: $640 in ETH per year.
3 and 4 character names have higher pricing to reflect the small number of these names available.
To read more about the pricing structure of .eth names [read more about pricing](/registry/eth)
#### How long can I register a name for?
You can register a name for as long as you would like.
There is no maximum registration duration.
#### What happens if I forget to renew my name?
If you forget to renew your name, it will be released back to the public pool of available names.
Luckily the expiration process has a 90 day grace period.
This means that once the name expires the original owner has 90 days to renew the name before it is released.
After the grace period, the name is released for registration by anyone with a temporary premium which decreases over a 21 days period.
The released name continues to resolve your ETH address until the new owner overwrites it.
#### In what way could I lose access to my name?
The .eth registrar is built to ensure once issued, a name cannot be revoked or taken away from its owner.
Potential loss can occur if the owner loses access to their private key, or if the owner forgets to renew their name.
### Root Registry
#### Who owns the ENS rootnode? What powers does it grant them?
The ENS rootnode is currently owned by the ENS DAO. It used to be owned by the ENS Multi-sig, a group of keyholders from different parts of the ecosystem, however as of [EP4.10](/dao/proposals/4.10) the ownership has been transferred to the ENS DAO.
Ownership of the rootnode grants the ability to do the following:
* Control allocation and replacement of TLDs other than .eth - this is required to implement DNSSEC integration.
* Enable and disable controllers for the .eth registrar, which affect registration and renewal policies for .eth names.
* Update the pricing for .eth names.
* Receive and manage registration revenue.
#### Can I register a TLD of my own within ENS?
Yes and No, We consider ENS to be part of the 'global namespace' in co-existence with DNS, and it is our priority to not pollute the namespace.
ENS-specific TLDs are restricted to only '.eth' on Ethereum Mainnet, or .eth and .test on testnets.
By default ENS allows users to [import their DNS name](/learn/dns) through the use of the [DNS Registrar](/registry/dns).
Existing DNS TLDs can [reach out to us](mailto\:info@ens.domains) to take control of their TLD.
### What are the differences between ENS and other naming services such as Namecoin or Handshake?
ENS complements and extends the usefulness of DNS with decentralised, trustworthy name resolution for web3 resources such as blockchain addresses and distributed content, while Namecoin and Handshake are efforts to replace all or part of DNS with a blockchain-based alternative.
### Governance Token
#### Can I recover tokens accidentally sent to the wrong address?
The answer depends on the address the token was sent to. If you accidentally sent the token to the token.ensdao.eth address (0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72) or the wallet.ensdao.eth address (0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7) then the tokens might be recoverable. Contact the [Meta-governance working group](/dao/stewards/) at the [ENS Forum](https://discuss.ens.domains) and explain the situation. Tokens can only be sent back to the address they were sent from, so if it was sent from an exchange, contact your exchange support to make sure the address can receive tokens.
If the tokens were sent to the null address (0x000..) or an address with a typo, then the tokens are unrecoverable and there's nothing that anyone can do. If the tokens were sent to an exchange or a third party, then contact that third party for help.
import { EmbedLink } from '../components/EmbedLink'
## Terminology
This page contains a glossary of terms used in the ENS documentation.
### Name
An ENS identifier such as 'alice.eth'. Names may consist of multiple parts, called labels, separated by dots. This also includes DNS names like `name.xyz`, or subnames like `sub.name.eth`.
### 2LD
Second-level domain.
This refers to a subname/subdomain of a top-level domain.
For example, `name.eth` and `name.com` are both second-level names.
A subname of a 2LD is a third-level domain or 3LD.
### Subname / Subdomain
A child name like `sub.name.eth`, whose parent is `name.eth`. Also referred to as a "subdomain". Every name (except for the root node) has a parent. For example, `name.eth` is a subname of `eth`.
```
sub.name.eth
```
### TLD
Top-level domain. This refers to names like `eth`, `com`, `xyz` which lie at the "top" of the naming hierarchy.
```
.eth .com .xyz
```
### Manager
The account that may edit the records of a name. The Manager may be changed by the Owner.
### Label
An individual component of a name, such as 'alice'.
### Labelhash
The keccak256 hash of an individual label.
### Namehash
The algorithm used to process an ENS name and return a cryptographic hash uniquely identifying that name. Namehash takes a name as input and produces a *node*.
### Node
A cryptographic hash uniquely identifying a name.
### Owner
The owner of a name is the entity referenced in the ENS registry's owner field. An owner may transfer ownership, set a resolver, and create or reassign subdomains.
### Record
A piece of information that an ENS name "resolves" to (points to). The most common record is the ETH Address record, which determines what ETH 0x address an ENS name points to.
### Registration
A registration is a registrar's record of a user's ownership of a name. This is distinct from the owner field in the Registry; registrations are maintained in the registrar contract and additionally store information on expiry date, fees paid, etc.
#### Registrar
A registrar is a contract responsible for allocating subdomains. Registrars can be configured at any level of ENS, and are pointed to by the owner field of the registry.
#### Registry
The core contract of ENS, the registry maintains a mapping from domain name (at any level - x, y.x, z.y.x etc) to owner, resolver, and time-to-live. All lookups start with the Registry.
#### Expiry
The date and time at which an ENS name expires.
The implications of expiration depend on the type of name it is.
When a .eth 2LD expires (and its grace period elapses), then you lose ownership of the name.
When a wrapped subname expires, you may or may not lose ownership, depending on whether the name was emancipated.
#### Grace Period
This is a short window of time after an ENS .eth name expires, in which the owner can still renew and retain the name. Currently this window is 90 days.
#### TTL
Stands for "Time To Live". This is a field in the core registry that can be set alongside the resolver. It can be used as a hint for clients to decide how long to cache resolved data.
### DNS
This is the Domain Name Service used by the internet to resolve addresses and other records from human-readable names. ENS aims to be fully complementary and compatible with DNS, and supports easy importing of DNS names via a special [DNSSEC](#dnssec) registrar.
#### DNSSEC
Stands for Domain Name System Security Extensions. When a particular DNS TLD supports DNSSEC, then the owners of names can cryptographically sign records. This allows ENS to support easy importing of DNS names into the ENS registry, as the owner of the DNS name can prove ownership with those signed records.
### Resolver
A resolver is a contract that maps from name to the resource (e.g., cryptocurrency addresses, content hash, etc). Resolvers are pointed to by the resolver field of the registry.
#### Wildcard Resolver
This refers to a resolver that supports [ENSIP-10](/ensip/10). This scheme allows clients to resolve data for subnames that either don't have a resolver of their own, or subnames that may not even exist onchain at all. For offchain names, this is typically used in conjunction with [CCIP Read](#ccip-read).
### Public Resolver
This is a standard resolver contract implementation written by ENS Labs. It supports all record types and anyone can use it. This is the default resolver used when registering a new name via the official manager app.
### Offchain
This term is typically used with respect to the Ethereum Mainnet blockchain. If data is not posted to the chain via an actual Ethereum Mainnet transaction, then it is "offchain". ENS names can also be offchain. For example names can use a special resolver to resolve records for subnames that don't exist onchain in the Registry. This is also typically done with [CCIP Read](#ccip-read).
#### CCIP Read
The "Cross Chain Interoperability Protocol Read" specification, also known as [EIP-3668](https://eips.ethereum.org/EIPS/eip-3668), authored by Nick Johnson, is a specification that allows for secure and trustless offchain data retrieval.
It allows for an Ethereum call to defer to an [offchain gateway](/resolvers/ccip-read#writing-a-gateway) and then securely verify the resulting data onchain.
With respect to ENS, this is typically used for offchain subnames that don't exist in the core Registry.
### Primary Name
The ENS name that you want a particular ETH account to be associated with. When set, it will be displayed instead of your 0x address on integrating websites/apps. This is also often referred to as the "reverse record".
#### Reverse Node
A node in the Registry that can be claimed for any Ethereum account. The name this node represents is `[addr].addr.reverse`, where `[addr]` is the Ethereum public address (lowercase, without the "0x"). These reverse nodes are typically used to set a [Primary Name](#primary-name) for an account.
#### Reverse Record
Usually, this is referring to the [Primary Name](#primary-name). Technically speaking, a [Reverse Node](#reverse-node) can have multiple records set on it, the same as any node.
### NameWrapper
#### Wrapped Name
The [ENS Name Wrapper](/wrapper/overview) is a contract for ENS that allows you to "wrap" any ENS name into a ERC-1155 NFT. This includes not only .eth 2LDs like `name.eth`, but also DNS names like `name.xyz`, or subnames like `sub.name.eth`.
#### Fuse
The technical term for a specific "permission" bit for a wrapped name. As the name implies, once that bit is flipped on, the fuse is burnt and cannot be unburnt (unless the name expires).
#### Emancipated
A name is considered emancipated if it's parent is unable to replace/delete it. This is typically used to describe trustless subnames.
### Subgraph
An indexed collection of data using TheGraph protocol.
In this documentation portal, "the subgraph" usually refers to the official ENS subgraph maintained by ENS Labs.
This is a useful offchain service that allows clients to query for information about names or accounts.
## Name Wrapper Contract Details
The Name Wrapper contract is deployed on these chains:
* Mainnet: [wrapper.ens.eth](https://etherscan.io/address/0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401#code)
* Sepolia: [wrapper.ens.eth](https://sepolia.etherscan.io/address/0x0635513f179D50A207757E05759CbD106d7dFcE8#code)
### Wrapping and Unwrapping
When wrapping a .eth 2LD, you're effectively transferring the ERC-721 NFT ownership to the Name Wrapper contract, which will take over the [Manager](/terminology#manager) role for the name as well.
You can do this by calling the [wrapETH2LD](https://github.com/ensdomains/ens-contracts/tree/master/contracts/wrapper#wrapeth2ld) method. Or, you can directly transfer the ERC-721 NFT to the Name Wrapper contract. In return, the contract issues you an ERC-1155 NFT.
```solidity
NameWrapper.wrapETH2LD(string label, address wrappedOwner, uint16 ownerControlledFuses, address resolver)
// For example
wrapETH2LD(
"myname", // "myname.eth" but only the label
0x1234..., // The address you want to own the wrapped name
0, // The owner-controlled fuse bits OR'd together, that you want to burn
0x1234... // The address of the resolver you want to use
)
```
When wrapping any other ENS name, you transfer the Manager of the name to the Name Wrapper contract. You can do this by calling the [wrap](https://github.com/ensdomains/ens-contracts/tree/master/contracts/wrapper#wrap) method. In return, the contract issues you an ERC-1155 NFT.
```solidity
NameWrapper.wrap(bytes name, address wrappedOwner, address resolver)
// For example
wrapETH2LD(
0x03737562046e616d650365746800, // The DNS-encoded version of "sub.myname.eth"
0x1234..., // The address you want to own the wrapped name
0x1234... // The address of the resolver you want to use
)
```
As the owner of the wrapped name, you can unwrap at any time by calling either [unwrapETH2LD](https://github.com/ensdomains/ens-contracts/tree/master/contracts/wrapper#unwrapeth2ld) or [unwrap](https://github.com/ensdomains/ens-contracts/tree/master/contracts/wrapper#unwrap). You can do this as long as the permission to unwrap has not been revoked.
```solidity
NameWrapper.unwrapETH2LD(bytes32 labelhash, address registrant, address controller)
// For example
unwrapETH2LD(
0x952f..., // "myname.eth" but only the labelhash: keccak256('myname')
0x1234..., // The address you want to own the unwrapped name
0x1234... // The address you want to be the manager of the unwrapped name
)
NameWrapper.unwrap(bytes32 parentNode, bytes32 labelhash, address controller)
// For example
unwrap(
0x6cbc..., // The namehash of the parent node, e.g. "myname.eth"
0xfa1e..., // The labelhash of the child to unwrap, e.g. keccak256('sub')
0x1234... // The address you want to be the manager of the unwrapped name
)
```
### Burning Fuses / Setting Expiry
If you are wrapping an existing .eth 2LD, then you can pass in the owner-controlled fuses at that time, see the above [Wrapping and Unwrapping](#wrapping-and-unwrapping) section. If you are creating a new subname, and you want to burn fuses at the same time, see the below [Creating Subnames](#creating-subnames) section.
For other existing wrapped names, you can burn fuses with either the `setFuses` or `setChildFuses` methods.
The `setFuses` method is used for a name that you own, but you do not necessarily own the parent of. You have the ability to burn any [Owner-Controlled Fuses](/wrapper/fuses#owner-controlled-fuses) you want. Note that your name must first be [Emancipated](/wrapper/states#emancipated) in order for you to be able to burn any owner-controlled fuses. All .eth 2LDs are automatically emancipated upon wrapping.
When burning owner-controlled fuses, at a minimum you must burn the **`CANNOT_UNWRAP`** fuse (if it has not already been burned).
```solidity
NameWrapper.setFuses(bytes32 node, uint16 ownerControlledFuses)
// For example
setFuses(
0x6cbc..., // The namehash of the node, e.g. "myname.eth"
1 // The owner-controlled fuse bits OR'd together, that you want to burn
)
```
The `setChildFuses` method is used for a subname that you own the parent of. As long as the subname has not yet been [Emancipated](/wrapper/states#emancipated), you can burn whatever [Parent-Controlled Fuses](/wrapper/fuses#parent-controlled-fuses) and [Owner-Controlled Fuses](/wrapper/fuses#owner-controlled-fuses) you want. At the same time, you must set an expiry for those fuses, if one is not already set. Note that your name must first be [Locked](/wrapper/states#locked) in order for you to burn fuses on any subnames.
If you are only burning parent-controlled fuses, then there are no further restrictions. However, if you are burning owner-controlled fuses, then you must at a minimum burn both **`PARENT_CANNOT_CONTROL`** and **`CANNOT_UNWRAP`** on the subname to lock it at the same time.
```solidity
NameWrapper.setChildFuses(bytes32 parentNode, bytes32 labelhash, uint32 fuses, uint64 expiry)
// For example
setChildFuses(
0x6cbc..., // The namehash of the parent node, e.g. "myname.eth"
0xfa1e..., // The labelhash of the child, e.g. keccak256('sub')
65537, // The fuse bits OR'd together, that you want to burn
2021232060 // The expiry for the subname
)
```
### Creating Subnames
This is done very similarly to how unwrapped subnames are created. You call either `setSubnodeOwner` or `setSubnodeRecord` on the wrapper contract. When a name is wrapped, all subnames created will also be wrapped by default.
You can also pass in the fuses and expiry at the same time, so that the subname will be created in the fuse/permission state that you want, without needing to perform an extra transaction.
```solidity
NameWrapper.setSubnodeOwner(bytes32 parentNode, string label, address owner, uint32 fuses, uint64 expiry)
// For example
setSubnodeOwner(
0x6cbc..., // The namehash of the parent node, e.g. "myname.eth"
"sub", // The label of the subname to create
0x1234..., // The address you want to be the owner of the new subname
65536, // The fuse bits OR'd together, that you want to burn
2021232060 // The expiry for the subname
)
NameWrapper.setSubnodeRecord(bytes32 parentNode, string label, address owner, address resolver, uint64 ttl, uint32 fuses, uint64 expiry)
// For example
setSubnodeRecord(
0x6cbc..., // The namehash of the parent node, e.g. "myname.eth"
"sub", // The label of the subname to create
0x1234..., // The address you want to be the owner of the new subname
0x5678..., // The address of the resolver to set for the new subname
0, // The TTL to set for the new subname
65536, // The fuse bits OR'd together, that you want to burn
2021232060 // The expiry for the subname
)
```
### Approved Operators
#### Full-Control Operator Batch Approvals
Your wrapped name is an ERC-1155 NFT that supports the `setApprovalForAll` method. When you approve an address using this method, it will have **full control** over all wrapped ENS names that you own.
This method is typically used by NFT marketplace contracts.
#### Name-Specific Subname Renewal Manager Approvals
The Name Wrapper also supports the ERC-721 `approve` method. This method is used to approve a single "Subname Renewal Manager" for a specific name.
The "Renewal Manager" does not have full control over your wrapped name, it can only set / extend the expiry on subnames.
Further, if you burn the **`CANNOT_APPROVE`** fuse on your name, then the approved renewal manager can no longer be changed. You can use this to "lock in" that contract, so that you can guarantee to all subname owners that renewals/extensions can always be done.
This approved renewal manager will be reset if the wrapped NFT is burned or re-minted, which happens if you unwrap the name, or if an expired name gets re-registered. It will also be reset if the wrapped NFT is transferred, **unless** the **`CANNOT_APPROVE`** fuse is burned.
#### Example - Subname Registrar Contract
You can use these operator approval methods to setup a separate contract that can take certain actions on your behalf. One example is setting up a "subname registrar" to allow users to register/renew subnames.
That subname registrar contract would act on your behalf and allow users to register subnames. To allow this, you would call `setApprovalForAll` to give that contract full control over your name (and thus the ability to create subnames).
Then, to enable "unruggable renewals", you could call `approve` on that same contract (or a separate one specific to renewals if you wish) and burn **`CANNOT_APPROVE`** to lock in subname renewals for that contract.
If you need to later on, you would still be able to revoke with `setApprovalForAll`. So the contract would lose full control over your name (and the ability to create new subnames), but it would still be able to perpetually renew/extend existing subnames.
And you can do all of this **without** needing to send your wrapped NFT to that contract.
## Creating a Subname Registrar
In the [Use Cases](/wrapper/usecases#sell-or-rent-subnames) section, we talked about the ability to stand up your own "registrar" to allow other people to register/claim subnames automatically. Maybe you want to give wrapped subnames out for free, or maybe you want to charge for them. Maybe you want to apply specific rules to the subnames, such as only allowing alphanumeric names. All of this is possible, and this article will break down what you need to do.
It's recommended to first read the [Use Cases](/wrapper/usecases#sell-or-rent-subnames) section to get an overview of the decisions you'll need to make.
### Prerequisites
This guide assumes that your parent name (such as `myname.eth`) is already wrapped. If you're not sure whether your name is wrapped, look at the "More" tab on the Manager app. If the name is unwrapped, it will say so, and it will show you a "Wrap Name" button.
If you want to issue [Emancipated](/wrapper/states#emancipated) subnames, or subnames with [any other fuses](/wrapper/fuses) burned, then your parent name must first be [Locked](/wrapper/states#locked). You can do this on the Permissions tab in the ENS manager app.
:::warning
Locking your name (in other words revoking the permission to unwrap) is an **irreversible** change. After you lock the name, you will no longer be able to unwrap it. This is a security guarantee for the holders of all subnames. It ensures that the owner of the parent name cannot get around the security guarantees of the Name Wrapper.
For development or testing purposes, it's best to do this on a Sepolia testnet name first.
:::
### Creating and Deploying your Registrar Contract
In order to create a new subname, your contract should call either `setSubnodeOwner` or `setSubnodeRecord` on the [NameWrapper contract](/learn/deployments#deployments). Also pass in the fuses and expiry at the same time, as needed.
```solidity
NameWrapper.setSubnodeOwner(bytes32 parentNode, string label, address owner, uint32 fuses, uint64 expiry)
// For example
setSubnodeOwner(
0x6cbc..., // The namehash of the parent node, e.g. "myname.eth"
"sub", // The label of the subname to create
0x1234..., // The address you want to be the owner of the new subname
65536, // The fuse bits OR'd together, that you want to burn
2021232060 // The expiry for the subname
)
NameWrapper.setSubnodeRecord(bytes32 parentNode, string label, address owner, address resolver, uint64 ttl, uint32 fuses, uint64 expiry)
// For example
setSubnodeRecord(
0x6cbc..., // The namehash of the parent node, e.g. "myname.eth"
"sub", // The label of the subname to create
0x1234..., // The address you want to be the owner of the new subname
0x5678..., // The address of the resolver to set for the new subname
0, // The TTL to set for the new subname
65536, // The fuse bits OR'd together, that you want to burn
2021232060 // The expiry for the subname
)
```
Your public-facing registration function would typically take at *least* the parent node (namehash) and subname label as inputs, such as:
```solidity
register(bytes32 parentNode, string calldata label)
```
Then under the hood, your contract will call `setSubnodeRecord` and fill in the rest of the parameters on behalf of the user:
* owner: Typically the caller account, `msg.sender`
* resolver: Typically the default public resolver, `resolver.eth`
* ttl: 0
* fuses: Up to you and your goals. See the [Use Cases](/wrapper/usecases#sell-or-rent-subnames) section for a discussion on this. Typically 65536 for an enamcipated rental subname, or 327680 for an emancipated "forever" name.
* expiry: Up to you and your goals. If you are renting subnames for a particular length of time, this expiry would reflect that. If you are allowing registration of "forever" names, then you can just set the expiry equal to the parent name's current expiry.
Of course, if you want to give the registrant more power/convenience, you could allow some of those parameters to be passed in to your public register function as well.
#### Setting Resolver Records
If you want your subname registrar to set records on a subname in the same registration transaction, then the flow will be slightly different. In that case, perform these steps:
* Call `setSubnodeOwner`, setting the *contract itself* (`address(this)`) as the owner of the subname, temporarily. This first step is needed for the default Public Resolver so that the contract has the authority to set records for the subname.
* Call whatever [resolver methods](/resolvers/interacting) you need to. Perhaps these are records that you want to be pre-set on your subnames (such as an ETH address that the subname points to). Or perhaps these are records that you allow the registrant to pass in, so that they can register their subname and set whatever records they want all in one transaction.
* Call `setSubnodeRecord`, but this time set the owner to the actual intended owner of the subname. This is the point at which you should set the appropriate fuses and expiry you want to, as well.
In addition, you will need to make sure your contract follows the [ERC-1155 Token Receiver rules](https://eips.ethereum.org/EIPS/eip-1155#erc-1155-token-receiver). This means implementing the `onERC1155Received` and `onERC1155BatchReceived` methods, and signaling support for them in your ERC-165 `supportsInterface` method. OpenZeppelin has an easy abstract contract you can include for all this: [ERC1155Holder.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC1155/utils/ERC1155Holder.sol)
#### Taking fees
If you are setting up a "rental" registrar, then your registration function should require a certain amount of ETH to be sent in as well.
Alternatively, you could choose to allow users to spend ERC-20 tokens instead. To accomplish that, you would typically call the ERC-20 method `transferFrom` on the token contract. This also means that the registrant would first need to approve your contract as a spender for that token, meaning they would need to execute a separate approval transaction first (either to approve unlimited spending, or to approve the specific number of tokens needed to register the subname).
#### Reference Implementation
Luckily, you don't need to start from scratch! The ENS Labs devs have created some example contracts you can start from:
[https://github.com/ensdomains/ens-contracts/tree/feature/subdomain-registrar/contracts/subdomainregistrar](https://github.com/ensdomains/ens-contracts/tree/feature/subdomain-registrar/contracts/subdomainregistrar)
These contracts include two different implementations:
##### Forever Subname Registrar
This is a basic FIFS (First in first serve) registrar. The registration can take a fixed fee, or this fee can be set to 0 if you wish for subnames to be free. Names automatically are set to the parent's expiry can the fuse for `CAN_EXTEND_EXPIRY` will be burnt on registration so the user can extend their expiry if the parent also extends theirs. For a better UX, it is recommended that the parent sets their expiration as high as possible to allow their users to not have to think about renewing.
##### Rental Subname Registrar
This is a basic FIFS (First in first serve) registrar. The key difference between this and the ForeverSubdomainRegistrar is that it does not auto-burn the `CAN_EXTEND_EXPIRY` fuse and instead exposes a `renew()` function that allows paid renewal. This registrar also needs to be paired with a rental-based pricing contract. For simplicity, the deployer can deploy this pricing contract and the UI can pass this address through to `setupDomain()` when a new user wants to setup a subname.
### Setting Everything Up
Once you have a parent name ready and a subname registrar contract deployed, then you just need a few extra steps to set everything up:
#### (If needed) Call setupDomain on your contract
This will only apply to you if you have a specific `setupDomain` method or something similar on your contract, such as the [reference implementation](/wrapper/creating-subname-registrar#reference-implementation) contracts do.
Calling this method will "enable" a specific parent name in your subname registrar. It can also allow you to set or update the pricing terms or beneficiary account, if needed.
#### Approve your contract
Call `setApprovalForAll` on the NameWrapper contract, approving your subname registrar contract as an operator for any names you own. This allows you to keep ownership of the parent name, and just delegate subname creation to your contract.
#### (If needed) Approve token spending
If your registrar contract takes ERC-20 tokens as a registration fee, then a potential registrant will need to approve your contract as a spender first.
#### Register a subname
Finally, the registrant will call your public registration method. Upon transaction success, they will own the wrapped name (ERC-1155 NFT) with whatever fuse/expiry guarantees that you setup in your registrar.
If you are allowing "forever" subnames to be registered (meaning that you've burned the `CAN_EXTEND_EXPIRY` fuse on the subnames), then the registrant can extend their own expiry at any time. Note that a subname's expiry can be set up to a maximum of whatever the parent name's expiry is.
And that's it!
import { Card } from '../../components/ui/Card'
## Name Wrapper Expiry
In order to burn any fuses on a name, you must also set an **expiry** on it. This expiry determines how long any burned fuses are active for, and may also determine whether the name itself has expired.
If the name is a .eth 2LD, then the expiry will automatically be set to the same expiry in the .eth Registrar. But for all other names, the parent can choose what expiry to set for a child name.
### Max Expiry for Subnames
By default, the expiry for a name can only be set by the parent, and can only be increased, not decreased. The maximum value for the expiry of a name is the expiry of its parent name.
For example, say a name expires in 5 years. The owner of the name can then set the expiry of its subnames to a maximum of 5 years as well. But the parent could also choose to set the expiry to something less. Let's say the parent sets the expiry of one of its subnames to 2 years.
Then in turn, the owner of the subname can set the expiry of its own subnames up to a maximum of 2 years, but it could also set it to something less, like 1 year.
The parent can set a different expiry for different subnames too, just as it can burn different fuses for different subnames.
### Renewals
When a wrapped .eth second-level name (like `name.eth`) is renewed, that new expiry is automatically set in the Name Wrapper as well as in the .eth Registrar. However, the expiry for any other .eth names (like `sub.name.eth`) will not be automatically extended when the parent expiry is extended.
The parent can extend the expiry for an existing subname at any time, even if the subname has been emancipated.
The parent can also choose to approve a separate contract to allow the expiry for subnames to be extended by the subname owner or other accounts.
That is basically how .eth second-level names work: Since the `eth` node is locked in the registrar contract and has the Name Wrapper (which exposes a renew method) approved as a controller, .eth second-level names can be directly renewed by their owners.
The parent can further lock this approved contract in by burning the **`CANNOT_APPROVE`** fuse.
There is also a special parent-controlled fuse called **`CAN_EXTEND_EXPIRY`**. If the parent burns this fuse on a subname, then the owner of that subname (or any approved controller) can also extend the expiry.
So, if you are running a subname registrar and you want to enable "unruggable renewals", you can use one of the above options (or both).
### Special Cases for .eth 2LDs
For .eth second-level names, the end of the name's grace period (from the .eth Registrar) is used for the expiry inside of the Name Wrapper.
So if the name's expiration date in the Registrar is January 1st, then the expiry in the Name Wrapper will reflect that date *plus* the grace period (currently 90 days, so approximately April 1st, depending on the year).
When the name's expiration date (from the .eth Registrar) has been reached, and the name is now in the grace period, all Name Wrapper operations on the name will be restricted.
The owner will *not* yet lose ownership of the name, but they will also not be able to unwrap or update the name until it has been renewed.
### Expiry Implications
When a name is merely **Wrapped** but not **Emancipated** or **Locked**, parent-controlled fuses can still be burned. This means that the parent can burn a custom fuse for a limited amount of time.
When the expiry (end of grace period for .eth 2LDs) is reached, all fuses will be reset, but the name will otherwise be unaffected.
When a name is **Emancipated** or **Locked**, the expiry has an important additional effect. In this scenario, when the expiry (end of grace period for .eth 2LDs) has been reached, **the name itself will expire**, and the owner **loses ownership** of the name.
## Name Wrapper Fuses
A "fuse" is a permission or perk that can be granted/revoked on a name. As the name implies, once the fuse is "burned", it cannot be unburned.
Fuses will only reset when the **expiry** is reached. In the ENS Manager UI, this is available in the "Permissions" section of the name.
By **wrapped expiry**, we mean that for .eth second-level names (like `name.eth`), this is the end of the 90-day grace period, the time at which the .eth 2LD is truly released. For all other names (such as subnames), there is no grace period, so the expiry is just the expiration date for that specific subname.
For example, by default when you wrap a name, you can transfer that NFT around freely, just as you can with other NFTs. However, if the **`CANNOT_TRANSFER`** fuse is burned, then the NFT becomes non-transferrable. In the ENS Manager UI, you would do this by revoking the "Can send this name" permission.
In order to burn fuses on a name, the parent name must be **Locked** (meaning, you cannot unwrap the name). The reason is, if the parent name was not locked, then the owner of the parent name could simply get around the constraints of the Name Wrapper by unwrapping the name, and replacing/revoking subnames against the core ENS Registry.
There are parent-controlled and owner-controlled fuses:
### Parent-Controlled Fuses
Only the owner of the parent name can burn one of these fuses on a name. These can generally be thought of as "perks" that can be granted to a name, though they can be used in other ways.
| Fuse name | Description |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`PARENT_CANNOT_CONTROL`** | Allows a parent owner to **Emancipate** a child name. After this is burned, the parent will no longer be able to burn any further fuses, and will no longer be able to replace/delete the child name. This fuse must be burned in order for any owner-controlled fuses to be burned on the name. |
| **`IS_DOT_ETH`** | This fuse cannot be burned by users of the Name Wrapper, it is only set internally when a .eth 2LD is wrapped. |
| **`CAN_EXTEND_EXPIRY`** | The owner of the child name will be able to extend their own expiry. Normally, only the parent owner can extend the expiry of a child name. See the [Expiry](/wrapper/expiry) section for more information. |
| **Custom Fuses** | There are 13 other parent-controlled fuses that are not reserved, and can be used in any custom way you want! |
### Owner-Controlled Fuses
Either the owner of the name or the owner of the parent name can burn one of these fuses. These can generally be thought of as "permissions" that can be revoked on a name, though they can be used in other ways.
| Fuse name | Description |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`CANNOT_UNWRAP`** | The name will now be **Locked**, and can no longer be unwrapped. This fuse must be burned in order for any other owner-controlled fuses to be burned on the name. |
| **`CANNOT_BURN_FUSES`** | No further fuses can be burned on the name. |
| **`CANNOT_TRANSFER`** | The name (wrapped NFT) can no longer be transferred. |
| **`CANNOT_SET_RESOLVER`** | The resolver contract for the name can no longer be updated. |
| **`CANNOT_SET_TTL`** | The TTL for the name can no longer be updated. |
| **`CANNOT_CREATE_SUBDOMAIN`** | New subdomains can no longer be created. |
| **`CANNOT_APPROVE`** | The approved "subname renewal manager" for the name can no longer be updated. See the [Approved Operators](#approved-operators) section for more information. |
| **Custom Fuses** | There are 9 other owner-controlled fuses that are not reserved, and can be used in any custom way you want! |
### The Emancipated and Locked States
This is also covered in the [Wrapped States](/wrapper/states) section, but here is a quick recap:
All .eth second-level names (like `name.eth`) are automatically placed into the Emancipated state when wrapped.
**Emancipated** means that the parent no longer has control over the child name. It can no longer burn any fuses or replace the subname, up until the expiry.
A name is Emancipated when the parent burns the **`PARENT_CANNOT_CONTROL`** (PCC) fuse. The parent must first be in the Locked state to be able to do this.
**Locked** means that the name cannot be unwrapped. This provides assurance to subnames that the parent owner cannot unwrap and then, for example, start replacing subnames directly against the registry.
An Emancipated name is Locked when the **`CANNOT_UNWRAP`** (CU) fuse is burned.
Think of the special PCC / CU fuses recursively:
* To burn owner-controlled or subname fuses, CU must be burned.
* To burn CU, PCC must be burned.
* Only the parent can burn PCC on the child name, and only if CU is first burned on the parent.
* Only the grandparent can burn PCC on the parent name, and only if CU is first burned on the grandparent.
* And so on...
Follow that chain up until you hit a .eth second-level name like `name.eth`, since .eth second-level names will have PCC automatically burned when wrapping. The parent `eth` node is already in the Locked state.
A parent name can burn all the fuses it needs to on a child name in one transaction. This can be done when the subname is created, or on an existing subname that has not yet been Emancipated.
### DNS Domains and Fuses
Currently, only .eth names support fuses, because only the `eth` node is onchain native and completely locked beyond anyone's control.
Technically speaking, the owner of a DNS TLD has the ability to burn fuses on that TLD in the Name Wrapper, and set it to the "Locked" state. And then from there, all subnames under that DNS TLD *will* be able to use fuses.
The DNS TLD owner would need to:
* Request the Controller of that TLD from the ENS DAO
* Wrap the TLD node in the Name Wrapper
* Burn the **`PARENT_CANNOT_CONTROL`** and **`CANNOT_UNWRAP`** fuses on the wrapped TLD to lock it
However, this still does not have all the immutable guarantees that .eth names do. This is because for DNS names, the "source of truth" always lies not in the Ethereum network, but in the DNS network, and the DNS root zone governed by ICANN stakeholders.
So even if the DNS TLD owner "Locks" that TLD in the ENS Name Wrapper, if that TLD were to ever change ownership on the DNS side, then (per the [ENS DAO Constitution](https://docs.ens.domains/v/governance/ens-dao-constitution#iv.-ens-integrates-with-the-global-namespace)) the new owner would be able to override control of that TLD on the ENS side, unwrap it, and replace/revoke all 2LDs. This is just something to keep in mind for wrapped DNS domains.
Even if wrapped DNS domains do not support fuses, you can still use them as ERC-1155 NFTs. They will still have their own NFT metadata and show up in your wallet, with whatever avatar you have set, etc. They just won't have all the extra functionality that comes with the fuse/permission system.
import { Card } from '../../components/ui/Card'
## Name Wrapper Overview
:::note
Are you looking for user-facing guides on how to interact with the Name Wrapper in the ENS Manager App? If so, see here instead: [Name Wrapper Guides](https://support.ens.domains/en/collections/4194784-name-wrapper-guides)
:::
The **Name Wrapper** is a contract for ENS that allows you to "wrap" any ENS name into a ERC-1155 NFT.
### Without the Name Wrapper
Before the Name Wrapper, only .eth 2LDs (second-level domains, like `ens.eth`) had ERC-721 NFTs associated with them, unless the owner created a separate custom contract.
### With the Name Wrapper
**Parent-Controlled Fuses:**
* [Fuses](/wrapper/fuses) that only the parent owner can burn
* "Perks" that can be given to the owner of a name
**Example:** By burning `CAN_EXTEND_EXPIRY`, you allow the owner to
extend/renew their own subname
**Owner-Controlled Fuses:**
* [Fuses](/wrapper/fuses) that either the owner or parent owner can burn
* "Permissions" that can be revoked on a name
**Example:** By burning `CANNOT_TRANSFER`, the wrapped NFT can no longer be
transferred or sold.
**Subname Fuses:**
* The parent owner has the power to burn fuses when creating subnames
* Decides what perks, permissions, or guarantees to give to subname owners
With this new contract, you can wrap:
* Any .eth name or subname (e.g. `name.eth`, `sub.name.eth`)
* Any DNS name or subname (e.g. `name.com`, `sub.name.com`)
Unwrapped .eth 2LDs have the concept of a separate **Owner** and **Manager**.
This changes after you wrap the name, because there is only a single account that serves as both the **Owner** and **Manager** for the wrapped name.
## Wrapped States
```mermaid
graph LR;
unregistered((Unregistered));
unwrapped(Unwrapped);
wrapped(Wrapped);
emancipated(Emancipated);
locked(Locked);
unregistered--->|register|unwrapped;
unwrapped--->|wrap|wrapped;
wrapped--->|unwrap|unwrapped;
wrapped--->|protect|emancipated;
emancipated--->|lock|locked;
emancipated--->|unwrap|unwrapped;
emancipated--->|expire|unregistered;
locked-->|expire|unregistered;
```
Taking the Name Wrapper into account, an ENS name can be in one of these possible states:
#### Unregistered
The name has not even been registered/created yet, or it has expired.
#### Unwrapped
The name exists and has not expired (in the case of .eth second-level names). The Name Wrapper contract does not have ownership over the name. You own the name in the registry and/or .eth registrar.
#### Wrapped
The Name Wrapper contract has ownership of the name (in the registry/registrar). You are issued an ERC-1155 NFT in return, which proves that you are the actual owner.
You can unwrap the name at any time, which burns the ERC-1155 NFT, and returns ownership in the registry/registrar back to you.
If your name is a subname like `sub.name.eth`, then the owner of `name.eth` can technically replace the subname and transfer it to a different owner.
In addition, the parent owner can burn parent-controlled fuses on your name.
#### Emancipated
The owner of the parent name is no longer able to replace this name, or burn any additional fuses on it. All .eth second-level names (like `name.eth`) are automatically put into the Emancipated state when they are wrapped.
The name can still be unwrapped and rewrapped by the owner.
#### Locked
The name can no longer be unwrapped. The owner can now burn owner-controlled fuses on the name. Fuses for subnames of this name can now be burned as well.
## Name Wrapper Use-Cases
### Lock the resolved records for a name
By default, newly registered names will use the Public Resolver, which just allows the current manager/controller of the name to update any records.
However, in some cases perhaps you want to make sure that a name resolves to specific records and **never** changes. You can accomplish this with the **`CANNOT_SET_RESOLVER`** fuse.
Say you own `mycoolcontract.eth` representing a smart contract. You can use ENS subnames to refer to specific versions of that contract, like `1.mycoolcontract.eth`. And perhaps you want those versioned subnames to always point to:
* The ETH address of that immutable contract
* The ABI for that contract
* The contenthash for some versioned documentation page
* etc.
One way to do this is just to make sure the name is **Locked**, all the records are set correctly, and then transfer the owner to some burn address so it can never be updated again.
But of course this isn't ideal, because maybe there are some records that you *do* want to update in the future. Or maybe you still want to keep ownership of that subname for other reasons.
Instead of essentially burning the name, you could create a custom resolver that locks in certain records forever. Then:
1. Set the resolver of that name to your custom contract
2. Set the records however you want and lock them into the resolver
3. Burn these fuses on the name:
* `PARENT_CANNOT_CONTROL | CANNOT_UNWRAP | CANNOT_SET_RESOLVER`
Now you can still keep ownership and even some limited management power over the name, while still guaranteeing that the ETH address, ABI, and whatever other records are completely immutable, as long as the expiry is set appropriately.
### Issue subdomains as tickets to an event
Maybe you have `mycoolevent.eth` and you want to issue tickets like `1.ticket.2023.mycoolevent.eth`.
If you want, you can choose to not Emancipate those subnames, but still burn some custom parent-controlled fuses. Those fuses might:
* Indicate what "tier" their event ticket is
* Maybe they can upgrade their ticket to a higher tier, which would burn some additional fuses
* Allow them access to the express line or some VIP room
* Maybe even automatically via some smart door
When you burn those fuses, perhaps you also set the expiry to the day after the event ends.
Or, maybe you want your attendees to be able to keep their subnames as a souvenir or proof-of-attendance!
If so, then instead of letting the names expire at the end of the event, you could extend the expiry and burn some additional fuses to allow the attendees to keep them forever! In that case you might want to burn these fuses:
* `CAN_EXTEND_EXPIRY | PARENT_CANNOT_CONTROL`
If you want those tickets to be non-transferrable (soulbound to the address that attended), then burn these fuses:
* `CAN_EXTEND_EXPIRY | PARENT_CANNOT_CONTROL | CANNOT_UNWRAP | CANNOT_TRANSFER`
### Sell or rent subnames
#### I want to sell / rent out subnames!
Say you own the wrapped name `verypopularname.eth`. Obviously you can just manually create wrapped subnames like `my.verypopularname.eth` and then sell them on an NFT marketplace. But that sure doesn't scale well.
To accomplish this, you will want to create a **subname registrar**. This is a contract that will handle all the registration / renewal for you, and then users will be able to interact with that contract in order to register their own subnames.
In fact, this is exactly how .eth 2LDs are registered. The owner of the `eth` TLD (the NFT contract) delegates registration / renewal to the ETHRegistrarController contract. It is acting as a subname registrar for the name `eth`.
Your contract would expose a `register` method that anyone can call. Under the hood it will use the [setSubnodeOwner](https://github.com/ensdomains/ens-contracts/tree/master/contracts/wrapper#setsubnodeowner) or [setSubnodeRecord](https://github.com/ensdomains/ens-contracts/tree/master/contracts/wrapper#setsubnoderecord) methods to create subnames, passing in the **fuses** and **expiry** you want to set.
#### What fuses should I burn???
First, note that if you want to burn any fuses on subnames, then your name must be **Locked** (meaning **`CANNOT_UNWRAP`** is burned).
Assuming that you want your subnames to be "unruggable", such that you cannot replace / revoke them, then you will want to burn **`PARENT_CANNOT_CONTROL`** on the subnames. This will place them in the **Emancipated** state upon registration.
If you want to sell "forever" subnames, where users register once and can then keep them for as long as they wish, then you can consider burning the **`CAN_EXTEND_EXPIRY`** fuse.
This will allow the subname owner to extend their own expiry whenever they want. The max expiry is the expiry of the parent name, but the .eth Registrar allows *anyone* to renew/extend a .eth 2LD as well.
If you just want to **rent** subnames, then do not burn **`CAN_EXTEND_EXPIRY`**. Instead, you could include a `renew` method on your contract that users can call for another fee.
If you want to enable "unruggable renewals" for your registrar, to guarantee that users will always be able to renew, then you can call `approve` on the Name Wrapper and approve your registrar contract as the "subname renewal manager" for your name.
Then, burn the **`CANNOT_APPROVE`** fuse on your name, to guarantee that you can never revoke that contract for subname renewals. See [Approved Operators](/wrapper/contracts/#approved-operators) for more info.
If you want to impose other restrictions on your registered subnames, then you can burn the **`CANNOT_UNWRAP`** fuse to Lock the subname, and also burn whatever other fuses you want.
For example, if you want to prevent owners of your subnames (like `my.verypopularname.eth` from creating their own subnames (like `buy.my.verypopularname.eth`), then you would burn **`CANNOT_UNWRAP`** and **`CANNOT_CREATE_SUBDOMAIN`**.
To recap on fuses...
* Sell permanent names:
* `CAN_EXTEND_EXPIRY | PARENT_CANNOT_CONTROL`
* Sell permanent names, but prevent them from creating their own subnames:
* `CAN_EXTEND_EXPIRY | PARENT_CANNOT_CONTROL | CANNOT_UNWRAP | CANNOT_CREATE_SUBDOMAIN`
* Rent out names:
* `PARENT_CANNOT_CONTROL`
* Rent out names, but prevent them from transferring or reselling them:
* `PARENT_CANNOT_CONTROL | CANNOT_UNWRAP | CANNOT_TRANSFER`
And so on, it's up to you. You can also burn whatever custom parent-controlled or owner-controlled fuses you want to.
#### Can I customize my own rules and fees?
Yes! It's your registrar contract, so you can impose whatever rules and fees you want.
For example, the .eth Registrar imposes a 3-character minimum on all names, as well as a [custom fee structure](https://support.ens.domains/core/registration/fees) and a [temporary premium auction](https://support.ens.domains/core/registration/temporary-premium) upon expiration.
By default there is no character limit on subnames, but your contract could have its own rules and fee structure or whatever you want. For example, you can:
* Allow or disallow specific addresses from registering / renewing
* Only allow registration based on some custom criteria like holding a specific NFT
* Custom length restrictions like only 3+ characters or \< 100 characters
* Only allow names with characters `[a-z0-9]` and nothing else
* Use a custom fee structure based on:
* The length of the name
* The specific characters that are in the name, like emojis
* A pre-curated list of "good" names like people's first names
* And whatever other rules you want.
#### More information
See this page for a step-by-step guide on creating and setting up your own subname registrar: [Creating a Subname Registrar](/wrapper/creating-subname-registrar)
There is even a set of [reference implementation contracts](/wrapper/creating-subname-registrar#reference-implementation) you can use as a starting base!
### Give subnames out to NFT holders
#### I want to give subnames out to all of my DAO members / NFT holders!
Say you own the wrapped name `mycoolnft.eth`, representing a popular NFT project you created. You want to distribute subnames like `6529.mycoolnft.eth` to all holders.
One option is to just bulk create the subnames and drop the wrapped NFTs into their wallets. This might be good at least as an initial drop, because then the holders don't need to interact with any contract or spend any gas, you're doing that for them!
To create the subnames, you'd use the [setSubnodeOwner](https://github.com/ensdomains/ens-contracts/tree/master/contracts/wrapper#setsubnodeowner) or [setSubnodeRecord](https://github.com/ensdomains/ens-contracts/tree/master/contracts/wrapper#setsubnoderecord) methods.
You must also decide:
#### How much control over the subnames do you want to relinquish?
Do you want to be able to revoke subnames? Or do you want them to be completely outside your control?
One thing to consider is whether you want the **current** holder of your NFT to always be able to claim/reclaim the corresponding ENS subname. If so, then you will **not** want to Emancipate those subnames (in other words, do not burn **`PARENT_CANNOT_CONTROL`**).
If the subname is Emancipated, then the NFT holder could sell/transfer the NFT but keep the subname (up until the expiry).
To make it easy for anyone to claim/reclaim a subname after your initial drop, you can set up a contract for this.
#### Setting up a subname claim contract
The claim method of your contract could:
1. Call `ownerOf` or `balanceOf` on your NFT contract to get or verify the current owner of the NFT
2. Call `ownerOf` or `balanceOf` on the ENS Name Wrapper contract to get or verify the current owner of the wrapped subname
* If both owner addresses are the same, just return, nothing to do
3. Call `setSubnodeOwner` or `setSubnodeRecord` on the ENS Name Wrapper:
* **owner:** The current owner of the NFT
* **fuses:** What fuses you want to burn (if any) on that subname. If you burn any fuses, you must also set an expiry.
* **expiry:** When the subname will expire.
Then, to give that contract access to create subnames on your behalf, you would call `setApprovalForAll` on the Name Wrapper to approve your contract as an operator.
Now, even if the NFT gets sold / transferred, the new owner will be able to claim their `mycoolnft.eth` subname at any time.
In addition, if you expand your NFT collection in the future and there are new owners, then those new owners would be able to claim their subnames as well.
If you are creating a new NFT contract, you could even bake this functionality **directly into the NFT contract** too, instead of needing a separate contract! By doing this, you wouldn't need a separate `claim` method either, your NFT contract would just **automatically transfer the wrapped ENS subname** whenever the NFT itself gets transferred!
#### Giving your subname owners perks
If you decide to not Emancipate the subnames that you issue, you *will* still be able to burn any Parent-Controlled Fuses. There are 13 unreserved parent-controlled fuses that you can use however you wish!
For example, perhaps you want to grant onchain "perks" or "roles" to certain holders. You would call [setChildFuses](https://github.com/ensdomains/ens-contracts/tree/master/contracts/wrapper#setchildfuses) on the Name Wrapper and pass in the fuses you want to burn, and the expiry.
This means that those "perks" or "roles" can also be time-boxed if you want. Maybe a perk expires in 1 week or something, up to you.
There is also the reserved **`CAN_EXTEND_EXPIRY`** parent-controlled fuse. If you burn this, then the subname owner will be able to extend their own expiry whenever they want.
import { EnsProfile } from '../../components/EnsProfile'
import { Card } from '../../components/ui/Card'
## Avatars
Personalization of profiles is what makes identity great.
This page covers the very special **avatar** record that enables users to take their avatar with them across the web.
### Getting the user's Avatar
Avatars are an awesome way for users to express themselves. To get the user's avatar, all you need is their **name**. If you only have their address, see [primary names](/web/reverse#get).
The following code snippets let you get the avatar for a user.
:::code-group
```tsx [Wagmi]
// https://wagmi.sh/react/hooks/useEnsAvatar
import { useEnsAvatar } from 'wagmi'
function App() {
const { data: ensAvatar } = useEnsAvatar({
address: 'nick.eth',
chainId: 1, // (1 = Ethereum Mainnet, 11155111 = Sepolia)
})
return (
)
}
```
```ts [Ethers]
// https://docs.ethers.org/v5/api/providers/provider/#Provider-getAvatar
const ensAvatar = await provider.getAvatar('nick.eth')
```
```ts [Viem]
// https://viem.sh/docs/ens/actions/getEnsAvatar.html
import { normalize } from 'viem/ens'
import { publicClient } from './client'
const ensAvatar = await publicClient.getEnsAvatar({
name: normalize('nick.eth'),
})
```
```py [Web3.py]
# https://web3py.readthedocs.io/en/latest/ens_overview.html#read-text-metadata-for-an-ens-record
from ens.auto import ns
avatar = ns.get_text('alice.eth', 'avatar')
```
:::
#### The Metadata Service
The [metadata service](https://metadata.ens.domains/docs) is run by ENS Labs. It is a free service web service that allows you to retrieve the
avatar of an ENS name via a web request, as opposed to adding extra logic to your application and interacting
with an ethereum node. This is of course centralised and should be used if absolutely necessary.
### What exactly is an Avatar Record?
An avatar record is simply a [text record](/web/records) that has "avatar" as its key and a URI as its value,
with some rules about what URI schemes are supported and how to process them. For more info, see [ENSIP-11](/ensip/12).
### Supported URI schemes
Clients are expected to support a number of URI schemas, which aren't always web URIs, so the final result you see in your application
will vary depending on how the library you are using has decided to handle avatar records.
* `http(s):` - URI Scheme for HTTP(S) URLs. Libraries will most likely return the result directly.
* `ipfs:` - URI scheme for [IPFS hashes](). Libraries may decide to fetch the result from a public gateway for you.
* `data:` - URI Scheme for [data URIs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs).
Libraries will most likely return the result directly.
* `eip155:` - The URI scheme for EIP-155 identifiers for linking to NFTs on Ethereum based chains. A little complicated
to resolve manually, most libraries should resolve this for you and return the underlying resource.
:::note
For EIP-155 NFT Avatars the nft must be owned by the wallet address the ENS
name resolves to. This is done by checking the `ownerOf` method on the NFT
contract.
:::
### Common schemes that aren't officially supported
* `ethereum:` - The URI scheme for Ethereum addresses
* `bzz:` - The URI scheme for Swarm hashes
### File Information
Avatars come in many different shapes and sizes. Not just the above URI schemas, but also in different file formats, sizes, and more.
Although standards exist for some of these, files are **not required** to follow these standards.
Below is some information about the avatars your app might be loading.
| FileProperty: | Info/Recommendation |
| -------------- | ------------------------------------------------------------------------------- |
| File Extension | Mostly `png`, `jpeg`, `jpg`, `webp`, `gif`, `webm`, but could be anything |
| File Size | We recommend having sensible timeouts |
| Aspect Ratio | We recommend `object-fit: cover` or setting a background color |
| Transparency | We recommend setting a background color as some images may contain transparency |
Luckily most browsers and network libraries have default timeouts to start with, we highly recommend that if you are doing any manual avatar downloading or fetching you add a sensible timeout.
import { EnsProfile } from '../../components/EnsProfile'
import { SendTransactionDemo } from '../../components/SendTransaction'
import { Card } from '../../components/ui/Card'
## Design Guidelines \[Guidelines for designing interfaces that use ENS names]
ENS is a tool to simplify the experience for your users by making blockchain addresses human-readable.
Here are a series of guidelines and tools that will help you make good design choices and better implement ENS in your product.
### When to show ENS names
In every instance where a user might otherwise see an Ethereum address, you can instead display an ENS name (with its avatar, if relevant).
This is true for both read and write operations.
An example of read operations where it's appropriate to show an ENS name is the connected wallet status or representing an action from another user like a vote ([Snapshot is a great example](https://snapshot.box/)).
An example of write operations where it's appropriate to show an ENS name is when a user is inputting an address of any kind (token transfer, smart contract interaction, etc.).
Beyond these use cases, remember that the [ENS Public Resolver](/resolvers/public) allows you to link [different kinds of resources](/web/records) to ENS names.
### 1. Replacing Ethereum addresses with ENS Names
:::note
An ENS name should only be shown in place of an Ethereum address if the user has set a reverse record for their address, and if the reverse record matches the forward resolution. [Learn more about primary names](/web/reverse).
:::
#### 1.1 - Displaying ENS names instead of Ethereum addresses
{/* TODO: build a sample UI of ENS names in a list, like votes or messages or transfers */}
When replacing Ethereum addresses with ENS names you should consider these facts and best practices:
* **Design a truncated version of the ENS name:** ENS names can be very long; besides not being character-limited, users can create an infinite number of nested subdomains.
If you do show a truncated version of the name, you should provide a way to view the full name, such as expanding it on hover.
* **Not all ENS names end with .eth**: ENS supports [.eth](/registry/eth) and most DNS TLDs such as [.com, .xyz, and 1200+ others](/dns/tlds).
A correct implementation of ENS treats any dot-separated name as a potential ENS name and will attempt a look-up.
#### 1.2 - Always provide an option to see the Ethereum address associated with the ENS name
If you are showing the ENS name in its entirety or a truncated version, you should:
* **Always provide the user a way to display the full Ethereum address**: Notice how if you type "ens.eth" in the [example above](#send_transaction), the resolved ETH address appears under the name.
This is especially important in high-risk situations, such as when the user is about to send a transaction or interact with a smart contract.
* **Allow the user to copy the full Ethereum address**: Allow the user to copy the full address either through a copy button or by selecting it.
* **Optionally give the user a way to automatically open the Ethereum address in a block explorer** such as Etherscan.
* **Optionally show the** **balance amount of signed-in users.** User research shows that users tend to recognise their own Ethereum address through their balance, as well as the address itself.
This is meant only for the currently "signed in" user: only show their own balance and avoid showing the balance of other users.
### 2. Resolving input fields
{/* */}
Input fields where a user is supposed to insert Ethereum addresses should also accept and resolve ENS names. These inputs indicate that the user wants to interact with another user's Ethereum address or contract.
Follow these guidelines to create the best experience:
* **Wait before resolving the ENS name**: Debounce input fields that accept ENS names to avoid unnecessary network calls. You can also wait for the user to type a minimum of 1 character on both sides of the dot before resolving the name. For example, if the user type "ens.", there is no chance of it being a valid ENS name and therefore no need to resolve it. But after the user types "ens.e", it should be treated as a potential ENS name.
* **Don't overwrite the input field with the Ethereum address:** Show the resolved ENS name near the input field instead.
* **Always display both the ENS name** ***and*** **the Ethereum address together** : Do this after it has successfully been resolved.
### Other guidelines and tips
#### Usernames for accounts that don't have an ENS name
You can offer free ENS names to your users which would not only improve their experience in your application, but also across the Ethereum ecosystem.
See [how to issue subdomains](/web/subdomains).
#### Caching and updating ENS Names
If your application needs to display many ENS Names in the UI, you can consider **caching** (for a short period of time) the ENS Name after it has been resolved or after the user has added the name in an input field.
Your **optimistic UI** can display the names from cache **in non-risky situations**, in which your user for example is simply browsing, but doesn't need to act or make decisions based on the information displayed.
However, **in all risky situations** (eg transferring anything of value or interacting with a smart contract), you should **perform a direct live resolution** and get the most up to date information from the ENS Registry.
Also consider that users can change their information at any time which may not be tracked in the onchain registry, so you should **periodically validate the information you cached**. [Learn more about offchain ENS names](/learn/ccip-read).
#### Notes on displaying Ethereum Addresses (with or without ENS names)
Even when ENS names are not available, [research](https://medium.com/@lyricalpolymath/web3designdecisionframework-e84075816515) [shows](https://medium.com/@lyricalpolymath/web3-design-principles-f21db2f240c1) that there are some good practices to follow when displaying Ethereum addresses in dApps.
* **Always show the initial ' 0x '** to indicate it's an address.
* When displaying the name in shorthand versions, **show the first 5 and last 4 characters of the address**.
This is not a security requirement as vanity addresses can be spoofed relatively simply; this is a good practice because some users check the beginning of the name and others check the end of the name.
Also, four is the highest number of elements that our mind can easily chunk, parse and remember well.
* **Always provide a way to display the full Ethereum address.**
### Front-end tools
[Thorin](https://thorin.ens.domains/) is a react component library for the ENS design system.
It provides a set of components that make it easier to follow the guidelines and best practices described above.
## Preparing for ENSv2 \[Everything you need to know to prepare your application for ENSv2.]
ENSv2 introduces a redesigned architecture and improved multi-chain interoperability. To ensure your application works seamlessly with ENSv2, you'll need to make a few key updates.
The good news? For most applications, preparing for ENSv2 is as simple as updating to the latest version of a [supported library](/web/libraries). At the time of writing, not all libraries have added ENSv2 support yet. Here's the current status:
* **viem:** [>= v2.35.0](https://github.com/wevm/viem/blob/main/src/CHANGELOG.md#2350).
* **ethers.js:** Native support not published yet. [Work in progress on v6.16.0](https://github.com/ethers-io/ethers.js/tree/wip-v6.16.0-ens). In the meantime, an [ENS ethers patch](https://github.com/ensdomains/ethers-patch) is available for both v5 and v6. Follow the instructions in the repository to apply it.
* **web3.js:** Deprecated.
:::info
**Using a supported library? You're done!** Everything is handled automatically.
The sections below are optional reading for those who want to understand the technical details or test their integration manually.
:::
### Universal Resolver
Even though ENSv2 is designed for multi-chain, all resolution still starts on Ethereum Mainnet. There is a [new Universal Resolver](/resolvers/universal) that acts as the canonical entry point. This is an upgradable proxy contract, owned be the ENS DAO, so its address won't change in the future if its implementation is changed.
Your application needs to use this new Universal Resolver in order to be ready for ENSv2. As mentioned above, updating to the latest version of your supported web3 library handles this automatically.
Learn more about the [Universal Resolver here](/resolvers/universal) and about the [resolution process in general here](/resolution).
#### Testing Universal Resolver Support
To test if your integration uses the Universal Resolver, try resolving the address for `ur.integration-tests.eth`. It should return `0x2222222222222222222222222222222222222222`. If it instead returns `0x1111111111111111111111111111111111111111`, you likely need to update your web3 library.
### Offchain and L2 Resolution with CCIP Read
ENSv1 already supports delegating resolution from Ethereum Mainnet to an L2 or completely offchain using [CCIP Read (ERC-3668)](/learn/ccip-read). All the libraries mentioned above implement CCIP Read. However, not all integrations handle it properly.
In a nutshell, CCIP Read defers resolution to a gateway. Think of a gateway as an HTTP API. The response of the gateway can be verified with a read-call to the ENS contracts on Ethereum Mainnet (or Sepolia for testing). This means that your application needs to be able to send HTTP requests as part of the ENS resolution process. As mentioned above, this is already handled by the web3 libraries in the background.
Learn more about [CCIP-Read, Offchain and L2 resolvers here](/resolvers/ccip-read).
#### Testing CCIP Read Support
To test if your integration properly implements CCIP Read, try resolving `test.offchaindemo.eth`. It should return the address `0x779981590E7Ccc0CFAe8040Ce7151324747cDb97`.
### DNS Names and Name Detection
ENS supports importing DNS names into ENS, allowing legacy domain names to work alongside .eth names. It's important that your application correctly also detects DNS names.
#### Common Mistake: Only Matching .eth
Many integrations check if the input ends with `.eth` in order to detect an ENS name:
```js
if (input.endsWith('.eth') {
// ...
}
```
This is **incorrect** because it excludes DNS names imported into ENS (like `ensfairy.xyz`).
#### Correct Pattern: Match All Valid Domains
Instead, your integration should treat any dot-separated string as a potential ENS name. For example, `a.co` should be treated as a potential ENS name.
```js
if (input.includes('.') && input.length > 2) {
// ...
}
```
This pattern correctly matches:
* `.eth` names like `vitalik.eth`
* DNS names like `ensfairy.xyz`
* Subdomains like `ses.fkey.id`
* Emoji domains like `🦇️🔊️🦇️🔊️🦇️🔊️.eth`
Learn more about [DNS integration here](/learn/dns). The full specification of name normalization is defined in [ENSIP-15](/ensip/15).
### Multichain Considerations
Even if your application only operates on an L2 like Base, ENS resolution always starts on Ethereum Mainnet. This means you need to configure a L1 client alongside your L2 chain.
#### Configuring Both L2 and Mainnet
Here's how to set up your application to use Base (or another L2) while ensuring ENS resolution works correctly by including Mainnet:
:::code-group
```ts [Viem]
import { createPublicClient, http, toCoinType } from 'viem'
import { base, mainnet } from 'viem/chains'
// Client for Base transactions
const baseClient = createPublicClient({
chain: base,
transport: http(),
})
// Client for ENS resolution on Mainnet
const mainnetClient = createPublicClient({
chain: mainnet,
transport: http(),
})
// Get the Base address for this ENS name
const baseAddress = await mainnetClient.getEnsAddress({
name: 'test.ses.eth',
coinType: toCoinType(base.id),
})
```
```tsx [Wagmi]
import { toCoinType } from 'viem'
import { createConfig, http, useEnsAddress } from 'wagmi'
import { base, mainnet } from 'wagmi/chains'
export const config = createConfig({
chains: [base, mainnet], // Include both your L2 and Mainnet
transports: {
[base.id]: http(),
[mainnet.id]: http(),
},
})
function MyComponent() {
const { data: baseAddress } = useEnsAddress({
name: 'test.ses.eth',
chainId: mainnet.id, // Always use mainnet for ENS resolution
coinType: toCoinType(base.id), // Always specify the coinType (chain)
})
}
```
```ts [Ethers]
import { ethers } from 'ethers'
// Provider for Base
const baseProvider = ethers.getDefaultProvider('base')
// Provider for ENS resolution on Mainnet
const mainnetProvider = ethers.getDefaultProvider('mainnet')
// Get the Base address for this ENS name
const resolver = await mainnetProvider.getResolver('test.ses.eth')
const baseAddress = await resolver?.getAddress(8453) // Base chain ID
```
:::
#### Chain-Specific Addresses
It is possible to configure a different address per chain for the same name:
* `test.ses.eth` resolves to `0x2B0F09F23193de2Fb66258a10886B9f06903276c` for Ethereum Mainnet, but
* `test.ses.eth` resolves to `0x7d3a48269416507E6d207a9449E7800971823Ffa` for Base.
From an application point of view it is important to be aware and always request the address for the correct chain, even on Ethereum Mainnet. All examples above explicitly set the `coinType` to Base, since they request the Base address for a given name.
:::warning
Omitting the coinType currently resolves the Ethereum Mainnet (or Sepolia on testnet) address. This address is not guaranteed to work on L2s. Always double check with your user when sending funds to such an address.
:::
## Listing a Users Names
In some cases you might want to show off all names that a user owns. Due to the nature of how the ENS Protocol works under the hood, this might be a slightly more difficult task than expected.
Fortunately, tooling has been developed to accommodate for this and to make it easier.
### Why not all names?
Not all ENS names exist onchain ([learn more about wildcard resolution](/ensip/10)), meaning we don't always know which names a user owns/controls.
The notable exception is [second-level](/terminology#first-layer) [.eth names](/registry/eth). Ownership of these names are onchain and indexable through scanning events on the appropriate smart contracts. Note that this does not necessarily mean address and text records associated with the name are onchain ([read more about offchain resolvers](/resolvers/ccip-read)).
### Guidelines
When using one of the methods described below it is important to keep in mind that you should always allow for a user to manually enter a name, as not all names are indexable.
It is generally recommended to allow users to input a name using an [input box](/web/design#2-resolving-input-fields) and to verify it resolves to the correct address upon user-completion.
### The Graph
The [ENS subgraph](/web/subgraph) indexes all events from relevant smart contracts and exposes them via a GraphQL endpoint. Note that addresses in filters must be lowercased.
ENSjs makes it easy to run common queries on the subgraph with strong type safety. Docs can be found [here](https://github.com/ensdomains/ensjs/tree/main/docs/subgraph).
```graphql
{
domains(where: { owner: "0x225f137127d9067788314bc7fcc1f36746a3c3b5" }) {
name
}
wrappedDomains(
where: { owner: "0x225f137127d9067788314bc7fcc1f36746a3c3b5" }
) {
name
}
}
```
import { EmbedLink } from '../../components/EmbedLink'
import { Card } from '../../components/ui/Card'
## Getting Started \[Integrate ENS into your dApp]
This section walks you through how to leverage the ENS open standards to improve the user experience of your app.
{/* TODO: Break the following examples into a component to fetch live data */}
### Quickstart
If you are looking to jumpstart your journey with ENS, or you are looking for a quick reference, visit the [Quickstart](/web/quickstart) page.
### Tools and Libraries
ENS is an integral part of the Ethereum ecosystem.
Fortunately, the open-source community is to the rescue, and almost all of the tools and libraries you use today support ENS.
To learn more check out the [tools & libraries section](/web/libraries).
### Avatars, Addresses & Records
Information about a name is fetched from its resolver. This can be done using pre-built features included in popular [web3 libraries](/web/libraries) (recommended), or by calling a resolver contract directly.
If you're interested in interacting with ENS resolvers, you might find the [Resolver Reference](/resolvers/interfaces) section helpful.
### Subnames
import { ConnectKits } from '../../components/ConnectKits'
import { Libraries } from '../../components/Libraries'
## Tools & Libraries \[Tools to help you interface with the ENS protocol]
### Quickstart Kits
There are a few plug-and-play kits that you can use to jumpstart your project. These kits will include everything you need to have users connect their wallet, have names showing, avatars, and more, right out of the box!
### Libraries
There are many ways to interface with the ENS Ethereum smart contracts, indexers, and metadata services. Whether you're building a dApp, a backend service, or interacting with ENS from your smart contract, there's a library out there to help you get started.
## Multichain \[L2 & Crosschain Resolution]
### ENS L2
The ENS Labs team recently announced our plans and roadmap for scaling ENS to the entire internet and beyond. You can read more [on our blog](https://blog.ens.domains/post/ensv2), [on X](https://twitter.com/ensdomains/status/1795440186513576318), and [the forums](https://discuss.ens.domains/t/technical-feedback-thread-for-ensv2/19233).
The roadmap involves migrating .eth registrations to a new system, in addition to improved support for existing L2 solutions.
You can find out more on the [changelog](/changelog).
### But isn't ENS on mainnet?
Yes, technically. The resolution process always starts on mainnet. There needs to be, one source of truth after all. However, the name
resolution process can branch off to other chains, offchain gateways and much more.
To read a more in-depth explanation of how resolution works, checkout the [section dedicated to the Resolution Process](/resolution/).
### My dapp is on X but I want ENS
The ENS Protocol can be used on/for any chain!
If you are building a non-mainnet dApp and want to use ENS names simply [add a Mainnet RPC to your Wagmi config](/web/libraries) and specify `chainId: 1` in your config like so:
```tsx
import { useAccount, useEnsAvatar, useEnsName } from 'wagmi'
const Name = () => {
const { data: ensName } = useEnsAddress({
name: 'nick.eth',
chainId: 1, // (1 = Ethereum, 11155111 = Sepolia) // [!code hl]
})
return
{ensName || address}
}
```
And voila! You can now resolve ENS names anywhere! 🎉
## Naming Contracts \[Learn how to name your smart contracts with ENS]
While it's commonly known that regular user accounts can have [primary names](/web/reverse), it's less known that smart contracts can also have names.
In order for you to manage the primary name of your smart contract, you need to own the [reverse node](/terminology#reverse-node) for the contract address. There are several ways of doing this, depending on if you are actively developing your contract or if it is already deployed.
:::note
To enable reverse resolution, you must set both the reverse record and the ETH address to the contract’s deployed address.
:::
Skip to [Naming Tools](#naming-tools) for a frontend solution to naming your smart contracts.
### New Contracts
Depending on your use case, there are a few ways to set a smart contract's primary name.
If you want to be able to change the name later, you have two options:
* **(Recommended)** Make the contract [Ownable](https://docs.openzeppelin.com/contracts/5.x/access-control) and set yourself as the owner.
* Take ownership of the reverse node (`{address}.addr.reverse`) for the contract. This only works for contracts on Ethereum Mainnet.
The Ownable method is preferred since it doesn't require any additional code in many cases, and has the best Etherscan support.
For immutable smart contracts (without an owner), you can set the reverse record directly in the constructor at the time of deployment.
Let's look at a few examples.
#### Ownable (recommended)
:::note
An example of this is [ownable.contract.gtest.eth](https://etherscan.io/address/ownable.contract.gtest.eth#code)
:::
If you want to be able to change the name in the future, you can make your smart contract [Ownable](https://docs.openzeppelin.com/contracts/5.x/api/access#Ownable).
```solidity
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract MyContract is Ownable {
constructor(address initialOwner) Ownable(initialOwner) {}
}
```
[Reverse Registrars](/registry/reverse) on all supported chains understand the Ownable interface and will let the `owner()` of a contract set its reverse record without having to add any ENS-specific code.
Once this contract is deployed, call `setNameForAddr()` on a Reverse Registrar from your authorized owner account on the relevant chain. Note that the arguments are slightly different on L1 and L2s.
* The first address argument should be the address of your contract (both L1 and L2s)
* The second address argument should be the owner of your smart contract (only L1)
* The third address argument should be the `defaultResolver()` from the Reverse Registrar (only L1)
* The last argument is the ENS name to set it to (both L1 and L2s)
#### Set a name in the constructor
:::note
An example of this [contract.gtest.eth](https://etherscan.io/address/contract.gtest.eth#code)
:::
If you don't want to be able to change the name in the future, you can call `setName()` on a Reverse Registrar directly from your contract's constructor. Your contract would look something like this:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;
interface IReverseRegistrar {
function setName(string memory name) external returns (bytes32);
}
contract MyContract {
/// @param reverseRegistrar The address of the Reverse Registrar contract on the relevant chain to use.
/// @param name The reverse name to set for this contract's address.
constructor(address reverseRegistrar, string memory name) {
IReverseRegistrar(reverseRegistrar).setName(name);
}
}
```
You can find the Reverse Registrar addresses [here](/registry/reverse#supported-chains).
#### ReverseClaimer.sol (L1 only)
:::note
While this method works perfectly at the ENS protocol level, Etherscan does not index the contract events correctly so it may not appear in their UI.
:::
This is a simple drop-in module that transfers ownership of the reverse node to an address of your choice, which can then update the reverse name at any time.
```solidity
import "@ensdomains/ens-contracts/contracts/registry/ENS.sol";
import "@ensdomains/ens-contracts/contracts/reverseRegistrar/ReverseClaimer.sol";
contract MyContract is ReverseClaimer {
constructor (
ENS ens
) ReverseClaimer(ens, msg.sender) {}
}
```
When you deploy your contract, the deployer account (`msg.sender`) will be given ownership of the reverse node for that contract address. This gives you authorization to call `setName(node, newName)` on the latest public resolver ([resolver.ens.eth](https://etherscan.io/address/resolver.ens.eth)), where `node` is the reverse node for the contract address and `newName` is the name you want to set it to.
To find the reverse node for your contract address, you can use the following viem script:
```ts
import { namehash } from 'viem/ens'
const myContractAddress = '0x...' // replace with your contract address
const node = namehash(
`${myContractAddress.slice(2).toLowerCase()}.addr.reverse`
)
console.log(node)
```
### Existing Contracts
If your contract is already deployed you might still be able to set a name for it.
If your contract supports the [Ownable](https://docs.openzeppelin.com/contracts/5.x/api/access#Ownable) interface from OpenZeppelin, [read the section above](#ownable-recommended).
#### Safe, Multisig & DAO
If your contract is a Safe, Multisig, DAO or otherwise has the ability to execute arbitrary calldata, you can use a [Reverse Registrar](/registry/reverse) contract directly to set a name for it.
You might even be able to use the [ENS Manager App](https://app.ens.domains/) inside of your safe app to set a primary name.
### Naming Tools
:::warning
These are 3rd party tools and not officially supported by ENS Labs.
:::
[Enscribe](https://app.enscribe.xyz/) is a tool designed to simplify the process of naming smart contracts with ENS names. The application enables users to deploy new smart contracts with a primary name directly and easily name existing smart contracts.
Enscribe simplifies what is otherwise a multi-step, error-prone process by offering:
* Atomic contract deployment using `CREATE2`
* Naming `Ownable`, `ERC173` and `ReverseClaimer` contracts as described above
* ENS subname creation, forward resolution and reverse record assignment
* Naming of existing contracts, with an easy way to locate contracts that you've already deployed
Even if you don't own an ENS name, you can still utilize Enscribe's hosted ENS parent, `deployd.eth`, to create subnames like `my-app.deployd.eth` and set them as the primary name for your contract.
To learn more, refer to the [Enscribe Docs](https://www.enscribe.xyz/docs/).
import { AddressRecords } from '../../components/AddressRecords'
import { ConnectKits } from '../../components/ConnectKits'
import { EnsProfile } from '../../components/EnsProfile'
import { TextRecords } from '../../components/TextRecords'
import { Card } from '../../components/ui/Card'
## Quickstart
Hey there 👋, this is the quickstart guide. If you want to learn the process checkout [everything about ENS in dApps](/web/).
If you would rather just clone an example repository checkout these:
### Starter Kits
### Add to your dApp
This quickstart guide assumes you have a basic understanding of React.
#### Installation
```bash [Terminal]
npm install wagmi viem @tanstack/react-query
```
#### Showing the User Profile
The below codesnippet demonstrates how you can create a basic user profile section that shows the users ENS name and avatar.
The snippet leverages the [useAccount](https://wagmi.sh/react/hooks/useAccount), [useEnsName](https://wagmi.sh/react/hooks/useEnsName), and [useEnsAvatar](https://wagmi.sh/react/hooks/useEnsAvatar) hooks from wagmi.
```tsx
import { useAccount, useEnsAvatar, useEnsName } from 'wagmi'
export const EnsProfile = () => {
const { address } = useAccount()
const { data: name } = useEnsName({ address, chainId: 1 })
const { data: avatar } = useEnsAvatar({ name, chainId: 1 })
return (
{name}{address}
)
}
```
:::info
ENS resolution always starts from L1 regardless of the chain the user is connected to. This is why we specify `chainId: 1` for Ethereum Mainnet in the wagmi hooks above.
:::
#### Text Record Lookups
:::code-group
```tsx [TextRecords.tsx]
// [!include ~/components/TextRecords.tsx]
```
```ts [useEnsTexts.ts]
// [!include ~/hooks/useEnsTexts.ts]
```
:::
#### Address Record Lookups
While ENS resolution always starts from Ethereum L1, you can store addresses for other chains in ENS records.
:::code-group
```tsx [AddressRecords.tsx]
// [!include ~/components/AddressRecords.tsx]
```
```ts [useEnsAddresses.ts]
// [!include ~/hooks/useEnsAddresses.ts]
```
:::
import { EmbedLink } from '../../components/EmbedLink'
import { TextRecords } from '../../components/TextRecords'
import { Card } from '../../components/ui/Card'
## Text Records
Text records are key-value pairs that can be used to store any arbitrary data associated with a name.
Think of this as a user's **digital backpack** utilized for the storage of preferences, public details, and more.
The most popular records have been standardised.
One example of a standardised record is the [avatar record](/web/avatars) which is used to store a user's profile picture.
### Getting Records
To fetch the record for a specific name, you can use one of the following methods:
:::code-group
```tsx [Wagmi]
// https://wagmi.sh/react/api/hooks/useEnsText
import { normalize } from 'viem/ens'
import { useEnsText } from 'wagmi'
export const MyProfile: FC<{ name: string }> = ({ name }) => {
const { data } = useEnsText({
name: normalize('nick.eth'),
key: 'com.twitter',
})
return (
Twitter: {data}
)
}
```
```tsx [Ethers]
// https://docs.ethers.org/v5/api/providers/provider/#EnsResolver
const provider = new ethers.providers.JsonRpcProvider()
const resolver = await provider.getResolver('nick.eth')
const twitter = await resolver.getText('com.twitter')
```
```tsx [Viem]
// https://viem.sh/docs/ens/actions/getEnsText.html
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
import { normalize } from 'viem/ens'
const publicClient = createPublicClient({
chain: mainnet,
transport: http(),
})
const ensText = await publicClient.getEnsText({
name: normalize('nick.eth'),
key: 'com.twitter',
})
```
```python [web3.py]
# https://web3py.readthedocs.io/en/latest/ens_overview.html#text-records
from ens.auto import ns
# set text
ns.set_text('alice.eth', 'url', 'https://example.com')
# get text
url = ns.get_text('alice.eth', 'url')
assert url == 'https://example.com'
```
:::
### Types of Records
[ENSIP-5](/ensip/5) and [ENSIP-18](/ensip/18) specify two sets of records that are considered standardized. Below are some of the most commonly ones:
| Name | Usage | Reference | Example |
| ----------- | --------------------------------------- | --------------------- | ------------------------------------------ |
| avatar | [Avatar](/web/avatars) | [ENSIP-5](/ensip/5) | eip155:1/erc1155:0x495f... |
| description | Bio or description of the profile | [ENSIP-5](/ensip/5) | Lead developer of ENS |
| com.twitter | Twitter/X handle | [ENSIP-5](/ensip/5) | nicksdjohnson |
| com.github | GitHub handle | [ENSIP-5](/ensip/5) | arachnid |
| url | Website URL | [ENSIP-5](/ensip/5) | [https://ens.domains](https://ens.domains) |
| header | Image URL to be used as a header/banner | [ENSIP-18](/ensip/18) | ipfs\://QmNtHN7WE... |
#### Custom Records
While standardized records are expected to have the best ecosystem support, it's possible to store any key-value pair you desire. We generally recommend to stick to a pattern, or prefix things with your app or protocol (eg. `com.discord`, or `org.reddit`), as such to avoid collisions.
### Setting Records
Text records are controlled by the resolver associated with a given name. Read more about [interacting with a resolver](/resolvers/interacting).
import { EmbedLink } from '../../components/EmbedLink'
import { EnsProfile } from '../../components/EnsProfile'
import { Card } from '../../components/ui/Card'
## Address Lookup \[Learn how to resolve blockchain addresses from human-readable names with ENS.]
The ENS Protocol aims to make it easy to use Ethereum.
It does this by providing a simple way to use human-readable names instead of long machine-readable addresses.
### Getting the users Ethereum Address
The goal here is to take a name, such as `nick.eth`, and convert it to an address, such as `0x225f137127d9067788314bc7fcc1f36746a3c3B5`.
The simplest thing you can do is start with a name, and resolve it to an address.
We call this a "forward lookup".
Think of places where users can enter names, such as sending transactions, chatting, etc.
Note that all dot-separated strings should be treated as potential ENS names, since ENS supports [many TLDs](/dns/tlds). A common mistake is to only treat strings that end in `.eth` as ENS names.
:::code-group
```tsx [Wagmi]
import { useAccount, useEnsAvatar, useEnsName } from 'wagmi'
export const Name = () => {
const { data: ensName } = useEnsAddress({
address: 'nick.eth', // The name to lookup
chainId: 1, // The chain to start resolution on (Ethereum Mainnet or a testnet)
})
return
{ensName || address}
}
```
```ts [Ethers.js]
const address = await provider.lookupAddress('nick.eth')
```
```ts [Viem]
import { normalize } from 'viem/ens'
import { publicClient } from './client'
const ensAddress = await publicClient.getEnsAddress({
name: normalize('nick.eth'),
})
```
```py [web3.py]
from ens.auto import ns
address = ns.address('alice.eth')
```
```rust [ethers-rs]
let provider = Provider::::try_from("https://mainnet.infura.io/v3/...")?;
let address = provider.lookup_address("nick.eth").await?;
```
```ts [ensjs]
import { createEnsPublicClient } from '@ensdomains/ensjs'
import { http } from 'viem'
import { mainnet } from 'viem/chains'
const client = createEnsPublicClient({
chain: mainnet,
transport: http(),
})
const subgraphRecords = client.getSubgraphRecords({ name: 'ens.eth' })
const records = client.getRecords({
name: 'ens.eth',
records: {
coins: [...(subgraphRecords?.coins || []), 'BTC', 'ETH', 'ETC', 'SOL'],
texts: [
...(subgraphRecords?.texts || []),
'avatar',
'email',
'description',
],
contentHash: true,
abi: true,
},
})
```
```csharp [nethereum]
var ensService = new Nethereum.ENS.ENSService(web3)
var address = await ensService.ResolveAddressAsync('alice.eth')
```
:::
To learn what happens under the hood when you do a forward lookup, read the [resolution](/resolution) section.
### Multichain Addresses
ENS Names aren't just limited to storing Ethereum addresses.
Any blockchain address (BTC, LTC, SOL, etc.) can be queried by [SLIP-0044](https://github.com/satoshilabs/slips/blob/master/slip-0044.md) coin type or a value derived from an EVM Chain ID (specified in [ENSIP-11](/ensip/11)). This includes Ethereum L2 networks such as OP Mainnet and Base.
For EVM Chains besides Ethereum Mainnet, always use its [ENSIP-11](/ensip/11) coin type, irrespective of being included in SLIP-0044 (like Ether Classic).
The standardization of multichain addresses was first introduced in [ENSIP-9](/ensip/9), and also [EIP-2304](https://eips.ethereum.org/EIPS/eip-2304).
:::note
Regardless of the chain you're resolving an address for, ENS resolution always starts from Ethereum L1.
:::
:::code-group
```tsx [Wagmi]
// https://wagmi.sh/react/api/hooks/useEnsAddress
import { toCoinType } from 'viem'
import { useEnsAddress } from 'wagmi'
import { arbitrum, base } from 'wagmi/chains'
const name = 'gregskril.eth'
export const MyAddresses = () => {
// SLIP-0044 Coin Types (see ENSIP-9)
const { data: bitcoinAddr } = useEnsAddress({ name, coinType: 0, chainId: 1 })
const { data: solanaAddr } = useEnsAddress({
name,
coinType: 501,
chainId: 1,
})
// EVM Chain IDs (see ENSIP-11)
const { data: baseAddr } = useEnsAddress({
name,
coinType: toCoinType(base.id),
chainId: 1,
})
const { data: arbitrumAddr } = useEnsAddress({
name,
coinType: toCoinType(arbitrum.id),
chainId: 1,
})
return (
)
}
```
```ts [Viem]
// https://viem.sh/docs/ens/actions/getEnsAddress.html#cointype-optional
const ensName = await publicClient.getEnsAddress({
name: normalize('wagmi-dev.eth'),
coinType: 0, // BTC
})
```
```ts [Ethers.js]
// https://docs.ethers.org/v5/api/providers/provider/#EnsResolver
const resolver = await provider.getResolver('ricmoo.eth')
const btcAddress = await resolver?.getAddress(0)
```
```py [web3.py (Python)]
# https://web3py.readthedocs.io/en/latest/ens_overview.html#multichain-address-resolution
from ens.auto import ns
eth_address = ns.address('alice.eth', coin_type=60)
```
:::
| Network | Coin Type |
| ------------ | ---------- |
| Bitcoin | 0 |
| Litecoin | 2 |
| Dogecoin | 3 |
| Ethereum | 60 |
| Solana | 501 |
| OP Mainnet | 2147483658 |
| Polygon | 2147483785 |
| Base | 2147492101 |
| Arbitrum One | 2147525809 |
... and many many more following [SLIP-0044](https://github.com/satoshilabs/slips/blob/master/slip-0044.md) and [ENSIP-11](/ensip/11)
#### Decoding Address Hashes
ENS resolvers store all addresses in bytes, which may have to be encoded to their respective address formats. To do this, we recommend using the [@ensdomains/address-encoder](https://www.npmjs.com/package/@ensdomains/address-encoder) package.
### Advanced
import { EmbedLink } from '../../components/EmbedLink'
import { EnsProfile } from '../../components/EnsProfile'
import { QandA } from '../../components/QandA'
import { Badge } from '../../components/ui/Badge'
import { Card } from '../../components/ui/Card'
## Primary Names
:::info
Primary names are now supported on both Ethereum Mainnet and popular L2s (Base, OP Mainnet, Arbitrum One, Scroll, and Linea). This enables users to have an end-to-end experience with ENS on their preferred L2!
:::
A "primary name" is the result of a bi-directional relationship between an EVM address and a human-readable ENS name. The two directions are:
1. Name -> Address (forward resolution)
2. Address -> Name (reverse resolution)
The outcome of this relationship makes it safe for applications to display ENS names instead of EVM addresses, leading to a better user experience.
0xb8c...67d5to
While forward resolution is configured in [Resolvers](/resolvers/quickstart), reverse records are typically set via smart contracts called Reverse Registrars which you can [read more about below](#setting-primary-names).
### L2 Primary Names
Before we dive into code examples, let's first understand why things work the way they do.
Prior to August 2025, ENS users had to make a transaction on Ethereum Mainnet (L1) to set a primary name. More specifically, they had to set a reverse record on the [Reverse Registrar](/registry/reverse) contract which was only available on L1.
Since a majority of user activity is now on L2s, we've added the ability to set a reverse record on the following Ethereum Rollups:
* Arbitrum
* Base
* Linea
* OP Mainnet
* Scroll
In addition to these chains, we've also added the ability to set a default reverse record on Ethereum Mainnet (L1) that serves as a fallback when no chain-specific primary name is set. This is the simplest way to set a universal primary name for users who have a wallet that supports all EVM chains.
While this may sound simple in theory, it's easy to get tripped on the details in practice. Let's look at an example.
#### Understanding the Verification Process
The key thing to understand is that the forward address *for a given chain* must match the reverse record on the respective chain's reverse registrar.
Say I own `nick.eth`. The name resolves to `0x1234...5678` because I've set the ETH address for that name. I call `setName("nick.eth")` on the Base reverse registrar, and I expect that my primary name is now `nick.eth` on Base. But that's actually not the case.
ENS names can resolve to [different addresses on different chains](/web/resolution), and since `nick.eth` in the example above has only specified an Ethereum Mainnet address, the verification process will fail. In order to fix this, I need to set the Base address for `nick.eth` which is on L1 in this case. This is done by calling the following function on the resolver for the name.
```solidity
setAddr(
namehash("nick.eth"), // node (see Name Processing)
convertEVMChainIdToCoinType(8453), // coinType (see ENSIP-11)
0x1234...5678 // the address to set
)
```
Now that `nick.eth` resolves to `0x1234...5678` via the Base cointype, and `name(0x1234...5678)` on the Base reverse registrar returns `nick.eth`, my primary name is fully set.
An alternative approach, which would be more efficient in this case, is to set the default EVM address for `nick.eth` on the latest [public resolver](/resolvers/public), and the default reverse record to `nick.eth` on the default reverse registrar. This would allow the name to resolve to the correct address on all chains.
### Getting a Primary Name
:::info
**Important**: After retrieving a name from reverse resolution, you **must** verify it by performing a forward resolution on that name to confirm it still resolves to the original address. This prevents spoofing or misconfiguration. If the addresses don't match, display the original address instead of the name. Most libraries will handle this for you.
:::
Looking up a users L1 primary name is very simple. In most web3 libraries (wagmi, viem, ethers, web3py, etc.), you will find a built-in function to do a lookup by address as shown below. In most cases, the library will handle the verification for you.
Remember that in all cases, ENS resolution always starts from Ethereum Mainnet.
:::code-group
```tsx [Wagmi]
// https://wagmi.sh/react/hooks/useEnsName
import { useEnsName } from 'wagmi'
import { mainnet } from 'wagmi/chains'
export const Name = () => {
const { data: name } = useEnsName({
address: '0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5',
chainId: mainnet.id, // resolution always starts from L1
})
return
Name: {name}
}
```
```ts [Ethers v5]
const address = '0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5';
const name = await provider.lookupAddress(address);
// Always verify the forward resolution
if (name) {
const resolvedAddress = await provider.resolveName(name);
if (resolvedAddress !== address) {
// If verification fails, use the original address
return address;
}
}
```
```ts [Viem]
// https://viem.sh/docs/ens/actions/getEnsName.html
import { publicClient } from './client'
const ensName = await publicClient.getEnsName({
address: '0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5',
})
```
```py [Web3.py]
# https://web3py.readthedocs.io/en/latest/ens_overview.html#get-the-ens-name-for-an-address
from ens.auto import ns
name = ns.name('0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5')
```
:::
As of September 2025, Wagmi and Viem are the only libraries that support L2 Primary Names, and they can be used like this:
```tsx [Wagmi]
// https://wagmi.sh/react/hooks/useEnsName
import { toCoinType } from 'viem'
import { useEnsName } from 'wagmi'
import { base, mainnet } from 'wagmi/chains'
export const Name = () => {
const { data: name } = useEnsName({
address: '0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5',
chainId: mainnet.id, // resolution always starts from L1
coinType: toCoinType(base.id), // [!code ++]
})
return
Name: {name}
}
```
:::info
The official implementation of L2 Primary Names has a propogation period of up to 6 hours. If you're participating in a hackathon and need to resolve a name immediately, you can use [this implementation](https://github.com/ensdomains/frontend-template/blob/main/src/hooks/useEnsNameOptimistic.tsx) instead (not recommended for production use).
:::
🎉 And that's it! Now you can turn all your pages from this, to this:
0xb8c2...67d5sent 0.1 ETH to0xd8dA....6045
turns into
nick.ethsent 0.1 ETH tovitalik.eth
:::info
If you're a library developer looking to implement this functionality, we recommend using the [Universal Resolver](/resolvers/universal). It's a utility contract that greatly simplifies the process of resolving a name.
:::
### Setting Primary Names
Since primary names require two-way resolution, there are technically two steps to setting it up. Let's say that user `0x1234...5678` wants to set their primary name on Base to `nick.eth`.
First, the user would need to set the Base address for `nick.eth` to `0x1234...5678`. [Read more about multichain addresses](/web/resolution#multi-chain-addresses-btc-ltc-etc) to understand how this works.
Next, the user would need to set the reverse record for `0x1234...5678` to `nick.eth` in the Base Reverse Registrar. [Read more about reverse records](/registry/reverse) to understand how this works.
In order to avoid doing this manually for multiple chains, the user can set their default reverse record to `nick.eth` on the default reverse registrar, and their default EVM address to `0x1234...5678` on the latest [public resolver](/resolvers/public).
import { Card } from '../../components/ui/Card'
## Sign In With Ethereum (SIWE) \[A specification that leverages Ethereum signatures to perform authentication]
[ERC-4361](https://eips.ethereum.org/EIPS/eip-4361) defines a message format that a user signs using their keys to authenticate.
An example payload looks like the following:
```
localhost wants you to sign in with your Ethereum account:
0x225f137127d9067788314bc7fcc1f36746a3c3B5
This is a test statement.
URI: https://localhost/login
Version: 1
Chain ID: 1
Nonce: abcdef1234567890
Issued At: 2023-01-30T00:00:00.000Z
```
After authentication, an app may resolve the user's ENS name and profile, as well as other [onchain resources](https://docs.siwe.xyz/quickstart/retrieve-onchain-data).
### Resources
* [ERC-4361](https://eips.ethereum.org/EIPS/eip-4361)
* [Project website](https://siwe.xyz/)
* [Docs](https://docs.siwe.xyz/)
* [Libraries](https://docs.siwe.xyz/libraries/)
import { EmbedLink } from '../../components/EmbedLink'
import { Card } from '../../components/ui/Card'
## Subdomains
We believe that any place an address is used, a name should be able to be used instead.
The smart contracts you interact with have names, the deposit address for your favorite exchange has a name, your favorite DAO has a name, or maybe you use subnames to keep your wallets organized.
Luckily, the ENS Protocol has so much to offer for you to play with. There are a variety of ways you can give out subdomains to your apps users, set them up for yourself, or more.
If you are interested in naming smart contracts specifically, check out the [Naming Smart Contracts](/web/naming-contracts) page.
### Different Types of Subnames
ENS subnames come in a variety of forms: L1, L2, and offchain. From a technical perspective, L2 and offchain subnames are quite similar, but there are some tradeoffs to consider when choosing which one to use.
#### L1 Subnames
If you own a .eth name like nick.eth and go to create a subname in [the manager app](https://app.ens.domains/nick.eth?tab=subnames), you will be creating a subname on Ethereum Mainnet (L1) by default. This is the simplest way to create a subname with the least amount of moving pieces, but ultimately you are limited by the gas fees of Ethereum Mainnet.
If you'd like to issue L1 subnames to your users, read our guide on [creating an onchain subname registrar](/wrapper/creating-subname-registrar).
#### L2 Subnames
Developers can connect an ENS name on L1 with their own smart contracts on any L2 network, and [depending on the implementation](/learn/ccip-read), this could be fully trustless while significantly reducing the cost of issuing subnames.
[Durin](https://durin.dev/) is an opinionated approach to issuing ENS subnames on L2. It takes care of the L1 Resolver and offchain gateway parts of the [CCIP Read stack](/resolvers/ccip-read) for you, so you can focus on the business logic of your L2 smart contracts.
#### Offchain Subnames
Offchain subnames are exactly what they sound like - subnames that live in a centralized database on private servers, also powered by [CCIP Read](/resolvers/ccip-read). If your goal is to name a large amount of EVM addresses quickly and cheaply, with a low barrier to entry, offchain subnames might be for you. Often times, managing offchain names is as simple as interacting with a REST API.
From a user perspective, offchain subnames are hardly different than onchain subnames. They will not appear in wallet applications as NFTs like the previous two approaches, but they can resolve all the same data (addresses, text records, etc).
There are multiple API providers that offer programmatic access to offchain subnames such as [NameStone](https://namestone.com/), [Namespace](https://namespace.ninja/) and [JustaName](https://justaname.id/), along with open-source examples like [gskril/ens-offchain-registrar](https://github.com/gskril/ens-offchain-registrar).
## Subgraph
This is a page covering the graph's ENS subgraph. The ENS subgraph indexes onchain events of second-level .eth names, and DNS imported names.
It allows us to build a reasonable approximation of the ENS names an address owns.
To read more about why not all names (such as Offchain & Gasless Names) show up in the subgraph read the [listing names](/web/enumerate) page.
### The Graph
The Graph is a protocol for indexing and querying data from blockchains. There are multiple subgraphs that you can use to query information about ENS names.
These subgraphs are available for [mainnet](https://thegraph.com/explorer/subgraphs/5XqPmWe6gjyrJtFn9cLy237i4cWw2j9HcUJEXsP5qGtH), [sepolia](https://api.studio.thegraph.com/query/49574/enssepolia/version/latest) and [holesky](https://api.studio.thegraph.com/query/49574/ensholesky/version/latest).
:::note
Developers are welcome to use our rate limited API endpoints above for
testing, but it is highly encouraged to [sign up for a free account with
TheGraph](https://thegraph.com/studio/apikeys/) to get your own API key.
:::
### GraphQL Schema
The schema for the ENS subgraph is defined in [/schema.graphql](https://github.com/ensdomains/ens-subgraph/blob/master/schema.graphql).
### Use Cases
There are certain use cases where the graph is better for querying ENS specific information than through the resolution process.
One of such use-cases is querying which NFT names are owned by a specific address.
### Terminology
When using the subgraph, you may encounter `registrant` and `controller` fields. These were the old terminology for [Owner](/terminology#owner) and [Manager](/terminology#manager) respectively.
The `registrant` address is the owner of a name. It's the same value that will be returned from calling `ownerOf()` on the Base Registrar Controller (registrar.ens.eth). A registrant/owner may transfer ownership and assign a controller/manager.
The `controller` address is the manager of a name. It's the same value that will be returned from calling `owner()` on the ENS Registry (registry.ens.eth). A controller/manager may change the resolver of a name and set its records.
### Example Queries
:::note
Most of these example queries have equivalent functionality in [ENSjs](https://github.com/ensdomains/ensjs/tree/main/docs).
:::
You can explore the following examples interactively via the [Graph Explorer Playground](https://thegraph.com/explorer/subgraphs/5XqPmWe6gjyrJtFn9cLy237i4cWw2j9HcUJEXsP5qGtH?view=Playground\&chain=arbitrum-one)
#### Getting a list of names owned by an account
Ensure the address is lowercase
```graphql
query getDomainsForAccount {
domains(where: { owner: "0xa508c16666c5b8981fa46eb32784fccc01942a71" }) {
name
}
}
```
#### Getting the top domain for an account based on the longest registry
```graphql
query getDomainForAccount {
account(id: "0xa508c16666c5b8981fa46eb32784fccc01942a71") {
registrations(first: 1, orderBy: expiryDate, orderDirection: desc) {
domain {
name
}
}
id
}
}
```
returns
```json
{
"data": {
"account": {
"registrations": [
{
"domain": {
"name": "datanexus.eth"
}
}
],
"id": "0xa508c16666c5b8981fa46eb32784fccc01942a71"
}
}
}
```
#### Searching for a subdomain
```graphql
query getSubDomains($Account: String = "messari.eth") {
domains(where: { name: "messari.eth" }) {
name
id
subdomains(first: 10) {
name
}
subdomainCount
}
}
```
returns
```json
{
"data": {
"domains": [
{
"name": "messari.eth",
"id": "0x498ada62251a1227664ace8d97b0de2dcc6652ddf61e6fb5d3150f43ccf599e6",
"subdomains": [
{
"name": "subgraphs.messari.eth"
},
{
"name": "bd.messari.eth"
}
],
"subdomainCount": 2
}
]
}
}
```
#### Getting the expiry of an ENS domain
```graphql
query getDomainExp($Account: String = "paulieb.eth") {
registrations(
where: { domain_: { name: $Account } }
first: 1
orderBy: expiryDate
orderDirection: desc
) {
expiryDate
}
}
```
returns
```json
{
"data": {
"registrations": [
{
"expiryDate": "1714752524"
}
]
}
}
```
import { EmbedLink } from '../../components/EmbedLink'
import { Properties } from '../../components/Properties'
import { Repository } from '../../components/Repository'
import { Card } from '../../components/ui/Card'
## Offchain / L2 Resolvers
While ENS name resolution always starts from Ethereum Mainnet, it's possible to store almost all data associated with a name and its subdomains elsewhere. By leveraging [EIP-3668](https://eips.ethereum.org/EIPS/eip-3668) (CCIP Read) in a [Resolver](/resolvers/quickstart), developers can effectively defer resolution to an L2 or offchain API.
Name
greg.base.eth
➡️
L1 Resolver
0xde90...4F31
➡️
Gateway
api.coinbase.com
➡️
Address
0x179A...9285
### How does CCIP Read work?
CCIP Read (Cross Chain Interoperability Protocol) is a specification that defines a standard error smart contracts can throw if they want to trigger an offchain HTTP request.
```solidity
error OffchainLookup(
address sender,
string[] urls,
bytes callData,
bytes4 callbackFunction,
bytes extraData
)
```
When a contract reverts with the `OffchainLookup` error, clients (wagmi, viem, ethers, etc.) unwrap the error and handle it appropriately.
#### How does ENS use CCIP Read?
By [leveraging CCIP Read in a Resolver](/resolvers/writing#offchain-resolvers), developers can store name data beyond Ethereum Mainnet. A common use case is managing subnames on Layer 2 networks - for example, Coinbase allows users to register names like `jesse.base.eth` on Base, while the parent name `base.eth` remains on Ethereum Mainnet.
This creates a seamless experience for application developers - they can resolve names like `jesse.base.eth` exactly the same way they would resolve mainnet names, without needing to know which network the data is actually stored on. The use of CCIP Read in name resolution is completely transparent to users.

To resolve an offchain/L2 name using CCIP Read, the steps are as follows:
1. A user types "example.eth" into their wallet.
2. The wallet's client calls `resolve()` on example.eth's Resolver.
3. The Resolver reverts with an `OffchainLookup` error.
4. The client makes a request to the gateway URL specified in the error with the calldata from the error.
5. The gateway processes the request and returns data to the client. This is where the data is fetched from L2 or an offchain database.
6. The client calls the callback function specified in the error with the data returned from the gateway, which usually performs some sort of validation.
7. If the callback function validates the data, the client returns the result to the user.
While this might sound complex, it all happens under the hood and is completely transparent to application developers.
### Offchain Subname Example
An example of offchain ENS names powered by CCIP Read can be found at [offchain.ens.gregskril.com](https://offchain.ens.gregskril.com/).
The name [offchaindemo.eth](https://ens.app/offchaindemo.eth) with Resolver [0x35b9...E237](https://etherscan.io/address/0x35b920d4329C5797727Af8b15358b43509e5E237#code), reverts with `OffchainLookup` and directs the client to a Gateway URL.
The Gateway returns the relevant information from an offchain database, signed by a trusted private key which the smart contract can verify. This prevents a compromised Gateway from returning false information.
### Offchain vs L2 Resolvers
From the perspective of the L1 Resolver contract, the process of resolving an L2 name is exactly the same as resolving on offchain name. The differences come from the Gateway implementation and the Resolver's callback function.
For names that are stored offchain like the example above, the Gateway would read from a normal web2 database and the Resolver's callback function would simply verify the Gateway operator's signature.
For names that are stored on L2, the Gateway would make an RPC call to the relevant L2 and the Resolver's callback function would ideally verify the response by using the L2's state roon on L1 (this assumes knowledge of how L2's work).
To implement trustless L2 resolution, developers should use a solution like [Unruggable Gateways](https://gateway-docs.unruggable.com/).
### Writing a CCIP Read Gateway
A gateway is an offchain API endpoint that implements the [Gateway Interface](https://eips.ethereum.org/EIPS/eip-3668#gateway-interface) specified in EIP-3668. It is responsible for decoding the calldata from an `OffchainLookup` error and returning a relevant response.
#### Implementing the Endpoint
Your gateway must implement either a `GET` or `POST` endpoint with `{sender}` and `{data}` parameters, and be stored in the implementing smart contract. The `OffchainLookup` error will include this URL, which is how the client knows where to send the request.
:::code-group
```yaml [POST]
// POST if URL does not include '{data}' parameter
URL: https://example.com/gateway
Method: POST
Body:
sender: "0x..."
data: "0x..."
```
```yaml [GET]
// GET if URL includes '{data}' parameter
URL: https://example.com/gateway/{sender}/{data}.json
Method: GET
```
:::
Lowercased address of the contract reverting with the `OffchainLookup`
error.
0x prefixed bytes of the data passed to the `OffchainLookup` error.
#### Example Gateway Implementation
The most basic gateway implementation is to return a static value without doing any signing. We even have a library [@ensdomains/ccip-read-router](http://github.com/ensdomains/ccip-read-router) to abstract decoding the calldata.
#### Trust Assumptions
As explained in [Offchain vs L2 Resolvers](#offchain-vs-l2-resolvers), trust assumptions are up to the implementing developer and can range from fully trusted to full trustless.
The worst case scenario of a trusted implementation is that a malicious actor gains control of the gateway and can return false information.
The worst case scenario of a trustless implementation is that a malicious actor can take a gateway offline, but it can never return false data.
### Writing an Offchain/L2 Resolver
See [Writing a Resolver](/resolvers/writing#offchain-resolvers) for more information on how to implement a Resolver with CCIP Read.
### Testing your offchain names
To test your implementation, search the relevant name in the [ENS Manager App](https://app.ens.domains). Make sure that you've configured your test name to return a result for common data like an ETH address or common text records like `avatar` or `description`. If you set an arbitrary text record key like `test`, the manager app has no way of knowing that it exists.
## Chain Registry-Resolver (on.eth) \[An on-chain single source of truth for blockchain metadata.]
### Overview
The Chain Registry-Resolver is a smart contract that acts as a **canonical, on-chain registry** for blockchain metadata. It serves as the resolver for the `on.eth` namespace and enables applications and users to retrieve metadata for *any* blockchain using a single human-readable identifier, such as `base` or `solana`.
Historically, blockchain metadata has been stored in centralized, fragmented repositories maintained by third parties. The Chain Registry-Resolver brings this metadata on-chain into a single, extensible registry, where control and update authority are delegated to the relevant chain operators.
### Architecture
#### Authorization Model
The Chain Registry-Resolver uses a two-tier authorization model:
##### Contract Owner
The contract owner can:
* Register new chains
* Update existing chain registrations
* Upgrade the contract implementation
* Set the default contenthash for the namespace
##### Chain Admin
When an address is registered by the contract owner, an administrator address is specified. This is an address provided by the chain operators.
The chain admin address can:
* Modify text, data, contenthash, and address records for their chain
* Transfer admin rights to another address
#### Upgrades
The Chain Registry-Resolver is deployed behind a [UUPS](https://docs.openzeppelin.com/contracts-stylus/uups-proxy) (Universal Upgradeable Proxy Standard) proxy. This allows the contract logic to be upgraded while preserving all stored chain data and maintaining a consistent contract address.
#### Namespace Structure
```
on.eth (root namespace)
├── optimism.on.eth → Chain record storage
├── base.on.eth → Chain record storage
├── arbitrum.on.eth → Chain record storage
├── ... → Any number of chains can be registered
└── reverse.on.eth → Reserved for reverse resolution
```
:::note
The `reverse.on.eth` name is reserved to allow for the conversion of an [ERC-7930](https://eips.ethereum.org/EIPS/eip-7930) *Interoperable Address* to a chain label.
:::
#### Resolver Profiles
All established ENS Resolver profiles are implemented by the contract to allow for the resolution of:
* Text Records ([ENSIP-5](/ensip/5))
* Contenthash ([ENSIP-7](/ensip/7))
* Addresses ([ENSIP-9](/ensip/9) / [ENSIP-11](/ensip/11))
* Arbitrary Data ([ENSIP-24](/ensip/24))
#### Immutable Records
For a given chain, the `interoperable-address` data key is set upon chain registration, and is immutable.
It references the [ERC-7930](https://eips.ethereum.org/EIPS/eip-7930) *Interoperable Address* for the chain in question.
As an example, for `optimism.on.eth` the value is set to `0x00010000010a00`.
#### Aliasing
Chains can have multiple aliases that point to the canonical label.
Aliases are transparent - resolution through an alias returns the same underlying data as the canonical label.
For example:
* `op.on.eth` → resolves the same underlying metadata as `optimism.on.eth`
* `arb.on.eth` → resolves the same underlying metadata as `arbitrum.on.eth`
### Usage Guide
#### Chain Discovery
The registry is fully enumerable, allowing applications to discover all registered chains.
:::note
Enumerating all chains is an O(n) operation, which may become expensive as the registry grows to hundreds or thousands of chains. Applications are expected to perform a full enumeration once, cache the results locally, and then use `chainCount()` to detect new registrations for incremental updates.
:::
:::code-group
```solidity [Solidity]
interface IChainResolver {
function chainCount() external view returns (uint256);
function getChainAtIndex(uint256 index) external view returns (
string memory label,
string memory name,
bytes memory interoperableAddress
);
}
```
```ts [Typescript (viem)]
const discoveryAbi = [
{
name: 'chainCount',
type: 'function',
stateMutability: 'view',
inputs: [],
outputs: [{ name: '', type: 'uint256' }],
},
{
name: 'getChainAtIndex',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'index', type: 'uint256' }],
outputs: [
{ name: 'label', type: 'string' },
{ name: 'name', type: 'string' },
{ name: 'interoperableAddress', type: 'bytes' },
],
},
] as const
// Get total number of registered chains
const count = await client.readContract({
address: '0x...',
abi: discoveryAbi,
functionName: 'chainCount',
})
// Enumerate all chains
for (let i = 0; i < count; i++) {
const [label, name, interopAddr] = await client.readContract({
address: '0x...',
abi: discoveryAbi,
functionName: 'getChainAtIndex',
args: [BigInt(i)],
})
console.log(`${label}: ${name} (${interopAddr})`)
}
```
:::
#### Chain Metadata Discovery
##### Data Records
The resolver implements the optional discoverability mechanism outlined in [ENSIP-24](/ensip/24).
```solidity
/// @dev Interface selector: `0x29fb1892`
interface ISupportedDataKeys {
/// @notice For a specific `node`, get an array of supported data keys.
/// @param node The node (namehash).
/// @return The keys for which we have associated data.
function supportedDataKeys(bytes32 node) external view returns (string[] memory);
}
```
Calling `supportedDataKeys` for a given chain e.g. `optimism.on.eth` will return an array of keys for which data is defined.
##### Text Records
The resolver also implements the discoverability mechanism for text records:
```solidity
interface ISupportedTextKeys {
/// @notice For a specific `node`, get an array of supported text keys.
/// @param node The node (namehash).
/// @return The keys for which we have associated text records.
function supportedTextKeys(bytes32 node) external view returns (string[] memory);
}
```
Calling `supportedTextKeys` for a given chain e.g. `optimism.on.eth` will return an array of keys for which text records are set.
#### Resolving Chain Metadata
As the resolver is set on the `on.eth` name, resolution for subnames is subject to the resolution process outlined in [ENSIP-10: Wildcard Resolution](/ensip/10).
The calldata that you will pass to the `resolve` method of the `IExtendedResolver` interface is dependent on whether the metadata that you are fetching is stored as a data record or a text record.
For data records (e.g. fetching an [ERC-7930](https://eips.ethereum.org/EIPS/eip-7930) *Interoperable Address*), use the `data(bytes32 node, string calldata key)` getter function defined in [ENSIP-24](/ensip/24).
```ts
import { createPublicClient, http, encodeFunctionData, decodeFunctionResult, parseAbi, toHex } from 'viem'
import { mainnet } from 'viem/chains'
import { namehash, packetToBytes } from 'viem/ens'
const client = createPublicClient({
chain: mainnet,
transport: http(),
})
const dataAbi = parseAbi([
'function data(bytes32 node, string key) view returns (bytes)',
])
const resolveAbi = parseAbi([
'function resolve(bytes name, bytes data) view returns (bytes)',
])
const name = 'optimism.on.eth'
const dnsEncodedName = toHex(packetToBytes(name))
const node = namehash(name)
// Encode the data() call for the interoperable-address key
const calldata = encodeFunctionData({
abi: dataAbi,
functionName: 'data',
args: [node, 'interoperable-address'],
})
// Call resolve()
const result = await client.readContract({
address: '0x...', // Chain Registry-Resolver address
abi: resolveAbi,
functionName: 'resolve',
args: [dnsEncodedName, calldata],
})
// Decode the result
const interopAddr = decodeFunctionResult({
abi: dataAbi,
functionName: 'data',
data: result,
})
// Returns: 0x00010000010a00
```
For text records (e.g. fetching an X handle), use the `text(bytes32 node, string key)` function defined in [ENSIP-5](/ensip/5) instead:
```ts
const textAbi = parseAbi([
'function text(bytes32 node, string key) view returns (string)',
])
const resolveAbi = parseAbi([
'function resolve(bytes name, bytes data) view returns (bytes)',
])
const name = 'optimism.on.eth'
const dnsEncodedName = toHex(packetToBytes(name))
const node = namehash(name)
// Encode the text() call for the com.x key
const calldata = encodeFunctionData({
abi: textAbi,
functionName: 'text',
args: [node, 'com.x'],
})
// Call resolve()
const result = await client.readContract({
address: '0x...', // Chain Registry-Resolver address
abi: resolveAbi,
functionName: 'resolve',
args: [dnsEncodedName, calldata],
})
// Decode the result
const xHandle = decodeFunctionResult({
abi: textAbi,
functionName: 'text',
data: result,
})
// Returns: "https://x.com/optimism"
```
##### Using Direct Getters
The resolver exposes helper functions that allow you to fetch frequently accessed chain metadata directly using the chain label, without needing to go through the ENSIP-10 resolution process.
```solidity
interface IChainResolver {
function interoperableAddress(string calldata label) external view returns (bytes memory);
function chainName(string calldata label) external view returns (string memory);
}
```
For other record types, use the generic getters:
:::code-group
```solidity [Solidity]
interface IChainResolver {
function getText(string calldata label, string calldata key) external view returns (string memory);
function getData(string calldata label, string calldata key) external view returns (bytes memory);
function getAddr(string calldata label, uint256 coinType) external view returns (bytes memory);
function getContenthash(string calldata label) external view returns (bytes memory);
}
```
```ts [Typescript (viem)]
const directGetterAbi = [
{
name: 'interoperableAddress',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'label', type: 'string' }],
outputs: [{ name: '', type: 'bytes' }],
},
{
name: 'chainName',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'label', type: 'string' }],
outputs: [{ name: '', type: 'string' }],
},
{
name: 'getText',
type: 'function',
stateMutability: 'view',
inputs: [
{ name: 'label', type: 'string' },
{ name: 'key', type: 'string' },
],
outputs: [{ name: '', type: 'string' }],
},
] as const
// Fetch the interoperable address for Optimism
const interopAddr = await client.readContract({
address: '0x...', // Chain Registry-Resolver address
abi: directGetterAbi,
functionName: 'interoperableAddress',
args: ['optimism'],
})
// Returns: 0x00010000010a
// Fetch the chain name for Optimism
const name = await client.readContract({
address: '0x...', // Chain Registry-Resolver address
abi: directGetterAbi,
functionName: 'chainName',
args: ['optimism'],
})
// Returns: "Optimism"
// Fetch the X handle for Optimism
const xHandle = await client.readContract({
address: '0x...', // Chain Registry-Resolver address
abi: directGetterAbi,
functionName: 'getText',
args: ['optimism', 'com.x'],
})
// Returns: "https://x.com/optimism"
```
:::
#### Reverse Resolution
Reverse resolution maps an [ERC-7930](https://eips.ethereum.org/EIPS/eip-7930) *Interoperable Address* back to its human-readable chain label.
Reverse resolution data is stored as text records on the special subdomain, `reverse.on.eth`. The key format is `chain-label:` appended with the *Interoperable Address* you want to reverse.
```ts
const textAbi = parseAbi([
'function text(bytes32 node, string key) view returns (string)',
])
const reverseName = 'reverse.on.eth'
const dnsEncodedReverse = toHex(packetToBytes(reverseName))
const reverseNode = namehash(reverseName)
// The key is "chain-label:" + Interoperable Address without 0x prefix
const textKey = 'chain-label:00010000010a00'
const calldata = encodeFunctionData({
abi: textAbi,
functionName: 'text',
args: [reverseNode, textKey],
})
const result = await client.readContract({
address: '0x...',
abi: resolveAbi,
functionName: 'resolve',
args: [dnsEncodedReverse, calldata],
})
const label = decodeFunctionResult({
abi: textAbi,
functionName: 'text',
data: result,
})
// Returns: "optimism"
```
##### Using Direct Getters
The resolver exposes a helper function to achieve the same result.
:::code-group
```solidity [Solidity]
interface IChainResolver {
function chainLabel(bytes calldata interoperableAddress) external view returns (string memory);
}
```
```ts [Typescript (viem)]
const chainLabelAbi = [
{
name: 'chainLabel',
type: 'function',
stateMutability: 'view',
inputs: [{ name: 'interoperableAddress', type: 'bytes' }],
outputs: [{ name: '', type: 'string' }],
},
] as const
// Reverse resolve an interoperable address
const label = await client.readContract({
address: '0x...', // Chain Registry-Resolver address
abi: chainLabelAbi,
functionName: 'chainLabel',
args: ['0x00010000010a00'],
})
// Returns: "optimism"
```
:::
### Interface Reference
#### Core Resolution
```solidity
interface IChainResolver {
// Container for canonical label information
struct CanonicalLabelInfo {
string label;
bytes32 labelhash;
}
// Forward resolution
function interoperableAddress(string calldata label) external view returns (bytes memory);
function chainName(string calldata label) external view returns (string memory);
// Reverse resolution
function chainLabel(bytes calldata interoperableAddress) external view returns (string memory);
// Discovery
function chainCount() external view returns (uint256);
function getChainAtIndex(uint256 index) external view returns (
string memory label,
string memory name,
bytes memory interoperableAddress
);
// Record getters
function getText(string calldata label, string calldata key) external view returns (string memory);
function getData(string calldata label, string calldata key) external view returns (bytes memory);
function getAddr(string calldata label, uint256 coinType) external view returns (bytes memory);
function getContenthash(string calldata label) external view returns (bytes memory);
// Alias resolution
function getCanonicalLabel(string calldata _label) external view returns (CanonicalLabelInfo memory info);
// ENSIP-10 wildcard resolution
function resolve(bytes calldata name, bytes calldata data) external view returns (bytes memory);
// Interface detection
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
```
#### Record management
These functions are available to the respective chain operators for setting metadata about their blockchain.
```solidity
interface IChainResolverAdmin {
// Text records
function setText(bytes32 labelhash, string calldata key, string calldata value) external;
function batchSetText(bytes32 labelhash, string[] calldata keys, string[] calldata values) external;
// Data records
function setData(bytes32 labelhash, string calldata key, bytes calldata value) external;
function batchSetData(bytes32 labelhash, string[] calldata keys, bytes[] calldata values) external;
// Address records
function setAddr(bytes32 labelhash, uint256 coinType, bytes calldata value) external;
// Contenthash
function setContenthash(bytes32 labelhash, bytes calldata contenthash) external;
// Admin transfer
function setChainAdmin(bytes32 labelhash, address newAdmin) external;
}
```
### Browsing Chain Metadata
The default contenthash for the `on.eth` namespace references a simple decentralized website that resolves data from the on-chain registry-resolver.
Using a service like eth.limo, you can interface with the registry by visiting [on.eth.limo](https://on.eth.limo).
The metadata for a specific chain can be viewed through this user interface by accessing the specific subname directly. For example [base.on.eth.limo](https://base.on.eth.limo), or [optimism.on.eth.limo](https://optimism.on.eth.limo).

Metadata that is supported by the ENS App will also be displayed when the domain is looked up. For example: [https://app.ens.domains/optimism.on.eth](https://app.ens.domains/optimism.on.eth)
### Source Code
The Chain Registry-Resolver source code is available on [GitHub](https://github.com/unruggable-labs/chain-resolver).
## Interacting with a Resolver \[Set Addresses, Text Records, and more]
Some apps may want to allow for users to edit, update, or modify their name and its behaviour at a more advanced level.
This is possible by interacting with the resolver contract of a name directly.
### Checking Interface Support
Before you start sending transactions to users resolvers, you should check if they support the interface you want to use. This is done by calling the `supportsInterface` (see [EIP-165](https://eips.ethereum.org/EIPS/eip-165)) function on the resolver contract.
```solidity
function supportsInterface(bytes4 interfaceID) external pure returns (bool)
```
In order to ensure that resolvers we interact with are compatible with specific standards you can call the above function on contracts with an interfaceID and then check the boolean it returns.
Interface IDs are calculated according to solidity ABI and stored in a four-byte value.
### Updating a User's Record
If you want to help a user set their avatar, specify a preferred color scheme, or set any other record on their ENS name you can do so in specific cases.
First we need to check if the user's resolver supports the interface we want to use (see [setText](/resolvers/interfaces#0x10f13a8c)).
Afterwhich you can call the `setText()` function on the user's resolver contract.
:::code-group
```solidity [Solidity]
interface Resolver {
function setText(bytes32 node, string calldata key, string calldata value) external;
}
```
```typescript [ENSjs]
// https://github.com/ensdomains/ensjs/blob/main/docs/wallet/function.setRecords.md
import { addEnsContracts } from '@ensdomains/ensjs'
import { setRecords } from '@ensdomains/ensjs/wallet'
import { createWalletClient, custom } from 'viem'
import { mainnet } from 'viem/chains'
const wallet = createWalletClient({
chain: addEnsContracts(mainnet),
transport: custom(window.ethereum),
})
const hash = await setRecords(wallet, {
name: 'ens.eth',
coins: [
{
coin: 'ETH',
value: '0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7',
},
],
texts: [{ key: 'foo', value: 'bar' }],
resolverAddress: '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41',
})
// 0x...
```
:::
Note that only the [Manager](/terminology#manager) of a name can set records on the resolver. For unwrapped names, the manager can be found by calling `owner()` on the ENS Registry. For wrapped names, the manager can be found by calling `ownerOf()` on the Name Wrapper. You can find the contract addresses [here](/learn/deployments#mainnet).
### Update a User's Resolver
Overwriting a user's resolver involves overwriting the behaviour of their ENS name.
To change the resolver of a name, the Manager must call `setResolver()` on either the ENS Registry or the Name Wrapper, depending on whether the name is wrapped or not.
To figure out if a name is wrapped, call `owner()` on the ENS Registry. If this returns the address of the Name Wrapper, that indicates the name is wrapped and you should call `ownerOf()` on the Name Wrapper to get the effective owner of the name. You can find the contract addresses [here](/learn/deployments#mainnet).
:::code-group
```solidity [Solidity]
// Both the ENS Registry and the Name Wrapper implement this interface
interface ENS {
function setResolver(bytes32 node, address resolver) external;
}
```
```typescript [ENSjs]
// https://github.com/ensdomains/ensjs/blob/main/docs/wallet/function.setResolver.md
import { addEnsContracts } from '@ensdomains/ensjs'
import { setResolver } from '@ensdomains/ensjs/wallet'
import { createWalletClient, custom } from 'viem'
import { mainnet } from 'viem/chains'
const wallet = createWalletClient({
chain: addEnsContracts(mainnet),
transport: custom(window.ethereum),
})
const hash = await setResolver(wallet, {
name: 'ens.eth',
contract: 'registry',
resolverAddress: '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41',
})
// 0x...
```
:::
Please do not change the resolver for a user without their permission. Overwriting the resolver is a destructive action and will overwrite any existing resolution logic.
### Layer 2 & Offchain Resolvers
At the time of writing this the ecosystem around multichain and "writing" to layer 2 & offchain resolvers has yet to be standardized and is still under active development.
Please check back at a later date.
import { InterfaceDetails } from '../../components/InterfaceDetails'
import { resolver_methods } from '../../data/resolver'
## Resolver Interface Standards
This page is a collection of methods that a resolver MAY implement.
## Public Resolver
:::info
Find the address of the latest public resolver on the [deployments page](/learn/deployments).
:::
The public resolver is a general-purpose ENS resolver that is suitable for most user needs.
It permits the owner of a name to update their records, includes permissions, and stores its data on layer-1 ethereum.
Most ENS names registered through the ENS Manager will use the latest version of the public resolver by default.
Names that resolve to a supported public resolver are editable from within the ENS Manager.
If you'd like to take a peek under the hood you can view the [public resolver source code](https://github.com/ensdomains/ens-contracts/blob/staging/contracts/resolvers/PublicResolver.sol) on GitHub.
### Features
The public resolver supports the following features:
* [EIP-137](https://eips.ethereum.org/EIPS/eip-137) - Contract address interface (`addr()`)
* [EIP-165](https://eips.ethereum.org/EIPS/eip-165) - Interface Detection (`supportsInterface()`)
* [EIP-181](https://eips.ethereum.org/EIPS/eip-181) - Reverse Resolution (`name()`)
* [EIP-205](https://eips.ethereum.org/EIPS/eip-205) - ABI Resolution for contracts (`ABI()`)
* [EIP-619](https://eips.ethereum.org/EIPS/eip-619) - SECP256k1 public keys (`pubkey()`)
* [EIP-634](https://eips.ethereum.org/EIPS/eip-634) - Text records (`text()`)
* [EIP-1577](https://eips.ethereum.org/EIPS/eip-1577) - Content hash resolution (`contenthash()`)
* [EIP-2304](https://eips.ethereum.org/EIPS/eip-2304) - Multicoin support (`addr()`)
* [ENSIP-19](/ensip/19#default-address) - Default EVM address resolution
:::note
While the `PublicResolver` provides a default implementation, remember that custom resolvers exist. It is therefore not recommended to hardcode any addresses. To ensure a safe implementation, always use the `supportsInterface()` method to check for the existence of a specific interface. See [Interacting with a Resolver](/resolvers/interacting) for how to do this.
:::
### Default EVM Address Resolution
The latest public resolver supports the notion of a "default EVM address" for a name, represented by coinType `0`. If this is set, it will be returned by the `addr(bytes32 node, uint256 coinType)` function regardless of the `coinType` parameter, unless overridden by a more specific address.
This pairs nicely with the default reverse registrar when dealing with [L2 primary names](/web/reverse#l2-primary-names).
## Resolvers Quickstart
At the heart of every ENS name is its resolver. A resolver is a smart contract that implements a specific set of Resolver features (see [Resolver Interface](/resolvers/interfaces)).
The resolvers smart contract functions have control over the resolution process of a ["node"](/resolution/names#namehash) (a name or subdomain) and onwards (subdomains of itself).
### Basic Resolver
A naive but very plausible example of a resolver is the following.
```solidity
contract MyResolver {
function addr(bytes32 node) external pure returns (address) {
return 0x225f137127d9067788314bc7fcc1f36746a3c3B5;
}
function supportsInterface(
bytes4 interfaceID
) external pure returns (bool) {
return
interfaceID == this.addr.selector ||
interfaceID == this.supportsInterface.selector;
}
}
```
Notice how the above would always return the same address regardless of the name it is queried for.
If you want to [write your own resolver resolver](/resolvers/writing), or see the [interface reference](/resolvers/interfaces).
### Public Resolver
The default resolver for all names is the Public Resolver, a swiss army knife of resolvers, written by the ENS Labs team, feature-packed with everything your everyday user might need.
You can read more about the [Public Resolver](/resolvers/public).
### Interacting with a resolver
Depending on the resolver in charge of a name, certain frontend apps will be able to interact with them as well.
This means you can set your favourite records, upgrade your name to different logic, and more, from your dApp of choice.
Are you writing a dApp and want to build this? Checkout the [Interacting with a Resolver](/resolvers/interacting) section.
### Offchain Resolution
Although by default ENS resolution is done onchain. You can leverage the power of CCIP Read to redirect resolution to an offchain gateway.
More about writing a CCIP Read-enabled resolver [here](/resolvers/ccip-read).
import { InterfaceDetails } from '../../components/InterfaceDetails'
import { universalresolver_methods } from '../../data/universal-resolver'
## Universal Resolver \[A swiss army knife for resolution.]
### Overview
:::note
Application developers typically don't need to interact with the universal resolver directly. This guide is directed at lower level-library developers.
:::
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](https://ens.domains/ensv2) seamless.
### Test Case
You might be asking yourself, "How do I know if my application is already using the Universal Resolver?"
To answer that question, we've prepared a test case. If you're using the Universal Resolver, your app should resolve `ur.integration-tests.eth` to `0x2222222222222222222222222222222222222222`. If it's not, it will resolve to `0x1111111111111111111111111111111111111111`.
### Implementation Guide
The Universal Resolver should be treated as the canonical entrypoint to ENS for name resolution. `0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe` is the official deployment address on Ethereum Mainnet and testnets, which is a proxy contract owned by ENS DAO that will be upgraded to support [ENSv2](https://ens.domains/ensv2) in the future.
This guide assumes that your application already supports [CCIP Read](https://eips.ethereum.org/EIPS/eip-3668).
#### 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](/resolution/names#dns) version of the name. Make sure to [normalize](/resolution/names#normalize) the name first, as well! For example, given the name `My.Name.eth`:
1. Normalize:
* `My.Name.eth` -> `my.name.eth`
2. 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:
```solidity
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:
```ts
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](/resolvers/interfaces) 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:
```ts
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: '0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe',
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](/web/resolution#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:
```ts
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: '0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe',
functionName: 'reverse',
args: [address, 60n],
})
// primaryName = "nick.eth"
```
#### Batch Gateways
The Universal Resolver utilizes a batch gateway to perform parallel [EIP-3668](https://eips.ethereum.org/EIPS/eip-3668) (CCIP-Read) requests and mitigate offchain server issues. [`CCIPBatcher.sol`](https://github.com/ensdomains/ens-contracts/blob/staging/contracts/ccipRead/CCIPBatcher.sol) is the recommended batch gateway client implementation. The protocol and server implementation is defined in [ENSIP-21: Batch Gateway Offchain Lookup Protocol](/ensip/21/) (BGOLP).
The latest batch gateways can be queried from the Universal Resolver via `batchGateways()` which returns `(string[] gateways)`.
1. **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.
2. **Local Batch Gateway** `x-batch-gateway:true` is a [special-purpose URL](/ensip/21/) which notifies ENSIP-21 aware clients that the `OffchainLookup` 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](https://eips.ethereum.org/EIPS/eip-3668#client-lookup-protocol) in EIP-3668.
:::note
Currently only [viem](https://github.com/wevm/viem/blob/main/src/utils/ens/localBatchGatewayRequest.ts) supports ENSIP-21.
:::
## Writing a Resolver
Every ENS name has a resolver, which is responsible for resolving information about a name.
Resolvers are a core part of the ENS protocol. They give each name, represented as a ["node"](/resolution/names#namehash), the power to control the resolution process for itself and all of its subnames. Resolvers were originally standardized in [EIP 137](https://eips.ethereum.org/EIPS/eip-137), but have since received a few updates such as [EIP 181](https://eips.ethereum.org/EIPS/eip-181), [EIP 2304](https://eips.ethereum.org/EIPS/eip-2304), and [ENSIP-10](/ensip/10).
You can find the latest default resolver implementation, called the Public Resolver, on [GitHub](https://github.com/ensdomains/ens-contracts/blob/mainnet/contracts/resolvers/PublicResolver.sol) and [Etherscan](/learn/deployments).
### Resolver Interface
You can view an extended list of resolver methods [here](/resolvers/interfaces), however a simple interface might look something like this:
```solidity
interface IMyResolver {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
function addr(bytes32 node) external view returns (address payable);
function addr(bytes32 node, uint256 coinType) external view returns (bytes memory);
function contenthash(bytes32 node) external view returns (bytes memory);
function text(bytes32 node, string calldata key) external view returns (string memory);
function setAddr(bytes32 node, address addr) external;
function setAddr(bytes32 node, uint256 coinType, bytes calldata a) external;
function setContenthash(bytes32 node, bytes calldata hash) external;
function setText(bytes32 node, string calldata key, string calldata value) external;
}
```
### Wildcard Resolution
In [ENSIP-10](/ensip/10) a new `resolve()` method was added to the resolver interface to allow for wildcard resolution.
```solidity
interface IExtendedResolver {
/**
* @dev Performs ENS name resolution for the supplied name and resolution data.
* @param name The name to resolve, in normalised and DNS-encoded form.
* @param data The resolution data, as specified in ENSIP-10.
* @return The result of resolving the name.
*/
function resolve(
bytes memory name,
bytes memory data
) external view returns (bytes memory);
}
```
:::note
Don't forget to add `0x9061b923` to your [EIP-165](https://eips.ethereum.org/) `supportsInterface()` implementation.
:::
### Onchain Resolvers
By default, ENS names use the [Public Resolver](/resolvers/public) which stores all data onchain. An extremely basic resolver that stores a mapping of ENS names to addresses might look like this:
```solidity
contract OnchainResolver {
mapping(bytes32 node => address addr) public addr;
function setAddr(bytes32 node, address _addr) external {
addr[node] = _addr;
}
function supportsInterface(
bytes4 interfaceID
) external pure returns (bool) {
return
interfaceID == OnchainResolver.supportsInterface.selector ||
// function addr(bytes32 node) external view returns (address)
interfaceID == 0x3b3b57de;
}
}
```
:::note
This is not secure since it doesn't implement any form of access control for updating a name's address, and is only suitable for demonstration purposes.
:::
Since the mapping is stored internally, it costs gas for the owner of a name to update their address. This is great for a maximal decentralization, but is not always practical.
### Offchain Resolvers
An offchain resolver is a resolver that implements CCIP Read to defer a name's resolution to an HTTP server. This server can then load data from any source including offchain databases, APIs, or other blockchains. [Learn more about CCIP Read](/resolvers/ccip-read).
An equivalent offchain resolver to the above onchain example looks something like this:
```solidity
contract OffchainResolver {
string public url =
"https://docs.ens.domains/api/example/basic-gateway";
error OffchainLookup(
address sender,
string[] urls,
bytes callData,
bytes4 callbackFunction,
bytes extraData
);
function addr(bytes32 node) external view returns (address) {
bytes memory callData = abi.encodeWithSelector(
OffchainResolver.addr.selector,
node
);
string[] memory urls = new string[](1);
urls[0] = url;
revert OffchainLookup(
address(this),
urls,
callData,
OffchainResolver.addrCallback.selector,
abi.encode(callData, address(this))
);
}
function addrCallback(
bytes calldata response,
bytes calldata
) external pure returns (address) {
address _addr = abi.decode(response, (address));
return _addr;
}
function supportsInterface(
bytes4 interfaceID
) external pure returns (bool) {
return
interfaceID == OffchainResolver.supportsInterface.selector ||
interfaceID == OffchainResolver.addr.selector;
}
}
```
:::note
This is not secure since it doesn't implement any form of verification in the callback function, and is only suitable for demonstration purposes.
:::
Any ENS name that sets its resolver to this contract would resolve to whatever address the Gateway returns, which can be changed at any time offchain for free. See the [gateway code here](https://github.com/ensdomains/docs/blob/master/functions/api/example/basic-gateway.ts).
For the same functionality to work with subnames, you'd need to implement the `resolve()` method from ENSIP-10. A feature-complete example can be found [here](https://github.com/ensdomains/ccip-tools/blob/master/contracts/OffchainResolver.sol), and easily deployed via [https://ccip-tools.pages.dev](https://ccip-tools.pages.dev).
import { Card } from '../../components/ui/Card'
## Resolution
The process by which we load information about a name is called resolution. It's a simple process, but it's important to understand.
Here is a diagram of some of the contracts involved when resolving a name.
The resolution process involves multiple parts. Most notably the [Registry](/registry/ens), multiple Registrars ([ETH Registrar](/registry/eth), [DNS Registrar](/registry/dns), [Reverse Registrar](/registry/reverse), etc)
and the concept of a [Resolver](/resolvers/quickstart).
### How to resolve
Here is a little peek at what happens under the hood of your favourite library when you do a name lookup.
#### 1. Find the Resolver
Every name has a "resolver". A resolver is simply a contract that implements the [resolver specification](/resolvers/quickstart) and can be queried for information about a name.
To get the resolver responsible for a name, you can query [The Registry](/registry/ens) for the `resolver` of a name.
:::code-group
```solidity [Solidity]
ENS.resolver(bytes32 node) view returns (address)
```
```tsx [Wagmi]
// https://wagmi.sh/react/api/hooks/useEnsResolver
import { normalize } from 'viem/ens'
import { useEnsResolver } from 'wagmi'
export const MyResolver = () => {
const { data: myResolver } = useEnsResolver({
name: normalize('nick.eth'), // The name to lookup
})
return
{myResolver}
}
```
```ts [Ethers]
const resolver = await provider.getResolver('nick.eth')
```
```ts [viem]
// https://viem.sh/docs/ens/actions/getEnsResolver.html
import { normalize } from 'viem/ens'
import { publicClient } from './client'
const ensResolver = await publicClient.getEnsResolver({
name: normalize('nick.eth'),
})
```
```py [Web3.py]
# https://web3py.readthedocs.io/en/latest/ens_overview.html#working-with-resolvers
from ens.auto import ns
resolver = ns.resolver('alice.eth')
```
:::
To verify which specifications are implemented by a resolver, you can call the `supportsInterface(bytes4 interfaceID)` on the resolver with the interfaceID you would like to test for.
#### 2. Query the Resolver
Now you have found the resolver responsible for the name in question, you can query it for the information you are interested in.
There are many ways you can query the resolver, `addr()` `text()` `contenthash()` `abi()` etc.
If the resolver supports text records, you can call `text()` to get that text record for the name.
More about loading information from a resolver can be found [here](/resolvers/interacting).
##### Wildcard Resolution
In addition, all of the above functions can be sent to the `resolve()` function, specified in [ENSIP-10](/ensip/10).
This allows for not only multicall functionality, but also easier implementation of EIP-3668, and more.
Most clients & many resolvers utilize wildcard resolution as their primary form of resolution.
### Reverse Resolution
Due to the modular nature of how ENS is designed, it is also possible to lookup the "primary name" of an address.
This process actually uses forward resolution under the hood, you read that right - its just forwards resolution.
To look up the primary name of a given address, you must do a resolver lookup for `addr.reverse` and then query the `name()` field on the resolver.
This name field returns the "preferred" name for the address. You should always follow up a reverse lookup with a forward lookup to verify that the resulting name points back to the original address. If the address doesn't match, display the address rather than the reversed name.
```solidity
/// @dev The starting point for all ENS resolution is the Registry
ENS ens = 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e;
/// @dev The node hash for "addr.reverse"
bytes32 ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2;
/// @dev Returns the node hash for a given account's reverse records, `{address}.addr.reverse`
function reverseNode(address addr) public pure returns (bytes32) {
return keccak256(
abi.encodePacked(ADDR_REVERSE_NODE, sha3HexAddress(addr))
);
}
/// @dev Get the reverse record for an address
function getReverseRecord(address addr) public view returns (string) {
bytes32 reverseNodeHash = reverseNode(addr);
// Get the resolver for the reverse node
Resolver resolver = ens.resolver(reverseNodeHash);
// Get the address's preferred name
return resolver.name(reverseNodeHash);
}
```
:::info
**Important**: The client MUST perform a forward resolution on a user's reverse record to verify the address matches the one you are looking up. The example above does not perform this verification.
:::
Please note that many libraries already have functionality to do this. You can read more about it in the [Getting a Primary Name](/web/reverse) section.
import { NameProcessing } from '../../components/NameProcessing'
import { Card } from '../../components/ui/Card'
## Name Processing \[Normalization and recommendations for how to handle names]
When interacting with the ENS smart contracts directly, it is important to note that names are not stored as strings. [Libraries](/web/libraries) handle name encoding for you when implementing basic name resolution, but you may need to handle the encoding yourself when interacting with the protocol directly.
Below is an interactive tool that shows all the different formats of names and how to implement them.
### Name Normalization
Normalization is the process of canonicalizing a name before running it through the [Namehash](#namehash) algorithm. It is important to always normalize all input, because even one little difference (like a capital vs lowercase character) will cause the namehash to be completely different.
For example, `NaMe.EtH` normalizes to `name.eth`. This ensures that the correct Registry node is used, no matter how the user types in the name.
ENS names are validated and normalized using the [ENSIP-15](/ensip/15) normalization algorithm.
Previously, [UTS-46](https://www.unicode.org/reports/tr46/) was used, but that is insufficient for emoji sequences. Correct emoji processing is only possible with [UTS-51](https://www.unicode.org/reports/tr51/). The [ENSIP-15](/ensip/15) normalization algorithm draws from those older Unicode standards, but also adds many other validation rules to prevent common spoofing techniques like inserting zero-width characters, or using confusable (look-alike) characters. See here for additional discussion on this: [Homogylphs](https://support.ens.domains/en/articles/7901658-homoglyphs)
A standard implementation of the algorithm is available at [@adraffy/ens-normalize](https://github.com/adraffy/ens-normalize.js). This library is used under the hood in [viem](https://viem.sh/docs/ens/utilities/normalize), [ENSjs](https://github.com/ensdomains/ensjs/blob/main/packages/ensjs/src/utils/normalise.ts#L27), and others.
```js
import { normalize } from 'viem/ens'
// Uses @adraffy/ens-normalize under the hood
const normalized = normalize('RaFFY🚴♂️.eTh')
// => "raffy🚴♂.eth"
```
If the name was not able to be normalized, then that method will throw an error. A name is valid if it is able to be normalized.
### Namehash
:::note
You **MUST** [normalize](#normalize) a name before you attempt to create a namehash! If you don't, then the hash you get may be incorrect. Some libraries like [ensjs](https://github.com/ensdomains/ensjs) will automatically do this for you.
:::
In the core ENS registry, names are stored as a hash instead of the raw string to optimize for gas, performance, and more. This hashed value is typically referred to as a `node`. The node is a hex-encoded 32-byte value that is derived from the name using the `namehash` algorithm defined in [ENSIP-1](/ensip/1).
Namehash is a recursive algorithm that hashes each part of the name, then hashes the results together. Because recursive functions aren't very efficient in Solidity, it's usually best to derive the namehash offchain and pass to it a contract. Luckily, there are libraries that do this for us.
:::code-group
```tsx [Viem]
// https://viem.sh/docs/ens/utilities/namehash
import { namehash, normalize } from 'viem/ens'
const normalizedName = normalize('name.eth')
const node = namehash(normalizedName)
```
```ts [Ethers.js]
// https://docs.ethers.org/v6/api/hashing/#namehash
import { ensNormalize, namehash } from 'ethers/hash'
const normalizedName = ensNormalize('name.eth')
const node = namehash(normalizedName)
```
```python [ens-namehash-py]
# https://github.com/ConsenSysMesh/ens-namehash-py
from namehash import namehash
node = namehash('name.eth')
```
```rust [namehash-rust]
// https://github.com/InstateDev/namehash-rust
fn main() {
let node = &namehash("name.eth");
let s = hex::encode(&node);
}
```
```solidity [Solidity]
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@ensdomains/ens-contracts/contracts/utils/NameEncoder.sol";
contract MyContract {
function namehash(string calldata name) public pure returns (bytes32) {
(, bytes32 node) = NameEncoder.dnsEncodeName(name);
return node;
}
}
```
:::
#### Algorithm
The specification for the namehash algorithm was originally defined in [EIP-137](https://eips.ethereum.org/EIPS/eip-137#namehash-algorithm) (same as [ENSIP-1](/ensip/1)).
It's a recursive algorithm that works its way down until you hit the root domain. For `ens.eth`, the algorithm works like so:
```
namehash('ens.eth') = keccak256(namehash('eth') + labelhash('ens'))
namehash('eth') = keccak256(namehash('') + labelhash('eth'))
namehash('') = 0x0000000000000000000000000000000000000000000000000000000000000000
```
That last line is a special case: The namehash for an empty string (representing the root domain) is 32 null bytes.
If you plug everything in above, you'll end up with the final namehash value:
```
namehash('') = 0x0000000000000000000000000000000000000000000000000000000000000000
labelhash('eth') = keccak256('eth') = 0x4f5b812789fc606be1b3b16908db13fc7a9adf7ca72641f84d75b47069d3d7f0
namehash('eth') = keccak256(namehash('') + labelhash('eth')) = keccak256(0x00000000000000000000000000000000000000000000000000000000000000004f5b812789fc606be1b3b16908db13fc7a9adf7ca72641f84d75b47069d3d7f0) = 0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae
labelhash('ens') = keccak256('ens') = 0x5cee339e13375638553bdf5a6e36ba80fb9f6a4f0783680884d92b558aa471da
namehash('ens.eth') = keccak256(namehash('eth') + labelhash('ens')) = keccak256(0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae5cee339e13375638553bdf5a6e36ba80fb9f6a4f0783680884d92b558aa471da) = 0x4e34d3a81dc3a20f71bbdf2160492ddaa17ee7e5523757d47153379c13cb46df
```
This brings us to the final node for ens.eth: `0x4e34d3a81dc3a20f71bbdf2160492ddaa17ee7e5523757d47153379c13cb46df`
#### Reverse Nodes
The [Reverse Node](/terminology#reverse-node) is a node in the Registry that can be claimed for any Ethereum account. The name this node represents is `[addr].addr.reverse`, where `[addr]` is the Ethereum public address (lowercase, without the "0x"). These reverse nodes are typically used to set a [Primary Name](#primary-name) for an account.
To generate the namehash for a reverse node:
* Take the input address and:
* Remove the "0x" at the beginning
* Convert all characters to lowercase
* Add `.addr.reverse` to the end
* Run this result through the namehash algorithm
For example, for address `0x481f50a5BdcCC0bc4322C4dca04301433dED50f0`, the name for the reverse node is:
* `481f50a5bdccc0bc4322c4dca04301433ded50f0.addr.reverse`
And the resulting namehash for the reverse node is:
* `0x58354ffdde6ac279f3a058aafbeeb14059bcb323a248fb338ee41f95fa544c86`
### Labelhash
:::note
You **MUST** [normalize](#normalize) a name before you attempt to create a labelhash! If you don't, then the hash you get may be incorrect.
:::
Labelhash is the Keccak-256 hash of a single label (e.g. `name` in `name.eth`), used in places that don't require the full name.
One example of where labelhash is used is in the [BaseRegistar](/registry/eth), since it only supports registering 2LDs (second-level domains, like `name.eth`) and not 3LDs+ (e.g. `sub.name.eth`). The token ID of a second-level .eth name in the BaseRegistar is the uint256 of the labelhash.
:::code-group
```tsx [Viem]
// https://viem.sh/docs/ens/utilities/labelhash
import { labelhash, normalize } from 'viem/ens'
const normalizedLabel = normalize('label')
const hash = labelhash(normalizedLabel)
```
```tsx [Ethers]
// https://docs.ethers.org/v6/api/crypto/#keccak256
import { keccak256 } from 'ethers/crypto'
import { ensNormalize } from 'ethers/hash'
import { toUtf8Bytes } from 'ethers/utils'
const normalizedLabel = ensNormalize('label')
const labelhash = keccak256(toUtf8Bytes(normalizedLabel))
```
```solidity [Solidity]
string constant label = "label";
bytes32 constant labelhash = keccak256(bytes(label));
```
:::
### DNS Encoding
:::note
You **MUST** [normalize](#normalize) a name before you DNS-encode it! If you
don't, then when you pass those DNS-encoded bytes into a contract method,
incorrect namehashes/labelhashes may be derived.
:::
This is a binary format for domain names, which encodes the length of each label along with the label itself. It is used by some of the ENS contracts, such as when wrapping names in the [Name Wrapper](/wrapper/overview) or resolving data with [ENSIP-10](/ensip/10).
:::code-group
```tsx [Viem]
import { packetToBytes } from 'viem/ens'
import { toHex } from 'viem/utils'
const name = 'name.eth'
const dnsEncodedName = toHex(packetToBytes(name))
// 0x046e616d650365746800
```
```tsx [Ethers]
// https://docs.ethers.org/v6/api/hashing/#dnsEncode
import { dnsEncode } from 'ethers/lib/utils'
const dnsEncodedName = dnsEncode('name.eth')
```
```solidity [Solidity]
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@ensdomains/ens-contracts/contracts/utils/NameEncoder.sol";
contract MyContract {
function dnsEncode(string calldata name) public pure returns (bytes memory) {
(bytes memory dnsEncodedName,) = NameEncoder.dnsEncodeName(name);
return dnsEncodedName;
}
}
```
:::
#### Decoding
To decode a DNS-encoded name, you can use `bytesToPacket()` from ENSjs.
```tsx
import { bytesToPacket } from '@ensdomains/ensjs/utils'
import { hexToBytes } from 'viem/utils'
const dnsEncodedName = '0x046e616d650365746800'
const name = bytesToPacket(hexToBytes(dnsEncodedName))
// name.eth
```
#### Algorithm
To DNS-encode a name, first split the name into labels (delimited by `.`). Then for each label from left-to-right:
* One byte to denote the length of the label
* The UTF-8 encoded bytes for the label
* If this is the last label, then one final NUL (`0x00`) byte.
For example, to DNS-encode `my.name.eth`:
* `0x02` (length of the label "my")
* `0x6D79` (UTF-8 encoded bytes of "my")
* `0x04` (length of the label "name")
* `0x6E616D65` (UTF-8 encoded bytes of "name")
* `0x03` (length of the label "eth")
* `0x657468` (UTF-8 encoded bytes of "eth")
* `0x00` (end of name marker)
Final result: `0x026d79046e616d650365746800`
:::note
Since the length of each label is stored in a single byte, that means that with this DNS-encoding scheme, each label is limited to being 255 UTF-8 encoded bytes in length. Because of this, names with longer labels cannot be wrapped in the [Name Wrapper](/wrapper/overview), as that contract uses the DNS-encoded name.
:::
## DNS Registrar
In [DNS on ENS](/learn/dns) we learned how ENS aims to extend the functionality of the DNS.
On this page we will explore the implementation of DNSSEC, the DNSRegistrar, and the building blocks for gasless DNSSEC.
:::note
Not all top level domains support DNSSEC, and some might have custom ENS
implementations. Please refer to the [TLD List](/dns/tlds) for TLD-specific
information.
:::
### DNSSEC
DNSSEC (Domain Name System Security Extensions) is an added layer of security on top of DNS that allows for cryptographic verification of records. It establishes a chain of trust from the root key (which is signed by ICANN) down to each key.
All ENS-enabled DNS names are required to use DNSSEC, and the [DNSSECOracle](https://github.com/ensdomains/ens-contracts/tree/master/contracts/dnssec-oracle) is responsible for verifying the signatures.
#### Claiming a Name Onchain
Since 2021, it has been possible to [import a DNS name](/learn/dns#importing-a-dns-name) and use that as an ENS name. This process involves enabling DNSSEC, setting a specific TXT record, and submitting a proof to the [DNSRegistrar](https://github.com/ensdomains/ens-contracts/tree/master/contracts/dnsregistrar) smart contract.
Expect your `TXT` record to look something like this:
```
TXT _ens a=;
```
You can learn more about [how to import a DNS name](/learn/dns#importing-a-dns-name) in the DNS section, or see how to [programmatically complete these steps](#programming-dnssec-proofs).
There is no ENS protocol fee to import a DNS name, but it requires a large amount of gas (up to a few million) to submit the proof onchain. Continue reading to learn how this has been mitigated.
### Offchain Verification (Gasless)
[EP 5.1](/dao/proposals/5.1) introduced a new DNSSECOracle and DNSRegistrar which makes it possible to perform DNSSEC verification at query time, enabling truly free usage of DNS names in ENS. We call this "gasless DNSSEC".
It works by enabling [wildcard resolution](/ensip/10) at the DNS TLD level. During the [name resolution process](/resolution), if a name doesn't already exist onchain but has been configured for usage in ENS, the DNSSEC proof will be fetched offchain via [CCIP Read](https://eips.ethereum.org/EIPS/eip-3668) and then verified onchain with the same DNSSECOracle mentioned above.
#### Import a DNS name gaslessly
To configure a DNS name for usage in ENS, simply add a `TXT` record with the following format:
```
TXT @ ENS1 <resolver-address>
```
The `resolver-address` implementation is customizable just like any other ENS resolver. To get started quickly, a special ExtendedDNSResolver has been deployed which allows users to specify an ETH address that the name should resolve to within the same `TXT` record. To use this setup, simply add a record with the following format:
```
TXT @ ENS1 <extended-resolver-address> <eth-address>
TXT @ ENS1 0x238A8F792dFA6033814B18618aD4100654aeef01 0x225f137127d9067788314bc7fcc1f36746a3c3B5
```
### Other
#### TLD Ownership
You can lookup the `owner` of any TLD by calling the `Registry.owner(bytes32 node)` function.
If at least one domain has been imported for this TLD (via the onchain method), the owner will be either the `DNSRegistrar` or a smart contract controlled by the respective registry operator.
If a TLD has not yet been activated, the `owner` will return `0x0` and it may require one user to import a name onchain to activate the TLD. See the [supported TLD list](/dns/tlds) for more info.
#### Programming DNSSEC Proofs
To help you interact with DNSSEC data and the DNSRegistrar, we provide a few libraries.
* [DNSProvejs](https://github.com/ensdomains/dnsprovejs) = A library for querying and validating DNSSEC data from DNS
* [ENSjs](https://github.com/ensdomains/ensjs) = A library for interacting with ENS smart contracts
##### Retrieving a proof
```ts
import { addEnsContracts } from '@ensdomains/ensjs'
import { getDnsImportData } from '@ensdomains/ensjs/dns'
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({
chain: addEnsContracts(mainnet),
transport: http(),
})
const dnsImportData = await getDnsImportData(client, {
name: 'example.com',
})
```
##### Submitting the proof to the DNSRegistrar
```ts
import { addEnsContracts } from '@ensdomains/ensjs'
import { getDnsImportData, importDnsName } from '@ensdomains/ensjs/dns'
import { createPublicClient, createWalletClient, custom, http } from 'viem'
import { mainnet } from 'viem/chains'
const mainnetWithEns = addEnsContracts(mainnet)
const client = createPublicClient({
chain: mainnetWithEns,
transport: http(),
})
const wallet = createWalletClient({
chain: mainnetWithEns,
transport: custom(window.ethereum),
})
const dnsImportData = await getDnsImportData(client, {
name: 'example.com',
})
await importDnsName(wallet, {
name: 'example.com',
dnsImportData,
})
```
### Other functions
```ts
// Get the list of suffixes
DNSRegistrar.suffixes
// Get Oracle
DNSRegistrar.oracle
```
```ts
DNSRegistrar.claim(bytes name, bytes proof)
DNSRegistrar.proveAndClaim(bytes name, tuple[] input, bytes proof)
DNSRegistrar.proveAndClaimWithResolver(bytes name, tuple[] input, bytes proof, address resolver, address addr)
```
## The Registry \[Root Registry of the Ethereum Name Service]
The ENS registry is the core contract that lies at the heart of ENS resolution. All ENS lookups start by querying the registry. The registry maintains a list of domains, recording the owner, resolver, and TTL for each, and allows the owner of a domain to make changes to that data.
The ENS registry is specified in [EIP 137](https://eips.ethereum.org/EIPS/eip-137).
### Why Registries?
Top-Level Domains (TLDs), like `.eth`, `.com`, and `.test`, are owned by smart contracts called registrars, which specify rules governing the allocation of their names.
Anyone may, by following the rules imposed by these registrar contracts, obtain ownership of a domain for their own use.
| TLD | Registrar Contract |
| ------------------- | -------------------------------------- |
| `[root]` | [The Registry](/registry/ens) |
| `.eth` | [ETH Registry](/registry/eth) |
| `.com`, `.xyz`, etc | [DNS Registrar](/registry/dns) |
| `.addr.reverse` | [Reverse Registrar](/registry/reverse) |
### Who owns the root Registry?
The [ENS Registry](https://etherscan.io/address/0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e) is owned by the [ENS Root](https://etherscan.io/address/0xaB528d626EC275E3faD363fF1393A41F581c5897) which is owned by the [ENS DAO Wallet](https://etherscan.io/address/0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7).
To verify this you can run the `owner` function on the registry & root contracts.
### Other Functions
```ts
// Get Owner
ENS.owner(bytes32 node) view returns (address)
// Get Resolver
ENS.resolver(bytes32 node) view returns (address)
// Get TTL
ENS.ttl(bytes32 node) view returns (uint64)
// Get Approval
ENS.isApprovedForAll(address owner, address operator) view returns (bool)
// Check Record Existence
ENS.recordExists(bytes32 node) view returns (bool)
```
```ts
// Set Owner (only callable by current owner)
ENS.setOwner(bytes32 node, address owner)
// Set Resolver
ENS.setResolver(bytes32 node, address resolver)
// Set TTL
ENS.setTTL(bytes32 node, uint64 ttl)
// Set Subnode Owner
ENS.setSubnodeOwner(bytes32 node, bytes32 label, address owner)
// Set Multiple (convenience function (setResolver, setTTL, setOwner))
ENS.setRecord(bytes32 node, address owner, address resolver, uint64 ttl)
// Set Multiple Subnode
ENS.setSubnodeRecord(bytes32 node, bytes32 label, address owner, address resolver, uint64 ttl)
// Set Approval
ENS.setApprovalForAll(address operator, bool approved)
```
Events
```ts
// Transfer Event
event Transfer(bytes32 indexed node, address owner)
// New Resolver Event
event NewResolver(bytes32 indexed node, address resolver)
// New TTL Event
event NewTTL(bytes32 indexed node, uint64 ttl)
// New Owner Event
event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner)
```
import { FiBookOpen, FiClock, FiHash } from 'react-icons/fi'
import { Card } from '../../components/ui/Card'
## ETH Registrar \[Smart contracts responsible for the ".eth" TLD]
The ETH Registrar is a special registrar. It allows for trustless onchain name registration and is in charge of the ".eth" TLD.
### BaseRegistrar vs Controller
The ETH Registrar is split into two contracts. The [BaseRegistrar](https://github.com/ensdomains/ens-contracts/blob/staging/contracts/ethregistrar/BaseRegistrarImplementation.sol) and the [ETHRegistrarController](https://github.com/ensdomains/ens-contracts/blob/staging/contracts/ethregistrar/ETHRegistrarController.sol).
The BaseRegistrar is responsible for name ownership, transfers, etc (ownership related), while the Controller is responsible for registration & renewal (pricing related). This separation is done to reduce the attack surface of the registrar, and provides users with the guarantees of continued ownership of a name so long as the registrar is in place.
#### Controllers
The [ETHRegistrarController](https://github.com/ensdomains/ens-contracts/blob/staging/contracts/ethregistrar/ETHRegistrarController.sol) is the main controller for the ETH Registrar, and provides a straightforward registration and renewal mechanism.
### Pricing Structure
The ETH Registrar charges a fee for registration.
This fee is paid in ETH and is set to prevent spamming the registrar.
Any protocol fees are sent to the ENS Treasury.
#### Pricing Oracle
Initially, a single pricing oracle was deployed, the [StablePriceOracle](https://github.com/ensdomains/ens-contracts/blob/master/contracts/ethregistrar/StablePriceOracle.sol).
This contract has owner-set prices for each name length (1, 2, 3, 4, 5 or more).
Users do not have to interact with this oracle directly, as the controller provides functionality to determine the pricing for a registration or renewal.
#### 3, 4, and 5 Letter Names
The ETH Registrar has special pricing for 3, 4, and 5 (and more) letter names. At the time of writing, a `5+` letter `.eth` will cost you `5 USD` per year.
A `4` letter `160 USD` per year, and a `3` letter `640 USD` per year.
This pricing structure is done to promote market diversity as there are an exponentially less amount of names the shorter they become.
The minimum length of a name is 3 characters.
| Name Length | Price (USD) |
| ----------- | ----------- |
| 5+ | 5 |
| 4 | 160 |
| 3 | 640 |
#### Premium & Auctions
In addition to length-based pricing the ETH Registrar also has a premium pricing structure.
90 days after a name expires (aka after the grace period), the name will go into a Temporary Premium Auction.
The Auction is a 21 day dutch auction, meaning that the price starts high (\~100 Million USD) and exponentially decrease till it hits 0 or a bid goes through.
This is done to prevent sniping of names, and ensures the name goes to the highest bidder fairly.
You can read more about the temporary premium in [this article](https://support.ens.domains/en/articles/7900612-temporary-premium).
#### Where does the money go?
Upon registration funds are sent to the ETHRegistrarController. The controller then sends the funds to the ENS Treasury (anyone can call the `withdraw` method to trigger this).
Income from the ETH Registrar is used to fund the development of ENS, its ecosystem, and other public goods.
Read more about our spending in [Article III of the Constitution](/dao/constitution#iii-income-funds-ens-and-other-public-goods).
### ERC721 and NFTs
In the early days of ENS, the ERC721 standard did not exist.
The original ETH Registrar formed the pre-cursor to the ERC721 standard.
As we witnessed the ERC721 being standardized, support for it was added to the ETH Registrar.
Today, users can interact with the ETH Registrar to transfer their name just like with any other ERC721 token.
### Registering a Name
Registering a name is a trustless process that takes place onchain (more on this below). Some open source frontends for registering names are the [ENS Manager App](https://app.ens.domains/), [ENS Fairy](https://ensfairy.xyz/), Rainbow Wallet.
The process of registering a `.eth` name uses a commit-reveal process.
Commit
Wait
Reveal
#### Commit-Reveal
The ETHRegistrarController, the highest level contract that users register names through, implements a commit reveal scheme to prevent frontrunning registrations.
We first call the `commit` function with an opaque bit of data (the `commitmenthash`), wait 60 seconds, and then call the `register` function. The `commit` function takes a commitment hash, which can be generated using the `makeCommitment` function. The commitment hash is opaque and revealed during the `register` function.
The commit-reveal process is to prevent a malicious actor from seeing your `register` transaction in the public mempool and frontrunning it.
```solidity
ETHRegistrarController.makeCommitment(
name string,
owner address,
duration uint256,
secret bytes32,
resolver address,
data bytes[],
reverseRecord bool,
ownerControlledFuses uint16
)
// For example
makeCommitment(
"myname", // "myname.eth" but only the label
0x1234..., // The address you want to own the name
31536000, // 1 year (in seconds)
0x1234..., // A randomly generated 32 byte secret you create
0x1234..., // The address of the resolver you want to use
[0x8b95dd71...], // Encoded function calls you want to pass to the resolver, like `setAddr()`
false, // Whether or not to set the new name as your primary name
0 // The NameWrapper fuses you want to set
);
```
Once you have calculated the commitment hash, submit the `commit` transaction.
```solidity
ETHRegistrarController.commit(commitment bytes32)
```
After having committed, it is required to wait at least the `MIN_COMMITMENT_AGE` (60 seconds) before making the subsequent `register` transaction.
#### Registering
Once you have made the onchain commitment and waited 60 seconds, you can register your name.
Registration takes in the same parameters as the `makeCommitment` function above.
Before initiating registration, ensure that:
* `available(label)` == `true`, where `label` is "name" in "name.eth"
* `duration` >= `MIN_REGISTRATION_DURATION`
* `commitments[commitment]` is between 1 min and 24 hrs old
* `msg.value` >= `rentPrice(name, duration)` + `5-10% (slippage)`
Because the rent price is paid in ETH but denominated in USD, callers are recommended to send slightly more than the value returned by `rentPrice` to avoid issues with fast price changes. A premium of 3-5% will likely be sufficient.
Any excess funds sent during registration are automatically returned to the caller.
```solidity
ETHRegistrarController.register(
name string,
owner address,
duration uint256,
secret bytes32,
resolver address,
data bytes[],
reverseRecord bool,
ownerControlledFuses uint16
)
// For example
register(
"myname", // "myname.eth" but only the label
0x1234..., // The address you want to own the name
31536000, // 1 year (in seconds)
0x1234..., // The same secret you used in the `commit` transaction
0x1234..., // The address of the resolver you want to use
[0x8b95dd71...], // Encoded function calls you want to pass to the resolver, like `setAddr()`
false, // Whether or not to set the new name as your primary name
0 // The NameWrapper fuses you want to set
);
```
### Renewing a Name
```solidity
ETHRegistrarController.renew()
```
Any user can renew a domain, not just the owner. This means that if you want to ensure a name doesn't expire you can renew it for someone.
By allowing renewal for any arbitrary amount of time users can ensure their name will not expire.
As per the separation between registry and controller, even with upgraded controller your name will still be yours.
### Other features
```solidity
ETHRegistrarController.MIN_COMMITMENT_AGE uint
ETHRegistrarController.MAX_COMMITMENT_AGE uint
ETHRegistrarController.MIN_REGISTRATION_DURATION uint
// Get Commitment Timestamp
ETHRegistrarController.commitments mapping(bytes32=>uint)
// Get Rent Price
ETHRegistrarController.rentPrice(string name, uint duration) view returns (uint)
// Check Name Validity
ETHRegistrarController.valid(string name) view returns (bool)
// Check Name Availability
// Returns true if the name is both valid and available for registration by this controller.
ETHRegistrarController.available(string name) view returns (bool)
// Calculate Commitment Hash
ETHRegistrarController.makeCommitment(string name, address owner, uint256 duration, bytes32 secret, address resolver, bytes[] data, bool reverseRecord, uint16 ownerControlledFuses) view returns (bytes32)
// Get Name Expiry (unix timestamp at which registration expires)
BaseRegistrar.nameExpires(uint256 label) view returns (uint)
// Check Name Availability (less specific, use ETHRegistrarController.available instead)
BaseRegistrar.available(uint256 label) view returns (bool)
// Get Transfer Period End (unix timestamp at which transfer period (from legacy registrar) ends)
BaseRegistrar.transferPeriodEnds uint
// Get Controller Status
BaseRegistrar.controllers mapping(address=>bool)
// Check Token Approval
BaseRegistrar.getApproved(uint256 tokenId) view returns (address operator)
// Check All Tokens Approval
BaseRegistrar.isApprovedForAll(address owner, address operator) view returns (bool)
// Get Token Owner
BaseRegistrar.ownerOf(uint256 tokenId) view returns (address)
// Get Token URI
BaseRegistrar.tokenURI(uint256 tokenId) view returns (string)
```
Writable
```solidity
// Transfer a Name
BaseRegistrar.transferFrom(address from, address to, uint256 tokenId)
BaseRegistrar.safeTransferFrom(address from, address to, uint256 tokenId)
BaseRegistrar.safeTransferFrom(address from, address to, uint256 tokenId, bytes _data)
// Approve Operator
BaseRegistrar.approve(address to, uint256 tokenId)
// Set Approval For All
BaseRegistrar.setApprovalForAll(address operator, bool approved)
// Reclaim ENS Record
BaseRegistrar.reclaim(uint256 label)
```
Events
```solidity
// BaseRegistrar
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event NameMigrated(uint256 indexed hash, address indexed owner, uint expires);
event NameRegistered(uint256 indexed hash, address indexed owner, uint expires);
event NameRenewed(uint256 indexed hash, uint expires);
// Controller
event NameRegistered(string name, bytes32 indexed label, address indexed owner, uint cost, uint expires);
event NameRenewed(string name, bytes32 indexed label, uint cost, uint expires);
```
## Reverse Registrars
:::info
Reverse resolution is part of the [primary name](/web/reverse) feature. If you're just trying to fetch a name for an address, you should start there.
:::
Reverse resolution is the process of mapping an EVM address (eg, `0x1234...5678`) to an ENS name on a number of different chains. This is accomplished by using special namespaces in the ENS registry:
| Reverse Namespace | Name in the ENS registry |
| ------------------ | ------------------------ |
| Default (Ethereum) | reverse |
| Ethereum | addr.reverse |
| Arbitrum | 8000a4b1.reverse |
| Base | 80002105.reverse |
| Linea | 8000e708.reverse |
| Optimism | 8000000a.reverse |
| Scroll | 80082750.reverse |
L2 namespaces are derived via `[coinTypeAsHex].reverse` as specified in [ENSIP-19](/ensip/19/), and users' reverse records can be resolved via `[address].[reverseNamespace]`.
For example, the account `0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5` can claim `b8c2c29ee19d8307cb7255e1cd9cbde883a267d5.addr.reverse` in the ENS registry. After doing so, it can configure a resolver and expose metadata, such as a canonical ENS name for this address.
The reverse registrar provides functions to `claim` a reverse record,
as well as a convenience function (`setName`) to configure the record as it's most commonly used, as a way of specifying a canonical name for an address.
### Supported Chains
Reverse Registrars are deployed on Ethereum Mainnet (L1) and popular L2s (Base, OP Mainnet, Arbitrum One, Scroll, and Linea). This enables users to set a chain-specific reverse record while also supporting a default reverse record on L1 that acts as a fallback when a chain-specific record is not set.
In practice, it's strongly recommended to **not** hardcode the reverse registrar addresses because they can change in the future and unexpectedly break your application. Instead, resolve them according to [ENSIP-19](/ensip/19).
For convenience, the latest deployments of the reverse registrars are listed below.
{/* TODO: Add an example of how to do this */}
#### Mainnet Deployments
| Chain | Address |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| Default (Ethereum) | [0x283F227c4Bd38ecE252C4Ae7ECE650B0e913f1f9](https://etherscan.io/address/0x283F227c4Bd38ecE252C4Ae7ECE650B0e913f1f9) |
| Ethereum | [0xa58E81fe9b61B5c3fE2AFD33CF304c454AbFc7Cb](https://etherscan.io/address/0xa58E81fe9b61B5c3fE2AFD33CF304c454AbFc7Cb) |
| Arbitrum One | [0x0000000000D8e504002cC26E3Ec46D81971C1664](https://arbiscan.io/address/0x0000000000D8e504002cC26E3Ec46D81971C1664) |
| Base | [0x0000000000D8e504002cC26E3Ec46D81971C1664](https://basescan.org/address/0x0000000000D8e504002cC26E3Ec46D81971C1664) |
| Linea | [0x0000000000D8e504002cC26E3Ec46D81971C1664](https://lineascan.build/address/0x0000000000D8e504002cC26E3Ec46D81971C1664) |
| Optimism | [0x0000000000D8e504002cC26E3Ec46D81971C1664](https://optimistic.etherscan.io/address/0x0000000000D8e504002cC26E3Ec46D81971C1664) |
| Scroll | [0x0000000000D8e504002cC26E3Ec46D81971C1664](https://scrollscan.com/address/0x0000000000D8e504002cC26E3Ec46D81971C1664) |
#### Testnet Deployments
| L2 Testnet Chain | Address |
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Default (Ethereum) | [0x4F382928805ba0e23B30cFB75fC9E848e82DFD47](https://sepolia.etherscan.io/address/0x4F382928805ba0e23B30cFB75fC9E848e82DFD47) |
| Ethereum | [0xA0a1AbcDAe1a2a4A2EF8e9113Ff0e02DD81DC0C6](https://sepolia.etherscan.io/address/0xA0a1AbcDAe1a2a4A2EF8e9113Ff0e02DD81DC0C6) |
| Arbitrum Sepolia | [0x00000BeEF055f7934784D6d81b6BC86665630dbA](https://sepolia.arbiscan.io/address/0x00000BeEF055f7934784D6d81b6BC86665630dbA) |
| Base Sepolia | [0x00000BeEF055f7934784D6d81b6BC86665630dbA](https://sepolia.basescan.org/address/0x00000BeEF055f7934784D6d81b6BC86665630dbA) |
| Linea Sepolia | [0x00000BeEF055f7934784D6d81b6BC86665630dbA](https://sepolia.lineascan.build/address/0x00000BeEF055f7934784D6d81b6BC86665630dbA) |
| Optimism | [0x00000BeEF055f7934784D6d81b6BC86665630dbA](https://sepolia-optimism.etherscan.io/address/address/0x00000BeEF055f7934784D6d81b6BC86665630dbA) |
| Scroll Sepolia | [0x00000BeEF055f7934784D6d81b6BC86665630dbA](https://sepolia.scrollscan.com/address/0x00000BeEF055f7934784D6d81b6BC86665630dbA) |
### Setting Records
The updated Reverse Registrar interface exposes methods to support that make it easier to set a reverse record for an EOA or smart contract:
```solidity
/// @notice Sets the `name()` record for the reverse ENS record associated with the calling account.
/// @param name The name to set
/// @return The ENS node hash of the reverse record
function setName(string memory name) external returns (bytes32);
/// @notice Sets the `name()` record for the reverse ENS record associated with the addr provided account.
/// Can be used if the addr is a contract that is owned by an SCA.
/// @param addr The address to set the name for
/// @param name The name to set
/// @return The ENS node hash of the reverse record
function setNameForAddr(
address addr,
string memory name
) external returns (bytes32);
/// @notice Sets the `name()` record for the reverse ENS record associated with the contract provided that is owned with `Ownable`.
/// @param contractAddr The address of the contract to set the name for (implementing Ownable)
/// @param owner The owner of the contract (via Ownable)
/// @param name The name to set
/// @param coinTypes The coin types to set. Must be inclusive of the coin type for the contract
/// @param signatureExpiry The expiry of the signature
/// @param signature The signature of an address that will return true on isValidSignature for the owner
/// @return The ENS node hash of the reverse record
function setNameForOwnableWithSignature(
address contractAddr,
address owner,
string calldata name,
uint256[] memory coinTypes,
uint256 signatureExpiry,
bytes calldata signature
) external returns (bytes32);
/// @notice Sets the `name()` record for the reverse ENS record associated with the addr provided account using a signature.
/// @param addr The address to set the name for
/// @param name The name of the reverse record
/// @param coinTypes The coin types to set. Must be inclusive of the coin type for the contract
/// @param signatureExpiry Date when the signature expires
/// @param signature The signature from the addr
/// @return The ENS node hash of the reverse record
function setNameForAddrWithSignature(
address addr,
string calldata name,
uint256[] calldata coinTypes,
uint256 signatureExpiry,
bytes calldata signature
) external returns (bytes32);
```
#### Signatures
Signature format for `setNameForAddrWithSignature`:
```
validatorAddress, // the address of the reverse registrar
functionSignature, // 0x2023a04c
name, // string name value
addr, // address to set name for
coinTypes, // array of coinTypes wanting to be set
signatureExpiry // expiry of the signature, up to 1 hour in the future
```
Signature format for `setNameForOwnableWithSignature`:
```
validatorAddress, // the address of the reverse registrar
functionSignature, // 0x975713ad
name, // string name value
contractAddr, // contract address to set name for
owner, // owner address of contract (i.e. the signature being verified)
coinTypes, // array of coinTypes wanting to be set
signatureExpiry // expiry of the signature, up to 1 hour in the future
```
### Other Functions
:::info
The following functions are only available on the ETH Reverse Registrar.
:::
#### Claim Address
```solidity
function claim(address owner) public returns (bytes32);
```
Claims the caller's address in the reverse registrar, assigning ownership of the reverse record to `owner`. Equivalent to calling `claimWithResolver(owner, 0)`. Doesn't actually set the reverse record.
```solidity
function claimWithResolver(address owner, address resolver) public returns (bytes32)
```
Claims the caller's address in the reverse registrar, assigning `ownership` of the reverse record to owner. If `resolver` is nonzero, also updates the record's resolver.
After calling this function:
* The reverse record for the caller (1234....addr.reverse) is owned by `owner`.
* If `resolver` is nonzero, the reverse record for the caller has its resolver set to `resolver`; otherwise it is left unchanged.
#### Get Default Resolver
```solidity
function defaultResolver() public view returns (address);
```
Returns the address of the resolver contract that the `ReverseRegistrar` uses for `setName`.
### Do's and Dont's
Under no situation is it recommended to force a user to change their primary name, nor doing so without clearly notifying the user of what the transaction they are about to execute could modify.
Doing so could be seen as hostile or undesired behaviour by end users and might degrade their experience with your app.
## Layer 2 & Offchain Resolution
All ENS resolution starts on Ethereum Mainnet (or testnet).
However, by leveraging [CCIP Read](/resolvers/ccip-read) and [Wildcard Resolution](/ensip/10), name resolution can be taken cross-chain, offchain, and more.
This allows for a lot of flexibility in how you can use your ENS and for storage of your ENS records on your favourite Layer 2, or even offchain.
### ENS on Layer 2
In the resolution process, clients first fetch the resolver associated with the name in the ENS registry on L1. That resolver is responsible for telling the client where to find the data associated with the name such as the addresses, text records, etc.
If you want to register and resolve (sub)names from L2, you would write a resolver smart contract that defers resolution to the L2 and ideally verifies that data against the L2's storage proofs posted to L1. This process can be done with the [Unruggable Gateway](https://gateway-docs.unruggable.com/).
An example implementation of Layer 2 resolving is:
#### linea.eth
Linea was the first L2 team to build a trust-minimized ENS subname system. Names are stored on Linea, verified with [storage proofs](https://docs.linea.build/developers/tooling/cross-chain/ccip-read-gateway) on L1, and function as ENS subnames such as [greg.linea.eth](https://app.ens.domains/greg.linea.eth). You can try it out [here](https://names.linea.build/).
#### clv.eth
Clave is focused on enhancing user experience and security through a mobile wallet that leverages account abstraction and device hardware. Clave accounts come with usernames that are now stored onchain in ZKsync Era, verified with [storage proofs](https://github.com/getclave/zksync-storage-proofs) in L1, and issued as ENS subnames such as [ulas.clv.eth](https://app.ens.domains/ulas.clv.eth). You can read more about the implementation [here](https://blog.getclave.io/introducing-onchain-clave-usernames-with-ens).
### Primary Names on Layer 2
The process of setting primary names from L2 is under active development. This doc will be updated as more information becomes available.
### Offchain Resolution
Moving resolution processes offchain offers numerous advantages, including efficiency gains and reduced congestion on the main blockchain; however, it also introduces trade-offs in terms of trust, as it necessitates reliance on external systems.
Depending on the implementation, names could be stored in a database or be ephemeral.
Advantages of offchain name storage include gaslessness and instant updates.
If this sounds appealing consider [writing an Offchain Resolver](/resolvers/ccip-read).
Popular implementations of offchain names include but are not limited to:
#### cb.id
Coinbase Wallet is one of the largest mobile wallets issuing free ENS subnames to their users.
These names are stored offchain on coinbase servers, and can be registered from the Coinbase Wallet App or Browser Extension.
An example of a cb.id is [jesse.cb.id](https://app.ens.domains/jesse.cb.id).
#### uni.eth
Uniswap Wallet is another popular mobile wallet that issues free ENS subnames to their users.
You can read more about the Uniswap Wallet ENS integration [here](https://blog.uniswap.org/introducing-uni-eth-your-unique-web3-username).
An example of a uni.eth is [chase.uni.eth](https://app.ens.domains/chase.uni.eth).
import { ContractDeployments } from '../../components/ContractDeployments'
import { EmbedLink } from '../../components/EmbedLink'
import { Card } from '../../components/ui/Card'
## Deployments
:::note
This page contains information that is only relevant to developers who would
like to interact with the contract manually. Most libraries will handle this
for you.
:::
ENS is multichain (read more [here](/web/multichain)) and can be used in any application.
In addition to being able to query many address formats and record types, data can be stored [practically anywhere](/learn/ccip-read).
However, resolution needs to start somewhere, so the entrypoint for resolution is Ethereum Mainnet, alongside the most popular testnets.
Ethereum Mainnet
→
BaseArbitrumOffchain...
### Deployments
Below you will find an abbreviated list of our latest contract deployments. The source of truth for ENS contract deployments is the [ensdomains/ens-contracts](https://github.com/ensdomains/ens-contracts/tree/staging/deployments) repository.
#### Mainnet
Interact with ENS on Ethereum Mainnet via [app.ens.domains](https://app.ens.domains).
#### Sepolia
Interact with ENS on the Sepolia testnet via [sepolia.app.ens.domains](https://sepolia.app.ens.domains).
#### Holesky
:::warning
Holesky testnet support is being phased out. Please migrate your workflows to Sepolia.
:::
Interact with ENS on the Holesky testnet via [holesky.app.ens.domains](https://holesky.app.ens.domains).
### But what about multichain?
While the core ENS protocol lives on Ethereum Mainnet, it can be used to resolve data for any chain!
import { DNSGrid } from '../../components/DNSGrid'
import { DNSUsageExamples } from '../../components/DNSUsageExamples'
import { EmbedLink } from '../../components/EmbedLink'
## DNS on ENS \[ENS supports DNS names, allowing users to import DNS names into ENS.]
The Ethereum Name Service is so much more than just `.eth` names. It is a general-purpose naming system that can be used for any kind of name. This includes DNS names.
DNS functionality was originally introduced in [ENSIP-6](/ensip/6).
### Importing a DNS name
There are currently two ways of importing a DNS name into ENS. Both methods require you enable DNSSEC on your domain, and setup a TXT record.
This record is then verified using smart contracts on the Ethereum blockchain.
To import a name, simply visit the [ENS Manager](https://ens.app), type in your name, and click "Import DNS". You will walked through setting up your DNS records.
Additionally you can read more about the records and specifications here:
### Why DNS on ENS?
ENS aims to extend the existing functionality of the DNS system. This also means that existing DNS names (such as `.com`, `.org`, or `.xyz`) should be able to leverage the benefits of the ENS resolution process.
### DNS Names in the wild
DNS names are widely used and many users may already have one without even realizing it. Some major platforms that issue subdomains of their DNS names include:
### Top-Level Domains
In addition to allowing any DNSSEC enabled name to be imported, ENS also allows existing DNS TLDs to take control of their smart-contract resolution process.
Resulting in even more seamless integration with the DNS system.
A list of all supported TLDs can be found [here](/dns/tlds)
import { DNSGrid } from '../../components/DNSGrid'
import { EmbedLink } from '../../components/EmbedLink'
import { EnsProfile } from '../../components/EnsProfile'
import { Card } from '../../components/ui/Card'
## What is the Ethereum Name Service?
The Ethereum Name Service (ENS) is a distributed, open, and extensible naming system based on the Ethereum blockchain.
ENS maps human-readable names like 'alice.eth' to machine-readable identifiers such as Ethereum addresses, other cryptocurrency addresses, content hashes, metadata, and more.
ENS also supports 'reverse resolution', making it possible to associate metadata such as primary names or interface descriptions with Ethereum addresses.
Top-Level Domains (TLDs), like `.eth` and `.test`, are owned by smart contracts called [registrars](/registry/eth), which specify rules governing the allocation of their names.
Enabling seamless interoperability with the DNS (Domain Name System).
### ETH Registrar
The [ETH Registrar](/registry/eth) is the registrar for the `.eth` TLD, it allows for trustless decentralized names to be issued as tokens on the Ethereum Blockchain.
Registration is done through smart contracts, and name ownership is secured by the Ethereum blockchain.
### DNS + ENS
ENS has similar goals to DNS, the existing Internet's Domain Name Service, and aims to extend its capability.
ENS also supports importing DNS names through the use of DNSSEC.
Allowing you to take your `.com`, `.xyz`, or `.art` (and more) into the ENS ecosystem. Read more about DNSSEC names [on this page](/learn/dns).
### Subnames
Because of the hierarchical nature of ENS, anyone who owns a domain at any level can take control of resolution.
Users can create subdomains manually, or take matters into their own hands and write their own resolution logic.
For instance, if Alice owns 'alice.eth', she can create 'pay.alice.eth' and configure it as she wishes.
Or, use a [Custom Resolver](/resolvers/quickstart), and programmatically issue subdomains, for example in an App, Community, or DAO.
### ENS Manager App
You can try ENS out for yourself now by using the [ENS Manager App](https://ens.app/), or by using any of the many ENS enabled applications on [our homepage](https://ens.domains/).
import { EnsProfile } from '../../components/EnsProfile'
import { Card } from '../../components/ui/Card'
## Resolution \[The ENS Resolution Process]
:::note
This document aims to provide a brief overview of how resolution works, to
read more about resolution checkout the [dedicated Resolution
Section](/resolution).
:::
One of the major parts of the ENS protocol is the resolution process. The Resolution process at its core is the process of converting a human-readable name to a machine-readable address.
Though there is a lot of smart contract magic under the hood, the ENS system consists of two main paths: [Forward Resolution](#forward-resolution), used to go from name to address (and load other extra data), and [Reverse Resolution](#reverse-resolution), used to go from address to name.
### Forward Resolution
Forwards resolution is the process of going **from name to address**. As well as to load the records associated to a name.
These records include but are not limited to **discord**, **twitter**, **github**, **email**, **timezone**, and more.
{/* TODO: Turn this into a component and fetch live data */}
➡️⬇️
ETH Address: 0x5555...3dCa
BTC Address: 1RicMoo...Jyn
Twitter: @ricmoo
Github: @ricmoo
...
Implementing forwards resolution in a dApp can be as simple as using a single line of code!
To learn more about how to implement forwards resolution, check out the [Address Resolution](/web/resolution) documentation.
### Reverse Resolution
Reverse resolution is the process of going **from address to name**. This is a crucial part of the ENS system, as it allows for any address, to be resolved into a human readable name.
Instead of pages filled with addresses, you can now show the names of the people behind the addresses.
0x225...c3B5to
Implementing reverse resolution in a dApp can be as simple as using a single line of code!
To learn more about how to implement reverse resolution, check out the [Address Resolution](/web/reverse) documentation.
import { FrenCallout } from '../../components/ensv2/FrenCallout'
## DNS Name Resolution
ENSv2 supports resolving traditional DNS domain names (like `.com`, `.xyz`) through the ENS protocol, provided the domain has DNSSEC enabled. This builds on the [DNS on ENS](/learn/dns) functionality from ENSv1, replacing the single `OffchainDNSResolver` ([ENSIP-17](/ensip/17)) with a set of three specialized contracts.
Users don't interact with these contracts directly. The [Universal Resolver V2](/ensv2/universal-resolver-v2) handles the entire flow transparently. This page explains what happens internally and how to configure DNS records for ENS resolution.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### How It Works
ENSv2's DNS resolution fetches DNSSEC proofs off-chain via [CCIP-Read](/resolvers/ccip-read) and verifies them on-chain. The on-chain DNSSEC proof submission used by the v1 [DNS Registrar](/registry/dns) for claiming DNS names remains on v1 infrastructure at launch.
When the Universal Resolver encounters a DNS name (e.g., `example.com`), it finds the **DNSTLDResolver** set as the resolver for that TLD on the root registry. The DNSTLDResolver then follows a multi-step resolution strategy:
1. **Check ENSv1**: look for an existing resolver in the ENSv1 registry. If one exists (and it's not the v1 DNS TLD resolver or the DNSTLDResolver itself), delegate to it directly. This preserves backward compatibility.
2. **Query DNSSEC**: initiate a [CCIP-Read](/resolvers/ccip-read) request to fetch DNSSEC-signed TXT records for the domain.
3. **Parse the TXT record**: find the first TXT record starting with `ENS1` that yields a valid resolver address, and extract the resolver address and context.
4. **Delegate to the parsed resolver**: forward the request to the resolver contract specified in the TXT record. ENS ships two standard resolver implementations: the **DNSAliasResolver** (for name rewriting) and the **DNSTXTResolver** (for inline record data), but any contract implementing `IExtendedDNSResolver` can be used.
```mermaid
flowchart LR
UR["Universal Resolver"] --> TLD["DNSTLDResolver"]
TLD --> V1{"Usable v1 resolver?"}
V1 -- yes --> Legacy["Delegate to v1 resolver"]
V1 -- no --> DNSSEC["Fetch & parse ENS1 TXT record (CCIP-Read)"]
DNSSEC -- "name rewriting" --> Alias["DNSAliasResolver"]
DNSSEC -- "inline records" --> TXT["DNSTXTResolver"]
```
### TXT Record Formats
To enable ENS resolution for a DNS domain, add a TXT record with the prefix `ENS1`:
```
ENS1
```
The `` can be either a hex address (`0x1234...`) or an ENS name that resolves to the resolver contract. The `` depends on which resolver is used.
#### Inline Records (DNSTXTResolver)
Set the resolver to the DNSTXTResolver address and encode records directly in the context. Multiple records are separated by spaces:
```
ENS1 dnstxt.ens.eth a[60]=0x1234... t[avatar]=https://example.com/pic.png
```
Supported record formats:
| Prefix | Record type | Example |
| --------------- | ---------------------------------------------------------- | ----------------------- |
| `a[60]` | Ethereum address ([ENSIP-9](/ensip/9)) | `a[60]=0x1234...` |
| `a[e0]` | Default EVM address, any chain ([ENSIP-19](/ensip/19)) | `a[e0]=0x1234...` |
| `a[e]` | EVM chain address by chain ID ([ENSIP-19](/ensip/19)) | `a[e8453]=0x5678...` |
| `a[]` | Non-EVM address by SLIP-44 coin type ([ENSIP-9](/ensip/9)) | `a[0]=bc1qar0...` |
| `t[key]` | Text record | `t[avatar]=https://...` |
| `c` | Content hash ([ENSIP-7](/ensip/7)) | `c=0xe301...` |
| `d[key]` | Data record | `d[mykey]=0x1234...` |
| `xy` | Public key (x and y concatenated, 64 bytes) | `xy=0x...` |
Values are separated by spaces. To include spaces in a value, wrap it in single quotes: `t[description]='Hello World'`. Escape single quotes with a backslash: `t[notice]='it\'s ENS'`.
This is the simplest approach: all record data lives in the DNS TXT record itself, with no on-chain registration required.
#### Alias Resolution (DNSAliasResolver)
Set the resolver to the DNSAliasResolver address and provide a rewrite rule in the context. Two modes are supported:
**Suffix replacement**: rewrite the DNS suffix to an ENS suffix. For example, setting this TXT record on `example.com`:
```
ENS1 dnsalias.ens.eth com base.eth
```
The context `com base.eth` tells the resolver to strip the `com` suffix and replace it with `base.eth`. So `sub.example.com` resolves as `sub.example.base.eth`, and `example.com` itself resolves as `example.base.eth`. The resolver then looks up the rewritten name through the ENSv2 registry hierarchy.
**Full replacement**: map a DNS domain to a single ENS name. When the context contains no space, the resolver ignores the queried name entirely and resolves the context as an ENS name. For example, setting this TXT record on `example.com`:
```
ENS1 dnsalias.ens.eth alice.eth
```
This makes `example.com` resolve as `alice.eth`.
import { FrenCallout } from '../../components/ensv2/FrenCallout'
import { ContractReference } from '../../components/ensv2/ContractReference'
import { MonoDiagram } from '../../components/ensv2/MonoDiagram'
## Enhanced Access Control
Enhanced Access Control (EAC) is the permission system used throughout ENSv2. It controls who is allowed to do what, and on which names. EAC supports up to 2^256 independent resources (e.g., individual names), 64 roles per resource (32 regular + 32 admin), and up to 15 holders of a role within the context of a single resource.
Think of it like a building where each room has its own set of locks: you can give someone a key to one room, or a master key that opens every room. EAC works the same way, but with ENS names and on-chain permissions.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### Resources
A **resource** is the thing you're controlling access to. In most ENS contracts, a resource is a name - but it can be any `uint256` identifier that makes sense for the contract.
Each ENSv2 contract defines how its resources are computed. For example, registries derive resources from a name's labelhash, while resolvers derive resources from a namehash and record type. See [How Contracts Use EAC](#how-contracts-use-eac) for the specific schemes.
There's also a special resource called **`ROOT_RESOURCE`** (`0x0`) that represents the contract itself. Permissions granted on `ROOT_RESOURCE` apply everywhere - like a master key. If you have a role on `ROOT_RESOURCE`, you automatically have that role on every individual resource too.
```mermaid
graph TD
ROOT["ROOT_RESOURCE (0x0)\nApplies to all names"]
A["alice.eth"]
B["bob.eth"]
C["carol.eth"]
ROOT -.->|"master key"| A
ROOT -.->|"master key"| B
ROOT -.->|"master key"| C
```
### Roles
A **role** represents a specific permission - for example, "can set the resolver" or "can register subnames". Each ENS contract defines the roles that are relevant to it.
Roles are always tied to a resource. Granting someone the "set resolver" role on `alice.eth` doesn't let them set the resolver on `bob.eth`. To give someone a permission across all names, grant the role on `ROOT_RESOURCE` instead.
Up to 15 accounts can hold the same role on the same resource, enabling shared management and delegation.
**How role checks work:** when a contract checks whether an account has a role on a specific name, it looks in two places - the name itself and `ROOT_RESOURCE` - and allows the action if the role is found in either. This is how the master key effect works.
### Admin Roles
Every role has a corresponding **admin role** that controls who can manage it. If you hold the admin role, you can:
* **Grant** the regular role to other accounts
* **Grant** the admin role itself to other accounts
* **Revoke** either role from other accounts
```mermaid
graph LR
Admin["Admin Role\n(manages access)"]
Regular["Regular Role\n(performs actions)"]
Admin -->|"grant / revoke"| Regular
Admin -->|"grant / revoke"| Admin
```
For example, the admin role for "set resolver" controls who is allowed to grant or revoke the "set resolver" permission. Admin roles follow the same resource-scoping - you can be an admin for a specific name or for all names via `ROOT_RESOURCE`.
### Granting and Revoking
Roles are managed through four functions:
| Grant | Revoke | Scope |
| ---------------- | ----------------- | ------------------------------- |
| `grantRoles` | `revokeRoles` | Specific resource (name) |
| `grantRootRoles` | `revokeRootRoles` | `ROOT_RESOURCE` (contract-wide) |
The caller must hold the admin role for every role being granted or revoked. Admin role holders can also revoke the admin role itself, including from themselves.
As a safety guardrail, `grantRoles` and `revokeRoles` reject `ROOT_RESOURCE`. You must use `grantRootRoles` / `revokeRootRoles` explicitly for contract-wide permissions. This prevents accidental global grants.
All four functions return `true` if the account's roles actually changed, or `false` if the roles were already in the desired state.
### Callback Hooks
EAC provides five internal hooks that contracts can override to customize permission behavior:
#### Role Resolution Hook
* **`_getRoles(resource, account)`**: returns the role bitmap for an account on a resource. By default, returns the stored roles directly. Contracts override this to inject additional role logic at read time. The [Permissioned Registry](/ensv2/permissioned-registry) overrides `_getRoles` so that ERC1155-approved operators (via `setApprovalForAll`) inherit the token owner's roles on that name.
#### Role Change Callbacks
* **`_onRolesGranted(resource, account, oldRoles, newRoles, roleBitmap)`**: called after roles are successfully granted. Receives the account's role bitmap before and after the change, plus the specific roles that were newly added.
* **`_onRolesRevoked(resource, account, oldRoles, newRoles, roleBitmap)`**: called after roles are successfully revoked. Same parameters, with `roleBitmap` containing the roles that were actually removed.
Both are no-ops in the base implementation. The [Permissioned Registry](/ensv2/permissioned-registry) overrides these to [regenerate](/ensv2/mutable-token-ids#regeneration) ERC1155 tokens when roles change, invalidating stale transfer approvals.
#### Grant/Revoke Restriction Hooks
* **`_getSettableRoles(resource, account)`**: returns which roles the account is allowed to grant on a given resource. By default, an account can grant any role for which it holds the corresponding admin role. Contracts override this to impose additional restrictions.
* **`_getRevokableRoles(resource, account)`**: returns which roles the account is allowed to revoke on a given resource. Same default behavior as settable roles.
The [Permissioned Registry](/ensv2/permissioned-registry) overrides `_getSettableRoles` to prevent admin role escalation on individual names after registration. Role changes on unregistered or reserved names are blocked implicitly: `_getRoles` returns no effective roles when a name has no owner.
### Bitmap Layout
Under the hood, roles are packed into a single `uint256` bitmap split into two halves. Each role occupies one nybble (4 bits), giving space for up to 32 regular roles and 32 corresponding admin roles:
Each nybble is a 4-bit slot. A regular role at nybble index `N` occupies bits `4N` to `4N + 3`, so nybble 0 is bits 0–3, nybble 1 is bits 4–7, and so on. Its admin counterpart sits at the same position in the upper half (`4N + 128` to `4N + 131`).
EAC tracks role assignments in two mappings:
* **`_roles[resource][account]`**: stores which roles a given account holds on a given resource. Each nybble is either `0` (no role) or `1` (has role).
* **`_roleCount[resource]`**: stores how many accounts hold each role on a given resource. Each nybble is a count from 0 to 15, using the same nybble-per-role layout. This is why the maximum number of assignees per role is 15: it's the largest value a 4-bit nybble can store.
### Replacing Fuses
EAC replaces the one-way [fuse system](/wrapper/fuses) from the Name Wrapper. The key conceptual shift: in ENSv1, you **burned** permissions to restrict what could be done. In ENSv2, you **revoke** roles instead. Both achieve the same end result, but revoking is reversible if you hold the admin role.
| Feature | ENSv1 Fuses | ENSv2 EAC |
| ----------------- | ------------------------------ | ----------------------------------------------------------- |
| Revocability | One-way burn — permanent | Reversible grant/revoke (while admin role is held) |
| Delegation | Single owner only | Up to 15 accounts per role per resource |
| Scope | Per-name only | Per-name or contract-wide via `ROOT_RESOURCE` |
| Extensibility | Fixed set of 16 fuses | Each contract defines its own roles (up to 32) |
| Transfer control | `CANNOT_TRANSFER` fuse | `ROLE_CAN_TRANSFER_ADMIN` (revoke to make non-transferable) |
| Resolver control | `CANNOT_SET_RESOLVER` fuse | `ROLE_SET_RESOLVER` (revoke to lock resolver) |
| Subdomain control | `CANNOT_CREATE_SUBDOMAIN` fuse | `ROLE_REGISTRAR` (revoke to prevent new subnames) |
### How Contracts Use EAC
Each ENSv2 contract defines its own roles and its own resource scheme. See the EAC Permissions section on each contract page for details:
* [Permissioned Registry: EAC Integration](/ensv2/permissioned-registry#eac-integration): 10 roles, labelhash-based resources with version isolation, anyId polymorphism
* [Permissioned Resolver: EAC Integration](/ensv2/permissioned-resolver#eac-integration): 11 roles (8 per-record), fine-grained scoping down to individual keys or coin types
### Reference
#### Write Functions
#### View Functions
#### Constants
#### Events
For code examples of granting and revoking roles, see the [Permissioned Registry](/ensv2/permissioned-registry#code-examples) and [Permissioned Resolver](/ensv2/permissioned-resolver#code-examples) pages.
import { FrenCallout } from '../../components/ensv2/FrenCallout'
## ERC1155Singleton
ENSv2 represents names as tokens using a modified ERC1155 implementation called **ERC1155Singleton**. Unlike standard ERC1155 where tokens can be fungible (multiple owners holding quantities of the same token), each ERC1155Singleton token has exactly one owner - combining the ownership semantics of ERC721 with the batching and interface compatibility of ERC1155.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### Why Not ERC721?
ENSv1 used ERC721 for `.eth` names via the BaseRegistrar. ENSv2 switched to a modified ERC1155 for several reasons:
* **Batch operations**: ERC1155 natively supports batch transfers and balance queries, which is useful when operating on multiple names at once
* **Interface compatibility**: the ERC1155 interface is well-supported by wallets, marketplaces, and indexers
* **Gas efficiency**: ERC1155Singleton removes the balance tracking overhead of standard ERC1155 while using a simpler approval model than the NameWrapper's hybrid per-token and operator approvals
### Key Differences from Standard ERC1155
| Feature | Standard ERC1155 | ERC1155Singleton |
| ------------------------ | ------------------------------- | --------------------------------------- |
| Tokens per ID | 0 to N (fungible) | 0 or 1 (singleton) |
| `ownerOf(id)` | Not available | Returns the single owner |
| `balanceOf(account, id)` | Returns quantity held | Returns `1` (owner) or `0` |
| Balance tracking | Mapping of balances per account | Flat `id -> address` mapping |
| Transfer value | Any amount | Must be exactly `1` (reverts otherwise) |
### Ownership Model
ERC1155Singleton provides a simple ownership model:
```solidity
// Returns the owner of a token, or address(0) if it doesn't exist
function ownerOf(uint256 id) external view returns (address)
// Returns 1 if `account` owns `id`, 0 otherwise
function balanceOf(address account, uint256 id) external view returns (uint256)
```
Under the hood, ownership is stored in a flat mapping from token ID to owner address. There's no separate balance counter - `balanceOf` simply checks whether the queried account matches the stored owner.
### Gas Optimization
The key gas saving comes from eliminating balance accounting. In a standard ERC1155, every transfer requires two storage writes: decrement the sender's balance and increment the receiver's balance.
In ERC1155Singleton, a transfer is just a single storage write: updating the `id -> owner` mapping. This makes transfers noticeably cheaper.
### HCA Support
ERC1155Singleton integrates with the [Hidden Contract Account (HCA)](/ensv2/hca) system. When resolving `msg.sender`, the contract checks whether the caller is a registered HCA (a smart contract wallet) and, if so, uses the HCA's owner address instead.
This means that if you interact with an ENSv2 registry through a smart contract wallet that is registered as an HCA, the registry recognizes you as the owner rather than the wallet contract. This is particularly important for:
* Smart contract wallets (e.g., Safe, ERC-4337 accounts) that hold names
* Proxy contracts that act on behalf of users
### Integration with the Registry
The [Permissioned Registry](/ensv2/permissioned-registry) inherits from ERC1155Singleton. Every registered name becomes a singleton token:
* **Minting** happens when a name is registered via `register()`
* **Burning** happens when a name is unregistered via `unregister()`, or when it expires and is re-registered
* **Token regeneration** happens when roles change on a name - the old token is burned and a new one is minted to the same owner with a new ID (see [Mutable Token IDs](/ensv2/mutable-token-ids))
The singleton constraint means that standard ERC1155 operations like `safeTransferFrom` work as expected, but attempting to transfer a value greater than `1` will revert.
### Marketplace Compatibility
Because ERC1155Singleton implements the full `IERC1155` interface, names are compatible with standard NFT marketplaces and tools:
* **Marketplaces** (OpenSea, Blur, etc.) can list and trade names
* **Wallets** display names alongside other ERC1155 tokens
* **Indexers** track ownership changes through standard `TransferSingle` and `TransferBatch` events
One important caveat: because [token IDs can change](/ensv2/mutable-token-ids) when roles are updated, marketplace listings tied to an old token ID will become invalid. This is by design - it prevents an attack where a seller changes the name's permissions after listing it for sale.
### Interface
`IERC1155Singleton` extends the standard `IERC1155` with a single addition, `ownerOf`, analogous to ERC721's function of the same name:
```solidity
/// @dev Interface selector: 0x6352211e
interface IERC1155Singleton is IERC1155 {
/// Returns the owner of a token, or address(0) if it doesn't exist.
function ownerOf(uint256 id) external view returns (address owner);
}
```
Internally, the contract replaces the standard ERC1155 nested balance mapping (`id → address → balance`) with a flat `id → address` ownership mapping. The [Permissioned Registry](/ensv2/permissioned-registry) overrides `ownerOf` to add expiry and version validation on top of raw ownership, returning `address(0)` for expired names or stale token IDs.
Unlike the standard ERC1155 which has a `_uri` storage variable, ERC1155Singleton makes `uri()` abstract with no built-in storage. The [Permissioned Registry](/ensv2/permissioned-registry) overrides it with a two-mode implementation: a static URI string, or delegation to an [`IRegistryURIRenderer`](/ensv2/permissioned-registry#token-metadata) contract for dynamic per-token metadata.
The contract emits all standard ERC1155 events (`TransferSingle`, `TransferBatch`, `ApprovalForAll`, `URI`).
import { FrenCallout } from '../../components/ensv2/FrenCallout'
import { ContractReference } from '../../components/ensv2/ContractReference'
import { Card } from '../../components/ui/Card'
## ETH Registrar
The ETH Registrar manages the registration and renewal of `.eth` names in ENSv2. It retains the proven [commit-reveal pattern from ENSv1](/registry/eth#registering-a-name) while adding multi-year registration discounts, ERC20 payment support, and integration with the [Permissioned Registry](/ensv2/permissioned-registry).
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### What Changed from ENSv1
| Feature | ENSv1 | ENSv2 |
| -------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| Grace period | 90-day grace period after expiry | **28-day grace period** |
| Payment | ETH only | ERC20 tokens via `safeTransferFrom` |
| Pricing | USD-denominated, no duration discounts | USD-denominated, multi-year duration discounts |
| Name ownership | ERC721 on BaseRegistrar (optionally wrapped to ERC1155 via [NameWrapper](/wrapper/overview)) | [ERC1155Singleton](/ensv2/erc1155-singleton) token on Permissioned Registry |
| Permissions | Owner has full control; no granular roles | Registrant receives a fixed set of [EAC](/ensv2/enhanced-access-control) roles |
### Pricing
#### Base Rate
Names are priced annually by character count:
| Name Length | Annual Price |
| ------------- | ------------ |
| 3 characters | $640/yr |
| 4 characters | $160/yr |
| 5+ characters | $8/yr |
#### Multi-Year Discounts
Longer registrations (and renewals) receive a discount applied to the entire duration. The discount is determined by the total term in a single transaction:
| Term | Discount | 5+ char /yr | 5+ char total | 4-char /yr | 4-char total | 3-char /yr | 3-char total |
| ---- | -------- | ----------- | ------------- | ---------- | ------------ | ---------- | ------------ |
| 1 yr | 0% | $8.00 | $8.00 | $160 | $160 | $640 | $640 |
| 2 yr | 12.5% | $7.00 | $14.00 | $140 | $280 | $560 | $1,120 |
| 3 yr | \~31% | $5.50 | $16.50 | $110 | $330 | $440 | $1,320 |
| 4 yr | \~31% | $5.50 | $22.00 | $110 | $440 | $440 | $1,760 |
| 5 yr | \~31% | $5.50 | $27.50 | $110 | $550 | $440 | $2,200 |
| 6 yr | \~44% | $4.50 | $27.00 | $90 | $540 | $360 | $2,160 |
Because the discount rate increases at six years, the total cost for six years is lower than for five. For example, renewing a 5+ character name for one year always costs $8 regardless of the existing expiration; renewing the same name for six years costs $27 ($4.50/yr).
Pricing parameters (base rates and discount tiers) are immutable per oracle deployment. Changing them requires deploying a new oracle and calling `setRentPriceOracle()`.
#### Expiry Premium
After the 28-day grace period, recently-expired names enter a 21-day temporary premium period to prevent sniping. The premium starts at \~$100M and decays exponentially (halving every day), reaching zero at the end of the window.
#### Querying Prices
Both the registrar and the oracle expose `getRegisterPrice` and `getRenewPrice`, but they serve different purposes:
* **Registrar** (stateful): `getRegisterPrice(label, duration, paymentToken)` returns the actual cost for a specific name right now. It reads registry state to determine how long the name has been available, then forwards to the oracle. Reverts if the name is not available or the duration is below the minimum.
* **Oracle** (stateless): `getRegisterPrice(label, available, duration, paymentToken)` takes a fully parameterized `available` duration and returns what the price would be for any hypothetical scenario. It has no knowledge of registry state or minimums.
Use the registrar's version when you need the true price for a specific name (e.g., before calling `register()`). Use the oracle directly when you need hypothetical pricing (e.g., a pricing calculator or "what would this cost?" UI).
### Multi-Token Payments
Unlike ENSv1 which only accepted ETH, the ENSv2 registrar supports payment in multiple ERC20 tokens. The `StandardRentPriceOracle` maintains exchange rate ratios for each accepted token. Check accepted tokens via `isPaymentToken(token)` on the `StandardRentPriceOracle`.
Payment is collected via `safeTransferFrom` to an immutable beneficiary address. The caller must approve the registrar for the payment token before calling `register()` or `renew()`.
### Registering a Name
Like [ENSv1](/registry/eth#registering-a-name), the ETH Registrar uses a commit-reveal scheme to prevent front-running registrations. You first call `commit` with an opaque commitment hash, wait at least `MIN_COMMITMENT_AGE` (60 seconds), then call `register` to reveal the parameters and complete registration.
#### Commit-Reveal
Generate a commitment hash using `makeCommitment`:
```solidity
ETHRegistrar.makeCommitment(
label string, // "alice" for alice.eth (label only, not full name)
owner address, // The address that will own the name
secret bytes32, // A randomly generated 32-byte secret
subregistry IRegistry, // Child registry to set (or address(0) for none)
resolver address, // Resolver contract to set
duration uint64, // Registration duration in seconds
referrer bytes32 // Referral identifier (or bytes32(0))
)
// For example
makeCommitment(
"alice",
0x1234...,
0xabcd..., // Random secret, generate off-chain
address(0), // No subregistry
0x5678..., // Resolver address
31536000, // 1 year in seconds
bytes32(0) // No referrer
);
```
Once you have calculated the commitment hash, submit it on-chain:
```solidity
ETHRegistrar.commit(commitment bytes32)
```
After committing, wait at least `MIN_COMMITMENT_AGE` (60 seconds) before calling `register`. The commitment expires after `MAX_COMMITMENT_AGE` (typically 24 hours).
#### Registering
Before initiating registration, ensure that:
* `isAvailable(label)` returns `true`
* `duration` >= `MIN_REGISTER_DURATION`
* The commitment is between `MIN_COMMITMENT_AGE` and `MAX_COMMITMENT_AGE` old
* The payment token is approved for `base + premium` (query via `getRegisterPrice`)
```solidity
ETHRegistrar.register(
label string, // Same label used in makeCommitment
owner address, // Same owner used in makeCommitment
secret bytes32, // Same secret used in makeCommitment
subregistry IRegistry, // Same subregistry used in makeCommitment
resolver address, // Same resolver used in makeCommitment
duration uint64, // Same duration used in makeCommitment
paymentToken IERC20, // ERC20 token to pay with (must be approved)
referrer bytes32 // Same referrer used in makeCommitment
) returns (uint256 tokenId)
// For example
register(
"alice",
0x1234...,
0xabcd..., // Same secret as in commit step
address(0),
0x5678...,
31536000,
0x9abc..., // USDC token address
bytes32(0)
);
```
##### Roles Granted at Registration
The registration grants the owner a fixed set of roles defined by `REGISTRATION_ROLE_BITMAP`:
* `ROLE_SET_SUBREGISTRY`: change the name's child registry
* `ROLE_SET_SUBREGISTRY_ADMIN`: delegate `ROLE_SET_SUBREGISTRY` to others
* `ROLE_SET_RESOLVER`: change the name's resolver
* `ROLE_SET_RESOLVER_ADMIN`: delegate `ROLE_SET_RESOLVER` to others
* `ROLE_CAN_TRANSFER_ADMIN`: controls whether the token owner can transfer the name (admin-only; there is no non-admin `ROLE_CAN_TRANSFER`)
Unlike ENSv1, the role set is not configurable per registration; it is hardcoded in the registrar. See [Enhanced Access Control](/ensv2/enhanced-access-control) for details on the role system.
### Renewing a Name
Any account can renew any name, not just the owner. Renewal extends the expiry without changing ownership or permissions. Only the base rate is charged (no premium).
```solidity
ETHRegistrar.renew(
label string, // The label to renew
duration uint64, // Duration to extend by (in seconds)
paymentToken IERC20, // ERC20 token to pay with (must be approved)
referrer bytes32 // Referral identifier
)
```
### BatchRegistrar
The **BatchRegistrar** is a companion contract used during migration from ENSv1. It has a single owner-only function, `batchRegister(registry, resolver, labels, expires)`:
* `AVAILABLE` names: registered as `RESERVED` (no owner, no payment)
* `RESERVED` names where the target expiry exceeds the current expiry: extended via `ETH_REGISTRY.renew()` directly (no payment, absolute timestamp)
* `REGISTERED` names: silently skipped
This contract is intended for pre-migration use, not for general public registration.
### EAC Integration
#### Registrar Governance
The ETH Registrar uses OpenZeppelin `Ownable` for its own governance. The owner can call `setRentPriceOracle()` to replace the pricing oracle. Ownership is transferable via `transferOwnership()`.
#### Roles on the ETH Registry
To register and renew names, the ETH Registrar needs roles on the `.eth` [Permissioned Registry](/ensv2/permissioned-registry). At deployment it is granted two [registry roles](/ensv2/permissioned-registry#roles) on `ROOT_RESOURCE`:
| Role | Purpose |
| ------------------------------------------------------ | ------------------------------------------------------ |
| [`ROLE_REGISTRAR`](/ensv2/permissioned-registry#roles) | Allows calling `register()` to create new `.eth` names |
| [`ROLE_RENEW`](/ensv2/permissioned-registry#roles) | Allows calling `renew()` on any `.eth` name |
Because these are granted at `ROOT_RESOURCE`, they apply to all names (the EAC [checks both root and token-level roles](/ensv2/enhanced-access-control#roles)). The registrar does not hold admin variants of these roles, so it cannot delegate its own authority to other contracts.
### Code Examples
#### Full Registration Flow
```ts [Viem]
import {
createPublicClient,
createWalletClient,
erc20Abi,
http,
keccak256,
toHex,
} from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({ chain: mainnet, transport: http() })
const wallet = createWalletClient({ chain: mainnet, transport: http() })
const label = 'alice'
const owner = '0x...' // Your address
const duration = 31536000n // 1 year in seconds
const secret = keccak256(toHex(crypto.randomUUID())) // Random secret
const resolver = '0x...' // Resolver address
const subregistry = '0x0000000000000000000000000000000000000000'
const paymentToken = '0x...' // ERC20 token address
const referrer = '0x0000000000000000000000000000000000000000000000000000000000000000'
// 1. Check availability
const available = await client.readContract({
address: ethRegistrarAddress,
abi: ethRegistrarAbi,
functionName: 'isAvailable',
args: [label],
})
if (!available) throw new Error('Name not available')
// 2. Get the price
const [base, premium] = await client.readContract({
address: ethRegistrarAddress,
abi: ethRegistrarAbi,
functionName: 'getRegisterPrice',
args: [label, duration, paymentToken],
})
// 3. Approve the registrar to spend the payment token
const totalCost = base + premium
await wallet.writeContract({
address: paymentToken,
abi: erc20Abi,
functionName: 'approve',
args: [ethRegistrarAddress, totalCost],
})
// 4. Create and submit commitment
const commitment = await client.readContract({
address: ethRegistrarAddress,
abi: ethRegistrarAbi,
functionName: 'makeCommitment',
args: [label, owner, secret, subregistry, resolver, duration, referrer],
})
await wallet.writeContract({
address: ethRegistrarAddress,
abi: ethRegistrarAbi,
functionName: 'commit',
args: [commitment],
})
// 5. Wait at least MIN_COMMITMENT_AGE (e.g., 60 seconds)
// ... wait ...
// 6. Register
await wallet.writeContract({
address: ethRegistrarAddress,
abi: ethRegistrarAbi,
functionName: 'register',
args: [
label,
owner,
secret,
subregistry,
resolver,
duration,
paymentToken,
referrer,
],
})
```
#### Renewing a Name
```ts [Viem]
const renewPrice = await client.readContract({
address: ethRegistrarAddress,
abi: ethRegistrarAbi,
functionName: 'getRenewPrice',
args: [label, duration, paymentToken],
})
// Approve the registrar to spend the payment token
await wallet.writeContract({
address: paymentToken,
abi: erc20Abi,
functionName: 'approve',
args: [ethRegistrarAddress, renewPrice],
})
await wallet.writeContract({
address: ethRegistrarAddress,
abi: ethRegistrarAbi,
functionName: 'renew',
args: [label, duration, paymentToken, referrer],
})
```
### Reference
#### Write Functions
#### View Functions
#### Constants
#### Events
import { FrenCallout } from '../../components/ensv2/FrenCallout'
## Hidden Contract Accounts
Hidden Contract Accounts (HCAs) let smart-account proxies act on behalf of their owners while still being attributed to the owner. ENSv2 wires HCA-aware sender resolution into every layer of the stack. When a user controls a name through an HCA proxy, the protocol records the owner as the actor for permission checks, ownership reads, and event indexing.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### The Problem
Smart-account flows usually involve a per-user proxy contract that signs and submits transactions on the user's behalf. Without intervention, every contract that inspects `msg.sender` would see the proxy address, not the controlling account. For ENS that means role checks would fail, ownership would land on the proxy, and indexers would track the wrong actor.
### Resolution Mechanism
Every HCA-aware contract inherits `HCAEquivalence`, which carries an immutable reference to an `IHCAFactoryBasic`. When `_msgSender()` is called:
1. If the factory address is `address(0)`, return `msg.sender` unchanged.
2. Otherwise call `HCA_FACTORY.getAccountOwner(msg.sender)`.
3. If the result is `address(0)` (the caller is not a registered HCA), return `msg.sender`.
4. Otherwise return the resolved owner.
```mermaid
flowchart TD
Call["_msgSender() called"] --> Factory{"HCA_FACTORY == address(0)?"}
Factory -- yes --> Sender["return msg.sender"]
Factory -- no --> Lookup["HCA_FACTORY.getAccountOwner(msg.sender)"]
Lookup --> Check{"result == 0?"}
Check -- yes --> Sender
Check -- no --> Owner["return resolved owner"]
```
The factory itself is treated as opaque. The protocol does not care how the owner-account mapping is implemented, only that it can ask.
### Two Context Flavours
OpenZeppelin contracts inherit from one of two `Context` base classes. ENSv2 ships HCA-aware drop-in replacements for both:
* **`HCAContext`** extends `Context`, used by non-upgradeable contracts.
* **`HCAContextUpgradeable`** extends `ContextUpgradeable`, used by UUPS proxies, e.g. `PermissionedResolver`.
Inheriting either makes every downstream `_msgSender()` call HCA-aware, including the role-check modifiers in [Enhanced Access Control](/ensv2/enhanced-access-control), `Ownable`, and standard ERC-1155 operator/approval logic.
### Where HCA Is Wired
HCA-aware sender resolution is built into every contract that gates actions or records actors:
| Contract | Reaches HCA via |
| ---------------------- | ---------------------------------------------- |
| `ERC1155Singleton` | `HCAContext` (registries inherit transitively) |
| `PermissionedRegistry` | via `ERC1155Singleton` |
| `WrapperRegistry` | via `PermissionedRegistry` |
| `UserRegistry` | via `PermissionedRegistry` |
| `PermissionedResolver` | `HCAContextUpgradeable` |
| `ETHRegistrar` | `HCAEquivalence` directly |
The HCA factory address is supplied to each contract at construction time.
### The Factory Interface
```solidity
interface IHCAFactoryBasic {
function getAccountOwner(address hca) external view returns (address);
}
```
A single read-only method. It returns the owner of `hca` if the address is a registered Hidden Contract Account, or `address(0)` otherwise. The interface selector is `0x442b172c`.
### What HCA Does Not Change
* `tx.origin`: untouched.
* The actual `msg.sender` for low-level calls and any third-party contracts.
* Balances, approvals, or behavior on contracts outside the ENSv2 stack.
HCA is a contract-side opt-in for attribution only. It changes who the protocol *thinks* is acting; it does not change who the EVM thinks is acting.
### Production Factory
The production HCA factory is deployed externally (currently from Rhinestone's ENS modules) and is audited separately. The protocol is implementation-agnostic: passing `address(0)` for the factory disables HCA entirely, which is what test fixtures and devnet deployments do by default.
import { FrenCallout } from '../../components/ensv2/FrenCallout'
import { MonoDiagram } from '../../components/ensv2/MonoDiagram'
## Indexing ENSv2
This page describes the ENSv2 contract events and functions relevant to building an indexer. It covers the full lifecycle of names - registration, transfer, renewal, subname creation, resolver record changes, role management, and aliasing.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### Contract Hierarchy
ENSv2 uses a hierarchical registry model. There is no single registry contract that holds all names. Instead:
Each registry is a [Permissioned Registry](/ensv2/permissioned-registry) (or UserRegistry for subnames), implementing `IRegistry`, `IStandardRegistry`, `IPermissionedRegistry`, and `IEnhancedAccessControl`. Tokens are [ERC1155Singleton](/ensv2/erc1155-singleton) (one token per name).
Resolvers are separate contracts ([Permissioned Resolver](/ensv2/permissioned-resolver)) deployed per-account as UUPS proxies. All names owned by the same account share one resolver. Names on the same resolver can also share records via [aliases](/ensv2/permissioned-resolver#aliasing) (`setAlias`), which rewrite the name suffix during resolution (e.g., `sub.alias.eth` → `sub.test.eth`).
### Registry Events
These events are emitted by any registry contract (PermissionedRegistry / UserRegistry).
#### RegistryCreated
```solidity
event RegistryCreated();
```
Emitted once per registry: by `PermissionedRegistry` in the constructor, and by `UserRegistry`/`WrapperRegistry` on `initialize()`. Allows indexers to discover new registry deployments on-chain.
#### LabelRegistered
```solidity
event LabelRegistered(
uint256 indexed tokenId,
bytes32 indexed labelHash,
string label,
address owner,
uint64 expiry,
address indexed sender
);
```
**Emitted by:** `IRegistryEvents` (on PermissionedRegistry, UserRegistry)
**When:** a new name is registered via `register()`. The full name is constructed by appending the parent name (e.g., label `"test"` under ETHRegistry = `"test.eth"`). The registry contract address that emitted this event identifies which level of the hierarchy this name belongs to.
The `sender` parameter is the account that called `register()`. For normal `.eth` registrations this is the ETHRegistrar; for names [migrated from ENSv1](/ensv2/migration) it is a migration controller, since migration bypasses the registrar and calls `register()` on the ETHRegistry directly. Checking `sender` against the known migration controller addresses is how an indexer distinguishes migrated names from fresh registrations.
#### LabelReserved
```solidity
event LabelReserved(
uint256 indexed tokenId,
bytes32 indexed labelHash,
string label,
uint64 expiry,
address indexed sender
);
```
**Emitted by:** `IRegistryEvents` (on PermissionedRegistry)
**When:** a name is reserved via `register()` with `owner = address(0)` and `roleBitmap = 0`. No token is minted and no owner is set. A reserved name can be promoted to `REGISTERED` by calling `register()` again with a real owner, which requires `ROLE_REGISTER_RESERVED`.
#### LabelUnregistered
```solidity
event LabelUnregistered(uint256 indexed tokenId, address indexed sender);
```
**Emitted by:** `IRegistryEvents` (on PermissionedRegistry, UserRegistry)
**When:** a name is explicitly deleted via `unregister()`. The ERC1155 token is burned and the expiry is set to `block.timestamp`.
#### ExpiryUpdated
```solidity
event ExpiryUpdated(uint256 indexed tokenId, uint64 newExpiry, address indexed sender);
```
**Emitted by:** `IRegistry` (on PermissionedRegistry, UserRegistry)
**When:** a name's expiry is extended via `renew()` on the registry.
#### SubregistryUpdated
```solidity
event SubregistryUpdated(
uint256 indexed tokenId,
IRegistry subregistry,
address indexed sender
);
```
**Emitted by:** `IRegistry` (on PermissionedRegistry, UserRegistry)
**When:** a name's child registry is set or changed via `setSubregistry()`. This also fires during `register()` if a subregistry is provided. New subregistry addresses indicate dynamically deployed UserRegistry contracts for subnames.
#### ResolverUpdated
```solidity
event ResolverUpdated(uint256 indexed tokenId, address resolver, address indexed sender);
```
**Emitted by:** `IRegistry` (on PermissionedRegistry, UserRegistry)
**When:** a name's resolver is set or changed via `setResolver()`. Also fires during `register()` if a resolver is provided. New resolver addresses indicate dynamically deployed PermissionedResolver contracts.
#### TokenRegenerated
```solidity
event TokenRegenerated(uint256 indexed oldTokenId, uint256 indexed newTokenId);
```
**Emitted by:** `IRegistry` (on PermissionedRegistry, UserRegistry)
**When:** a name's [EAC roles](/ensv2/enhanced-access-control) are modified via `grantRoles()` or `revokeRoles()`. The ERC1155 token ID changes (its version counter increments), while the underlying name (canonical ID / resource) remains the same. Always accompanied by ERC1155 `TransferSingle` events (burn old + mint new). See [Mutable Token IDs](/ensv2/mutable-token-ids) for details.
#### ParentUpdated
```solidity
event ParentUpdated(IRegistry indexed parent, string label, address indexed sender);
```
**Emitted by:** `IRegistry` (on PermissionedRegistry, UserRegistry)
**When:** a registry's parent reference is set via `setParent()`. This establishes the upward link in the registry hierarchy, complementing `SubregistryUpdated` (which links parent → child) by establishing the child → parent direction.
`setParent()` is an optional, separately-permissioned call (`ROLE_SET_PARENT`), so this event is **not** emitted for every child registry and `getParent()` may return empty for registries where it was never called. Treat `SubregistryUpdated` (emitted by the parent) as the authoritative parent → child link, and use `ParentUpdated`/`getParent()` only as a supplementary child → parent hint when present.
#### TokenResource
```solidity
event TokenResource(uint256 indexed tokenId, uint256 indexed resource);
```
**Emitted by:** `IPermissionedRegistry`
**When:** a token is created during registration (`register()`). Maps a `tokenId` to its corresponding `resource`. It is **not** re-emitted on token regeneration: the `resource` is derived from the `labelHash` and `eacVersionId`, so it stays stable across role-change regenerations (which only bump the `tokenVersionId`), and you should carry it onto the new `tokenId` by following [`TokenRegenerated`](#tokenregenerated) rather than expecting a fresh `TokenResource`. The `resource` does change on re-registration (which increments `eacVersionId`), at which point the new `register()` emits a fresh `TokenResource`. See [Mutable Token IDs](/ensv2/mutable-token-ids#id-types) for details.
### ERC1155 Transfer Events
These standard ERC1155 events are emitted by all registries (which extend ERC1155Singleton).
#### TransferSingle
```solidity
event TransferSingle(
address indexed operator,
address indexed from,
address indexed to,
uint256 id,
uint256 value
);
```
**When:**
* **Registration (mint):** `from = address(0)`, `to = owner` - a new name token is minted
* **Transfer:** `from = previousOwner`, `to = newOwner` - ownership changes via `safeTransferFrom()`
* **Unregistration (burn):** `from = owner`, `to = address(0)` - name token is burned
* **Token regeneration:** two events fire - burn old tokenId + mint new tokenId. Correlate with `TokenRegenerated` to avoid treating it as a separate domain.
#### TransferBatch
```solidity
event TransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values
);
```
**When:** batch transfers of multiple name tokens. Same semantics as `TransferSingle` but for multiple tokens at once.
### Registrar Events
These events are emitted by the [ETH Registrar](/ensv2/eth-registrar) contract, which is the user-facing entry point for `.eth` name registration (with commit-reveal and pricing).
#### CommitmentMade
```solidity
event CommitmentMade(bytes32 commitment);
```
**Emitted by:** `IETHRegistrar`
**When:** step 1 of the commit-reveal registration via `commit()`. The commitment hash can be matched to the subsequent registration.
#### NameRegistered (Registrar)
```solidity
event NameRegistered(
uint256 indexed tokenId,
string label,
address owner,
IRegistry subregistry,
address resolver,
uint64 duration,
IERC20 paymentToken,
bytes32 referrer,
uint256 base,
uint256 premium
);
```
**Emitted by:** `IETHRegistrar`
**When:** step 2 of the commit-reveal registration via `register()`. This is distinct from the registry's `LabelRegistered` event. The registrar emits its own event with pricing information, and the underlying ETHRegistry also emits `LabelRegistered`.
Both events fire for the same registration. The registrar event has pricing data; the registry event has the canonical registration data.
#### NameRenewed
```solidity
event NameRenewed(
uint256 indexed tokenId,
string label,
uint64 duration,
uint64 newExpiry,
IERC20 paymentToken,
bytes32 referrer,
uint256 base
);
```
**Emitted by:** `IETHRegistrar`
**When:** a `.eth` name is renewed via `renew()`. The registrar also calls `renew()` on the ETHRegistry, which emits `ExpiryUpdated`.
### Resolver Events
These events are emitted by [Permissioned Resolver](/ensv2/permissioned-resolver) contracts and any contract implementing the standard resolver profile interfaces. Resolvers are keyed by `node` (namehash of the full name).
#### AddressChanged
```solidity
event AddressChanged(bytes32 indexed node, uint256 coinType, bytes newAddress);
```
**When:** an address record is set via `setAddr(node, coinType, address)`. `coinType = 60` is ETH. Other coin types follow [SLIP-44](https://github.com/AdrianSimionov/slip-0044/blob/main/slip-0044.md) (e.g., 0 = BTC, 501 = SOL). The `node` maps to a domain via namehash.
#### TextChanged
```solidity
event TextChanged(
bytes32 indexed node,
string indexed indexedKey,
string key,
string value
);
```
**When:** a text record is set via `setText(node, key, value)`. Common keys: `avatar`, `url`, `description`, `com.twitter`, `com.github`, `email`, etc.
#### ContenthashChanged
```solidity
event ContenthashChanged(bytes32 indexed node, bytes hash);
```
**When:** a content hash is set via `setContenthash(node, hash)`. Supports IPFS, Arweave, Swarm, etc.
#### ABIChanged
```solidity
event ABIChanged(bytes32 indexed node, uint256 indexed contentType);
```
**When:** an ABI record is set.
#### PubkeyChanged
```solidity
event PubkeyChanged(bytes32 indexed node, bytes32 x, bytes32 y);
```
**When:** a public key record is set (secp256k1 x, y coordinates).
#### NameChanged
```solidity
event NameChanged(bytes32 indexed node, string name);
```
**When:** a reverse name record is set.
#### InterfaceChanged
```solidity
event InterfaceChanged(
bytes32 indexed node,
bytes4 indexed interfaceID,
address implementer
);
```
**When:** an EIP-165 interface implementer is set.
#### VersionChanged
```solidity
event VersionChanged(bytes32 indexed node, uint64 newVersion);
```
**When:** all records for a node are cleared via `clearRecords(node)`. The version counter is incremented, invalidating all previously stored records for that node.
#### AliasChanged
```solidity
event AliasChanged(
bytes indexed indexedFromName,
bytes indexed indexedToName,
bytes fromName,
bytes toName
);
```
**When:** an alias is set via `setAlias(fromName, toName)`. Names are DNS-encoded. Setting `toName` to empty bytes removes the alias. The resolver rewrites the suffix during resolution (e.g., if `alias.eth → test.eth`, then `sub.alias.eth` resolves records for `sub.test.eth`). Aliases are resolver-level constructs - the registry does not know about them.
#### NamedResource
```solidity
event NamedResource(uint256 indexed resource, bytes name);
```
**When:** an EAC resource is associated with a name for [fine-grained permission control](/ensv2/permissioned-resolver#eac-integration) on the resolver.
#### NamedTextResource
```solidity
event NamedTextResource(uint256 indexed resource, bytes name, bytes32 indexed keyHash, string key);
```
**When:** an EAC resource is associated with a specific text record key for a name, allowing fine-grained permission to modify only a specific text record (e.g., only the `avatar` key).
#### NamedAddrResource
```solidity
event NamedAddrResource(uint256 indexed resource, bytes name, uint256 indexed coinType);
```
**When:** an EAC resource is associated with a specific address coin type for a name, allowing fine-grained permission to modify only a specific address record (e.g., only the ETH address).
### Role Events
#### EACRolesChanged
```solidity
event EACRolesChanged(
uint256 indexed resource,
address indexed account,
uint256 oldRoleBitmap,
uint256 newRoleBitmap
);
```
**Emitted by:** every contract inheriting [Enhanced Access Control](/ensv2/enhanced-access-control), which includes **both registries and resolvers**.
**When:** the roles of `account` on `resource` change. On registries this happens via `grantRoles()` / `revokeRoles()` (which also trigger [`TokenRegenerated`](#tokenregenerated)) and once at creation, when the initial root role bitmap is granted to the registry owner (in the `constructor`, or in `initialize()` for proxy-deployed registries). On resolvers it happens via `initialize()` and the `authorize(Name|Text|Addr)Roles()` functions.
Registries and resolvers use different resource ID spaces: a registry resource is derived from the labelhash and its EAC version counter, while a resolver resource is `keccak256(node, part)`. The same numeric `resource` value can therefore mean different things on different contracts. Always key role state by the emitting contract address together with the resource.
### Key Concepts for Indexers
#### Dynamic Contract Discovery
An indexer cannot know all contract addresses at startup. The core pattern is:
1. Start by watching the **RootRegistry** and **ETHRegistry** (known addresses from deployment).
2. When a `SubregistryUpdated` event fires with a new subregistry address, add that address to the watch list.
3. When a `ResolverUpdated` event fires with a new resolver address, add that address to the watch list for resolver events.
This creates a self-expanding set of monitored contracts.
Link-time discovery has a blind spot: anything a registry or resolver emits **before** it is linked (its `RegistryCreated`, the initial `EACRolesChanged` root grant from `initialize()`, or records set before linking) predates the discovery point. Since registries and resolvers are deployed as proxies through the [Verifiable Factory](/ensv2/verifiable-factory), which emits `ProxyDeployed(sender, proxy, salt, implementation)`, watching the factory address discovers contracts at deployment time instead and closes that gap.
Note the log order inside a deployment transaction: `deployProxy()` calls `initialize()` on the new proxy **before** emitting `ProxyDeployed`. For a UserRegistry deployment the sequence is `Upgraded` → `RegistryCreated` → `EACRolesChanged` → `ProxyDeployed`. An indexer that discovers a contract from `ProxyDeployed` must therefore also process the earlier logs of that same transaction.
#### TokenId vs Resource (Canonical ID)
* **tokenId**: the ERC1155 token ID. Changes when roles are modified (`TokenRegenerated`), because it embeds a version counter that increments on each change.
* **resource**: the EAC permission key for a name within a registry. Derived from the `labelHash` and `eacVersionId`. Stable across role modifications, but changes on re-registration (when `eacVersionId` increments).
Use the `TokenResource` event (emitted at registration) to establish the `tokenId → resource` mapping, and follow `TokenRegenerated` to move that mapping onto the new `tokenId` when roles change, since no new `TokenResource` is emitted then. The `resource` should be the primary key for domain lookups, as it stays stable across regenerations.
#### Name Construction
The indexer must track the registry hierarchy to construct full names:
1. **ETHRegistry** emits `LabelRegistered` with `label = "test"` → full name is `"test.eth"`
2. A `SubregistryUpdated` on `test.eth` points to a UserRegistry at address `0xABC`
3. That UserRegistry emits `LabelRegistered` with `label = "sub"` → full name is `"sub.test.eth"`
The indexer must map each registry address to its parent name to build the complete DNS name.
#### Shared Subregistries (Linked Names)
Multiple parent names can point to the same subregistry via `setSubregistry()`. For example:
* `sub1.sub2.parent.eth` has subregistry at `0xABC`
* `linked.parent.eth` also has subregistry at `0xABC`
Children registered in `0xABC` appear under both parents. The token `wallet` in registry `0xABC` is simultaneously `wallet.sub1.sub2.parent.eth` and `wallet.linked.parent.eth` - they share the same tokenId.
#### Alias Resolution
Aliases are a resolver-level concept, not a registry-level one:
* `alias.eth` and `test.eth` may share the same resolver
* The resolver stores an `alias.eth → test.eth` alias mapping
* When resolving `sub.alias.eth`, the resolver rewrites it to `sub.test.eth` and returns those records
* The registry hierarchy knows nothing about aliases - they exist only in the resolver's storage
Two consequences for record indexing: record events (`AddressChanged`, `TextChanged`, ...) always fire under the node they were set on, and `setAlias()` emits no record events. During resolution the alias is checked **first**, so it shadows any records stored directly on the source name: once `alias.eth → test.eth` is set, records previously written under `alias.eth` are no longer returned by `resolve()`, even though they remain in storage. Indexed records therefore reflect stored state, not effective resolution; apply alias rewriting before treating them as what a name resolves to.
#### Registration Status
Names can be in one of three states (from `IPermissionedRegistry.Status`):
| Status | Value | Description |
| ------------ | ----- | ------------------------------------------------------------------------------------------------ |
| `AVAILABLE` | 0 | Name can be registered |
| `RESERVED` | 1 | Name is reserved (cannot be registered until expiry, unless caller has `ROLE_REGISTER_RESERVED`) |
| `REGISTERED` | 2 | Name is actively registered with an owner |
After expiry, names return to `AVAILABLE`. Re-registration creates both a new `tokenId` and a new `resource` (both version counters increment).
For `.eth` names, the [ETH Registrar](/ensv2/eth-registrar) adds a grace period on top of the registry status: `GRACE_PERIOD` is an immutable constructor parameter, and while it runs an expired name cannot be re-registered but can still be renewed. No event marks entering or leaving grace (or the premium window that follows), so `.eth` availability must be computed client-side from the expiry, the grace constant, and the current time. A plain "expired means available" check is wrong for `.eth` names in grace.
#### Event Processing Order
For a single registration via `ETHRegistrar.register()`, events fire in this order. The registrar calls `ETHRegistry.register()` first (which emits all the registry events) and only then emits its own event, so `NameRegistered` comes **last**, not first:
1. `IRegistryEvents.LabelRegistered` - registry-level event with registration details
2. `TransferSingle` (mint) - ERC1155 token creation
3. `TokenResource` - tokenId-to-resource mapping
4. `SubregistryUpdated` - if a subregistry was provided
5. `ResolverUpdated` - if a resolver was provided
6. `IETHRegistrar.NameRegistered` - registrar-level event with pricing
For a direct `IStandardRegistry.register()` call (e.g., on a UserRegistry):
1. `IRegistryEvents.LabelRegistered`
2. `TransferSingle` (mint)
3. `TokenResource`
4. `SubregistryUpdated` - if a subregistry was provided
5. `ResolverUpdated` - if a resolver was provided
For a name [migrated from ENSv1](/ensv2/migration), the migration controllers call `register()` on the ETHRegistry directly, so only the registry sequence fires: there is no `CommitmentMade` and no registrar `NameRegistered`, and `LabelRegistered.sender` is the migration controller.
### Contract Address Summary
| Contract | Role | Events to Watch |
| ----------------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| PermissionedRegistry (ETHRegistry) | Manages `.eth` names | All `IRegistry` events, `TokenResource`, ERC1155 transfers, `EACRolesChanged` |
| PermissionedRegistry (RootRegistry) | Manages TLDs | Same as above |
| UserRegistry | Manages subnames (dynamically deployed) | Same as above |
| ETHRegistrar | User-facing `.eth` registration | `CommitmentMade`, `NameRegistered`, `NameRenewed` |
| PermissionedResolver | Stores resolver records (dynamically deployed) | `AddressChanged`, `TextChanged`, `ContenthashChanged`, `ABIChanged`, `PubkeyChanged`, `NameChanged`, `InterfaceChanged`, `VersionChanged`, `AliasChanged`, `NamedResource`, `NamedTextResource`, `NamedAddrResource`, `EACRolesChanged` |
| VerifiableFactory | Deploys registry/resolver proxies | `ProxyDeployed` |
### Read Functions for State Verification
These view functions are useful for verifying indexed state or backfilling data:
Several of these views mask state once a name is expired (`block.timestamp >= expiry`): `getOwner()`, `getSubregistry()`, and `getResolver()` return zero, `ownerOf()` returns zero for expired or stale token IDs, and `getResource()` (and `getState().resource`) return the *next* resource, one version ahead of what `TokenResource` announced, without any storage write or event. A mismatch between indexed state and these functions for an expired name is expected behavior, not an indexing bug. `getExpiry()` and `getTokenId()` return raw, unmasked values.
#### Registry State
```solidity
// Get full state of a name (status, expiry, owner, tokenId, resource)
function getState(uint256 anyId) external view returns (State memory);
// Get the subregistry for a label
function getSubregistry(string calldata label) external view returns (IRegistry);
// Get the resolver for a label
function getResolver(string calldata label) external view returns (address);
// Get the owner of a token
function ownerOf(uint256 tokenId) external view returns (address);
// Get the owner of a name by any of its IDs
function getOwner(uint256 anyId) external view returns (address);
// Get the stable resource ID
function getResource(uint256 anyId) external view returns (uint256);
// Get the current tokenId (may change after role modifications)
function getTokenId(uint256 anyId) external view returns (uint256);
// Get the expiry
function getExpiry(uint256 anyId) external view returns (uint64);
```
#### Registrar State
```solidity
// Check if a name is available for registration
function isAvailable(string memory label) external view returns (bool);
// Get rental price
function rentPrice(
string memory label,
address buyer,
uint64 duration,
IERC20 paymentToken
) external view returns (uint256 base, uint256 premium);
```
#### Resolver State
```solidity
// Get address record
function addr(bytes32 node, uint256 coinType) external view returns (bytes memory);
// Get text record
function text(bytes32 node, string calldata key) external view returns (string memory);
// Get content hash
function contenthash(bytes32 node) external view returns (bytes memory);
// Get alias
function getAlias(bytes calldata name) external view returns (bytes memory);
```
import { FrenCallout } from '../../components/ensv2/FrenCallout'
## Migration
ENSv2 provides a migration framework for transitioning ENSv1 `.eth` names to the new system. Migration is a two-phase process: **premigration** reserves all existing names in v2 automatically, then **migration** lets owners claim their names by transferring their v1 tokens to a migration controller.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
Once ENSv2 is live, v1 `.eth` registrations and renewals are disabled. New registrations go through the v2 [ETH Registrar](/ensv2/eth-registrar). Migrated names are renewed via the ETH Registrar; unmigrated ([RESERVED](#key-definitions)) names are renewed via [`ETHRenewerV1`](#renewals), which syncs the expiry back to v1. Names in v1 will simply expire and can never be re-registered through v1.
### What You Need to Do
#### .eth Name Owners
If you just own a `.eth` name and aren't a developer, you don't need to understand the technical details below. A frontend migration tool will be available when the time comes that handles everything for you.
Premigration happens automatically. To migrate your name to v2:
1. Transfer your v1 token to the appropriate migration controller:
* **Unwrapped or unlocked**: `UnlockedMigrationController`
* **Locked**: `LockedMigrationController`
2. Specify the name's label plus the new v2 owner, resolver, and subregistry in the transfer data (the label must match the transferred token, otherwise migration reverts with `NameDataMismatch`)
3. The v2 owner can be a different address than the v1 owner (e.g., migrating to a smart account)
If your name is in the v1 grace period, use `ETHRenewerV1` to renew it first.
Migration is **not required** immediately. Unmigrated names continue to resolve through v1 via `ENSV1Resolver`. However, migrating gives you access to v2 features like [per-record permissions](/ensv2/permissioned-resolver#eac-integration), [aliasing](/ensv2/permissioned-resolver#aliasing), and the new resolver.
#### Subname Owners
If you own an emancipated subname (3LD+), your parent must migrate first. Once the parent has migrated, transfer your token to the parent's `WrapperRegistry`:
* **Locked subnames** (`CANNOT_UNWRAP` set): fuses are converted to v2 roles, a new WrapperRegistry is deployed as the subregistry
* **Detached subnames** (emancipated without `CANNOT_UNWRAP`): unwrapped to the [Graveyard](#graveyard) and registered with the standard role set (plus renewal roles if `CAN_EXTEND_EXPIRY` was set)
Non-emancipated 3LD+ subnames cannot be migrated through the migration controllers. They must be registered directly in v2 once their parent has a v2 registry.
#### What Happens to Records
* **Locked names with `CANNOT_SET_RESOLVER`**: the v1 resolver is preserved. If it's a known `PublicResolver`, it's replaced with `PublicResolverV2` (records may need to be copied separately).
* **All other names**: the v1 resolver is cleared during migration. The v2 resolver is set from the migration transfer data (or can be set up afterwards).
### Overview
```mermaid
flowchart TB
subgraph Phase 1: Premigration
batch["BatchRegistrar reserves all\nregistered/in-grace v1 names in v2"]
reserved["RESERVED in v2 ETH Registry\n(expiry = v1 + ~62 days, no owner)"]
batch --> reserved
end
subgraph Phase 2: Migration
transfer["Owner transfers v1 token\nto migration controller"]
registered["REGISTERED in v2 ETH Registry\n(owner, resolver, subregistry set)"]
graveyard["v1 token sent to Graveyard"]
transfer --> registered
transfer --> graveyard
end
reserved -.->|"owner initiates"| transfer
```
#### ENSv1 Token Types
Migration supports `.eth` 2LDs and emancipated subnames. The migration path depends on the token type:
| Type | Token Standard | Description |
| ------------- | --------------------- | ------------------------------------------------------------------ |
| **Unwrapped** | BaseRegistrar ERC-721 | 2LD only |
| **Unlocked** | NameWrapper ERC-1155 | Without `CANNOT_UNWRAP` (2LD only) |
| **Locked** | NameWrapper ERC-1155 | Emancipated + `CANNOT_UNWRAP` (2LD and 3LD+) |
| **Detached** | NameWrapper ERC-1155 | Emancipated, without `CANNOT_UNWRAP`, parent is Locked (3LD+ only) |
Both Locked and Detached are subtypes of **Emancipated** (`PARENT_CANNOT_CONTROL` set). The distinction determines the migration path: Locked names preserve their fuse restrictions as v2 roles, while Detached names are unwrapped and registered with the standard role set (see [Detached Names](#detached-names)).
#### Key Definitions
* **RESERVED**: a v2 registry slot with an expiry and resolver but no owner. Created during premigration.
* **REGISTERED**: a v2 registry slot with an owner. Created when a user migrates their v1 token.
* **Graveyard**: the contract that receives migrated v1 tokens and can clear stale v1 registry entries so they no longer resolve.
#### Time Constants
| Constant | Value | Description |
| ----------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GRACE_PERIOD_V1` | 90 days | Post-expiry window in ENSv1 where the owner can still renew |
| `GRACE_PERIOD_V2` | 28 days | Post-expiry window in ENSv2 where the owner can still renew via the [ETH Registrar](/ensv2/eth-registrar) |
| `BONUS_PERIOD` | \~62 days | `GRACE_PERIOD_V1 - GRACE_PERIOD_V2` (plus 1 second due to boundary handling). Added to v1 expiry during premigration so that the v2 reservation does not expire before v1's grace period ends |
### Premigration
Before users can migrate, every registered or in-grace v1 name is batch-reserved in the v2 ETH Registry by the `BatchRegistrar`. This happens automatically and does not require any user action.
For each name:
* **Status**: set to `RESERVED`
* **Expiry**: `expiryV1 + BONUS_PERIOD`
* **Resolver**: set to `ENSV1Resolver`, which performs wildcard fallback to the v1 ENS registry
* **Owner**: not set (RESERVED names have no owner)
After premigration, v1 `.eth` registrations and v1 renewals are disabled. Migrated (REGISTERED) names are renewed via the `ETHRegistrar`. Unmigrated (RESERVED) names are renewed via `ETHRenewerV1`, which syncs the expiry back to the v1 BaseRegistrar. Names continue to resolve through v1 during this phase because `ENSV1Resolver` mirrors v1 resolution.
### Migration Paths
Migration is initiated by the name owner transferring their v1 token to the appropriate controller. The controller promotes the pre-existing RESERVED slot to REGISTERED, setting the owner, resolver, subregistry, and role bitmap. The v1 token is forwarded to the Graveyard. Promoted names additionally carry the non-revokable `ROLE_WAS_RESERVED` marker role, which fresh registrations do not have.
The v2 owner specified during migration **does not need to match** the v1 owner. This allows migrating directly to a new wallet or smart account.
#### Unwrapped Names
Unwrapped 2LD names (BaseRegistrar ERC-721 tokens) are migrated via the `UnlockedMigrationController`.
1. Transfer the ERC-721 token via `safeTransferFrom` with an encoded data payload specifying: label, new owner, resolver, and subregistry
2. The controller reclaims the token on the BaseRegistrar, transfers v1 registry ownership to the Graveyard, and clears the v1 resolver
3. The ERC-721 token is sent to the Graveyard
4. The name is promoted from RESERVED to REGISTERED in the v2 ETH Registry with the provided parameters
5. The token is granted the same roles as a fresh `ETHRegistrar.register()` ([REGISTRATION\_ROLE\_BITMAP](/ensv2/eth-registrar#roles-granted-at-registration))
#### Unlocked Wrapped Names
Unlocked 2LD names (NameWrapper ERC-1155 tokens without `CANNOT_UNWRAP`) are also migrated via the `UnlockedMigrationController`.
1. Transfer the ERC-1155 token via `safeTransferFrom` (or batch via `safeBatchTransferFrom`) with an encoded data payload
2. The controller verifies the name is NOT locked (reverts `NameIsLocked` otherwise)
3. The token is unwrapped to the Graveyard and the v1 resolver is cleared
4. The name is promoted from RESERVED to REGISTERED with the provided parameters
5. Token roles are the same as `ETHRegistrar.register()`
Unlocked 3LD+ names cannot be migrated through this path. They must be registered directly in v2 once their parent has a v2 registry.
#### Locked Names
Locked names (NameWrapper ERC-1155 tokens with `CANNOT_UNWRAP`) have irrevocable restrictions that must be preserved. The migration path depends on the name level:
* **2LD**: transfer to `LockedMigrationController`
* **3LD+**: transfer to the parent name's `WrapperRegistry` (the parent must have migrated first)
The migration flow:
1. Transfer the ERC-1155 token to the appropriate receiver
2. The receiver branches on the token type: locked tokens continue with the steps below, detached tokens follow their own path (see [Detached Names](#detached-names)), and anything else reverts (`NameNotLocked`)
3. If `CANNOT_APPROVE` is burned and `getApproved()` is non-null after transfer, migration reverts (`FrozenTokenApproval`). If `CANNOT_APPROVE` is not burned, any approval is automatically cleared during the transfer, so this cannot trigger.
4. ENSv1 [fuses](/wrapper/fuses) are converted to ENSv2 roles (see [Fuse-to-Role Conversion](#fuse-to-role-conversion))
5. A `WrapperRegistry` is always deployed as the subregistry, replicating the v1 fuse-based access control using v2's [EAC](/ensv2/enhanced-access-control) system
6. The v1 resolver is cleared and the v2 name gets the resolver from the transfer data, **unless** `CANNOT_SET_RESOLVER` is burned, in which case the transfer-data resolver is ignored and the existing v1 resolver is carried over to v2. If the carried-over resolver is a known `PublicResolver`, it is replaced with `PublicResolverV2`
7. The token is **not unwrapped** but is transferred to the Graveyard as an ERC-1155 token
8. The name is promoted from RESERVED to REGISTERED (for 2LD) or registered directly (for 3LD+) with the converted roles
#### Detached Names
Detached names are emancipated children of locked parents that don't have `CANNOT_UNWRAP` set (3LD+ only). They are migrated via the parent's `WrapperRegistry`.
1. Transfer the ERC-1155 token to the parent's WrapperRegistry
2. The token is unwrapped to the Graveyard and the v1 resolver is cleared (the v2 name gets the resolver from the transfer data)
3. The name is registered with the same token roles as `ETHRegistrar.register()`, plus `ROLE_RENEW` and `ROLE_RENEW_ADMIN` if the v1 token had `CAN_EXTEND_EXPIRY` set
### Fuse-to-Role Conversion
When migrating locked names, ENSv1 fuses are converted to ENSv2 roles. The key principle: a burned fuse that restricts an action means the corresponding role is **not granted**.
Admin roles (the `<< 128` shifted counterparts) are only granted when `CANNOT_BURN_FUSES` is **not** set. If fuses are frozen, only regular roles are granted, preventing any further permission changes in v2. The one exception is `ROLE_CAN_TRANSFER_ADMIN`: it has no non-admin counterpart and is granted whenever `CANNOT_TRANSFER` is not burned, frozen or not.
#### Token Roles
| ENSv1 Fuse | ENSv2 Role | Granted when fuse is... |
| --------------------- | --------------------------- | ----------------------- |
| `CAN_EXTEND_EXPIRY` | `ROLE_RENEW` | Set (enabled) |
| `CANNOT_SET_RESOLVER` | `ROLE_SET_RESOLVER` | Not set (not burned) |
| `CANNOT_TRANSFER` | `ROLE_CAN_TRANSFER_ADMIN` | Not set (not burned) |
| `CANNOT_BURN_FUSES` | Admin counterparts of above | Not set (not frozen) |
| `CANNOT_SET_TTL` | N/A | Ignored (no TTL in v2) |
#### Subregistry Roles
These roles are granted to the name owner on the WrapperRegistry's [`ROOT_RESOURCE`](/ensv2/enhanced-access-control#resources), giving them contract-wide authority over the subregistry.
| ENSv1 Fuse | ENSv2 Role | Granted when fuse is... |
| ------------------------- | ----------------------------------------------- | ----------------------- |
| `CANNOT_CREATE_SUBDOMAIN` | `ROLE_REGISTRAR` | Not set (not burned) |
| (always) | `ROLE_RENEW` + `ROLE_UPGRADE` + `ROLE_CAN_NAME` | Always granted |
| `CANNOT_BURN_FUSES` | Admin counterparts of all of the above | Not set (not frozen) |
### Graveyard
The `Graveyard` contract receives migrated v1 tokens and provides functionality to clear stale v1 registry entries so they no longer resolve. It has no mechanism to transfer or upgrade the tokens it holds.
During migration, the resolver of the migrated name is cleared (when possible) and, for unwrapped names, v1 registry ownership is transferred to the Graveyard. However, the unemancipated subname namespace under the migrated name is left unchanged. The `clear()` function handles this cleanup separately:
```solidity
// Anyone can call this to clean up v1 registry entries
Graveyard.clear(names)
```
`clear()` recursively walks the v1 namespace hierarchy for each name, clearing resolvers and transferring subnode ownership to the Graveyard. It is permissionless: callers cannot harm names they don't own, because the function only succeeds for names the Graveyard controls or names that have expired past the v1 grace period.
For expired 2LD names that were never migrated, `clear()` re-registers them to the Graveyard via the v1 BaseRegistrar with a near-permanent duration, then clears their resolver. This is used by an off-chain service that periodically calls `clear()` on expired names to prevent stale v1 resolution. Locked names that are not owned by the Graveyard cannot be cleared (reverts `NameNotClearable`).
### ENSv1 Continuity
Not all names will migrate immediately. ENSv2 provides mechanisms to keep unmigrated names functional.
#### Renewals
Renewals are handled by two contracts depending on the name's status:
* **`ETHRegistrar`**: renews REGISTERED names (migrated names). Does not sync v1.
* **`ETHRenewerV1`**: renews RESERVED names (unmigrated names). Syncs the expiry back to the v1 BaseRegistrar so both systems stay in lockstep.
**Key invariants:**
* The v2 expiry is always `BONUS_PERIOD` ahead of the v1 expiry
* The `ETHRegistrar` cannot register RESERVED names (it lacks `ROLE_REGISTER_RESERVED`), so unmigrated names are protected until they expire
* `ENSV1Resolver` continues resolving unmigrated names until the v2 reservation expires
#### Unmigratable Names
Some names are structurally unable to migrate (see [Restrictions](#restrictions)). These names remain fully functional on v1:
* Resolution continues via `ENSV1Resolver` as long as the v2 reservation is active
* Renewals work via `ETHRenewerV1`, keeping the v1 and v2 expiries in sync
* v1 fuses, resolver, and ownership remain unchanged
The only v2 features unavailable to unmigratable names are per-record permissions, aliasing, and the new resolver. Once the v2 reservation expires and the 28-day v2 grace period has passed, the name becomes available for fresh registration in v2 (during the grace window it can still be renewed via `ETHRenewerV1`). The `Graveyard` can clear the expired v1 namespace.
#### What Happens to Unmigrated Names
Names that *could* migrate but don't will eventually expire. Since v1 registrations are disabled, expired names never become available in v1 again. The v2 RESERVED slot also expires. Once expired in v2 and past the 28-day v2 grace period (during which `ETHRenewerV1` can still renew it), the name becomes available for fresh registration via the `ETHRegistrar`. An off-chain service re-registers expired names to the Graveyard, which then clears their v1 resolvers so they no longer produce stale results.
### Scenarios
#### 100 days remaining
*A name with 100 days left in v1 is premigrated.*
The RESERVED slot has 162 days left (100 + 62 day bonus). The v1 token expires after 100 days and its grace period ends after 190 days (100 + 90). The v2 reservation expires after 162 days and becomes available for registration after 190 days (162 + 28 day grace). The v2 availability aligns with the end of the v1 grace period.
#### Migrates then renews
*A name with 50 days remaining is premigrated, migrated, then renewed for 50 days.*
The RESERVED slot has 112 days (50 + 62). The owner migrates, promoting to REGISTERED. The v1 token goes to the Graveyard with 50 days left. The owner renews via `ETHRegistrar` for 50 days, extending the v2 registration to 162 days (50 + 62 + 50). The v2 grace period begins after 162 days and lasts 28 days.
#### In grace period, renews before migrating
*A name expired 61 days ago and has 29 days left in the v1 grace period (i.e., -61 days remaining). The owner cannot migrate because the name is expired in v1.*
The RESERVED slot has 1 day left (-61 + 62 day bonus). The owner renews for 62 days via `ETHRenewerV1` (the minimum needed to make the name active again), which extends both expiries:
* **v1 expiry**: -61 + 62 = 1 day from now. Grace period ends after 91 days (1 + 90).
* **v2 expiry**: 1 + 62 = 63 days from now. Becomes available after 91 days (63 + 28).
The name is active again (no longer in grace), so the token can be transferred and migrated to v2.
### Restrictions
Migration is not possible in the following cases:
* The v1 token is not transferable (owner or approval restrictions)
* Locked names with `CANNOT_TRANSFER` burned
* Locked names with `CANNOT_APPROVE` burned and a non-null `getApproved()` (reverts `FrozenTokenApproval`)
* 3LD+ subnames whose parent has not migrated yet
### Contracts
| Contract | Purpose |
| ----------------------------- | ---------------------------------------------------------------------------------- |
| `BatchRegistrar` | Batch-reserves v1 names in v2 during premigration |
| `UnlockedMigrationController` | Migrates unwrapped and unlocked 2LD names |
| `LockedMigrationController` | Migrates locked 2LD names |
| `MigrationHelper` | Batch migration of mixed unwrapped/unlocked/locked tokens using operator approvals |
| `WrapperRegistry` | Migrates locked/detached 3LD+ names; replicates fuse-based access control in v2 |
| `Graveyard` | Receives migrated v1 tokens; clears stale v1 registry entries and resolvers |
| `ETHRenewerV1` | Renews RESERVED (unmigrated) names with v1 BaseRegistrar sync |
| `ENSV1Resolver` | Wildcard fallback resolver for premigrated names |
| `PublicResolverV2` | Replacement for v1 PublicResolver that respects v2 ownership |
import { Card } from '../../components/ui/Card'
import { FrenCallout } from '../../components/ensv2/FrenCallout'
import { IdExplorer } from '../../components/ensv2/IdExplorer'
## Mutable Token IDs
Names in the [Permissioned Registry](/ensv2/permissioned-registry) are represented as [ERC1155Singleton](/ensv2/erc1155-singleton) tokens, where each registered name is a token with exactly one owner. ENSv2's [Enhanced Access Control](/ensv2/enhanced-access-control) system allows name owners to delegate fine-grained permissions to multiple accounts. This flexibility requires a mechanism to keep permissions in sync with a name's lifecycle, for example invalidating delegated roles when a name changes hands or expires. ENSv2 solves this with **mutable token IDs** that change in response to security-relevant events, providing automatic protection against two attack vectors: stale permissions and transfer griefing.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### The Problem
#### Stale Permissions
Imagine Alice registers `alice.eth` and grants Bob `ROLE_SET_RESOLVER`. Later, Alice's name expires, and Carol registers `alice.eth`. If permissions were tied to a fixed identifier, Bob's role grant from Alice's registration would carry over - Carol wouldn't know that Bob has resolver permissions on her newly-registered name.
#### Transfer Griefing
Imagine Alice owns `alice.eth` with `ROLE_CAN_TRANSFER_ADMIN` and `ROLE_SET_RESOLVER_ADMIN`, and lists it for sale on a marketplace. The marketplace holds an approval to transfer the token. A buyer submits a purchase transaction, which enters the mempool. Alice sees it and frontruns with a `revokeRoles` call that strips `ROLE_SET_RESOLVER_ADMIN` from the name. The buyer's transaction then executes - they receive the name, but it's been silently degraded: they can no longer change the resolver. Without mutable token IDs, the buyer would have no protection against this.
### How Token IDs Work
#### Canonical ID
Each name in the registry is internally tracked by its **canonical ID**, a stable 256-bit identifier derived from the name's **labelhash** (`keccak256` of the label string) with the lower 32 bits zeroed out. The canonical ID never changes for a given label, regardless of how many times the name is registered, transferred, or has its permissions modified.
The registry uses the canonical ID as the key to look up the name's storage entry, which contains the name's current state: subregistry, resolver, expiry, and two version counters. From this entry, the registry derives the name's current **token ID** (used for ERC1155 ownership) and its current **resource** (used as the key for [EAC](/ensv2/enhanced-access-control) permission storage). Both are produced by encoding a version counter into the lower 32 bits of the canonical ID.
#### Version Counters
The [Permissioned Registry](/ensv2/permissioned-registry) maintains two version counters in each name's storage entry:
| Counter | Incremented when | Encoded into | Purpose |
| ---------------- | ------------------------------------------------------------------- | ------------ | ---------------------------------------------------------------------------- |
| `tokenVersionId` | Roles are granted or revoked, name is unregistered or re-registered | Token ID | Invalidates marketplace approvals when the name's permission profile changes |
| `eacVersionId` | Name is unregistered or re-registered | Resource | Isolates permissions across different registrations of the same label |
When a counter increments, the derived ID changes, producing a new token ID or a new resource. Re-registration increments both counters simultaneously.
#### ID Types
All three identifier types share the same upper 224 bits (from the labelhash) and differ only in their lower 32 bits:
| ID | Lower 32 bits | Used for |
| ---------------- | ---------------- | ------------------------------------------- |
| **Canonical ID** | `0x00000000` | `_entries` mapping key (name storage) |
| **Token ID** | `tokenVersionId` | ERC1155 ownership and marketplace approvals |
| **Resource** | `eacVersionId` | EAC `_roles` mapping (permission checks) |
#### ID Explorer
Explore how these IDs are derived for any label. Slide the version counters to see how the token ID and resource change while the canonical ID stays fixed.
#### Regeneration
When a version counter increments, the registry performs a **regeneration**: the old token is burned and a new one with the updated token ID is minted to the same owner, atomically in a single transaction. The owner doesn't change - only the ID does. The registry emits a `TokenRegenerated(oldTokenId, newTokenId)` event to signal this.
#### How This Solves the Problems
**Stale permissions** are solved by `eacVersionId`. When a name expires and is re-registered, both version counters increment. The new registration gets a fresh [resource](/ensv2/enhanced-access-control#resources) (because `eacVersionId` changed), and all role grants from the previous registration are effectively orphaned - they're stored under the old resource, which no longer corresponds to any active name.
**Transfer griefing** is solved by `tokenVersionId`. When Alice frontruns with `revokeRoles`, the registry's `_onRolesRevoked` hook fires, triggering regeneration. This increments `tokenVersionId`, burning the old token and minting a new one. The marketplace's `safeTransferFrom` still references the old token ID - but that token no longer exists. The transaction reverts with `ERC1155InsufficientBalance`, protecting the buyer from receiving a degraded name. If the buyer still wants it, they must re-approve using the new token ID, at which point they can inspect the current roles.
Note that legitimate ERC1155 transfers (`safeTransferFrom`) move roles from the old owner to the new owner *without* triggering regeneration. The internal role transfer bypasses the callbacks. This is intentional: a transfer should not invalidate the token ID that the transfer itself is using.
#### When Token IDs Change
| Event | eacVersionId | tokenVersionId | Token ID changes? | Effect |
| ------------------------------ | :----------: | :------------: | :--------------------------: | ----------------------------------------------------------- |
| First registration | - | - | New token minted | Both counters start at 0 (storage default) |
| Role grant/revoke | - | +1 | Yes (regeneration) | Invalidates marketplace approvals |
| Transfer | - | - | No | Owner changes, same token ID |
| Re-registration (after expiry) | +1 | +1 | Yes (old burned, new minted) | Old token burned, all prior role grants invalidated |
| Unregistration (registered) | +1 | +1 | Yes (burned) | Token destroyed, versions incremented for next registration |
| Unregistration (reserved) | - | - | No | No token exists to burn; counters unchanged |
| Renewal | - | - | No | Only expiry changes |
A token ID changes in exactly three situations: a role is granted or revoked, the name is re-registered after expiry, or the name is unregistered. Transfers and renewals never change the token ID.
### `anyId` Polymorphism
Because token IDs change, you might not always have the current token ID on hand. To make this easier, most [Permissioned Registry](/ensv2/permissioned-registry) functions accept a `uint256 anyId` parameter that can be any of:
* A **labelhash** (`keccak256` of the label string)
* A **token ID** (current or even stale)
* A **resource** (EAC resource identifier)
* A **canonical ID**
Internally, the registry applies `anyId ^ uint32(anyId)` to strip the lower 32 bits, resolving any of these to the same canonical ID and therefore the same storage entry. From that entry, the current token ID and resource are reconstructed as needed. This means you can pass whichever identifier you have - the registry figures out the rest.
```mermaid
flowchart TD
subgraph input [" "]
direction LR
LH(["labelhash"])
TK(["tokenId current or stale"])
RS(["resource"])
SI(["canonicalId"])
end
LH --> STRIP
TK --> STRIP
RS --> STRIP
SI --> STRIP
STRIP["_entry(anyId) anyId ^ uint32(anyId) → storageId (= canonicalId)"]
STRIP --> ENTRY["Entry storage subregistry · resolver · expiry eacVersionId · tokenVersionId"]
ENTRY --> BUILD_TK["_constructTokenId withVersion(anyId, tokenVersionId) lower 32 = tokenVersionId"]
ENTRY --> BUILD_RS["_constructResource withVersion(anyId, eacVersionId) lower 32 = eacVersionId"]
BUILD_TK --> USE_TK["tokenId ERC1155 ownership & marketplace approvals"]
BUILD_RS --> USE_RS["resource EAC _roles mapping & permission checks"]
style input fill:none,stroke:none
style LH fill:#CEE1E8,stroke:#0080BC,color:#011A25
style TK fill:#CEE1E8,stroke:#0080BC,color:#011A25
style RS fill:#CEE1E8,stroke:#0080BC,color:#011A25
style SI fill:#CEE1E8,stroke:#0080BC,color:#011A25
style STRIP fill:#0080BC,stroke:#011A25,color:#fff
style ENTRY fill:#011A25,stroke:#0080BC,color:#fff
style BUILD_TK fill:#F5F5F5,stroke:#0080BC,color:#011A25
style BUILD_RS fill:#F5F5F5,stroke:#0080BC,color:#011A25
style USE_TK fill:#0080BC,stroke:#011A25,color:#fff
style USE_RS fill:#0080BC,stroke:#011A25,color:#fff
```
Functions that accept `anyId` include: `setSubregistry()`, `setResolver()`, `renew()`, `unregister()`, `getExpiry()`, `getStatus()`, `getState()`, `getTokenId()`, `getResource()`, and all EAC role functions.
:::note
`anyId` polymorphism is specific to the [Permissioned Registry](/ensv2/permissioned-registry). The [Permissioned Resolver](/ensv2/permissioned-resolver) uses a [different resource scheme](/ensv2/enhanced-access-control#resources) based on `keccak256(node, part)` and does not use `anyId`.
:::
### Implications for Developers
#### Don't Cache Token IDs
Token IDs are not stable identifiers. If you need to reference a name, store the **labelhash** instead and use `getTokenId(anyId)` to get the current token ID when needed.
#### Use `anyId` Where Possible
Since most functions accept `anyId`, you can simply pass the labelhash and avoid dealing with token IDs entirely in many cases.
#### Marketplace Integrations
If you're building a marketplace or trading contract, be aware that:
1. Approvals tied to a specific token ID will be invalidated when the token ID changes
2. Use `getState(anyId)` to verify the current token ID before executing a trade
3. The `TokenRegenerated(oldTokenId, newTokenId)` event signals when a token ID changes
#### Reading Token ID Changes
```ts [Viem]
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({
chain: mainnet,
transport: http(),
})
// Get the current token ID for a name using its labelhash
const state = await client.readContract({
address: registryAddress,
abi: permissionedRegistryAbi,
functionName: 'getState',
args: [labelhash],
})
// state.tokenId is the current token ID
// state.resource is the current EAC resource
// state.status is AVAILABLE (0), RESERVED (1), or REGISTERED (2)
// state.latestOwner is the current owner
// state.expiry is the expiration timestamp
```
import { ENSv2Deployments } from '../../components/ensv2/ENSv2Deployments'
import { FrenCallout } from '../../components/ensv2/FrenCallout'
## ENSv2 Overview
ENSv2 is deployed on the Sepolia testnet, where you can already try the new
contracts with the [ENS Explorer](https://explorer.ens.dev). This is an early
preview rather than an official release, so expect the contracts to keep
evolving.
Welcome to the next evolution of the Ethereum Name Service!
ENSv2 introduces a suite of upgraded smart-contracts designed to make the protocol
more scalable, modular and future-proof. This section will outline the high-level
architecture, guiding principles and migration strategy for ENSv2.
The contracts and interfaces described here are **not yet final** and may
change prior to mainnet deployment.
### What's new in ENSv2?
* **Hierarchical Registries** - While ENSv1 used a single flat registry for all
names, ENSv2 uses a [hierarchical model](/ensv2/registry-hierarchy) where a full
name like `sub.alice.eth` is a chain of entries across registries linked by
subregistry pointers. Name owners can deploy their own subname registry on
demand. Registries and resolvers are deployed as proxies via the
[Verifiable Factory](/ensv2/verifiable-factory).
* **Permissions as Standard** - All of the functionality enabled by the [Name Wrapper
in ENSv1](/wrapper/overview) has been integrated into the core of ENSv2 via a new role-based
permission system called [Enhanced Access Control](/ensv2/enhanced-access-control).
* **Shorter Grace Period** - The grace period has been reduced from 90 days in ENSv1 to 28 days. During this window, the expired name can still be renewed (by anyone, not just the owner). After the grace period, names enter a temporary premium period.
* **Per-Account Resolvers** - Rather than sharing a single Public Resolver, every
account gets its own [Permissioned Resolver](/ensv2/permissioned-resolver) proxy
with fine-grained per-record permissions and [record aliasing](/ensv2/permissioned-resolver#aliasing).
```mermaid
flowchart LR
subgraph once ["One-time setup"]
VF["Verifiable\nFactory"]
PR["PermissionedResolver\n(per account)"]
VF -->|"deploy proxy"| PR
end
subgraph names ["Name registration"]
N1["alice.eth"]
N2["bob.eth"]
N3["wallet.eth"]
end
subgraph subnames ["On demand"]
UR["UserRegistry"]
end
N1 & N2 & N3 -.->|"resolver"| PR
N1 -->|"subregistry"| UR
```
### What hasn't changed?
* **Resolvers** - while we have developed new resolver contracts to take advantage
of the changed environment enabled by ENSv2, the resolver interface remains the
same. Custom resolver implementations are fully supported.
* **True Ownership** - ENSv2 continues to prioritize trust minimization, enabling
you to own your name fully, without having to worry about interference from
centralized third-parties.
### Architecture
| Page | Description |
| --------------------------------------------------------- | --------------------------------------------------------------------------- |
| [Registry Hierarchy](/ensv2/registry-hierarchy) | Hierarchical registry model, tree structure, resolution, namespace aliasing |
| [Enhanced Access Control](/ensv2/enhanced-access-control) | Role-based permission system replacing ENSv1 fuses |
| [ERC1155Singleton](/ensv2/erc1155-singleton) | Gas-optimized token standard with single ownership per token |
| [Mutable Token IDs](/ensv2/mutable-token-ids) | Dynamic token IDs that protect against permission leaks and griefing |
| [Hidden Contract Accounts](/ensv2/hca) | Smart-account attribution for HCA proxy wallets |
| [Verifiable Factory](/ensv2/verifiable-factory) | Deterministic CREATE2 proxy deployment with on-chain verification |
### Contracts
| Page | Description |
| ----------------------------------------------------- | ------------------------------------------------------------------ |
| [Permissioned Registry](/ensv2/permissioned-registry) | Tokenized registry managing name ownership, state, and permissions |
| [Permissioned Resolver](/ensv2/permissioned-resolver) | Per-account resolver with per-record roles and record aliasing |
| [ETH Registrar](/ensv2/eth-registrar) | Commit-reveal registration and renewal for .eth names |
| [Universal Resolver V2](/ensv2/universal-resolver-v2) | Single entry point for name resolution across the hierarchy |
| [DNS Name Resolution](/ensv2/dns-resolvers) | Resolving DNS domain names through the ENS protocol |
| [Reverse Resolution](/ensv2/reverse-resolution) | Primary names and multi-chain reverse resolution |
### Guides
| Page | Description |
| -------------------------------------------------------------- | --------------------------------------------------------------------- |
| [Migration](/ensv2/migration) | Guide to migrating ENSv1 names (locked, unlocked, unwrapped) to ENSv2 |
| [For App Developers](/ensv2/tutorial-app-developers) | What changes for apps that resolve ENS names |
| [For Contract Developers](/ensv2/tutorial-contract-developers) | Tutorial: build a subname registrar on the Permissioned Registry |
| [Registry Template](/ensv2/registry-template) | Building custom registries, configuration patterns, emancipation |
| [Indexing](/ensv2/indexing) | Events and functions for building indexers and subgraphs |
### Deployments (Sepolia)
import { Card } from '../../components/ui/Card'
import { FrenCallout } from '../../components/ensv2/FrenCallout'
import { RoleBitmapComposer } from '../../components/ensv2/RoleBitmapComposer'
import { ContractReference } from '../../components/ensv2/ContractReference'
import { MonoDiagram } from '../../components/ensv2/MonoDiagram'
## Permissioned Registry
The Permissioned Registry is the tokenized registry at the heart of ENSv2 name management. Each registered name becomes an [ERC1155Singleton](/ensv2/erc1155-singleton) token with exactly one owner, and all permissions are managed through [Enhanced Access Control](/ensv2/enhanced-access-control).
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### What Changed from ENSv1
In ENSv1, name management was split across three separate contracts: the ENS Registry (flat mapping of all names), the BaseRegistrar (ERC721 tokens for .eth), and the Name Wrapper (ERC1155 wrapping + fuses). ENSv2 replaces all three with a single unified contract. The table below summarizes the key differences; each concept is explained in the sections that follow.
| Feature | ENSv1 | ENSv2 Permissioned Registry |
| --------------- | ------------------------------------------------ | ------------------------------------------------------------------------------- |
| Architecture | Single flat registry for all names | [Hierarchical](/ensv2/registry-hierarchy): each name can have its own registry |
| Token standard | ERC721 (BaseRegistrar) or ERC1155 (Name Wrapper) | [ERC1155Singleton](/ensv2/erc1155-singleton) with single ownership |
| Permissions | One-way fuse burning (Name Wrapper) | Reversible role-based [EAC](/ensv2/enhanced-access-control) |
| Token IDs | Fixed (derived from namehash/labelhash) | [Mutable](/ensv2/mutable-token-ids): change on role updates and re-registration |
| Subname control | Requires Name Wrapper + fuse configuration | Built-in via subregistry pointer + per-name roles |
| Upgradeability | Not upgradeable | UUPS proxy pattern (for [UserRegistry](/ensv2/registry-template#userregistry)) |
| Name states | Registered or not | Three-state lifecycle (see [Name Lifecycle](#name-lifecycle)) |
### Names
Each name in the registry is identified by its **labelhash** (the `keccak256` hash of the label string). The on-chain data for a name is stored in an `Entry` struct:
| Field | Type | Purpose |
| ---------------- | ----------- | ----------------------------------------------------------------------------------------------------- |
| `subregistry` | `IRegistry` | Pointer to a subregistry for managing subnames |
| `resolver` | `address` | Resolver contract that holds this name's records |
| `expiry` | `uint64` | Timestamp after which the name is considered expired (`block.timestamp >= expiry`) |
| `eacVersionId` | `uint32` | Isolates permissions across registrations (see [Mutable Token IDs](/ensv2/mutable-token-ids)) |
| `tokenVersionId` | `uint32` | Invalidates marketplace approvals on role changes (see [Mutable Token IDs](/ensv2/mutable-token-ids)) |
Entries are stored in a mapping keyed by the [canonical ID](/ensv2/mutable-token-ids#canonical-id), the labelhash with its lower 32 bits zeroed.
### Name Lifecycle
Names exist in one of three states:
```mermaid
stateDiagram-v2
[*] --> AVAILABLE
AVAILABLE --> REGISTERED: register()
AVAILABLE --> RESERVED: register() with owner=0
RESERVED --> REGISTERED: register()
REGISTERED --> AVAILABLE: unregister() or expiry
RESERVED --> AVAILABLE: unregister() or expiry
```
* `AVAILABLE`: never registered or expired. Open for registration.
* `RESERVED`: placeholder with no owner and no token. Useful for pre-allocating names before assigning them.
* `REGISTERED`: has an owner, a token, and active permissions.
**State transitions:** each transition requires a specific [EAC role](#roles) with the indicated scope.
| From | To | Required role | [Scope](/ensv2/enhanced-access-control#resources) |
| --------------------- | ---------- | ------------------------ | ------------------------------------------------- |
| AVAILABLE | REGISTERED | `ROLE_REGISTRAR` | root |
| AVAILABLE | RESERVED | `ROLE_REGISTRAR` | root |
| RESERVED | REGISTERED | `ROLE_REGISTER_RESERVED` | root |
| REGISTERED / RESERVED | AVAILABLE | `ROLE_UNREGISTER` | root or name |
#### Registration
`register()` accepts a `label` (string), `owner`, `registry` (subregistry), `resolver`, `roleBitmap` (initial roles granted to the owner), and `expiry`. Labels are validated for size before registration. If `owner` is `address(0)`, the name is reserved instead of registered, and `roleBitmap` must be `0`.
* A non-expired registered name cannot be re-registered; it must be unregistered first.
* A reserved name cannot be re-reserved; it can only be promoted to registered.
* When promoting a `RESERVED` name to `REGISTERED`, if `expiry` is `0` the current expiry is preserved.
* Re-registering an expired name that had a previous owner burns the old token and increments both version counters. This ensures stale permissions and token approvals don't carry over.
#### Unregistration
`unregister()` sets the name's expiry to `block.timestamp`, making it immediately available. If the name was `REGISTERED` (has an owner), the token is burned and both version counters are incremented.
#### Renewal
`renew()` extends a name's expiry but cannot reduce it. Both `REGISTERED` and `RESERVED` names can be renewed. Expired names cannot be renewed; they must be re-registered.
### anyId Polymorphism
Most functions accept an `anyId` parameter that can be a `labelhash`, [token ID](/ensv2/mutable-token-ids#id-types), or [resource](/ensv2/enhanced-access-control#resources) interchangeably. Internally, `_entry()` zeroes the version bits to find the canonical storage slot for the name. This means you can pass whichever identifier you have on hand, and the registry resolves it to the same underlying entry.
See [Mutable Token IDs](/ensv2/mutable-token-ids#anyid-polymorphism) for the full explanation and diagram.
### Ownership
The token ID for a name changes when it is re-registered or when roles change (see [Mutable Token IDs](/ensv2/mutable-token-ids)).
`ownerOf()` returns `address(0)` for:
* Expired names (ownership is time-bounded)
* Stale token IDs (after versioning changes, old token IDs are no longer valid)
`latestOwnerOf()` returns the owner regardless of expiry or version staleness. This is useful for historical queries or determining who last held a name.
### EAC Integration
All permissions are managed through [Enhanced Access Control](/ensv2/enhanced-access-control).
#### Roles
| Role | Value | Scope | Purpose |
| ------------------------- | ------------------ | ------------ | ------------------------------------ |
| `ROLE_REGISTRAR` | `1 << 0` | root | Register or reserve names |
| `ROLE_REGISTER_RESERVED` | `1 << 4` | root | Promote reserved names to registered |
| `ROLE_SET_PARENT` | `1 << 8` | root | Set parent registry |
| `ROLE_UNREGISTER` | `1 << 12` | root or name | Unregister names |
| `ROLE_RENEW` | `1 << 16` | root or name | Extend expiry |
| `ROLE_SET_SUBREGISTRY` | `1 << 20` | root or name | Set subregistry |
| `ROLE_SET_RESOLVER` | `1 << 24` | root or name | Set resolver |
| `ROLE_CAN_TRANSFER_ADMIN` | `(1 << 28) << 128` | root or name | Authorize ERC1155 token transfers |
| `ROLE_SET_URI` | `1 << 36` | root | Set metadata URI and renderer |
| `ROLE_UPGRADE` | `1 << 124` | root | Authorize proxy upgrades |
Each role has a corresponding admin role at `role << 128` (e.g., `ROLE_SET_RESOLVER_ADMIN = (1 << 24) << 128`), except `ROLE_CAN_TRANSFER_ADMIN` which exists only as an admin role. In TypeScript, use `1n << 24n` for the bigint equivalent.
`ROLE_CAN_TRANSFER_ADMIN` has no regular (non-admin) variant and is checked against the token owner, not the operator. See [Transfers](#transfers) for details.
"Root" scope means the role only works on `ROOT_RESOURCE`. "Root or name" means it can be granted on either scope, and the two compose: a root grant applies to all names.
Admin roles on individual names are restricted to registration time (see [EAC Hook Overrides](#eac-hook-overrides)).
#### Role Bitmap Composer
Select roles to compose a bitmap value for use with `grantRoles` and `revokeRoles`.
#### Granting and Revoking Roles
The registry uses the standard EAC [`grantRoles` and `revokeRoles`](/ensv2/enhanced-access-control#granting-and-revoking) functions with [anyId polymorphism](#anyid-polymorphism). See [Code Examples](#code-examples) for usage.
You can only grant roles for which you hold the corresponding admin role. The set of roles granted at registration determines what the owner can delegate; see [Emancipation](#emancipation) for the default bitmap.
#### EAC Hook Overrides
The Permissioned Registry overrides several [EAC callback hooks](/ensv2/enhanced-access-control#callback-hooks) to enforce registry-specific invariants:
**Token regeneration on role changes**: when roles are granted or revoked via `grantRoles()` / `revokeRoles()`, the `_onRolesGranted` and `_onRolesRevoked` hooks trigger a token regeneration (burn + mint with a new token ID). This invalidates any pending ERC1155 transfer approvals tied to the old token ID, preventing an attacker from racing to transfer a token after their roles have been revoked.
**Admin role restriction on names**: `_getSettableRoles` is overridden so that admin roles on individual names can only be assigned at registration time. After registration, only regular (non-admin) roles can be granted on a name, but admin roles can still be revoked (including by the holder revoking their own). On `ROOT_RESOURCE`, admin roles work normally. This prevents a name owner from escalating their own permissions after registration.
#### Resource Scheme
This section explains how [EAC resources](/ensv2/enhanced-access-control#resources) are computed internally. You don't need this for normal use. `grantRoles` and `revokeRoles` handle resource computation automatically via [anyId polymorphism](/ensv2/mutable-token-ids#anyid-polymorphism).
The registry derives each EAC resource from the name's labelhash and its current `eacVersionId`, so permissions are scoped per-name and automatically invalidated on re-registration:
Registry resources participate in [anyId polymorphism](/ensv2/mutable-token-ids#anyid-polymorphism). See [Mutable Token IDs](/ensv2/mutable-token-ids) for how resources, token IDs, and canonical IDs relate.
### Transfers
Transferring a name's token requires `ROLE_CAN_TRANSFER_ADMIN` as an admin role on the **current owner** of the token. It does not matter who initiates the transfer; the role is always checked against the owner.
When a token transfers, all roles are atomically moved from the old owner to the new owner: the old owner's roles are revoked first (freeing assignee slots), then granted to the new owner. Roles granted to other accounts on the same name are unaffected. Without `ROLE_CAN_TRANSFER_ADMIN`, the name is effectively non-transferable, similar to the `CANNOT_TRANSFER` fuse in the Name Wrapper.
#### Operator Approval
The owner of a name can call `setApprovalForAll(operator, true)` to approve an operator. An approved operator inherits all of the owner's EAC roles on every name the owner holds (via the [`_getRoles` override](/ensv2/enhanced-access-control#role-resolution-hook)), allowing them to perform any role-gated action (set resolver, set subregistry, transfer, etc.) on the owner's behalf. This is an all-or-nothing delegation: an approved operator can act on every name the owner holds, with the owner's full set of roles. There is no way to scope operator approval to specific names or roles.
### Token Metadata
The registry's `uri(tokenId)` function returns token metadata following the [EIP-1155](https://eips.ethereum.org/EIPS/eip-1155) standard. It supports two modes:
* **Static URI:** a single string returned for all tokens. Clients substitute `{id}` with the hex token ID per the [EIP-1155 metadata spec](https://eips.ethereum.org/EIPS/eip-1155#metadata). Suitable when an off-chain service resolves per-token data from the URL template.
* **Dynamic renderer:** an `IRegistryURIRenderer` contract that generates metadata per token. The registry calls `renderURI(registry, tokenId)`, passing itself so the renderer can query on-chain state (owner, expiry, records) to compose SVGs or JSON.
The renderer takes precedence: if set, the static URI is ignored. If neither is set, `uri()` returns an empty string.
`setURI(uri_, renderer)` sets both atomically and requires `ROLE_SET_URI` on `ROOT_RESOURCE`.
#### Building a Custom Renderer
Implement `IRegistryURIRenderer`:
```solidity
interface IRegistryURIRenderer {
function renderURI(IRegistry registry, uint256 tokenId)
external view returns (string memory);
}
```
The registry passes itself as the first argument, so the renderer can call `getExpiry()`, `latestOwnerOf()`, etc. to build metadata from on-chain state.
### Parent Pointer
Each name's `subregistry` field creates a forward pointer from parent to child. The registry also stores a backward pointer to its own parent via `setParent()` / `getParent()`, creating a two-way link at every level:
.eth
parent
⇄
nick.eth
child
⇄
sub.nick.eth
grandchild
The backward pointer records both the parent registry's address and the label this registry is known by in the parent. The Universal Resolver's [`findCanonicalName()`](/ensv2/universal-resolver-v2#findcanonicalname) uses these backward pointers to walk up the tree, verifying at each step that `parent.getSubregistry(label)` points back to the current registry, and reconstructing the full name along the way.
Once this pointer is set, the registry has committed to its position in the hierarchy:
The **canonical parent** of a registry is the parent registry set by calling `setParent(parent, label)`. It identifies where this registry sits in the hierarchy: as `label` under `parent`.
However, as long as `ROLE_SET_PARENT` is still held on `ROOT_RESOURCE`, the pointer can be changed, effectively remounting the registry at a different position (e.g. moving from `nick.eth` to `nick.xyz`). To prevent this, the owner revokes `ROLE_SET_PARENT` and `ROLE_SET_PARENT_ADMIN` on `ROOT_RESOURCE`:
```ts [Viem]
import { createWalletClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const wallet = createWalletClient({ chain: mainnet, transport: http() })
const ROLE_SET_PARENT = 1n << 8n
const ROLE_SET_PARENT_ADMIN = ROLE_SET_PARENT << 128n
// Set the canonical parent: this registry is "sub" under the nick.eth registry
await wallet.writeContract({
address: subRegistryAddress,
abi: permissionedRegistryAbi,
functionName: 'setParent',
args: [parentRegistryAddress, 'sub'],
})
// Lock the parent pointer permanently by revoking both the role and its admin
await wallet.writeContract({
address: subRegistryAddress,
abi: permissionedRegistryAbi,
functionName: 'revokeRootRoles',
args: [ROLE_SET_PARENT | ROLE_SET_PARENT_ADMIN, ownerAddress],
})
```
A registry has a **locked canonical parent** when it has a canonical parent and no account holds `ROLE_SET_PARENT` or `ROLE_SET_PARENT_ADMIN` on `ROOT_RESOURCE`, making the pointer permanent.
### Emancipation
A registry is **emancipated** when it is a [verified](/ensv2/verifiable-factory#on-chain-verification) registry implementation and no account holds dangerous roles on `ROOT_RESOURCE`. Custom registry implementations cannot be emancipated by definition, because their code has not been verified.
Emancipation is a property of a registry, but its purpose is protecting names. This section covers how a registry is emancipated, how to verify it on-chain, and how emancipation composes across the hierarchy to fully protect individual names.
Certain roles on `ROOT_RESOURCE` can override individual name owners. For example, `ROLE_SET_RESOLVER` on ROOT can change the resolver on every name, `ROLE_UNREGISTER` can delete any name, and `ROLE_UPGRADE` can replace the registry implementation entirely. These are examples of **dangerous roles**: roles that can override individual name owners' permissions. A registry is emancipated when none of them have any assignees on `ROOT_RESOURCE`.
Non-dangerous roles like `ROLE_REGISTRAR` and `ROLE_RENEW` can safely remain on `ROOT_RESOURCE` (the registrar needs them to function). This is how the `.eth` registry works: the [ETH Registrar](/ensv2/eth-registrar) holds `ROLE_REGISTRAR` and `ROLE_RENEW` on `ROOT_RESOURCE`, while each name owner holds the roles defined by [`REGISTRATION_ROLE_BITMAP`](/ensv2/eth-registrar#roles-granted-at-registration). Once all dangerous roles are revoked, the separation is irreversible (you need the admin role to grant the admin role).
#### Verifying Emancipation
Call [`roleCount(ROOT_RESOURCE)`](/ensv2/enhanced-access-control#bitmap-layout) on the registry to inspect the assignee counts. For emancipation, two categories of roles must have zero assignees on `ROOT_RESOURCE`:
**Roles that directly endanger name owners** (any account holding these on `ROOT_RESOURCE` can act on any name):
| Role | Danger |
| ---------------------- | ---------------------------------------- |
| `ROLE_SET_RESOLVER` | Holder can change any name's resolver |
| `ROLE_SET_SUBREGISTRY` | Holder can change any name's subregistry |
| `ROLE_UNREGISTER` | Holder can delete any name |
**Roles that enable escalation or bypass access control:**
| Role | Danger |
| ---------------------------- | ------------------------------------------------------ |
| `ROLE_SET_RESOLVER_ADMIN` | Can grant `ROLE_SET_RESOLVER` |
| `ROLE_SET_SUBREGISTRY_ADMIN` | Can grant `ROLE_SET_SUBREGISTRY` |
| `ROLE_UNREGISTER_ADMIN` | Can grant `ROLE_UNREGISTER` |
| `ROLE_CAN_TRANSFER_ADMIN` | Can grant or revoke transfer rights on any name |
| `ROLE_UPGRADE` | Can replace the registry implementation via UUPS proxy |
| `ROLE_UPGRADE_ADMIN` | Can grant `ROLE_UPGRADE` |
`ROLE_REGISTRAR` and `ROLE_RENEW` on `ROOT_RESOURCE` are expected (the registrar needs them to function). They don't endanger existing registered names: `ROLE_REGISTRAR` can only register available names, and `ROLE_RENEW` only extends expiry (`CannotReduceExpiry`). Their admin counterparts (`ROLE_REGISTRAR_ADMIN`, `ROLE_RENEW_ADMIN`) are also excluded: granting someone the ability to register or renew does not endanger existing name owners.
#### Emancipation Across the Hierarchy
Emancipation at one level is not sufficient if a higher level can still interfere. If the `.eth` registry is emancipated but the root registry is not, an account with `ROLE_SET_SUBREGISTRY` on the root registry's `ROOT_RESOURCE` could swap the `.eth` subregistry pointer. True emancipation requires the entire chain from root down to be secured:
* **Root registry**: governed by ENS governance (trusted by design)
* **.eth registry**: emancipated (only `ROLE_REGISTRAR` + `ROLE_RENEW` granted on `ROOT_RESOURCE`)
* **2LD registries** (e.g., alice.eth): depends on the 2LD owner's configuration
For a subname like `sub.alice.eth` to be fully emancipated, the `alice.eth` owner must emancipate their registry (revoke the dangerous roles on `ROOT_RESOURCE`).
### Code Examples
Subname registries are deployed as UserRegistry proxies through the Verifiable Factory. See [Deploying a Registry Proxy](/ensv2/verifiable-factory#deploying-a-registry-proxy) for a code example. The examples below use the ETHRegistry, but the same functions are available on any PermissionedRegistry instance.
#### Querying Name State
`getState` returns the full state of a name in a single call: its registration status, expiry, current owner, token ID, and EAC resource. Both `getState` and `getStatus` accept any of a name's three identifiers (labelhash, tokenId, or resource) thanks to [anyId polymorphism](/ensv2/permissioned-registry#anyid-polymorphism):
```ts [Viem]
import { createPublicClient, http, keccak256, toHex } from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({ chain: mainnet, transport: http() })
const labelhash = BigInt(keccak256(toHex('alice')))
// Query the full state of a name
const state = await client.readContract({
address: registryAddress,
abi: permissionedRegistryAbi,
functionName: 'getState',
args: [labelhash],
})
// state: { status, expiry, latestOwner, tokenId, resource }
const STATUS = ['AVAILABLE', 'RESERVED', 'REGISTERED'] as const
console.log(STATUS[state.status]) // "REGISTERED"
console.log(state.latestOwner) // "0x..."
console.log(state.expiry) // expiry as unix timestamp
// anyId polymorphism: labelhash, tokenId, and resource all resolve
// to the same name, so this returns the same result
const sameState = await client.readContract({
address: registryAddress,
abi: permissionedRegistryAbi,
functionName: 'getState',
args: [state.tokenId],
})
```
If you only need to check whether a name is available, `getStatus` is a lighter alternative that returns just the status enum:
```ts [Viem]
const status = await client.readContract({
address: registryAddress,
abi: permissionedRegistryAbi,
functionName: 'getStatus',
args: [labelhash],
})
// 0 = AVAILABLE, 1 = RESERVED, 2 = REGISTERED
```
#### Granting and Revoking Roles
A name owner can delegate specific capabilities to other accounts by granting roles on the name's labelhash. Multiple roles can be combined into a single call using bitwise OR:
```ts [Viem]
import { createWalletClient, http, keccak256, toHex } from 'viem'
import { mainnet } from 'viem/chains'
const wallet = createWalletClient({ chain: mainnet, transport: http() })
const ROLE_SET_RESOLVER = 1n << 24n
const ROLE_SET_SUBREGISTRY = 1n << 20n
const labelhash = BigInt(keccak256(toHex('alice')))
// Grant a single role
await wallet.writeContract({
address: registryAddress,
abi: permissionedRegistryAbi,
functionName: 'grantRoles',
args: [labelhash, ROLE_SET_RESOLVER, operatorAddress],
})
// Grant multiple roles at once using bitwise OR
await wallet.writeContract({
address: registryAddress,
abi: permissionedRegistryAbi,
functionName: 'grantRoles',
args: [labelhash, ROLE_SET_RESOLVER | ROLE_SET_SUBREGISTRY, operatorAddress],
})
```
To revoke a role, call `revokeRoles` with the same arguments. Only the specified role is removed; other grants on the same name remain active:
```ts [Viem]
await wallet.writeContract({
address: registryAddress,
abi: permissionedRegistryAbi,
functionName: 'revokeRoles',
args: [labelhash, ROLE_SET_RESOLVER, operatorAddress],
})
// ROLE_SET_SUBREGISTRY remains active
```
### Reference
#### Write Functions
= current)' },
],
},
{
name: 'setSubregistry',
description: 'Set the subregistry for a name. Pointing two names to the same registry creates a namespace alias.',
params: [
{ name: 'anyId', type: 'uint256', description: 'Labelhash, token ID, or resource of the name' },
{ name: 'registry', type: 'IRegistry', description: 'Subregistry address' },
],
},
{
name: 'setResolver',
description: 'Set the resolver for a name. Requires ROLE_SET_RESOLVER.',
params: [
{ name: 'anyId', type: 'uint256', description: 'Labelhash, token ID, or resource of the name' },
{ name: 'resolver', type: 'address', description: 'Resolver address' },
],
},
{
name: 'setParent',
description: 'Set this registry\'s canonical parent. Requires ROLE_SET_PARENT on ROOT_RESOURCE.',
params: [
{ name: 'parent', type: 'IRegistry', description: 'Parent registry address' },
{ name: 'label', type: 'string', description: 'This registry\'s label within the parent' },
],
},
{
name: 'setURI',
description: 'Set the metadata URI and optional renderer. Requires ROLE_SET_URI on ROOT_RESOURCE.',
params: [
{ name: 'uri_', type: 'string', description: 'Static metadata URI' },
{ name: 'renderer', type: 'IRegistryURIRenderer', description: 'Renderer contract, or address(0) for static URI only' },
],
},
{
name: 'grantRoles',
description: 'Grant roles on a name. Triggers token regeneration.',
params: [
{ name: 'anyId', type: 'uint256', description: 'Labelhash, token ID, or resource of the name' },
{ name: 'roleBitmap', type: 'uint256', description: 'Bitmap of roles to grant' },
{ name: 'account', type: 'address', description: 'Account to grant roles to' },
],
returns: [
{ name: 'success', type: 'bool', description: 'Whether the roles were updated' },
],
},
{
name: 'revokeRoles',
description: 'Revoke roles on a name. Triggers token regeneration.',
params: [
{ name: 'anyId', type: 'uint256', description: 'Labelhash, token ID, or resource of the name' },
{ name: 'roleBitmap', type: 'uint256', description: 'Bitmap of roles to revoke' },
{ name: 'account', type: 'address', description: 'Account to revoke roles from' },
],
returns: [
{ name: 'success', type: 'bool', description: 'Whether the roles were updated' },
],
},
{
name: 'grantRootRoles',
description: 'Grant contract-wide roles on ROOT_RESOURCE.',
params: [
{ name: 'roleBitmap', type: 'uint256', description: 'Bitmap of roles to grant' },
{ name: 'account', type: 'address', description: 'Account to grant roles to' },
],
returns: [
{ name: 'success', type: 'bool', description: 'Whether the roles were updated' },
],
},
{
name: 'revokeRootRoles',
description: 'Revoke contract-wide roles on ROOT_RESOURCE.',
params: [
{ name: 'roleBitmap', type: 'uint256', description: 'Bitmap of roles to revoke' },
{ name: 'account', type: 'address', description: 'Account to revoke roles from' },
],
returns: [
{ name: 'success', type: 'bool', description: 'Whether the roles were updated' },
],
},
]}
/>
#### View Functions
#### Events
import { Card } from '../../components/ui/Card'
import { FrenCallout } from '../../components/ensv2/FrenCallout'
import { ResourceCalculator } from '../../components/ensv2/ResourceCalculator'
import { RoleBitmapComposer } from '../../components/ensv2/RoleBitmapComposer'
import { ContractReference } from '../../components/ensv2/ContractReference'
## Permissioned Resolver
In ENSv1, most names shared a single [Public Resolver](/resolvers/public) contract. In ENSv2, **each account gets its own resolver instance**, deployed as a UUPS-upgradeable proxy. All names owned by the same account share one resolver. This gives name owners fine-grained control over who can set which records, and enables new features like record aliasing.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### What Changed from ENSv1
| Feature | ENSv1 Public Resolver | ENSv2 Permissioned Resolver |
| --------------- | -------------------------- | --------------------------------------------------------------- |
| Deployment | Single shared contract | Per-account proxy instances |
| Permissions | Owner controls all records | Per-record-type roles via [EAC](/ensv2/enhanced-access-control) |
| Aliasing | Not supported | Built-in name aliasing with cycle protection |
| Record clearing | `clearRecords()` by owner | Record versioning |
| Upgradeability | Not upgradeable | UUPS proxy pattern |
### Supported Record Types
The Permissioned Resolver supports all standard ENS record types:
| Record | Getter | Setter | Standard |
| -------------------- | --------------------------------------- | ------------------------------------------- | ------------------------------------------------- |
| Address (ETH) | `addr(bytes32 node)` | `setAddr(bytes32 node, address)` | [ENSIP-1](/ensip/1) |
| Address (multichain) | `addr(bytes32 node, uint256 coinType)` | `setAddr(bytes32 node, uint256, bytes)` | [ENSIP-9](/ensip/9) |
| Text | `text(bytes32 node, string key)` | `setText(bytes32 node, string, string)` | [ENSIP-5](/ensip/5) |
| Content hash | `contenthash(bytes32 node)` | `setContenthash(bytes32 node, bytes)` | [ENSIP-7](/ensip/7) |
| Name (reverse) | `name(bytes32 node)` | `setName(bytes32 node, string)` | [EIP-181](https://eips.ethereum.org/EIPS/eip-181) |
| Public key | `pubkey(bytes32 node)` | `setPubkey(bytes32 node, bytes32, bytes32)` | [EIP-619](https://eips.ethereum.org/EIPS/eip-619) |
| ABI | `ABI(bytes32 node, uint256)` | `setABI(bytes32 node, uint256, bytes)` | [EIP-205](https://eips.ethereum.org/EIPS/eip-205) |
| Interface | `interfaceImplementer(bytes32, bytes4)` | `setInterface(bytes32, bytes4, address)` | [ENSIP-8](/ensip/8) |
| Data | `data(bytes32 node, string key)` | `setData(bytes32 node, string, bytes)` | [ENSIP-24](/ensip/24) |
### EAC Integration
All permissions are managed through [Enhanced Access Control](/ensv2/enhanced-access-control).
#### Roles
Each record type has its own role, allowing you to delegate specific record-setting permissions to different accounts:
| Role | Value | Scope | Purpose |
| ---------------------- | ---------- | --------------------- | ------------------------ |
| `ROLE_SET_ADDR` | `1 << 0` | root, name, or record | Set address records |
| `ROLE_SET_TEXT` | `1 << 4` | root, name, or record | Set text records |
| `ROLE_SET_CONTENTHASH` | `1 << 8` | root or name | Set content hash |
| `ROLE_SET_PUBKEY` | `1 << 12` | root or name | Set public key |
| `ROLE_SET_ABI` | `1 << 16` | root or name | Set ABI data |
| `ROLE_SET_INTERFACE` | `1 << 20` | root or name | Set interface records |
| `ROLE_SET_NAME` | `1 << 24` | root or name | Set reverse name |
| `ROLE_SET_ALIAS` | `1 << 28` | root | Set name aliases |
| `ROLE_CLEAR` | `1 << 32` | root or name | Clear all records |
| `ROLE_SET_DATA` | `1 << 36` | root, name, or record | Set data records |
| `ROLE_UPGRADE` | `1 << 124` | root | Authorize proxy upgrades |
Each role has a corresponding admin role at `role << 128` (e.g., `ROLE_SET_TEXT_ADMIN = (1 << 4) << 128`). In TypeScript, use `1n << 4n` for the bigint equivalent.
When deployed through the [Verifiable Factory](/ensv2/verifiable-factory), the deployer is granted **every role and its admin counterpart** on `ROOT_RESOURCE` by default, giving full control over all record types, aliasing, clearing, and upgrades.
#### Role Bitmap Composer
Select roles to compose a bitmap value for use with `authorizeNameRoles`.
#### Granting and Revoking Roles
Use the `authorize*` functions below to manage permissions (pass `true` to grant, `false` to revoke).
For root-level (`ROOT_RESOURCE`) permissions, you can also use `grantRootRoles()` / `revokeRootRoles()` inherited from [EAC](/ensv2/enhanced-access-control). `grantRoles()` and `revokeRoles()` are disabled on the Permissioned Resolver; use the `authorize*` functions for name and record-level permissions.
| Function | Scope | Grants/revokes |
| -------------------------------------------------------- | ------------ | ---------------------------------------- |
| `authorizeNameRoles(toName, roleBitmap, account, grant)` | Name-level | Any role(s) on a specific name |
| `authorizeTextRoles(toName, key, account, grant)` | Record-level | `ROLE_SET_TEXT` for a specific text key |
| `authorizeDataRoles(toName, key, account, grant)` | Record-level | `ROLE_SET_DATA` for a specific data key |
| `authorizeAddrRoles(toName, coinType, account, grant)` | Record-level | `ROLE_SET_ADDR` for a specific coin type |
All four functions take a DNS-encoded name (`bytes`) as the first parameter (`toName`). For example, `authorizeNameRoles(toName, ROLE_SET_TEXT, account, true)` grants the account permission to set any text key on that name, while `authorizeTextRoles(toName, "avatar", account, true)` restricts it to only the `avatar` key. To revoke either, make the same call with `false`.
`ROLE_SET_ADDR`, `ROLE_SET_TEXT`, and `ROLE_SET_DATA` support both levels of scoping. A name-level grant (via `authorizeNameRoles`) covers all keys or coin types on that name. A record-level grant (via `authorizeTextRoles`, `authorizeDataRoles`, or `authorizeAddrRoles`) covers only the specific key or coin type. When checking permissions, the resolver allows the action if the account has the role at either scope, so a name-level grant is a superset of any record-level grant.
#### Resource Scheme
This section explains how [EAC resources](/ensv2/enhanced-access-control#resources) are computed internally. You don't need this for normal use. The `authorize*` functions handle resource computation automatically.
Unlike the registry's labelhash-based resources, resolver resources are opaque hashes with no version structure, and the [anyId polymorphism](/ensv2/mutable-token-ids#anyid-polymorphism) used in the registry does not apply here. The resolver computes each EAC resource as a hash of the name's namehash and a record-type identifier:
```solidity
resource = keccak256(node, part)
```
Where `node` is the namehash of the full name (e.g., `namehash("alice.eth")`) and `part` is computed via `partHash`:
| Record type | Part computation |
| ----------------------- | -------------------------------------------------------- |
| Name-level (any record) | `bytes32(0)` |
| Text key | `partHash(key)` = `keccak256(bytes(key))` |
| Data key | `partHash(key)` = `keccak256(bytes(key))` |
| Coin type | `partHash(coinType)` = `keccak256(abi.encode(coinType))` |
When checking permissions, the resolver looks across four combinations of `node` and `part` and allows the action if any grant the required role. Using the shorthand `resource(node, part)` for `keccak256(node, part)`:
| | Any record | Specific record |
| ----------------- | -------------------------------------------------- | -------------------------- |
| **Any name** | `ROOT_RESOURCE` (when both node and part are zero) | `resource(0, part)` |
| **Specific name** | `resource(namehash, 0)` | `resource(namehash, part)` |
`authorizeNameRoles` sets the role on `resource(namehash, 0)` (second row, first column), while `authorizeTextRoles` with a specific key sets it on `resource(namehash, partHash(key))` (second row, second column). A role granted at `ROOT_RESOURCE` covers all four cells.
#### Resource Calculator
Compute the EAC resource for any name and record type.
### Aliasing
ENSv2 introduces **record aliasing**. Within a single resolver instance, you can make one name's records point to another name's records, so they always resolve identically without duplicating data.
```mermaid
flowchart LR
from["wallet.eth\n(alias)"]
to["alice.eth\n(canonical)"]
from -->|"resolves to"| to
query["Query: addr(wallet.eth)"]
result["Result: addr(alice.eth)"]
query -.-> from
to -.-> result
```
#### Setting an Alias
Both `setAlias` parameters are DNS-encoded names. The alias and target must use the same resolver instance:
```ts [Viem]
import { createWalletClient, http, toHex } from 'viem'
import { packetToBytes } from 'viem/ens'
import { mainnet } from 'viem/chains'
const wallet = createWalletClient({ chain: mainnet, transport: http() })
// Make wallet.eth resolve to the same records as alice.eth
await wallet.writeContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'setAlias',
args: [
toHex(packetToBytes('wallet.eth')), // from: the alias name
toHex(packetToBytes('alice.eth')), // to: the canonical name
],
})
// To remove an alias, set the target to empty
await wallet.writeContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'setAlias',
args: [toHex(packetToBytes('wallet.eth')), '0x00'],
})
```
Aliasing only works through the Universal Resolver's `resolve()` function. Calling `addr()` or `text()` directly on the resolver contract with an aliased name's namehash will return empty results, because the alias rewrite is part of the extended resolver resolution flow.
`setAlias` requires `ROLE_SET_ALIAS`, which is a root-only role. This ensures only the resolver's admin can set up aliases. To alias names that were registered with separate resolvers, first point them to the same resolver via `setResolver` on the registry.
#### Cycle Protection
The resolver detects self-referential aliases (A aliased to itself) and applies them only once. However, longer alias cycles (e.g., A -> B -> A or A -> B -> C -> A) are **not** detected and will cause an out-of-gas revert during resolution. Avoid creating circular alias chains.
#### Use Cases
* **Multiple domains, same records**: point `wallet.eth`, `brand.eth`, and `company.eth` to the same resolver, then alias them so they share one set of records
* **Name migration**: alias an old name to a new one so existing references continue to work
* **Subname delegation**: let subnames of a parent (e.g., `pay.alice.eth`, `nft.alice.eth`) resolve to the parent's records without duplicating them
Note that aliasing at the resolver level is different from [namespace aliasing](/ensv2/registry-hierarchy#namespace-aliasing) at the registry level. Resolver aliasing shares records; registry aliasing shares entire namespaces.
### Record Versioning
The resolver supports record versioning. The `clearRecords(node)` function increments the version, effectively clearing all records at once without individual delete calls. This is useful when you want a clean slate, for example when transferring a name to a new owner.
### UUPS Upgradeability
Each resolver instance is a UUPS proxy pointing to a shared implementation contract. The `ROLE_UPGRADE` (root-only) controls who can upgrade the implementation. This means:
* All resolver instances share the same logic, keeping deployment costs low
* The implementation can be upgraded to support new record types in the future
* Individual name owners can upgrade their resolver if they hold the upgrade role
See [Verifiable Factory](/ensv2/verifiable-factory) for how resolver proxies are deployed.
### Code Examples
Each account deploys its own resolver instance. See [Deploying a Resolver Proxy](/ensv2/verifiable-factory#deploying-a-resolver-proxy) for a code example showing how to deploy a Permissioned Resolver through the Verifiable Factory. The examples below assume you already have a `resolverAddress`.
#### Setting and Reading Records
The Permissioned Resolver implements the same record interfaces as ENSv1 resolvers (`addr`, `text`, `contenthash`, etc.), so existing viem patterns work unchanged. The only difference is that each account deploys its own resolver instance rather than sharing one:
```ts [Viem]
import { createPublicClient, createWalletClient, http, namehash } from 'viem'
import { normalize } from 'viem/ens'
import { mainnet } from 'viem/chains'
const client = createPublicClient({ chain: mainnet, transport: http() })
const wallet = createWalletClient({ chain: mainnet, transport: http() })
const name = normalize('alice.eth')
const node = namehash(name)
// Set an ETH address
await wallet.writeContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'setAddr',
args: [node, '0x1234...'],
})
// Set a text record
await wallet.writeContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'setText',
args: [node, 'avatar', 'https://example.com/avatar.png'],
})
// Read them back using viem's built-in ENS functions
const ethAddr = await client.getEnsAddress({ name })
const avatar = await client.getEnsText({ name, key: 'avatar' })
```
#### Delegating a Single Text Key
A name owner can grant a dApp permission to set only a specific text record, for example allowing it to update the `avatar` key without giving access to any other records:
```ts [Viem]
import { createWalletClient, http, namehash, toHex } from 'viem'
import { packetToBytes } from 'viem/ens'
import { mainnet } from 'viem/chains'
const wallet = createWalletClient({ chain: mainnet, transport: http() })
// authorize* functions take DNS-encoded names; record setters take namehashes
const dnsName = toHex(packetToBytes('alice.eth'))
const node = namehash('alice.eth')
// Grant a dApp permission to set ONLY the "avatar" text key on alice.eth
await wallet.writeContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'authorizeTextRoles',
args: [dnsName, 'avatar', dappAddress, true],
})
// The dApp can now set the avatar record...
await dappWallet.writeContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'setText',
args: [node, 'avatar', 'https://example.com/avatar.png'],
})
// ...but attempting to set any other key will revert
// setText(node, 'description', '...') → reverts with EACUnauthorizedAccountRoles
```
To grant access to all text keys on a name (not just one), use `authorizeNameRoles` with `ROLE_SET_TEXT`:
```ts [Viem]
const ROLE_SET_TEXT = 1n << 4n
await wallet.writeContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'authorizeNameRoles',
args: [dnsName, ROLE_SET_TEXT, dappAddress, true],
})
```
#### Revoking Permissions
To revoke a permission, call the same `authorize*` function with `false` as the last argument:
```ts [Viem]
// Revoke the dApp's permission to set the "avatar" text key
await wallet.writeContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'authorizeTextRoles',
args: [dnsName, 'avatar', dappAddress, false],
})
// Revoke name-level ROLE_SET_TEXT (all text keys)
await wallet.writeContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'authorizeNameRoles',
args: [dnsName, ROLE_SET_TEXT, dappAddress, false],
})
```
#### Locking a Record Permanently
Because each role has a corresponding admin role, an owner can make a record type permanently immutable by revoking both the role and its admin from themselves. Without the admin role, nobody can grant the role back.
```ts [Viem]
import { createWalletClient, http, namehash } from 'viem'
import { mainnet } from 'viem/chains'
const wallet = createWalletClient({ chain: mainnet, transport: http() })
const node = namehash('alice.eth')
const ROLE_SET_CONTENTHASH = 1n << 8n
const ROLE_SET_CONTENTHASH_ADMIN = ROLE_SET_CONTENTHASH << 128n
// Set the content hash one final time
await wallet.writeContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'setContenthash',
args: [node, contenthashBytes], // your encoded content hash
})
// Permanently lock: revoke both the role and its admin in one call
await wallet.writeContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'revokeRootRoles',
args: [ROLE_SET_CONTENTHASH | ROLE_SET_CONTENTHASH_ADMIN, ownerAddress], // your address
})
// The content hash can never be changed again, even by the owner
```
This is irreversible. Once both a role and its admin are revoked, there is no way to restore the ability to modify that record type.
#### Clearing All Records
The `clearRecords` function increments the record version for a name, effectively clearing all records at once without individual delete calls. This is useful when transferring a name to a new owner or starting fresh:
```ts [Viem]
const node = namehash('alice.eth')
// Check the current version
const versionBefore = await client.readContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'recordVersions',
args: [node],
})
// Clear all records in one call
await wallet.writeContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'clearRecords',
args: [node],
})
// The version is incremented; all previous records now return empty
const versionAfter = await client.readContract({
address: resolverAddress,
abi: permissionedResolverAbi,
functionName: 'recordVersions',
args: [node],
})
// versionAfter === versionBefore + 1n
```
### Reference
#### Write Functions
#### View Functions
#### Events
import { FrenCallout } from '../../components/ensv2/FrenCallout'
## Registry Hierarchy
ENSv2 replaces ENSv1's single flat registry with a hierarchical model where each name can have its own registry for managing subnames. This page explains the tree structure, how resolution works within it, and the implications for name ownership.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### From Flat to Hierarchical
ENSv1 used a simple architecture, where a single flat registry maintained a mapping
from all names to their owner and resolver addresses. Hierarchical ownership was enforced
through the use of [namehash](/resolution/names#namehash) to calculate IDs for subnames. This has the advantage of simplicity,
but means that ownership rules are enforced by a single, non-upgradeable contract, and
changes in the status of a parent name do not automatically ripple down to affect
subnames.
```mermaid
---
title: ENSv1 Structure
---
flowchart LR
subgraph Registry
direction LR
root["<root>owner: 0x0123..."]
eth["ethowner: 0x1234..."]
montoya["montoya.ethowner: 0x5678... • resolver: 0x6789..."]
inigo["inigo.montoya.ethowner: 0x7890... • resolver: 0x890A..."]
domingo["domingo.montoya.ethowner: 0x90AB... • resolver: 0x0ABC..."]
end
resolvers@{label: "Resolvers", shape: processes}
Registry --> resolvers
```
In ENSv2, registries are hierarchical: each name can have a resolver and a subregistry:
```mermaid
---
title: ENSv2 Structure
---
flowchart TB
root@{label: "<root>owner: 0x0123..."}
eth@{label: "ethowner: 0x1234..."}
montoya@{label: "montoya.ethowner: 0x5678..."}
inigo@{label: "Resolver 2owner: 0x7890... addr(60): 0x7890...", shape: notch-rect}
domingo@{label: "domingo.montoya.ethowner: 0x90AB..."}
montoyaResolver@{label: "Resolver 1owner: 0x5678... addr(60): 0x5678...", shape: notch-rect}
root-- eth --> eth
eth-- montoya --> montoya
eth-- montoya --> montoyaResolver
montoya-- inigo --> inigo
montoya-- domingo --> domingo
```
Here, registries are shown as rectangles, while resolvers are shown as notched rectangles.
Note that the registry for `.eth` has both a subregistry and a resolver defined for `montoya.eth`.
Note also that there's a resolver defined for `inigo.montoya.eth` but no subregistry, while
`domingo.montoya.eth` has a subregistry but no resolver. A name only needs to have a subregistry
defined if it wants the ability to create subnames, and it only needs a resolver defined if it wants to
define records to resolve for that name or its subnames.
### Names as Chains of Entries
A full name like `inigo.montoya.eth` does not exist as a single on-chain object. Instead, it's a chain of entries across registries: `inigo` is an entry in the `montoya.eth` registry, `montoya` is an entry in the `.eth` registry, and these are linked by the `subregistry` field on each entry. This means that on-chain, a "name" always refers to a single label within a specific registry. The full name is reconstructed by walking up the registry hierarchy.
### The IRegistry Interface
Every registry in the hierarchy, whether it's the root registry, a TLD registry like `.eth`, or a user's subname registry, must implement the `IRegistry` interface:
```solidity
interface IRegistry is IRegistryEvents {
/// Returns the child registry for a label, or address(0) if none exists.
function getSubregistry(string calldata label) external view returns (IRegistry);
/// Returns the resolver for a label, or address(0) if none exists.
function getResolver(string calldata label) external view returns (address);
/// Returns this registry's parent and the label it's registered under.
function getParent() external view returns (IRegistry parent, string memory label);
}
```
These three functions are what enable the hierarchical model:
* **`getSubregistry(label)`**: links a name to its child registry. This is how the tree is traversed downward during resolution.
* **`getResolver(label)`**: links a name to its resolver contract. This is how records are found during resolution.
* **`getParent()`**: links a registry back to its parent. This is how the [Universal Resolver V2](/ensv2/universal-resolver-v2) verifies canonical registry chains.
`IRegistry` is deliberately minimal. It says nothing about tokens, permissions, or name lifecycle. The [Permissioned Registry](/ensv2/permissioned-registry) is the standard implementation that adds [ERC1155Singleton](/ensv2/erc1155-singleton) tokenization, [Enhanced Access Control](/ensv2/enhanced-access-control), expiry management, and [mutable token IDs](/ensv2/mutable-token-ids). But a custom registry could implement `IRegistry` with entirely different ownership and access models.
#### IRegistryEvents
`IRegistry` inherits `IRegistryEvents`, which defines the events any registry is expected to emit:
| Event | Emitted when |
| -------------------- | ----------------------------------- |
| `LabelRegistered` | A name is registered with an owner |
| `LabelReserved` | A name is reserved (no owner) |
| `LabelUnregistered` | A name is unregistered |
| `ExpiryUpdated` | Expiry extended via `renew()` |
| `SubregistryUpdated` | Child registry pointer changed |
| `ResolverUpdated` | Resolver address changed |
| `TokenRegenerated` | Token ID changed due to role update |
| `ParentUpdated` | Canonical parent reference changed |
### Resolution
Resolution works by walking down the registry tree from root, querying `getResolver()` and `getSubregistry()` at each level. The deepest resolver found along the path wins (longest-suffix match). If a name has no resolver of its own, it inherits the resolver of its closest ancestor that does.
See [Resolution: findResolver](/ensv2/universal-resolver-v2#resolution) on the Universal Resolver V2 page for the full algorithm and step-by-step examples with diagrams.
### Subtree Operations
One consequence of this tree structure is that it becomes possible to delete or reassign entire subtrees in a
single operation. For example, suppose `montoya.eth` is transferred to a new owner, who wants to
configure their own set of subdomains; they can simply replace the subregistry responsible for subnames of
`montoya.eth` with their own new subregistry:
```mermaid
flowchart TB
eth@{label: "ethowner: 0x1234..."}
montoya@{label: " owner: 0x5678..."}
newMontoya@{label: "montoya.ethowner: 0xABCD..."}
inigo@{label: "Resolver 2owner: 0x7890... addr(60): 0x7890...", shape: notch-rect}
domingo@{label: "domingo.montoya.ethowner: 0x90AB..."}
montoyaResolver@{label: "Resolver 1owner: 0x5678... addr(60): 0x5678...", shape: notch-rect}
eth ~~~ montoya
eth-- montoya --> newMontoya
eth-- montoya --> montoyaResolver
montoya-- inigo --> inigo
montoya-- domingo --> domingo
```
All resolvers and subregistries previously associated with `montoya.eth` are thus removed in a single operation.
The new owner will still need to replace the resolver for `montoya.eth` if they wish to change how the bare
name itself is resolved, however.
### Namespace Aliasing
This hierarchical structure need not be limited to trees, either; by reusing the same subregistry for more
than one name, entire namespaces can be aliased to each other:
```mermaid
flowchart TB
root@{label: "Registry 1owner: 0x0123..."}
eth@{label: "Registry 2owner: 0x1234..."}
montoya@{label: "Registry 3owner: 0x5678..."}
inigo@{label: "Resolver 4owner: 0x7890... addr(60): 0x7890...", shape: notch-rect}
domingo@{label: "Registry 5owner: 0x90AB..."}
montoyaResolver@{label: "Resolver 1owner: 0x5678... addr(60): 0x5678...", shape: notch-rect}
root-- eth --> eth
eth-- montoya --> montoya
eth-- wallet --> montoya
eth-- montoya --> montoyaResolver
montoya-- inigo --> inigo
montoya-- domingo --> domingo
```
In this example, both `inigo.montoya.eth` and `inigo.wallet.eth` resolve identically, as would any other
subnames with resolvers set. Notably, `domingo.montoya.eth` will resolve using Resolver 1, while
`domingo.wallet.eth` will not resolve at all - there are no resolvers set anywhere in its hierarchy! If we
set a resolver for `domingo` in Registry 3, both names would resolve identically using it, just as they do
for `inigo`.
This example also illustrates that in ENSv2, registries have no individual concept of 'their name'.
In earlier diagrams we labelled each registry with a name for convenience, but as these examples demonstrate,
there's no requirement that a registry have exactly one name associated with it - it can have thousands, or none
at all!
### Token Representation and Permissions
Registries in ENSv2 are typically implemented as [ERC1155Singleton](/ensv2/erc1155-singleton) token contracts, where each subname is an NFT with exactly one owner. Because of the hierarchical structure, merely owning a subname token does not guarantee anything in isolation: the registry must be referenced by a parent registry, and so on up to the root. Client authors must take care in how they represent names to users. The [Universal Resolver V2](/ensv2/universal-resolver-v2) provides `findCanonicalRegistry()` to verify a registry's position in the hierarchy.
Permissions are managed through [Enhanced Access Control](/ensv2/enhanced-access-control), a role-based system where each name has its own set of grantable and revokable roles. By selectively revoking roles, all the functionality of the [ENSv1 Name Wrapper](/wrapper/overview) can be replicated. See the [Permissioned Registry](/ensv2/permissioned-registry) page for the full role table, transfer behavior, and code examples.
import { FrenCallout } from '../../components/ensv2/FrenCallout'
## Registry Template
In ENSv1, different parts of the namespace used different registry contracts: the root ENS Registry, the Name Wrapper, and dedicated subname registrars were all separate, incompatible implementations. ENSv2 unifies this with a single extensible base: **PermissionedRegistry**.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### One Registry to Rule Them All
[PermissionedRegistry](/ensv2/permissioned-registry) is not just the contract that manages `.eth` names. It's the template that every registry in the ENSv2 hierarchy is built from. Whether it's the root `.eth` registry, a project running a subdomain service, or a DAO managing community names, they all use the same base contract.
This means every registry in the tree shares the same:
* **[ERC1155Singleton](/ensv2/erc1155-singleton)** token model: each name is an NFT with a single owner
* **[Enhanced Access Control](/ensv2/enhanced-access-control)** permission system: roles, admin roles, resource scoping
* **[Mutable Token IDs](/ensv2/mutable-token-ids)**: version counters that protect against stale permissions and transfer griefing
* **[anyId polymorphism](/ensv2/mutable-token-ids#anyid-polymorphism)**: accept labelhash, tokenId, or resource interchangeably
* **Name lifecycle**: the same AVAILABLE → REGISTERED state machine (with optional reservation)
### Customizing via Inheritance
To create a custom registry, you inherit from `PermissionedRegistry` and override the behaviors you need. The base contract is designed with virtual functions at the key extension points.
ENSv2 ships two derived registries:
* **UserRegistry**: for user-owned subdomain registries (described below)
* **WrapperRegistry**: a migration-specific variant that receives locked Name Wrapper tokens from ENSv1 (see [Migration](/ensv2/migration))
#### UserRegistry
A UUPS-upgradeable proxy designed for user-owned subdomain registries. It adds:
* Proxy-based deployment via [`VerifiableFactory`](/ensv2/verifiable-factory) (cheap per-name deployments)
* An `initialize()` function that grants initial roles to the `rootAccount`
* Upgrade authorization gated by `ROLE_UPGRADE`
```solidity
contract UserRegistry is Initializable, PermissionedRegistry, UUPSUpgradeable, IProxyAuthorization {
// ... constructor, supportsInterface, _authorizeUpgrade omitted
function initialize(address rootAccount, uint256 roleBitmap) public initializer {
if (rootAccount == address(0)) {
revert InvalidOwner();
}
emit RegistryCreated();
_grantRoles(ROOT_RESOURCE, roleBitmap, rootAccount, false);
}
}
```
This is the registry that gets deployed when someone creates subnames under their name. Each child namespace gets its own `UserRegistry` proxy instance.
### Registry Interface Hierarchy
Registry functionality is split across layered interfaces. Each layer adds a capability, and PermissionedRegistry implements all of them. A custom registry can implement only the layers it needs.
```mermaid
graph BT
IRegistry["IRegistry"]
IOwnedRegistry["IOwnedRegistry"]
ITemporalRegistry["ITemporalRegistry"]
IERC1155Singleton["IERC1155Singleton"]
ITokenizedRegistry["ITokenizedRegistry"]
IStandardRegistry["IStandardRegistry"]
IEnhancedAccessControl["IEnhancedAccessControl"]
IPermissionedRegistry["IPermissionedRegistry"]
IRegistry --> IOwnedRegistry
IRegistry --> ITemporalRegistry
IERC1155Singleton --> ITokenizedRegistry
IOwnedRegistry --> ITokenizedRegistry
ITokenizedRegistry --> IStandardRegistry
ITemporalRegistry --> IStandardRegistry
IStandardRegistry --> IPermissionedRegistry
IEnhancedAccessControl --> IPermissionedRegistry
```
| Interface | Extends | Adds |
| ----------------------- | --------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `IRegistry` | `IRegistryEvents` | `getSubregistry`, `getResolver`, `getParent` |
| `IOwnedRegistry` | `IRegistry` | `findOwner` |
| `ITemporalRegistry` | `IRegistry` | `findExpiry` |
| `ITokenizedRegistry` | `IOwnedRegistry`, `IERC1155Singleton` | `findTokenId` |
| `IStandardRegistry` | `ITemporalRegistry`, `ITokenizedRegistry` | `register`, `renew`, `unregister`, `setSubregistry`, `setResolver`, `setParent`, `getExpiry` |
| `IPermissionedRegistry` | `IStandardRegistry`, `IEnhancedAccessControl` | `latestOwnerOf`, `getState`, `getStatus`, `getResource`, `getTokenId` |
`IRegistry` is the minimum for participating in ENS resolution: the [Universal Resolver V2](/ensv2/universal-resolver-v2) only needs `getSubregistry`, `getResolver`, and `getParent` to walk the hierarchy. The intermediate interfaces are opt-in capability layers that URv2 detects via [ERC-165](https://eips.ethereum.org/EIPS/eip-165) (for example, [`findOwner`](/ensv2/universal-resolver-v2#findowner) checks for `IOwnedRegistry` before calling it). Most custom registries should extend PermissionedRegistry directly for the full feature set.
#### Which Interface to Target
| Goal | Interface | What you get |
| ---------------------------------------- | ----------------------------- | ------------------------------------------------------------- |
| Resolution only (no ownership or tokens) | `IRegistry` | Participates in the hierarchy, URv2 can resolve through it |
| Resolution + ownership queries | `IOwnedRegistry` | URv2's `findOwner` works |
| Full ENS-compatible registry | `IStandardRegistry` | Tokens, expiry, lifecycle, compatible with ENSjs and indexers |
| Full registry + permissions | Extend `PermissionedRegistry` | Everything above, plus EAC roles out of the box |
PermissionedRegistry registers all of these interfaces via ERC-165 (`supportsInterface`), so external contracts can detect exactly which capabilities a given registry supports at runtime.
### Configuration Patterns
While every registry uses the same base contract, the way roles are configured determines the trust model. The two main patterns are:
#### Fully Controlled (Managed)
The registry operator keeps operational roles on `ROOT_RESOURCE` and configures each name's token roles at registration via the `roleBitmap` parameter of `register()`. This gives the operator flexibility to decide exactly how much control each name owner gets.
For example, a company managing `xyzcompany.eth` subnames for its members could:
**Operator roles on `ROOT_RESOURCE`:**
* `ROLE_REGISTRAR`: register new member names
* `ROLE_UNREGISTER`: remove members who leave
* `ROLE_RENEW`: extend member name expiry
* `ROLE_SET_RESOLVER`: set or override resolvers for any member
* `ROLE_SET_SUBREGISTRY`: override or set subregistry for any member
**Member token roles (set per name at registration):**
* Give `ROLE_SET_RESOLVER` if members should control their own records
* Withhold `ROLE_CAN_TRANSFER_ADMIN` if members shouldn't transfer their name (admin-only role; there is no non-admin `ROLE_CAN_TRANSFER`)
* Withhold `ROLE_SET_SUBREGISTRY` if members shouldn't create sub-subnames
Since the roles on `ROOT_RESOURCE` overlap with member token roles (e.g. the operator also holds `ROLE_SET_RESOLVER`), the operator retains the ability to override individual members. This is intentional for managed setups where the operator needs administrative access.
#### Emancipated (ETH-like)
An emancipated registry separates operator and owner roles so that no `ROOT_RESOURCE` role can interfere with individual name owners. This is how the `.eth` registry works: the registrar holds only `ROLE_REGISTRAR` and `ROLE_RENEW`, while name owners hold `ROLE_SET_RESOLVER`, `ROLE_SET_SUBREGISTRY`, and `ROLE_CAN_TRANSFER_ADMIN` on their own tokens.
See [Permissioned Registry: Emancipation](/ensv2/permissioned-registry#emancipation) for the full definition, verification steps, and how emancipation works across the hierarchy.
### Building Your Own Registry
To build a custom registry for your project, extend `PermissionedRegistry` and override what you need. Common customization points include:
| Override | Purpose | Example |
| -------------------------------------------------- | -------------------------- | --------------------------------------------- |
| `register()` | Custom registration logic | Add allowlists, custom pricing, or validation |
| `getResolver()` / `getSubregistry()` | Custom resolution behavior | Fallback to external data sources |
| `_authorizeUpgrade()` (requires `UUPSUpgradeable`) | Upgrade control | Gate upgrades behind a multisig or governance |
All of the inherited infrastructure (EAC roles, ERC1155 tokens, anyId polymorphism, the name lifecycle state machine) works out of the box. You only override the parts you want to change.
import { FrenCallout } from '../../components/ensv2/FrenCallout'
## Reverse Resolution
Reverse resolution maps an address back to an ENS name (the "primary name"). ENSv1 already supports multi-chain reverse resolution via per-chain [L2 Reverse Registrars](/registry/reverse) with signature-based claims ([ENSIP-19](/ensip/19)). ENSv2 refines the signature-based claim system and integrates reverse resolution into the v2 registry hierarchy.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### At Launch
At launch, the reverse namespace (`addr.reverse`, `default.reverse`) stays on v1 infrastructure. The v2 `ReverseRegistry` holds entries for these names, but resolution is handled by v1 contracts. Existing primary names continue to work without any user action.
**`addr.reverse`**: Registered in the `ReverseRegistry` with `ENSV1Resolver` as its resolver. This mirrors resolution through the v1 ENS registry, so existing v1 reverse records are automatically visible in v2. Users update their reverse record via the v1 `ReverseRegistrar.claimForAddr()`.
**`default.reverse`**: Uses the v1 `DefaultReverseRegistrar`, which stores primary names directly and resolves them via wildcard resolution on-contract. Users set their name via `DefaultReverseRegistrar.setNameForAddr()`.
#### Contract Account Adapters
Contract accounts (multisigs, smart wallets, protocol contracts) can't always interact with the v1 reverse registrars directly, because those contracts authorize via `msg.sender`. Two adapter contracts, added as controllers on the v1 registrars, forward reverse-record updates on behalf of a contract:
| Adapter | Method | Forwards to |
| -------------------------------- | -------------------------- | ------------------------------------------ |
| `ReverseRegistrarAdapter` | `claim(account, resolver)` | `ReverseRegistrar.claimForAddr()` |
| `DefaultReverseRegistrarAdapter` | `setName(account, name)` | `DefaultReverseRegistrar.setNameForAddr()` |
Both adapters authorize the caller with the same "namer" check: the caller must be the account itself, the account's `Ownable` owner, or approved by the account's own `IContractNamer.isContractNamer()` implementation.
#### Migration to v2-native
The reverse namespace will be migrated to v2-native infrastructure post-launch. The same migration pattern applies to both testnet and mainnet. The full multi-chain reverse resolution system described below will be rolled out as L2 partner integrations are completed.
### Multi-Chain Reverse Resolution (Upcoming)
ENSv2 introduces a new `L2ReverseRegistrar` designed to be deployed on each chain individually. Each deployment stores the mapping from addresses to their primary names for that chain. The [Universal Resolver V2](/ensv2/universal-resolver-v2) resolves reverse lookups via the inherited `reverse(lookupAddress, coinType)` function, which queries the appropriate reverse registrar based on the coin type.
#### Setting a Primary Name
The L2ReverseRegistrar provides four methods for setting a primary name, covering different authorization models:
| Method | Authorization | Use case |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- |
| `setName(name)` | `msg.sender` only | EOA setting its own primary name |
| `setNameForAddr(addr, name)` | Caller is the address, its `Ownable` owner, or approved via the address's `IContractNamer` | Setting from a different caller context |
| `setNameForAddrWithSignature(claim, signature)` | ERC-191 signature from the address | Gasless or cross-chain claims for EOAs |
| `setNameForContractWithSignature(claim, owner, signature)` | `owner` passes the namer check (Ownable or `IContractNamer`) and provides an ERC-191 signature (ERC-1271/6492 supported) | Contracts owned by smart contract wallets (Safe, ERC-4337) |
#### Signature-Based Claims
For gasless or cross-chain primary name claims, the signature methods accept a `NameClaim` struct:
```solidity
struct NameClaim {
string name; // The ENS name to set as primary
address addr; // The address to set it for
uint256[] chainIds; // Chain IDs where this claim applies
uint256 signedAt; // Timestamp for replay protection
}
```
The `chainIds` array allows a single signature to set the primary name across multiple chains simultaneously. Chain IDs must be in strictly ascending order.
#### Signature Format
Signatures use ERC-191 plaintext format:
```
You are setting your ENS primary name to:
{name}
Address: {address}
Chains: {chainList}
Signed At: {signedAt}
```
Where `{chainList}` is a comma-separated list of chain IDs and `{signedAt}` is an ISO 8601 UTC datetime string.
For contracts (via `setNameForContractWithSignature`), the format includes an additional `Owner` field:
```
You are setting the ENS primary name for a contract you own to:
{name}
Contract Address: {address}
Owner: {owner}
Chains: {chainList}
Signed At: {signedAt}
```
#### Replay Protection
Signature-based methods use an **inception timestamp** system. Each address has a stored inception timestamp on-chain. For a signature to be valid:
1. The signature's `signedAt` must be strictly greater than the current inception for that address
2. The `signedAt` must not be in the future (`signedAt <= block.timestamp`)
When a valid signature is used, the inception is updated to the `signedAt` value. This ensures each signature can only be used once per chain, and newer signatures always supersede older ones. Query the current inception via `inceptionOf(address)`.
#### Contract Name Sync
The L2ReverseRegistrar also supports `syncName(addr)` for contracts that implement the `IContractName` interface. This allows anyone to sync a contract's primary name without requiring a signature, using the name the contract itself declares.
### Contracts
| Contract | Status | Purpose |
| --------------------------------- | ------------------ | --------------------------------------------------------------------- |
| `ReverseRegistry` | Deployed at launch | PermissionedRegistry for the `.reverse` TLD |
| `ENSV1Resolver` | Deployed at launch | Mirrors v1 reverse resolution for `addr.reverse` |
| `ReverseRegistrarAdapter` | Deployed at launch | Contract-account adapter for `addr.reverse` claims |
| `DefaultReverseRegistrarAdapter` | Deployed at launch | Contract-account adapter for `default.reverse` name setting |
| `L2ReverseRegistrar` | Upcoming | Per-chain reverse registrar with signature-based claims |
| `StandaloneReverseRegistrar` | Upcoming | Abstract base for L2ReverseRegistrar |
| `L2ReverseRegistrarWithMigration` | Upcoming | Variant supporting migration from older reverse registrar deployments |
import { FrenCallout } from '../../components/ensv2/FrenCallout'
## For App Developers
This guide walks through integrating ENSv2 into an application: resolving names like `nick.eth` to addresses, reading profile records, displaying primary names, and letting users update their own records.
**If your application only reads ENS data, updating to a [supported library](#supported-libraries) is the whole job.** The resolver interface is unchanged from ENSv1 and resolution goes through the Universal Resolver at a fixed address, so your integration keeps working even as the ENSv2 contracts (**not yet final**) evolve underneath. Only the write flows are ENSv2-specific, and their authorization details ([Who Can Write](#who-can-write)) may still change before mainnet.
### What Changes for Apps
The short answer: very little, by design. Forward resolution, text records, avatars, and primary names all work through the same library calls you use today; for read-only integrations, a library update is the entire migration. The ENSv2-specific parts of this page start at [Writing Records](#writing-records): letting users update their records touches the new permission model.
What changes underneath:
* **One entry point, new address.** All resolution goes through the [Universal Resolver](/ensv2/universal-resolver-v2), which walks the new hierarchical registries and orchestrates CCIP-Read (the actual gateway HTTP requests are made by your client library, as in ENSv1; a contract cannot fetch offchain data itself). Updated libraries target it automatically.
* **Records move to per-account resolvers.** In ENSv1 most names shared a single Public Resolver contract. In the standard ENSv2 flow each account gets its own [Permissioned Resolver](/ensv2/permissioned-resolver) instance. Reading records is unchanged (the Universal Resolver finds the right contract for you), but *writing* records means calling whatever resolver the name actually uses instead of a well-known shared one.
* **Names are ERC1155 tokens in per-name registries.** Each parent name can have its own registry contract, so subnames form separate NFT collections. Token IDs are [mutable](/ensv2/mutable-token-ids), which matters if you index or cache them.
* **ENSv1 names keep working.** Names that have not migrated resolve through a mirror resolver that forwards lookups into the v1 registry, so your integration does not need to distinguish between migrated and unmigrated names.
### Supported Libraries
The examples in this guide show four libraries side by side: viem, wagmi (which inherits ENS support from its installed viem), ethers, and ENSjs. Everything below assumes an ENSv2-ready version of your library; the [ENSv2 readiness page](/web/ensv2-readiness) tracks which versions those are, both for these four and for the wider ecosystem (web3.py, web3j, and others). ENSjs stands out for write flows: among the four shown here, it is the only one with purpose-built helpers for updating records.
### Project Setup
ENSv2 is currently deployed on Sepolia for testing. The Universal Resolver is an upgradeable proxy that lives at the **same address on mainnet and Sepolia**, and supported libraries ship that address for both networks. Targeting the ENSv2 test deployment is therefore nothing more than selecting the Sepolia chain; no address configuration is needed, and the same code runs against mainnet by switching the chain back.
:::code-group
```ts [viem]
import { createPublicClient, http } from 'viem'
import { sepolia } from 'viem/chains'
const client = createPublicClient({
chain: sepolia,
transport: http(),
})
```
```tsx [Wagmi]
import { createConfig, http } from 'wagmi'
import { sepolia } from 'wagmi/chains'
export const config = createConfig({
chains: [sepolia],
transports: {
[sepolia.id]: http(),
},
})
```
```ts [Ethers]
import { JsonRpcProvider } from 'ethers'
// Naming the network selects the ENS configuration,
// including the Universal Resolver address
const provider = new JsonRpcProvider(RPC_URL, 'sepolia')
```
```ts [ENSjs]
import { createPublicClient, http } from 'viem'
import { sepolia } from 'viem/chains'
import { addEnsContracts } from '@ensdomains/ensjs'
const client = createPublicClient({
chain: addEnsContracts(sepolia),
transport: http(),
})
```
:::
The `client`, `config`, and `provider` objects created here are reused by every snippet below. Write snippets additionally assume a connected wallet client (`wallet` in viem/ENSjs, `signer` in ethers).
Do not hardcode a Universal Resolver address in your application. Your library resolves through the canonical proxy on its own, and any address you pin today (for example, an implementation address from a deployments table) can be superseded while the proxy stays current.
### Resolving Names
Forward resolution (name to address) is one call. Always [normalize](/resolution/names) user input first:
:::code-group
```ts [viem]
import { normalize } from 'viem/ens'
const address = await client.getEnsAddress({
name: normalize('nick.eth'),
})
```
```tsx [Wagmi]
import { normalize } from 'viem/ens'
import { useEnsAddress } from 'wagmi'
import { sepolia } from 'wagmi/chains'
function Address() {
const { data: address } = useEnsAddress({
name: normalize('nick.eth'),
chainId: sepolia.id,
})
}
```
```ts [Ethers]
import { ensNormalize } from 'ethers'
const address = await provider.resolveName(ensNormalize('nick.eth'))
```
```ts [ENSjs]
import { normalize } from 'viem/ens'
import { getAddressRecord } from '@ensdomains/ensjs/public'
const record = await getAddressRecord(client, {
name: normalize('nick.eth'),
})
const address = record?.value
```
:::
Under the hood, the Universal Resolver starts at the root registry and walks down one label at a time, asking each registry for the next one (`sub.nick.eth`: root to `eth` to `nick` to `sub`). Along the way it remembers the nearest resolver it has seen and calls it. When a name's data lives offchain or on an L2, the Universal Resolver drives the CCIP-Read protocol by telling your library which gateway to query; the library performs the HTTP request and feeds the response back for onchain verification. Your app never touches this machinery directly, but one consequence is worth knowing:
* A subname without its own resolver is served by the closest ancestor resolver. Registration alone is enough for a subname to resolve if its parent's resolver has records for it.
For chain-specific addresses (resolving a name for use on an L2), pass a `coinType`. See [Multichain Considerations](/web/ensv2-readiness#multichain-considerations) for the full pattern, including why resolution always runs against L1 even for L2 apps.
### Reading Records
Text records and avatars follow the same shape:
:::code-group
```ts [viem]
import { normalize } from 'viem/ens'
const twitter = await client.getEnsText({
name: normalize('nick.eth'),
key: 'com.twitter',
})
const avatar = await client.getEnsAvatar({
name: normalize('nick.eth'),
})
```
```tsx [Wagmi]
import { normalize } from 'viem/ens'
import { useEnsAvatar, useEnsText } from 'wagmi'
import { sepolia } from 'wagmi/chains'
function Profile() {
const { data: twitter } = useEnsText({
name: normalize('nick.eth'),
key: 'com.twitter',
chainId: sepolia.id,
})
const { data: avatar } = useEnsAvatar({
name: normalize('nick.eth'),
chainId: sepolia.id,
})
}
```
```ts [Ethers]
import { ensNormalize } from 'ethers'
const resolver = await provider.getResolver(ensNormalize('nick.eth'))
const twitter = await resolver?.getText('com.twitter')
const avatar = await resolver?.getAvatar()
```
```ts [ENSjs]
import { normalize } from 'viem/ens'
import { getRecords } from '@ensdomains/ensjs/public'
const records = await getRecords(client, {
name: normalize('nick.eth'),
texts: ['com.twitter', 'avatar'],
})
```
:::
The standard record keys (`avatar`, `description`, `com.twitter`, and so on) are unchanged from ENSv1; see [Text Records](/web/records) for the list.
### Primary Names
Displaying a primary name (reverse resolution: address to name) is also unchanged at the library level:
:::code-group
```ts [viem]
const name = await client.getEnsName({
address: '0x1111111111111111111111111111111111111111',
})
```
```tsx [Wagmi]
import { useEnsName } from 'wagmi'
import { sepolia } from 'wagmi/chains'
function Name() {
const { data: name } = useEnsName({
address: '0x1111111111111111111111111111111111111111',
chainId: sepolia.id,
})
}
```
```ts [Ethers]
const name = await provider.lookupAddress(
'0x1111111111111111111111111111111111111111',
)
```
```ts [ENSjs]
import { getName } from '@ensdomains/ensjs/public'
const result = await getName(client, {
address: '0x1111111111111111111111111111111111111111',
})
const name = result?.name
```
:::
A primary name must never be displayed without verifying that it forward-resolves back to the address. In ENSv2 the Universal Resolver enforces this onchain: during reverse resolution it forward-resolves the returned name and reverts with `ReverseAddressMismatch` if the addresses differ, so any result your library hands you has already passed the check. How primary names are *set* is evolving in ENSv2, including multi-chain primary names; see [Reverse Resolution](/ensv2/reverse-resolution) for the current state.
### Writing Records
This is the one place where ENSv2 changes your app's write path. The setter functions themselves are unchanged from ENSv1's public resolver interface, but there is no longer one well-known shared resolver your app can assume every name uses. In the standard flow, each account's records live on its own resolver instance, and records are keyed by the namehash of the full name. What is new is *where* records live and *who* is authorized to write them, not how they are written.
The flow: find the resolver the name actually uses, then call its setters as the name owner.
#### Find the Resolver
:::code-group
```ts [viem]
import { normalize } from 'viem/ens'
const resolverAddress = await client.getEnsResolver({
name: normalize('nick.eth'),
})
```
```tsx [Wagmi]
import { normalize } from 'viem/ens'
import { useEnsResolver } from 'wagmi'
import { sepolia } from 'wagmi/chains'
function Resolver() {
const { data: resolverAddress } = useEnsResolver({
name: normalize('nick.eth'),
chainId: sepolia.id,
})
}
```
```ts [Ethers]
import { ensNormalize } from 'ethers'
const resolver = await provider.getResolver(ensNormalize('nick.eth'))
const resolverAddress = resolver?.address
```
```ts [ENSjs]
import { normalize } from 'viem/ens'
import { getResolver } from '@ensdomains/ensjs/public'
const resolverAddress = await getResolver(client, {
name: normalize('nick.eth'),
})
```
:::
#### Set Records
ENSjs is the only library in this guide with dedicated record-writing helpers (`setRecords`, `setTextRecord`, `setAddressRecord`, and friends); it computes the namehash and batches multiple updates into a single resolver `multicall` for you. With the other libraries you call the resolver contract directly: the setters take the name's namehash as their first parameter, and the signatures below are the resolver's actual interface.
:::code-group
```ts [viem]
import { parseAbi } from 'viem'
import { namehash, normalize } from 'viem/ens'
const resolverAbi = parseAbi([
'function setAddr(bytes32 node, address addr_)',
'function setText(bytes32 node, string key, string value)',
'function multicall(bytes[] data) returns (bytes[])',
])
const node = namehash(normalize('nick.eth'))
// wallet is a viem wallet client connected to the name owner's account
// resolverAddress: from "Find the Resolver" above
await wallet.writeContract({
address: resolverAddress,
abi: resolverAbi,
functionName: 'setText',
args: [node, 'com.twitter', 'nicksdjohnson'],
})
```
```tsx [Wagmi]
import { useWriteContract } from 'wagmi'
import { parseAbi } from 'viem'
import { namehash, normalize } from 'viem/ens'
const resolverAbi = parseAbi([
'function setAddr(bytes32 node, address addr_)',
'function setText(bytes32 node, string key, string value)',
'function multicall(bytes[] data) returns (bytes[])',
])
const node = namehash(normalize('nick.eth'))
// resolverAddress: from "Find the Resolver" above
function EditRecord() {
const { writeContract } = useWriteContract()
return (
)
}
```
```ts [Ethers]
import { Contract, ensNormalize, namehash } from 'ethers'
const resolverAbi = [
'function setAddr(bytes32 node, address addr_)',
'function setText(bytes32 node, string key, string value)',
'function multicall(bytes[] data) returns (bytes[])',
]
const node = namehash(ensNormalize('nick.eth'))
// signer is connected to the name owner's account
// resolverAddress: from "Find the Resolver" above
const contract = new Contract(resolverAddress, resolverAbi, signer)
await contract.setText(node, 'com.twitter', 'nicksdjohnson')
```
```ts [ENSjs]
import { normalize } from 'viem/ens'
import { setRecords } from '@ensdomains/ensjs/wallet'
// wallet is a viem wallet client created with
// chain: addEnsContracts(sepolia), connected to the name owner
// userAddress: the address the name should resolve to
const hash = await setRecords(wallet, {
name: normalize('nick.eth'),
texts: [{ key: 'com.twitter', value: 'nicksdjohnson' }],
coins: [{ coin: 'ETH', value: userAddress }],
resolverAddress,
})
```
:::
To update several records in one transaction with viem, wagmi, or ethers, encode the individual calls and batch them through the resolver's `multicall` (ENSjs's `setRecords` does this automatically whenever you pass more than one record; wagmi's `writeContract` takes the same arguments as the viem call below):
:::code-group
```ts [viem]
import { encodeFunctionData, parseAbi } from 'viem'
import { namehash, normalize } from 'viem/ens'
const resolverAbi = parseAbi([
'function setAddr(bytes32 node, address addr_)',
'function setText(bytes32 node, string key, string value)',
'function multicall(bytes[] data) returns (bytes[])',
])
const node = namehash(normalize('nick.eth'))
// userAddress: the address the name should resolve to
await wallet.writeContract({
address: resolverAddress,
abi: resolverAbi,
functionName: 'multicall',
args: [[
encodeFunctionData({
abi: resolverAbi,
functionName: 'setAddr',
args: [node, userAddress],
}),
encodeFunctionData({
abi: resolverAbi,
functionName: 'setText',
args: [node, 'com.twitter', 'nicksdjohnson'],
}),
]],
})
```
```ts [Ethers]
import { Contract, ensNormalize, namehash } from 'ethers'
const resolverAbi = [
'function setAddr(bytes32 node, address addr_)',
'function setText(bytes32 node, string key, string value)',
'function multicall(bytes[] data) returns (bytes[])',
]
const node = namehash(ensNormalize('nick.eth'))
const contract = new Contract(resolverAddress, resolverAbi, signer)
// userAddress: the address the name should resolve to
await contract.multicall([
contract.interface.encodeFunctionData('setAddr', [node, userAddress]),
contract.interface.encodeFunctionData('setText', [
node,
'com.twitter',
'nicksdjohnson',
]),
])
```
:::
#### Who Can Write
Writes are permissioned through [Enhanced Access Control](/ensv2/enhanced-access-control) roles on the resolver. In the common case this is invisible to your app: an account that registers a name and deploys its resolver typically holds every role, so its setter calls simply succeed.
The case to be aware of is subnames. A subname owner typically uses the parent's resolver and holds **no** roles on it, so a `setText` from their wallet reverts with `EACUnauthorizedAccountRoles`. Depending on the setup, records for such names are managed by the parent owner, delegated per name or per record key via the resolver's `authorize*Roles` functions, or moved fully under the subname owner's control by pointing the subname at a resolver of their own. See [Permissioned Resolver](/ensv2/permissioned-resolver) for the delegation model.
Do not hardcode a resolver address for write flows, not even one you deployed. A name's resolver pointer can change at any time, and writing to a resolver a name no longer uses silently updates records nobody reads. Always look the resolver up fresh, and handle the revert case in your UI.
### Listing a User's Names
Enumerating all names an account owns is an indexed-data problem in ENSv2, same as in ENSv1: onchain lookups alone cannot enumerate names. Two v2-specific points if you build or consume an index:
* Names are ERC1155 tokens, but each registry is its own contract and collection, and token IDs change when roles change. Key any cache by labelhash, never by token ID; see [Mutable Token IDs](/ensv2/mutable-token-ids).
* See [Indexing ENSv2](/ensv2/indexing) for the event-level details needed to index registries and resolvers yourself.
For the general patterns (and ENSv1 options that keep working), see [Listing Names](/web/enumerate).
### Testing Your Integration
* **Resolution path**: the [readiness page](/web/ensv2-readiness#universal-resolver) provides test names (like `ur.integration-tests.eth`) that verify your app reaches the correct Universal Resolver and handles CCIP-Read.
* **End to end on Sepolia**: register a test name on the Sepolia deployment, set records with the snippets above, and confirm the resolution calls return them. The protocol contract addresses are in the [Deployments table](/ensv2/overview#deployments-sepolia).
* **DNS names**: make sure your name detection does not assume `.eth`; see [name detection](/web/ensv2-readiness#dns-names-and-name-detection).
#### Getting Test Funds
Registering a name on the Sepolia deployment costs two things: Sepolia ETH for gas (any public faucet works) and the registration fee, which the [ETH Registrar](/ensv2/eth-registrar) collects in an ERC20 token. On Sepolia that token is `MockUSDC` (address in the [Deployments table](/ensv2/overview#deployments-sepolia)), and it is free: its `mint` function has no access control, so anyone can mint themselves a balance.
```typescript
import { parseAbi } from 'viem'
// MockUSDC, from the Deployments table
await wallet.writeContract({
address: mockUsdcAddress,
abi: parseAbi(['function mint(address to, uint256 amount)']),
functionName: 'mint',
args: [account, 100_000_000n], // 100 USDC (6 decimals)
})
```
Before registering, `approve` the ETH Registrar to spend the minted balance; the registration flow itself is described on the [ETH Registrar](/ensv2/eth-registrar) page.
### Troubleshooting
| Symptom | Likely cause |
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Names that resolve in other apps return `null` in yours | Library version predates ENSv2 support; check the [readiness page](/web/ensv2-readiness) minimums |
| A name your user just registered resolves to `null` | No address record set yet; registration and records are separate steps |
| Record writes revert with `EACUnauthorizedAccountRoles` | The connected account holds no roles on that resolver, most commonly a subname owner writing to the parent's resolver (see [Who Can Write](#who-can-write)) |
| Record writes succeed but reads return old values | The write went to a resolver the name no longer points at; look up the resolver again instead of caching it |
| Resolution works in scripts but fails in the app | The app's environment blocks the HTTP requests CCIP-Read needs; see [CCIP Read](/web/ensv2-readiness#offchain-and-l2-resolution-with-ccip-read) |
### Next Steps
* Track library support and test names on the [ENSv2 readiness page](/web/ensv2-readiness)
* Understand the resolution machinery in [Universal Resolver V2](/ensv2/universal-resolver-v2)
* Go deeper on record permissions and delegation in [Permissioned Resolver](/ensv2/permissioned-resolver)
* Building a subname product on top of your integration? Continue with the [contract developers guide](/ensv2/tutorial-contract-developers)
import { FrenCallout } from '../../components/ensv2/FrenCallout'
## For Contract Developers
This guide walks through building a simplified subname registrar: a minimal smart contract that lets users register and renew subnames under a parent name you own. It demonstrates the core pattern that all ENSv2 registrars follow, including the production [ETH Registrar](/ensv2/eth-registrar).
The registrar built here is based on the ETH Registrar, but heavily simplified: commit-reveal, oracle-based pricing, grace periods, and referral tracking are stripped away, leaving the essentials of availability checks, flat-fee ERC20 payments, registration, and renewal.
By the end you will know how a registrar interfaces with an ENSv2 registry at the contract level, how the two are connected and authorized, how to interact with them through a client library like viem, and how to index the events they emit.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
You should understand the basics of the [Permissioned Registry](/ensv2/permissioned-registry) (where names are stored and permissions are enforced), [Enhanced Access Control](/ensv2/enhanced-access-control) (the role-based permission system), and the [Verifiable Factory](/ensv2/verifiable-factory) (how per-name registries are deployed).
### Architecture
In ENSv2, registries and registrars have distinct responsibilities. The **registry** (a [Permissioned Registry](/ensv2/permissioned-registry) or [UserRegistry](/ensv2/registry-template#userregistry)) stores names, manages ERC1155 tokens, and enforces permissions. Since each registry is its own contract, the subnames it manages form their own NFT collection, distinct from `.eth` names and from every other subname project. The **registrar** is a separate contract that sits in front of the registry and handles business logic: pricing, payment collection, availability checks, and any registration constraints.
The registrar calls `registry.register()` to create names and `registry.renew()` to extend them. For this to work, the registry owner must grant the registrar two roles on `ROOT_RESOURCE` (the registry-wide resource, whose roles apply to every name in the registry; see [Enhanced Access Control](/ensv2/enhanced-access-control)):
| Role | Value | Purpose |
| ---------------- | --------- | ----------------------------------------------- |
| `ROLE_REGISTRAR` | `1 << 0` | Authorizes calling `register()` on the registry |
| `ROLE_RENEW` | `1 << 16` | Authorizes calling `renew()` on the registry |
The registrar itself does not store names or manage tokens. It is a thin gatekeeper that validates inputs, collects payment, and delegates to the registry.
```mermaid
sequenceDiagram
participant User
participant Registrar as SimpleSubnameRegistrar
participant Token as ERC20 Token
participant Registry as UserRegistry
User->>Registrar: register("sub", owner, resolver, duration)
Registrar->>Registrar: validate availability and duration
Registrar->>Token: safeTransferFrom(user, beneficiary, price)
Registrar->>Registry: register("sub", owner, ..., expiry)
Registry->>Registry: mint ERC1155 token to owner
Registry-->>Registrar: tokenId
Registrar-->>User: tokenId
```
### Project Setup
This tutorial uses [Foundry](https://getfoundry.sh/). Install the ENSv2 contracts directly from their GitHub repository:
```bash
forge init simple-subname-registrar && cd simple-subname-registrar
forge install ensdomains/contracts-v2
```
The repository brings its own OpenZeppelin checkout along as a git submodule, so no separate OpenZeppelin install is needed. Add both remappings to `foundry.toml` so the import paths used below resolve:
```toml
remappings = [
"@ensdomains/contracts-v2/=lib/contracts-v2/contracts/src/",
"@openzeppelin/contracts/=lib/contracts-v2/contracts/lib/openzeppelin-contracts/contracts/",
]
```
You will build the contract in `src/SimpleSubnameRegistrar.sol`.
### Building the Contract
#### Imports and Role Bitmap
Start the file with the pragma and imports:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IPermissionedRegistry} from "@ensdomains/contracts-v2/registry/interfaces/IPermissionedRegistry.sol";
import {IRegistry} from "@ensdomains/contracts-v2/registry/interfaces/IRegistry.sol";
import {RegistryRolesLib} from "@ensdomains/contracts-v2/registry/libraries/RegistryRolesLib.sol";
```
Next, define `REGISTRATION_ROLE_BITMAP` as a file-level constant, between the imports and the contract declaration. It determines what permissions each name owner receives at registration. We use the same bitmap as the [ETH Registrar](/ensv2/eth-registrar#roles-granted-at-registration):
```solidity
uint256 constant REGISTRATION_ROLE_BITMAP =
RegistryRolesLib.ROLE_SET_SUBREGISTRY
| RegistryRolesLib.ROLE_SET_SUBREGISTRY_ADMIN
| RegistryRolesLib.ROLE_SET_RESOLVER
| RegistryRolesLib.ROLE_SET_RESOLVER_ADMIN
| RegistryRolesLib.ROLE_CAN_TRANSFER_ADMIN;
```
This grants name owners the ability to change their resolver, set up a child registry for sub-subnames, and transfer their name. The `_ADMIN` variants of the two setter roles let owners delegate those permissions to other accounts. `ROLE_CAN_TRANSFER_ADMIN` works differently: it is the transfer permission itself, an admin-only role with no non-admin counterpart. See [EAC roles](/ensv2/enhanced-access-control) for full details.
A role bitmap is a 256-bit integer where each role occupies one 4-bit slot (nybble); see [Bitmap Layout](/ensv2/enhanced-access-control#bitmap-layout). When `register()` is called on the registry, the bitmap determines which [EAC](/ensv2/enhanced-access-control) roles the new owner receives on their name's resource. Different registrars can use different bitmaps to create different trust models.
#### Contract Shell
The contract stores its configuration as immutable values set at deployment. All five parameters are fixed for the lifetime of the registrar: which registry it manages, which token it accepts, where payments go, the annual price, and the minimum registration duration.
```solidity
contract SimpleSubnameRegistrar {
using SafeERC20 for IERC20;
error NameNotAvailable(string label);
error NameNotRegistered(string label);
error InvalidOwner();
error DurationTooShort(uint64 duration, uint64 minimum);
event NameRegistered(
uint256 indexed tokenId, string label, address owner,
uint64 duration, uint256 price
);
event NameRenewed(
uint256 indexed tokenId, string label,
uint64 duration, uint64 newExpiry, uint256 price
);
IPermissionedRegistry public immutable REGISTRY;
IERC20 public immutable PAYMENT_TOKEN;
address public immutable BENEFICIARY;
uint256 public immutable PRICE;
uint64 public immutable MIN_DURATION;
constructor(
IPermissionedRegistry registry,
IERC20 paymentToken,
address beneficiary,
uint256 price,
uint64 minDuration
) {
REGISTRY = registry;
PAYMENT_TOKEN = paymentToken;
BENEFICIARY = beneficiary;
PRICE = price;
MIN_DURATION = minDuration;
}
```
`PRICE` is denominated in the payment token's smallest unit (e.g., for USDC with 6 decimals, a price of `5_000_000` means $5/year). `MIN_DURATION` is in seconds.
All functions in the following sections go inside this contract body.
#### Checking Availability
To check whether a label is available for registration, query the registry's `getState()` function. It returns a [State struct](/ensv2/permissioned-registry#querying-name-state) containing the name's status, expiry, latest owner (`latestOwner`), token ID, and resource. A name is available if its status is `AVAILABLE` (either never registered or expired).
The `getState()` function accepts an [anyId](/ensv2/mutable-token-ids#anyid-polymorphism): a labelhash, token ID, or resource. For a fresh lookup by label, use the labelhash (`keccak256(bytes(label))`).
```solidity
function isAvailable(string calldata label) public view returns (bool) {
IPermissionedRegistry.State memory state =
REGISTRY.getState(uint256(keccak256(bytes(label))));
return state.status == IPermissionedRegistry.Status.AVAILABLE;
}
function getPrice(uint64 duration) public view returns (uint256) {
return PRICE * duration / 365 days;
}
```
`getPrice()` pro-rates the annual price by duration. For example, if `PRICE` is 5 USDC and `duration` is six months (15768000 seconds), the cost is \~2.5 USDC. Integer division truncates toward zero, so pick `PRICE` and `MIN_DURATION` such that the minimum charge (`PRICE * MIN_DURATION / 365 days`) stays well above zero; otherwise short registrations become free.
#### Registering Names
The `register()` function validates inputs, collects payment, and delegates to `REGISTRY.register()`. The registry handles all token minting and role assignment internally.
```solidity
function register(
string calldata label,
address owner,
address resolver,
uint64 duration
) external returns (uint256 tokenId) {
if (!isAvailable(label)) revert NameNotAvailable(label);
if (owner == address(0)) revert InvalidOwner();
if (duration < MIN_DURATION) revert DurationTooShort(duration, MIN_DURATION);
uint256 price = getPrice(duration);
PAYMENT_TOKEN.safeTransferFrom(msg.sender, BENEFICIARY, price);
tokenId = REGISTRY.register(
label,
owner,
IRegistry(address(0)),
resolver,
REGISTRATION_ROLE_BITMAP,
uint64(block.timestamp) + duration
);
emit NameRegistered(tokenId, label, owner, duration, price);
}
```
A few things to note about the `REGISTRY.register()` call:
* `label` is the subname label only (e.g., `"sub"` for `sub.nick.eth`), not the full name
* `IRegistry(address(0))` means no child registry is set; the owner can set one later via `setSubregistry()` if they want sub-subnames
* `resolver` is the address of a [Permissioned Resolver](/ensv2/permissioned-resolver) proxy (or any contract implementing the resolver interface), typically [deployed per account via the Verifiable Factory](/ensv2/verifiable-factory#deploying-a-resolver-proxy); the owner can change it later with `setResolver()` since they hold `ROLE_SET_RESOLVER`
* `expiry` is an **absolute Unix timestamp**, not a duration
* The returned `tokenId` is the [ERC1155Singleton](/ensv2/erc1155-singleton) token minted to the owner
Unlike the ETH Registrar, this contract does not use commit-reveal. Commit-reveal prevents front-running by hiding registration parameters until after the commitment is recorded. For subnames this is typically unnecessary: the parent name owner controls the registry and can [reserve names](/ensv2/permissioned-registry#name-lifecycle) if needed.
#### Renewing Names
Renewal extends a name's expiry without changing ownership or permissions. Anyone can renew any name (not just the owner), which matches the ETH Registrar's behavior.
```solidity
function renew(string calldata label, uint64 duration) external {
if (duration < MIN_DURATION) revert DurationTooShort(duration, MIN_DURATION);
uint256 labelId = uint256(keccak256(bytes(label)));
IPermissionedRegistry.State memory state = REGISTRY.getState(labelId);
if (state.status != IPermissionedRegistry.Status.REGISTERED) {
revert NameNotRegistered(label);
}
uint256 price = getPrice(duration);
PAYMENT_TOKEN.safeTransferFrom(msg.sender, BENEFICIARY, price);
uint64 newExpiry = state.expiry + duration;
REGISTRY.renew(labelId, newExpiry);
emit NameRenewed(state.tokenId, label, duration, newExpiry, price);
}
}
```
The registry's `renew()` function accepts an [anyId](/ensv2/mutable-token-ids#anyid-polymorphism) (here we pass the labelhash) and an absolute `newExpiry` timestamp. The registry enforces that `newExpiry >= oldExpiry`, so the expiry can only increase.
The closing brace after `renew()` completes the contract. The full file is below; `forge build` should compile it cleanly.
View the complete SimpleSubnameRegistrar.sol
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IPermissionedRegistry} from "@ensdomains/contracts-v2/registry/interfaces/IPermissionedRegistry.sol";
import {IRegistry} from "@ensdomains/contracts-v2/registry/interfaces/IRegistry.sol";
import {RegistryRolesLib} from "@ensdomains/contracts-v2/registry/libraries/RegistryRolesLib.sol";
uint256 constant REGISTRATION_ROLE_BITMAP =
RegistryRolesLib.ROLE_SET_SUBREGISTRY
| RegistryRolesLib.ROLE_SET_SUBREGISTRY_ADMIN
| RegistryRolesLib.ROLE_SET_RESOLVER
| RegistryRolesLib.ROLE_SET_RESOLVER_ADMIN
| RegistryRolesLib.ROLE_CAN_TRANSFER_ADMIN;
contract SimpleSubnameRegistrar {
using SafeERC20 for IERC20;
error NameNotAvailable(string label);
error NameNotRegistered(string label);
error InvalidOwner();
error DurationTooShort(uint64 duration, uint64 minimum);
event NameRegistered(
uint256 indexed tokenId, string label, address owner,
uint64 duration, uint256 price
);
event NameRenewed(
uint256 indexed tokenId, string label,
uint64 duration, uint64 newExpiry, uint256 price
);
IPermissionedRegistry public immutable REGISTRY;
IERC20 public immutable PAYMENT_TOKEN;
address public immutable BENEFICIARY;
uint256 public immutable PRICE;
uint64 public immutable MIN_DURATION;
constructor(
IPermissionedRegistry registry,
IERC20 paymentToken,
address beneficiary,
uint256 price,
uint64 minDuration
) {
REGISTRY = registry;
PAYMENT_TOKEN = paymentToken;
BENEFICIARY = beneficiary;
PRICE = price;
MIN_DURATION = minDuration;
}
function isAvailable(string calldata label) public view returns (bool) {
IPermissionedRegistry.State memory state =
REGISTRY.getState(uint256(keccak256(bytes(label))));
return state.status == IPermissionedRegistry.Status.AVAILABLE;
}
function getPrice(uint64 duration) public view returns (uint256) {
return PRICE * duration / 365 days;
}
function register(
string calldata label,
address owner,
address resolver,
uint64 duration
) external returns (uint256 tokenId) {
if (!isAvailable(label)) revert NameNotAvailable(label);
if (owner == address(0)) revert InvalidOwner();
if (duration < MIN_DURATION) revert DurationTooShort(duration, MIN_DURATION);
uint256 price = getPrice(duration);
PAYMENT_TOKEN.safeTransferFrom(msg.sender, BENEFICIARY, price);
tokenId = REGISTRY.register(
label,
owner,
IRegistry(address(0)),
resolver,
REGISTRATION_ROLE_BITMAP,
uint64(block.timestamp) + duration
);
emit NameRegistered(tokenId, label, owner, duration, price);
}
function renew(string calldata label, uint64 duration) external {
if (duration < MIN_DURATION) revert DurationTooShort(duration, MIN_DURATION);
uint256 labelId = uint256(keccak256(bytes(label)));
IPermissionedRegistry.State memory state = REGISTRY.getState(labelId);
if (state.status != IPermissionedRegistry.Status.REGISTERED) {
revert NameNotRegistered(label);
}
uint256 price = getPrice(duration);
PAYMENT_TOKEN.safeTransferFrom(msg.sender, BENEFICIARY, price);
uint64 newExpiry = state.expiry + duration;
REGISTRY.renew(labelId, newExpiry);
emit NameRenewed(state.tokenId, label, duration, newExpiry, price);
}
}
```
This contract is provided for educational purposes only, not as a production-grade implementation. It is intentionally minimal and unaudited; review, test, and harden it before using it for anything real.
### Deploying and Authorizing
#### Set Up the UserRegistry
The registrar needs a registry to register into. If your parent name does not have one yet, two setup steps are required: deploy a UserRegistry proxy via the Verifiable Factory, then point your parent name at it with `setSubregistry()` on the parent registry.
For the first step, follow [Deploying a Registry Proxy](/ensv2/verifiable-factory#deploying-a-registry-proxy); the `ProxyDeployed` event in the receipt gives you the proxy address (`userRegistryAddress` below). Choose the `roleBitmap` passed to `initialize()` carefully: granting a role later via `grantRootRoles()` requires holding that role's `_ADMIN` variant, so the bitmap must include at least `ROLE_REGISTRAR_ADMIN` and `ROLE_RENEW_ADMIN` for the authorization step below to succeed.
The second step is what connects the new registry to the ENS hierarchy. The [Universal Resolver](/ensv2/universal-resolver-v2) finds subnames by walking `getSubregistry()` calls down from the root, so until your parent name points at the UserRegistry, names registered in it mint tokens but never resolve. You hold `ROLE_SET_SUBREGISTRY` on your name from registration, so this call needs no extra setup:
```typescript
import { keccak256, toHex, parseAbi } from 'viem'
const permissionedRegistryAbi = parseAbi([
'function setSubregistry(uint256 anyId, address subregistry)',
])
// Point the parent name at the UserRegistry
// (here: nick.eth, so this runs on the .eth registry)
await wallet.writeContract({
address: ethRegistryAddress,
abi: permissionedRegistryAbi,
functionName: 'setSubregistry',
args: [BigInt(keccak256(toHex('nick'))), userRegistryAddress],
})
```
The `wallet` object in the TypeScript snippets on this page is a viem wallet client; its setup is shown in [Client Integration](#client-integration) below. The protocol contract addresses (like `ethRegistryAddress`) are listed in the [Deployments](/ensv2/overview#deployments-sepolia) table.
#### Deploy the Registrar
The registrar is a plain contract (not a proxy), so deployment is straightforward. You need:
* The address of your UserRegistry (from the previous step)
* An ERC20 payment token address (on a testnet, deploy your own mintable mock ERC20; any token implementing `transferFrom`/`approve` works)
* A beneficiary address for receiving payments
* The annual price in the token's smallest unit
* A minimum registration duration in seconds
```solidity
SimpleSubnameRegistrar registrar = new SimpleSubnameRegistrar(
IPermissionedRegistry(userRegistryAddress),
IERC20(usdcAddress),
beneficiaryAddress,
5_000_000, // 5 USDC per year (6 decimals)
30 days // minimum 30-day registration
);
```
For example, deploying with Foundry:
```bash
forge create src/SimpleSubnameRegistrar.sol:SimpleSubnameRegistrar \
--rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast \
--constructor-args $USER_REGISTRY $PAYMENT_TOKEN $BENEFICIARY 5000000 2592000
```
The last two arguments are the price (5 USDC with 6 decimals) and the minimum duration (30 days in seconds). Passing a raw private key is acceptable for testnet experiments; in general, follow Foundry's [key management best practices](https://getfoundry.sh/guides/best-practices/key-management/) (encrypted keystores via `cast wallet import`, hardware wallets for production).
#### Grant Roles to the Registrar
After deployment, the registry owner must grant the registrar `ROLE_REGISTRAR` and `ROLE_RENEW` on `ROOT_RESOURCE`. Without these roles, calls to `registry.register()` and `registry.renew()` will revert.
In Solidity:
```solidity
registry.grantRootRoles(
RegistryRolesLib.ROLE_REGISTRAR | RegistryRolesLib.ROLE_RENEW,
address(registrar)
);
```
Or via viem:
```typescript
const ROLE_REGISTRAR = 1n << 0n
const ROLE_RENEW = 1n << 16n
await wallet.writeContract({
address: userRegistryAddress,
abi: [{
name: 'grantRootRoles',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'roleBitmap', type: 'uint256' },
{ name: 'account', type: 'address' },
],
outputs: [{ name: '', type: 'bool' }],
}],
functionName: 'grantRootRoles',
args: [ROLE_REGISTRAR | ROLE_RENEW, registrarAddress],
})
```
`ROLE_REGISTRAR` authorizes the registrar to register **any** name under your registry, and `ROLE_RENEW` lets it extend **any** name's expiry. Only grant these roles to contracts you trust and have audited.
Trust works in both directions: the roles that remain on `ROOT_RESOURCE` define how much subname owners must trust *you*. See [Configuration Patterns](/ensv2/registry-template#configuration-patterns) for the managed versus emancipated trade-off, and [Emancipation](/ensv2/permissioned-registry#emancipation) for how owners can verify your registry's setup.
### Client Integration
Once the registrar is deployed and authorized, users interact with it via standard `writeContract` calls. Here is a complete example using viem. ENSv2 is currently deployed on Sepolia, so the clients target that chain; in a browser app you would create the wallet client with `custom(window.ethereum)` instead of a private key account.
```typescript
import { createPublicClient, createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { sepolia } from 'viem/chains'
const registrarAddress = '0x...' // your deployed SimpleSubnameRegistrar
const paymentTokenAddress = '0x...' // ERC20 payment token (e.g., USDC)
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
const client = createPublicClient({ chain: sepolia, transport: http() })
const wallet = createWalletClient({ account, chain: sepolia, transport: http() })
const simpleRegistrarAbi = [
{
name: 'isAvailable', type: 'function', stateMutability: 'view',
inputs: [{ name: 'label', type: 'string' }],
outputs: [{ name: '', type: 'bool' }],
},
{
name: 'getPrice', type: 'function', stateMutability: 'view',
inputs: [{ name: 'duration', type: 'uint64' }],
outputs: [{ name: '', type: 'uint256' }],
},
{
name: 'register', type: 'function', stateMutability: 'nonpayable',
inputs: [
{ name: 'label', type: 'string' },
{ name: 'owner', type: 'address' },
{ name: 'resolver', type: 'address' },
{ name: 'duration', type: 'uint64' },
],
outputs: [{ name: 'tokenId', type: 'uint256' }],
},
{
name: 'renew', type: 'function', stateMutability: 'nonpayable',
inputs: [
{ name: 'label', type: 'string' },
{ name: 'duration', type: 'uint64' },
],
outputs: [],
},
] as const
```
#### Check Availability and Price
```typescript
const available = await client.readContract({
address: registrarAddress,
abi: simpleRegistrarAbi,
functionName: 'isAvailable',
args: ['sub'],
})
const oneYear = BigInt(365 * 24 * 60 * 60)
const price = await client.readContract({
address: registrarAddress,
abi: simpleRegistrarAbi,
functionName: 'getPrice',
args: [oneYear],
})
```
#### Register a Subname
Before calling `register()`, the caller must approve the registrar to spend the payment token:
```typescript
import { erc20Abi } from 'viem'
// Approve the registrar to spend the payment token
await wallet.writeContract({
address: paymentTokenAddress,
abi: erc20Abi,
functionName: 'approve',
args: [registrarAddress, price],
})
// Register sub.nick.eth
const hash = await wallet.writeContract({
address: registrarAddress,
abi: simpleRegistrarAbi,
functionName: 'register',
args: ['sub', ownerAddress, resolverAddress, oneYear],
})
```
To confirm the registration worked, check availability again; it should now return `false`:
```typescript
const stillAvailable = await client.readContract({
address: registrarAddress,
abi: simpleRegistrarAbi,
functionName: 'isAvailable',
args: ['sub'],
})
console.log(stillAvailable) // false
```
Once the owner sets records on the resolver, the name resolves like any other ENS name (e.g., via viem's `getEnsAddress`).
#### Renew a Subname
```typescript
// Approve payment for renewal
await wallet.writeContract({
address: paymentTokenAddress,
abi: erc20Abi,
functionName: 'approve',
args: [registrarAddress, price],
})
// Extend sub.nick.eth by one year
await wallet.writeContract({
address: registrarAddress,
abi: simpleRegistrarAbi,
functionName: 'renew',
args: ['sub', oneYear],
})
```
### Events and Indexing
A single `register()` call triggers events from **two contracts**: the registrar and the underlying registry. If you are building an indexer, you need to listen to both.
**From the registrar:**
| Event | Fields |
| ---------------- | ---------------------------------------------------- |
| `NameRegistered` | `tokenId`, `label`, `owner`, `duration`, `price` |
| `NameRenewed` | `tokenId`, `label`, `duration`, `newExpiry`, `price` |
**From the registry** (emitted inside `REGISTRY.register()`):
| Event | Purpose |
| ----------------- | ----------------------------------------------------- |
| `LabelRegistered` | Records the label, owner, expiry, and sender |
| `TokenResource` | Associates the ERC1155 token ID with the EAC resource |
| `TransferSingle` | ERC1155 mint (from `address(0)` to owner) |
| `ResolverUpdated` | Records the resolver address (if not `address(0)`) |
Renewals follow the same pattern: `renew()` emits the registrar's `NameRenewed` and the registry's `ExpiryUpdated`. Indexing only the registrar's events would miss renewals performed by other `ROLE_RENEW` holders directly on the registry.
Both custom events index the token ID, but token IDs in ENSv2 are **mutable**: they change whenever roles are granted or revoked on a name, and on re-registration after expiry. Key your index on the labelhash and follow `TokenRegenerated` events to track ID changes. See [Mutable Token IDs](/ensv2/mutable-token-ids#dont-cache-token-ids).
For the full event reference, parameter details, and indexing patterns, see [Indexing ENSv2](/ensv2/indexing).
### Troubleshooting
| Symptom | Likely cause |
| -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `register()` or `renew()` reverts with `EACUnauthorizedAccountRoles` | The registrar was not granted `ROLE_REGISTRAR` (or `ROLE_RENEW`) on the registry's `ROOT_RESOURCE` |
| `grantRootRoles()` reverts with `EACCannotGrantRoles` | The caller does not hold the `_ADMIN` variants of the roles being granted; check the `roleBitmap` passed to the UserRegistry's `initialize()` |
| `register()` reverts with an ERC20 allowance error | The caller did not `approve()` the registrar for the payment token, or approved less than `getPrice(duration)` |
| Registration succeeds but the name does not resolve | The parent name does not point at the UserRegistry; call `setSubregistry()` (see [Set Up the UserRegistry](#set-up-the-userregistry)), and make sure the name's resolver has records set |
### Next Steps
The `SimpleSubnameRegistrar` covers the core registration pattern. Here are common extensions you might add:
* **Commit-reveal**: prevent front-running by requiring a commitment before registration. See how the [ETH Registrar](/ensv2/eth-registrar#commit-reveal) implements this.
* **Allowlists or NFT-gating**: restrict who can register by checking token balances or merkle proofs in `register()`.
* **Custom role bitmaps**: use different `REGISTRATION_ROLE_BITMAP` values for different registration tiers (e.g., premium names get fewer restrictions).
* **Infinite-duration claims**: for permanent subnames, pass `type(uint64).max` as the expiry instead of computing it from a duration. `renew()` and the `ROLE_RENEW` grant become unnecessary, but abandoned names never return to the available pool; removing one then requires the operator to hold `ROLE_UNREGISTER`, a trust tradeoff expiry-based recycling avoids.
* **Grace periods**: keep expired names renewable for a window before they become available to others.
* **Custom registries**: for deeper customization, build your own registry by extending `PermissionedRegistry` directly. See [Registry Template](/ensv2/registry-template#building-your-own-registry).
import { FrenCallout } from '../../components/ensv2/FrenCallout'
import { ContractReference } from '../../components/ensv2/ContractReference'
import { MonoDiagram } from '../../components/ensv2/MonoDiagram'
## Universal Resolver V2
The Universal Resolver V2 is the primary public entry point for ENS name resolution. It resolves names by traversing the [hierarchical registry tree](/ensv2/registry-hierarchy), walking from the root registry down through subregistries to locate the correct resolver for any name. It also provides functions for navigating and verifying the registry hierarchy itself.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### Architecture
`UniversalResolverV2` extends `AbstractUniversalResolver`, a shared base contract that implements all resolution logic: forward resolution, reverse resolution, [CCIP-Read](/resolvers/ccip-read) gateway batching, and callback chaining. The only function the base leaves abstract is `findResolver()`, which each version overrides with its own registry traversal strategy:
* **URv1** walks a flat ENS registry
* **URv2** walks the hierarchical registry tree via `getSubregistry()` at each level
Because the walk passes through each parent registry, if a parent name expires or its subregistry is removed, the registry returns `address(0)` and all subnames stop resolving automatically.
`resolve()`, `reverse()`, and all CCIP-Read infrastructure work identically across both versions.
### Resolution
The Universal Resolver resolves names by walking down the registry tree from the root, looking for the **deepest resolver** along the path. At each level, it calls `getResolver(label)` on the current registry. If a resolver exists, it's remembered. Then it calls `getSubregistry(label)` to descend to the next level. The resolver that covers the longest matching suffix of the name wins.
`resolve()` and `reverse()` both call `findResolver` internally via `requireResolver`, which reverts if no suitable resolver is found.
Using the structure from the [registry hierarchy diagrams](/ensv2/registry-hierarchy#from-flat-to-hierarchical), here are two examples showing how `findResolver()` walks the tree:
#### Example 1
Resolving `inigo.montoya.eth`: the walk descends from root, checking for resolvers at each level:
```mermaid
flowchart LR
subgraph s1 [" "]
direction TB
r1@{label: "<root>"}
e1@{label: "eth"}
m1@{label: "montoya.eth"}
i1@{label: "Resolver 2", shape: notch-rect}
d1@{label: "domingo.montoya.eth"}
mr1@{label: "Resolver 1", shape: notch-rect}
r1-- eth --> e1
e1-- montoya --> m1
e1-- montoya --> mr1
m1-- inigo --> i1
m1-- domingo --> d1
style r1 fill:#0080BC,stroke:#011A25,color:#fff
style e1 fill:#CEE1E8,stroke:#0080BC,color:#011A25
style m1 fill:#CEE1E8,stroke:#0080BC,color:#011A25
style i1 fill:#CEE1E8,stroke:#CEE1E8,color:#999
style d1 fill:#CEE1E8,stroke:#CEE1E8,color:#999
style mr1 fill:#CEE1E8,stroke:#CEE1E8,color:#999
end
subgraph s2 [" "]
direction TB
r2@{label: "<root>"}
e2@{label: "eth"}
m2@{label: "montoya.eth"}
i2@{label: "Resolver 2", shape: notch-rect}
d2@{label: "domingo.montoya.eth"}
mr2@{label: "Resolver 1", shape: notch-rect}
r2-- eth --> e2
e2-- montoya --> m2
e2-- montoya --> mr2
m2-- inigo --> i2
m2-- domingo --> d2
style r2 fill:#0080BC,stroke:#011A25,color:#fff
style e2 fill:#0080BC,stroke:#011A25,color:#fff
style m2 fill:#CEE1E8,stroke:#0080BC,color:#011A25
style i2 fill:#CEE1E8,stroke:#CEE1E8,color:#999
style d2 fill:#CEE1E8,stroke:#CEE1E8,color:#999
style mr2 fill:#CEE1E8,stroke:#0080BC,color:#011A25
end
subgraph s3 [" "]
direction TB
r3@{label: "<root>"}
e3@{label: "eth"}
m3@{label: "montoya.eth"}
i3@{label: "Resolver 2", shape: notch-rect}
d3@{label: "domingo.montoya.eth"}
mr3@{label: "Resolver 1", shape: notch-rect}
r3-- eth --> e3
e3-- montoya --> m3
e3-- montoya --> mr3
m3-- inigo --> i3
m3-- domingo --> d3
style r3 fill:#0080BC,stroke:#011A25,color:#fff
style e3 fill:#0080BC,stroke:#011A25,color:#fff
style m3 fill:#0080BC,stroke:#011A25,color:#fff
style i3 fill:#011A25,stroke:#0080BC,color:#fff
style d3 fill:#CEE1E8,stroke:#CEE1E8,color:#999
style mr3 fill:#CEE1E8,stroke:#0080BC,color:#011A25
end
style s1 fill:none,stroke:none
style s2 fill:none,stroke:none
style s3 fill:none,stroke:none
s1 ~~~ s2 ~~~ s3
```
Resolver 1 is found at the .eth level, but Resolver 2 is found one level deeper at montoya.eth. **Resolver 2 wins** because it covers the longest matching suffix, in this case the full name `inigo.montoya.eth`.
#### Example 2
Resolving `domingo.montoya.eth`: the walk follows the same path, but `domingo` has no resolver:
```mermaid
flowchart LR
subgraph s1 [" "]
direction TB
r1@{label: "<root>"}
e1@{label: "eth"}
m1@{label: "montoya.eth"}
i1@{label: "Resolver 2", shape: notch-rect}
d1@{label: "domingo.montoya.eth"}
mr1@{label: "Resolver 1", shape: notch-rect}
r1-- eth --> e1
e1-- montoya --> m1
e1-- montoya --> mr1
m1-- inigo --> i1
m1-- domingo --> d1
style r1 fill:#0080BC,stroke:#011A25,color:#fff
style e1 fill:#CEE1E8,stroke:#0080BC,color:#011A25
style m1 fill:#CEE1E8,stroke:#0080BC,color:#011A25
style i1 fill:#CEE1E8,stroke:#CEE1E8,color:#999
style d1 fill:#CEE1E8,stroke:#CEE1E8,color:#999
style mr1 fill:#CEE1E8,stroke:#CEE1E8,color:#999
end
subgraph s2 [" "]
direction TB
r2@{label: "<root>"}
e2@{label: "eth"}
m2@{label: "montoya.eth"}
i2@{label: "Resolver 2", shape: notch-rect}
d2@{label: "domingo.montoya.eth"}
mr2@{label: "Resolver 1", shape: notch-rect}
r2-- eth --> e2
e2-- montoya --> m2
e2-- montoya --> mr2
m2-- inigo --> i2
m2-- domingo --> d2
style r2 fill:#0080BC,stroke:#011A25,color:#fff
style e2 fill:#0080BC,stroke:#011A25,color:#fff
style m2 fill:#CEE1E8,stroke:#0080BC,color:#011A25
style i2 fill:#CEE1E8,stroke:#CEE1E8,color:#999
style d2 fill:#CEE1E8,stroke:#CEE1E8,color:#999
style mr2 fill:#CEE1E8,stroke:#0080BC,color:#011A25
end
subgraph s3 [" "]
direction TB
r3@{label: "<root>"}
e3@{label: "eth"}
m3@{label: "montoya.eth"}
i3@{label: "Resolver 2", shape: notch-rect}
d3@{label: "domingo.montoya.eth"}
mr3@{label: "Resolver 1", shape: notch-rect}
r3-- eth --> e3
e3-- montoya --> m3
e3-- montoya --> mr3
m3-- inigo --> i3
m3-- domingo --> d3
style r3 fill:#0080BC,stroke:#011A25,color:#fff
style e3 fill:#0080BC,stroke:#011A25,color:#fff
style m3 fill:#0080BC,stroke:#011A25,color:#fff
style i3 fill:#CEE1E8,stroke:#CEE1E8,color:#999
style d3 fill:#CEE1E8,stroke:#0080BC,color:#011A25
style mr3 fill:#011A25,stroke:#0080BC,color:#fff
end
style s1 fill:none,stroke:none
style s2 fill:none,stroke:none
style s3 fill:none,stroke:none
s1 ~~~ s2 ~~~ s3
```
Resolver 1 is found at the .eth level, but `domingo` has no resolver set. **Resolver 1 wins** as the longest-suffix match, covering `montoya.eth`. Any subname of `montoya.eth` without its own resolver will fall back to Resolver 1 the same way.
#### The Algorithm
`findResolver()` implements this longest-suffix match. It recursively descends from the root, and at each level:
1. Calls `getResolver(label)`. If non-zero, overwrites the previously remembered resolver.
2. Calls `getSubregistry(label)`. If non-zero, continues descending into the subregistry.
The final remembered resolver is the one used for the actual record query.
### Registry Navigation
ENSv2 provides functions for locating registries within the hierarchy and verifying their position. All functions described here are exposed on the `UniversalResolverV2` contract.
All navigation functions walk **down** from the root registry except `findCanonicalName`, which walks **up** via `getParent()`, and `findCanonicalRegistry`, which does both.
#### findExactRegistry
Walks top-down from root, calling `getSubregistry()` at each label. Returns the subregistry that the target name points to, or `address(0)` if any link in the chain is missing.
For `nick.eth`:
This is the registry where `nick.eth`'s subnames live. If `nick.eth` has no subregistry set, the final step returns `address(0)`.
Use `findExactRegistry` when you need to interact with a specific registry and trust the hierarchy (e.g., checking roles or reading name state). For security-sensitive contexts, use [`findCanonicalRegistry`](#findcanonicalregistry) instead.
#### findParentRegistry
Walks top-down to find the registry that **contains** a name's entry, rather than the subregistry the name points to. It strips the first label and calls `findExactRegistry` on the parent suffix.
For `nick.eth`:
The `.eth` registry is where `nick` is an entry. Contrast with `findExactRegistry("nick.eth")`, which returns `nick.eth`'s own subregistry (one level deeper).
This is used internally by [`findOwner`](#findowner) to locate the registry that holds ownership information for a name.
#### findRegistries
Walks top-down and builds the complete ancestry array for a name, from innermost to outermost. Each position in the array corresponds to one label in the name, with the root registry appended at the end.
```
findRegistries("") → []
findRegistries("eth") → [, ]
findRegistries("nick.eth") → [, , ]
findRegistries("sub.nick.eth") → [address(0), , , ]
```
If a name has no subregistry at some level, that position in the array is `address(0)`. In the last example, `sub.nick.eth` has no subregistry, so the first element is zero, but its parent registries are all present.
#### findCanonicalName
A registry's **canonical name** is the DNS-encoded name produced by `findCanonicalName`: it walks up the verified [parent pointer](/ensv2/permissioned-registry#parent-pointer) chain from the registry to root, reconstructing the name at each step.
Because registries have no inherent concept of "their name" (a registry can be mounted at multiple positions via [namespace aliasing](/ensv2/registry-hierarchy#namespace-aliasing)), a canonical name only exists when both directions of the hierarchy agree: `setParent()` establishes the backward pointer, and `findCanonicalName` verifies at each step that the parent's forward pointer (`getSubregistry`) points back to the same registry.
For the `nick.eth` subregistry (walking upward):
Returns empty bytes if any link is broken: if a registry has no parent set, or if `parent.getSubregistry(label)` points to a different address.
#### findCanonicalRegistry
The **canonical registry** for a name is the registry at that position in the hierarchy whose [canonical name](#findcanonicalname) equals the name.
`findCanonicalRegistry` verifies this by combining both directions:
For `nick.eth`:
The bidirectional check prevents aliasing attacks: a registry could be mounted at one position in the tree but claim (via `getParent()`) to be at another. This is the function to use when verifying a registry's legitimacy, for example in marketplaces or any context where a name is being purchased or trusted.
#### findOwner
Finds the current owner of a name. `IOwnedRegistry` is a minimal interface that extends `IRegistry` with a single `findOwner(label)` function, returning the owner of a label. [PermissionedRegistry](/ensv2/permissioned-registry) implements it, but any custom registry with ownership can too.
For `nick.eth`:
Returns `address(0)` if the parent registry doesn't exist, doesn't implement `IOwnedRegistry`, or the label has no owner.
### Reference
#### View Functions
, , ] for nick.eth' },
],
},
{
name: 'findCanonicalName',
description: "Reconstruct a registry's DNS-encoded name by walking up via getParent().",
params: [
{ name: 'registry', type: 'IRegistry', description: 'The registry to name' },
],
returns: [
{ name: 'name', type: 'bytes', description: 'DNS-encoded name, or empty if not canonical' },
],
},
{
name: 'findCanonicalRegistry',
description: 'Find the registry for a name, verified canonical via bidirectional walk.',
params: [
{ name: 'name', type: 'bytes', description: 'DNS-encoded name' },
],
returns: [
{ name: 'registry', type: 'IRegistry', description: 'Canonical registry, or address(0) if not found or not canonical' },
],
},
{
name: 'findOwner',
description: 'Find the current owner of a name.',
params: [
{ name: 'name', type: 'bytes', description: 'DNS-encoded name' },
],
returns: [
{ name: 'owner', type: 'address', description: 'Owner address, or address(0) if not found or registry does not implement IOwnedRegistry' },
],
},
{
name: 'resolve',
description: 'Forward-resolve a single record for a DNS-encoded name. For batch resolution, encode data as multicall(bytes[]).',
params: [
{ name: 'name', type: 'bytes', description: 'DNS-encoded name to resolve' },
{ name: 'data', type: 'bytes', description: 'ABI-encoded resolver calldata' },
],
returns: [
{ name: 'result', type: 'bytes', description: 'ABI-encoded response' },
{ name: 'resolver', type: 'address', description: 'The resolver that was used' },
],
},
{
name: 'resolveWithGateways',
description: 'Same as resolve, but with custom CCIP-Read gateway URLs.',
params: [
{ name: 'name', type: 'bytes', description: 'DNS-encoded name to resolve' },
{ name: 'data', type: 'bytes', description: 'ABI-encoded resolver calldata' },
{ name: 'gateways', type: 'string[]', description: 'Custom CCIP-Read batch gateway URLs' },
],
returns: [
{ name: 'result', type: 'bytes', description: 'ABI-encoded response' },
{ name: 'resolver', type: 'address', description: 'The resolver that was used' },
],
},
{
name: 'resolveWithResolver',
description: 'Resolve using a specific resolver address, bypassing the findResolver lookup.',
params: [
{ name: 'resolver', type: 'address', description: 'Resolver address to use directly' },
{ name: 'name', type: 'bytes', description: 'DNS-encoded name' },
{ name: 'data', type: 'bytes', description: 'ABI-encoded resolver calldata' },
{ name: 'gateways', type: 'string[]', description: 'CCIP-Read batch gateway URLs' },
],
returns: [
{ name: 'result', type: 'bytes', description: 'ABI-encoded response' },
],
},
{
name: 'reverse',
description: 'Reverse resolution per ENSIP-19: look up the primary name for an address, then verify via forward resolution.',
params: [
{ name: 'lookupAddress', type: 'bytes', description: 'Byte-encoded address to look up' },
{ name: 'coinType', type: 'uint256', description: 'Coin type of the address (e.g. 60 for Ethereum)' },
],
returns: [
{ name: 'primary', type: 'string', description: 'Verified primary name, or empty if not set' },
{ name: 'resolver', type: 'address', description: 'Forward resolver for the primary name' },
{ name: 'reverseResolver', type: 'address', description: 'Reverse resolver that provided the name' },
],
},
{
name: 'reverseWithGateways',
description: 'Same as reverse, but with custom CCIP-Read gateway URLs.',
params: [
{ name: 'lookupAddress', type: 'bytes', description: 'Byte-encoded address to look up' },
{ name: 'coinType', type: 'uint256', description: 'Coin type of the address' },
{ name: 'gateways', type: 'string[]', description: 'Custom CCIP-Read batch gateway URLs' },
],
returns: [
{ name: 'primary', type: 'string', description: 'Verified primary name, or empty if not set' },
{ name: 'resolver', type: 'address', description: 'Forward resolver for the primary name' },
{ name: 'reverseResolver', type: 'address', description: 'Reverse resolver that provided the name' },
],
},
{
name: 'requireResolver',
description: 'Same as findResolver, but reverts if no suitable resolver is found. Reverts with ResolverNotFound if: no resolver exists, or the resolver does not support IExtendedResolver and was found at a parent suffix (non-zero offset). Reverts with ResolverNotContract if the resolver was matched exactly (zero offset), does not support IExtendedResolver, and has no deployed code. Used internally by resolve() and reverse().',
params: [
{ name: 'name', type: 'bytes', description: 'DNS-encoded name to search' },
],
returns: [
{ name: 'info', type: 'ResolverInfo', description: 'Struct with name, offset, node, resolver address, and whether it supports IExtendedResolver' },
],
},
]}
/>
#### Constants
#### Errors
import { FrenCallout } from '../../components/ensv2/FrenCallout'
## Verifiable Factory
ENSv2's default setup deploys per-account resolvers and per-name subname registries through a single shared factory, replacing the v1 Public Resolver with individual [Permissioned Resolver](/ensv2/permissioned-resolver) proxies per account. Each instance is a UUPS proxy with a deterministic CREATE2 address and an on-chain proof of provenance. The factory is maintained in a [separate repository](https://github.com/ensdomains/verifiable-factory). This page covers how the protocol uses it.
The factory is the **default** deployment mechanism, not a protocol requirement. Custom resolver contracts are fully supported: `setResolver()` on the [Permissioned Registry](/ensv2/permissioned-registry) accepts any address. You can deploy your own resolver implementing `IExtendedResolver` (or the standard resolver profiles) without using the factory.
The contracts and interfaces described here are **not yet final** and may change prior to mainnet deployment.
### Why a Factory
ENSv2 chooses per-account resolver and per-name registry instances over a single shared contract (see [Overview](/ensv2/overview#whats-new-in-ensv2)). Each instance is a UUPS proxy, so deployment is cheap and any single instance can be upgraded without touching the rest of the system. The factory makes every such deployment:
* **Deterministic**: the address is known before deployment.
* **Verifiable**: anyone can prove on-chain that an arbitrary address really came from this factory with a known proxy bytecode.
* **Cheap**: only the proxy is deployed; the implementation is shared.
### Deterministic Deployment
```solidity
function deployProxy(
address implementation,
uint256 salt,
bytes memory data
) external returns (address proxy);
```
`deployProxy` uses CREATE2 to deploy a `UUPSProxy` with `outerSalt = keccak256(abi.encode(msg.sender, salt))`. After construction, the factory calls `proxy.initialize(implementation, data)` to point the proxy at the implementation and forward `data` as initialisation calldata.
Because `outerSalt` mixes in `msg.sender`, the same `salt` value submitted by two different deployers produces two different proxy addresses. There is no contention between callers and no need for global salt coordination.
```mermaid
sequenceDiagram
participant Caller
participant Factory as VerifiableFactory
participant Proxy as UUPSProxy
participant Impl as Implementation
Caller->>Factory: deployProxy(impl, salt, data)
Factory->>Factory: outerSalt = keccak256(sender, salt)
Factory->>Proxy: CREATE2 with outerSalt
Proxy->>Proxy: store factory & salt
Factory->>Proxy: initialize(impl, data)
Proxy->>Impl: delegatecall(data)
Factory-->>Caller: proxy address
```
### The UUPSProxy
Each proxy stores two pieces of provenance:
* An immutable `verifiableProxyFactory` address baked into the proxy bytecode at construction.
* A salt appended to the proxy bytecode at construction, readable via `getVerifiableProxyData()` (which also returns the current implementation address from the ERC-1967 slot).
`initialize(implementation, data)` runs once. It calls OpenZeppelin's `ERC1967Utils.upgradeToAndCall`, which sets the implementation slot and delegate-calls the implementation with `data` so it can run its own initializer.
### On-chain Verification
```solidity
function verifyContract(address proxy, address expectedImplementation) external view returns (bool);
```
`verifyContract` calls `getVerifiableProxyData()` on the proxy to retrieve its stored salt and actual implementation address. It first checks that the implementation matches `expectedImplementation`, then reconstructs the expected CREATE2 address from `(UUPSProxy creation code, factory, salt)` and returns `true` if it matches `proxy`. This is the property that makes the factory **verifiable**: a smart contract can prove that an arbitrary address really is a UUPS proxy deployed by this factory with a known implementation, without trusting metadata or off-chain data.
### Available Implementations
Three implementation contracts are deployed once and then proxied through the factory:
| Implementation | Salt scheme | Where it's deployed |
| -------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------- |
| `PermissionedResolverImpl` | `keccak256("OwnedResolver", owner, version)` — one resolver per owner | On demand by users |
| `UserRegistryImpl` | `keccak256("UserRegistry", namehash, version)` — one subname registry per name | On demand by name owners |
| `WrapperRegistryImpl` | per-name | Inside migration controllers when a locked v1 name is wrapped |
### Upgrade Authorization
Each implementation overrides `_authorizeUpgrade`. In ENSv2 the check is uniform: only an account holding `ROLE_UPGRADE` on the proxy's `ROOT_RESOURCE` can upgrade the implementation:
```solidity
function _authorizeUpgrade(address newImplementation)
internal
override
onlyRootRoles(ROLE_UPGRADE)
{}
```
Upgrades target a single proxy, not the shared implementation; an upgrade to one user's resolver or subname registry does not affect anyone else's.
### Computing Addresses Off-chain
Because the proxy address is fully determined by `(factory, deployer, salt, UUPSProxy bytecode)`, clients can compute it before any transaction is sent:
```ts
import { encodeAbiParameters, getCreate2Address, keccak256 } from 'viem'
function predictProxyAddress({
factoryAddress,
proxyBytecode,
deployer,
salt,
}: {
factoryAddress: `0x${string}`
proxyBytecode: `0x${string}`
deployer: `0x${string}`
salt: bigint
}) {
const outerSalt = keccak256(
encodeAbiParameters(
[{ type: 'address' }, { type: 'uint256' }],
[deployer, salt]
)
)
const initCode = `${proxyBytecode}${encodeAbiParameters(
[{ type: 'address' }, { type: 'bytes32' }],
[factoryAddress, outerSalt]
).slice(2)}` as `0x${string}`
return getCreate2Address({
from: factoryAddress,
salt: outerSalt,
bytecodeHash: keccak256(initCode),
})
}
```
This is useful whenever you need a name's resolver or subname-registry address before the user actually creates it.
### Code Examples
Every proxy deployment follows the same steps: compute a salt, encode the implementation's `initialize` call as the `data` argument, and call `deployProxy` on the factory. The salt and initializer differ per implementation.
The contract addresses used below (`VERIFIABLE_FACTORY`, `PERMISSIONED_RESOLVER_IMPL`, `USER_REGISTRY_IMPL`) are protocol-deployed contracts. Find them in the [Deployments](/ensv2/overview#deployments-sepolia) table.
Both examples below share this setup:
```ts [Viem]
import {
createPublicClient,
createWalletClient,
encodeAbiParameters,
encodeFunctionData,
http,
keccak256,
namehash,
parseAbi,
parseEventLogs,
stringToHex,
} from 'viem'
import { mainnet } from 'viem/chains'
const client = createPublicClient({ chain: mainnet, transport: http() })
const wallet = createWalletClient({
account,
chain: mainnet,
transport: http(),
})
const verifiableFactoryAbi = parseAbi([
'function deployProxy(address implementation, uint256 salt, bytes data)',
'event ProxyDeployed(address indexed sender, address indexed proxyAddress, uint256 salt, address implementation)',
])
// Grant all roles and their admin counterparts
const ALL_ROLES =
0x1111111111111111111111111111111111111111111111111111111111111111n
```
#### Deploying a Resolver Proxy
Deploy a per-account [Permissioned Resolver](/ensv2/permissioned-resolver). The salt is derived from the owner's address so that any client can predict the resolver address before it exists.
```ts [Viem]
const resolverInitAbi = parseAbi([
'function initialize(address admin, uint256 roleBitmap, bytes[] setters)',
])
// Salt scheme: keccak256("OwnedResolver", owner, version)
const version = 0n
const resolverSalt = BigInt(
keccak256(
encodeAbiParameters(
[{ type: 'bytes32' }, { type: 'address' }, { type: 'uint256' }],
[keccak256(stringToHex('OwnedResolver')), account.address, version]
)
)
)
const resolverInitData = encodeFunctionData({
abi: resolverInitAbi,
functionName: 'initialize',
args: [account.address, ALL_ROLES, []],
})
const resolverTx = await wallet.writeContract({
address: VERIFIABLE_FACTORY,
abi: verifiableFactoryAbi,
functionName: 'deployProxy',
args: [PERMISSIONED_RESOLVER_IMPL, resolverSalt, resolverInitData],
})
const resolverReceipt = await client.waitForTransactionReceipt({
hash: resolverTx,
})
const [resolverLog] = parseEventLogs({
abi: verifiableFactoryAbi,
eventName: 'ProxyDeployed',
logs: resolverReceipt.logs,
})
const resolverAddress = resolverLog.args.proxyAddress
```
The `resolverAddress` is now a fully initialized Permissioned Resolver proxy. Point a name to it via `setResolver` on the registry, then use it to [set records and delegate access](/ensv2/permissioned-resolver#code-examples).
#### Deploying a Registry Proxy
Deploy a per-name [User Registry](/ensv2/permissioned-registry) for managing subnames. The salt is derived from the name's namehash.
```ts [Viem]
const registryInitAbi = parseAbi([
'function initialize(address rootAccount, uint256 roleBitmap)',
])
// Salt scheme: keccak256("UserRegistry", namehash, version)
const version = 0n
const registrySalt = BigInt(
keccak256(
encodeAbiParameters(
[{ type: 'bytes32' }, { type: 'bytes32' }, { type: 'uint256' }],
[keccak256(stringToHex('UserRegistry')), namehash('alice.eth'), version]
)
)
)
const registryInitData = encodeFunctionData({
abi: registryInitAbi,
functionName: 'initialize',
args: [account.address, ALL_ROLES],
})
const registryTx = await wallet.writeContract({
address: VERIFIABLE_FACTORY,
abi: verifiableFactoryAbi,
functionName: 'deployProxy',
args: [USER_REGISTRY_IMPL, registrySalt, registryInitData],
})
const registryReceipt = await client.waitForTransactionReceipt({
hash: registryTx,
})
const [registryLog] = parseEventLogs({
abi: verifiableFactoryAbi,
eventName: 'ProxyDeployed',
logs: registryReceipt.logs,
})
const registryAddress = registryLog.args.proxyAddress
```
The `registryAddress` is now a User Registry proxy. Set it as the subregistry for a name via `setSubregistry` on the parent registry, then use it to [manage subnames and roles](/ensv2/permissioned-registry#code-examples).
import { EnsipHeader } from "../../components/EnsipHeader";
## ENSIP-1: ENS
### Abstract
This ENSIP describes the details of the Ethereum Name Service, a proposed protocol and ABI definition that provides flexible resolution of short, human-readable names to service and resource identifiers. This permits users and developers to refer to human-readable and easy to remember names, and permits those names to be updated as necessary when the underlying resource (contract, content-addressed data, etc) changes.
The goal of domain names is to provide stable, human-readable identifiers that can be used to specify network resources. In this way, users can enter a memorable string, such as 'vitalik.wallet' or '[www.mysite.swarm](http://www.mysite.swarm)', and be directed to the appropriate resource. The mapping between names and resources may change over time, so a user may change wallets, a website may change hosts, or a swarm document may be updated to a new version, without the domain name changing. Further, a domain need not specify a single resource; different record types allow the same domain to reference different resources. For instance, a browser may resolve 'mysite.swarm' to the IP address of its server by fetching its A (address) record, while a mail client may resolve the same address to a mail server by fetching its MX (mail exchanger) record.
### Motivation
Existing [specifications](https://github.com/ethereum/wiki/wiki/Registrar-ABI) and [implementations](https://ethereum.gitbooks.io/frontier-guide/content/registrar_services.html) for name resolution in Ethereum provide basic functionality, but suffer several shortcomings that will significantly limit their long-term usefulness:
* A single global namespace for all names with a single 'centralised' resolver.
* Limited or no support for delegation and sub-names/sub-domains.
* Only one record type, and no support for associating multiple copies of a record with a domain.
* Due to a single global implementation, no support for multiple different name allocation systems.
* Conflation of responsibilities: Name resolution, registration, and whois information.
Use-cases that these features would permit include:
* Support for subnames/sub-domains - eg, live.mysite.tld and forum.mysite.tld.
* Multiple services under a single name, such as a DApp hosted in Swarm, a Whisper address, and a mail server.
* Support for DNS record types, allowing blockchain hosting of 'legacy' names. This would permit an Ethereum client such as Mist to resolve the address of a traditional website, or the mail server for an email address, from a blockchain name.
* DNS gateways, exposing ENS domains via the Domain Name Service, providing easier means for legacy clients to resolve and connect to blockchain services.
The first two use-cases, in particular, can be observed everywhere on the present-day internet under DNS, and we believe them to be fundamental features of a name service that will continue to be useful as the Ethereum platform develops and matures.
The normative parts of this document does not specify an implementation of the proposed system; its purpose is to document a protocol that different resolver implementations can adhere to in order to facilitate consistent name resolution. An appendix provides sample implementations of resolver contracts and libraries, which should be treated as illustrative examples only.
Likewise, this document does not attempt to specify how domains should be registered or updated, or how systems can find the owner responsible for a given domain. Registration is the responsibility of registrars, and is a governance matter that will necessarily vary between top-level domains.
Updating of domain records can also be handled separately from resolution. Some systems, such as swarm, may require a well defined interface for updating domains, in which event we anticipate the development of a standard for this.
### Specification
#### Overview
The ENS system comprises three main parts:
* The ENS registry
* Resolvers
* Registrars
The registry is a single contract that provides a mapping from any registered name to the resolver responsible for it, and permits the owner of a name to set the resolver address, and to create subdomains, potentially with different owners to the parent domain.
Resolvers are responsible for performing resource lookups for a name - for instance, returning a contract address, a content hash, or IP address(es) as appropriate. The resolver specification, defined here and extended in other ENSIPs, defines what methods a resolver may implement to support resolving different types of records.
Registrars are responsible for allocating domain names to users of the system, and are the only entities capable of updating the ENS; the owner of a node in the ENS registry is its registrar. Registrars may be contracts or externally owned accounts, though it is expected that the root and top-level registrars, at a minimum, will be implemented as contracts.
Resolving a name in ENS is a two-step process. First, the ENS registry is called with the name to resolve, after hashing it using the procedure described below. If the record exists, the registry returns the address of its resolver. Then, the resolver is called, using the method appropriate to the resource being requested. The resolver then returns the desired result.
For example, suppose you wish to find the address of the token contract associated with 'beercoin.eth'. First, get the resolver:
```javascript
var node = namehash("beercoin.eth");
var resolver = ens.resolver(node);
```
Then, ask the resolver for the address for the contract:
```javascript
var address = resolver.addr(node);
```
Because the `namehash` procedure depends only on the name itself, this can be precomputed and inserted into a contract, removing the need for string manipulation, and permitting O(1) lookup of ENS records regardless of the number of components in the raw name.
#### Name Syntax
ENS names must conform to the following syntax:
```go
::=