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.
Contract Hierarchy
ENSv2 uses a hierarchical registry model. There is no single registry contract that holds all names. Instead:
RootRegistry (PermissionedRegistry)
└── ETHRegistry (PermissionedRegistry) — manages *.eth
├── name.eth (token in ETHRegistry)
│ └── UserRegistry — manages *.name.eth
│ ├── sub.name.eth (token in UserRegistry)
│ │ └── UserRegistry — manages *.sub.name.eth
│ │ └── ...
│ └── ...
└── ...Each registry is a Permissioned Registry (or UserRegistry for subnames), implementing IRegistry, IStandardRegistry, IPermissionedRegistry, and IEnhancedAccessControl. Tokens are ERC1155Singleton (one token per name).
Resolvers are separate contracts (Permissioned Resolver) deployed per-name as UUPS proxies. Multiple names can share the same resolver via aliases (setAlias), which allow one name to reuse another's resolver records by rewriting 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).
LabelRegistered
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.
LabelReserved
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
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
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
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
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(). New resolver addresses indicate dynamically deployed PermissionedResolver contracts.
TokenRegenerated
event TokenRegenerated(uint256 indexed oldTokenId, uint256 indexed newTokenId);Emitted by: IRegistry (on PermissionedRegistry, UserRegistry)
When: a name's EAC roles are modified via grantRoles() or revokeRoles(). The ERC1155 token ID changes to encode the new role configuration, while the underlying name (canonical ID / resource) remains the same. Always accompanied by ERC1155 TransferSingle events (burn old + mint new). See Mutable Token IDs for details.
ParentUpdated
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.
TokenResource
event TokenResource(uint256 indexed tokenId, uint256 indexed resource);Emitted by: IPermissionedRegistry
When: a token is created or regenerated. Maps a tokenId to its corresponding resource. The resource is derived from the labelHash and eacVersionId. It remains stable across role-change regenerations (which only change the tokenVersionId), but changes on re-registration (which increments eacVersionId). See Mutable Token IDs for details.
ERC1155 Transfer Events
These standard ERC1155 events are emitted by all registries (which extend ERC1155Singleton).
TransferSingle
event TransferSingle(
address indexed operator,
address indexed from,
address indexed to,
uint256 id,
uint256 value
);- Registration (mint):
from = address(0),to = owner- a new name token is minted - Transfer:
from = previousOwner,to = newOwner- ownership changes viasafeTransferFrom() - Unregistration (burn):
from = owner,to = address(0)- name token is burned - Token regeneration: two events fire - burn old tokenId + mint new tokenId. Correlate with
TokenRegeneratedto avoid treating it as a separate domain.
TransferBatch
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 contract, which is the user-facing entry point for .eth name registration (with commit-reveal and pricing).
CommitmentMade
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)
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.
NameRenewed
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 contracts and any contract implementing the standard resolver profile interfaces. Resolvers are keyed by node (namehash of the full name).
AddressChanged
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 (e.g., 0 = BTC, 501 = SOL). The node maps to a domain via namehash.
TextChanged
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
event ContenthashChanged(bytes32 indexed node, bytes hash);When: a content hash is set via setContenthash(node, hash). Supports IPFS, Arweave, Swarm, etc.
ABIChanged
event ABIChanged(bytes32 indexed node, uint256 indexed contentType);When: an ABI record is set.
PubkeyChanged
event PubkeyChanged(bytes32 indexed node, bytes32 x, bytes32 y);When: a public key record is set (secp256k1 x, y coordinates).
NameChanged
event NameChanged(bytes32 indexed node, string name);When: a reverse name record is set.
InterfaceChanged
event InterfaceChanged(
bytes32 indexed node,
bytes4 indexed interfaceID,
address implementer
);When: an EIP-165 interface implementer is set.
VersionChanged
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
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
event NamedResource(uint256 indexed resource, bytes name);When: an EAC resource is associated with a name for fine-grained permission control on the resolver.
NamedTextResource
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
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).
Key Concepts for Indexers
Dynamic Contract Discovery
An indexer cannot know all contract addresses at startup. The core pattern is:
- Start by watching the RootRegistry and ETHRegistry (known addresses from deployment).
- When a
SubregistryUpdatedevent fires with a new subregistry address, add that address to the watch list. - When a
ResolverUpdatedevent 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.
TokenId vs Resource (Canonical ID)
- tokenId: the ERC1155 token ID. Changes when roles are modified (
TokenRegenerated). Encodes role configuration. - resource: the EAC permission key for a name within a registry. Derived from the
labelHashandeacVersionId. Stable across role modifications, but changes on re-registration (wheneacVersionIdincrements).
Use the TokenResource event to maintain the mapping. The resource should be the primary key for domain lookups.
Name Construction
The indexer must track the registry hierarchy to construct full names:
- ETHRegistry emits
LabelRegisteredwithlabel = "test"→ full name is"test.eth" - A
SubregistryUpdatedontest.ethpoints to a UserRegistry at address0xABC - That UserRegistry emits
LabelRegisteredwithlabel = "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.ethhas subregistry at0xABClinked.parent.ethalso has subregistry at0xABC
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.ethandtest.ethmay share the same resolver- The resolver stores an
alias.eth → test.ethalias mapping - When resolving
sub.alias.eth, the resolver rewrites it tosub.test.ethand returns those records - The registry hierarchy knows nothing about aliases - they exist only in the resolver's storage
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 a new tokenId but keeps the same resource.
Event Processing Order
For a single registration via ETHRegistrar.register(), events fire in this order:
IETHRegistrar.NameRegistered- registrar-level event with pricingIRegistryEvents.LabelRegistered- registry-level event with registration detailsTokenResource- tokenId-to-resource mappingTransferSingle(mint) - ERC1155 token creationSubregistryUpdated- if a subregistry was providedResolverUpdated- if a resolver was provided
For a direct IStandardRegistry.register() call (e.g., on a UserRegistry):
IRegistryEvents.LabelRegisteredTokenResourceTransferSingle(mint)SubregistryUpdated- if a subregistry was providedResolverUpdated- if a resolver was provided
Contract Address Summary
| Contract | Role | Events to Watch |
|---|---|---|
| PermissionedRegistry (ETHRegistry) | Manages .eth names | All IRegistry events, TokenResource, ERC1155 transfers |
| 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 |
Read Functions for State Verification
These view functions are useful for verifying indexed state or backfilling data:
Registry State
// 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 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
// 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
// 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);