Skip to main content

Architecture Components

The ENS architecture is built on three foundational components that work together to provide a flexible, upgradeable naming system.

The ENS Registry

The registry is the central component of ENS. It’s a single smart contract that maintains a mapping of domain names (as namehashes) to their associated records.

Registry Data Structure

From contracts/registry/ENSRegistry.sol:7-11:
struct Record {
    address owner;
    address resolver;
    uint64 ttl;
}

mapping(bytes32 => Record) records;
Each domain in ENS has a record containing:
  • owner - The address that controls this domain
  • resolver - The contract responsible for resolving this domain to addresses/resources
  • ttl - Time-to-live for caching (in seconds)

Core Registry Functions

The ENS interface defines these essential operations:
// Query functions
function owner(bytes32 node) external view returns (address);
function resolver(bytes32 node) external view returns (address);
function ttl(bytes32 node) external view returns (uint64);
function recordExists(bytes32 node) external view returns (bool);

// Management functions (owner only)
function setOwner(bytes32 node, address owner) external;
function setResolver(bytes32 node, address resolver) external;
function setTTL(bytes32 node, uint64 ttl) external;
function setRecord(bytes32 node, address owner, address resolver, uint64 ttl) external;
All registry operations use bytes32 node hashes, not human-readable names. Names are converted to hashes using the namehash algorithm.

Subdomain Management

The registry enables domain owners to create and manage subdomains:
function setSubnodeOwner(
    bytes32 node,
    bytes32 label,
    address owner
) public virtual override authorised(node) returns (bytes32) {
    bytes32 subnode = keccak256(abi.encodePacked(node, label));
    _setOwner(subnode, owner);
    emit NewOwner(node, label, owner);
    return subnode;
}
This function:
  1. Takes the parent node and a label (hash of subdomain name)
  2. Computes the subnode hash: keccak256(parent_node, label)
  3. Sets the owner of the new subdomain
  4. Returns the new subdomain hash
Only the owner of a domain can create its subdomains. This maintains the hierarchical trust model.

Authorization Model

Owner-Based Authorization

The registry uses a modifier to restrict operations to domain owners:
modifier authorised(bytes32 node) {
    address owner = records[node].owner;
    require(owner == msg.sender || operators[owner][msg.sender]);
    _;
}
This ensures only the owner (or their approved operators) can modify a domain’s records.

Operator Approval

Domain owners can approve operators to manage all their domains:
function setApprovalForAll(address operator, bool approved) external;
Implementation from contracts/registry/ENSRegistry.sol:112-118:
function setApprovalForAll(
    address operator,
    bool approved
) external virtual override {
    operators[msg.sender][operator] = approved;
    emit ApprovalForAll(msg.sender, operator, approved);
}
Approving an operator gives them control over ALL domains you own. Use this feature carefully.

Registrars

Registrars are contracts that own a domain in the registry and implement rules for allocating subdomains.

Registrar Types

BaseRegistrar Pattern

The BaseRegistrar:
  • Owns the TLD (e.g., .eth) in the registry
  • Maintains a minimal set of ownership functions
  • Delegates registration logic to controllers
  • Provides strong guarantees: controllers can register new names but cannot modify existing ownership
This separation provides security guarantees:
  • Name owners have confidence that even if the controller is upgraded, their existing names remain secure
  • Controllers can be replaced to improve registration UX or pricing without risking existing domains
  • Innovation can happen in registration mechanics while ownership remains constant

ETHRegistrarController

Implements the .eth registration process:
  1. Commit Phase - User commits to a hash of (name + secret)
  2. Wait Period - Minimum delay to prevent frontrunning
  3. Reveal Phase - User reveals name and secret, pays registration fee
  4. Registration - If valid, name is registered through BaseRegistrar

Resolvers

Resolvers are responsible for storing and returning the actual data associated with a name.

Resolver Interface

From contracts/resolvers/Resolver.sol, a resolver can implement multiple profiles:
interface Resolver is
    IERC165,
    IABIResolver,        // Contract ABIs
    IAddressResolver,    // Ethereum addresses  
    IAddrResolver,       // Multi-chain addresses
    IContentHashResolver, // IPFS/Swarm content
    IDNSRecordResolver,  // DNS records
    IDNSZoneResolver,    // DNS zones
    IInterfaceResolver,  // Interface implementations
    INameResolver,       // Reverse resolution
    IPubkeyResolver,     // Public keys
    ITextResolver,       // Arbitrary text records
    IExtendedResolver    // Off-chain resolution
Resolvers use ERC-165 to advertise which interfaces they support, allowing applications to check capabilities before querying.

Record Storage

Resolvers store records indexed by the namehash:
// Example: Address resolver stores addresses by node
mapping(bytes32 => address) addresses;

function addr(bytes32 node) external view returns (address) {
    return addresses[node];
}

function setAddr(bytes32 node, address addr) external {
    // Authorization check
    addresses[node] = addr;
}

PublicResolver

The most common resolver implementation that supports all standard record types. Domain owners can:
  • Set Ethereum addresses: setAddr(node, address)
  • Set content hashes: setContenthash(node, hash)
  • Set text records: setText(node, key, value)
  • Set multi-chain addresses: setAddr(node, coinType, addressData)

Complete Resolution Flow

Here’s how a complete ENS lookup works:

Registry Events

The registry emits events for all changes:
// Logged when the owner of a node assigns a new owner to a subnode
event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner);

// Logged when the owner of a node transfers ownership
event Transfer(bytes32 indexed node, address owner);

// Logged when the resolver for a node changes
event NewResolver(bytes32 indexed node, address resolver);

// Logged when the TTL of a node changes
event NewTTL(bytes32 indexed node, uint64 ttl);

// Logged when an operator is added or removed
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
These events allow applications to track ENS changes off-chain and maintain caches of ENS data.

Security Considerations

Ownership Guarantees

From contracts/registry/ENSRegistry.sol:24-26, the registry is initialized with:
constructor() public {
    records[0x0].owner = msg.sender;
}
The root node (0x0) owner controls the entire namespace. In production:
  • The root is owned by a multisig or governance contract
  • TLD owners are typically set once and rarely changed
  • Domain owners have full control over their domains and subdomains

Resolver Trust

When you query ENS:
  1. You trust the registry to return the correct resolver
  2. You trust the resolver to return correct data
A malicious domain owner could point to a malicious resolver that returns incorrect data. Always verify you’re interacting with the intended domain.

Next Steps

Namehash Algorithm

Learn how names are converted to unique identifiers

Registry Contracts

Complete API reference for the ENS registry

Build docs developers (and LLMs) love