Skip to main content

Overview

The ETHRegistrar contract manages the registration and renewal of .eth second-level domain names in ENS v2. It implements a commit-reveal registration scheme to prevent frontrunning and supports multiple payment tokens with configurable pricing through a rent price oracle. Contract Address: Deployed per network (see deployment addresses) Interface ID: 0x29071951

Key Features

  • Two-step commit-reveal registration process
  • Support for multiple ERC-20 payment tokens
  • Configurable pricing with premium pricing for recently expired names
  • Integration with ENS registry for name ownership
  • Access control for administrative functions

Constants

REGISTRY

IPermissionedRegistry public immutable REGISTRY
The ENS registry contract that stores name ownership records.

BENEFICIARY

address public immutable BENEFICIARY
The address that receives registration and renewal payments.

MIN_COMMITMENT_AGE

uint64 public immutable MIN_COMMITMENT_AGE
Minimum time (in seconds) that must pass between commitment and registration. Prevents frontrunning by requiring commitments to be mined before registration.

MAX_COMMITMENT_AGE

uint64 public immutable MAX_COMMITMENT_AGE
Maximum time (in seconds) a commitment remains valid. Commitments expire after this period to prevent commitment reuse.

MIN_REGISTER_DURATION

uint64 public immutable MIN_REGISTER_DURATION
Minimum registration duration (in seconds) for new name registrations.

State Variables

rentPriceOracle

IRentPriceOracle public rentPriceOracle
The pricing oracle contract used to calculate registration and renewal costs.

commitmentAt

mapping(bytes32 commitment => uint64 commitTime) public commitmentAt
Maps commitment hashes to their creation timestamps.
commitment
bytes32
The commitment hash from makeCommitment()
commitTime
uint64
The block timestamp when the commitment was made, or 0 if no commitment exists

Functions

commit

function commit(bytes32 commitment) external
Registration step 1: Record your intent to register a name without revealing the actual name or parameters. This prevents frontrunning attacks.
commitment
bytes32
required
The commitment hash generated by makeCommitment()
Events Emitted:
  • CommitmentMade(bytes32 commitment) - When the commitment is successfully recorded
Reverts:
  • UnexpiredCommitmentExists(bytes32 commitment) - If an unexpired commitment already exists for this hash
Example:
// First, create a commitment hash
bytes32 commitment = registrar.makeCommitment(
    "myname",
    msg.sender,
    secret,
    subregistry,
    resolver,
    365 days,
    referrer
);

// Then commit it
registrar.commit(commitment);

// Wait for MIN_COMMITMENT_AGE before calling register()

register

function register(
    string calldata label,
    address owner,
    bytes32 secret,
    IRegistry subregistry,
    address resolver,
    uint64 duration,
    IERC20 paymentToken,
    bytes32 referrer
) external returns (uint256 tokenId)
Registration step 2: Reveal your committed registration parameters and complete the registration. You must have a valid commitment that is between MIN_COMMITMENT_AGE and MAX_COMMITMENT_AGE old.
label
string
required
The name to register (without .eth suffix)
owner
address
required
The address that will own the registered name. Cannot be zero address.
secret
bytes32
required
A secret value used in the commitment to prevent others from guessing your commitment
subregistry
IRegistry
required
The initial registry contract for subdomains under this name
resolver
address
required
The initial resolver address for this name
duration
uint64
required
Registration duration in seconds. Must be at least MIN_REGISTER_DURATION.
paymentToken
IERC20
required
The ERC-20 token to use for payment. Must be supported by the price oracle.
referrer
bytes32
required
A referrer identifier (can be zero bytes if no referrer)
tokenId
uint256
The registry token ID for the newly registered name
Events Emitted:
  • NameRegistered(uint256 indexed tokenId, string label, address owner, IRegistry subregistry, address resolver, uint64 duration, IERC20 paymentToken, bytes32 referrer, uint256 base, uint256 premium)
Reverts:
  • DurationTooShort(uint64 duration, uint64 minDuration) - If duration is less than MIN_REGISTER_DURATION
  • InvalidOwner() - If owner is the zero address
  • NameNotAvailable(string label) - If the name is already registered
  • CommitmentTooNew(bytes32 commitment, uint64 validFrom, uint64 blockTimestamp) - If commitment hasn’t aged enough
  • CommitmentTooOld(bytes32 commitment, uint64 validTo, uint64 blockTimestamp) - If commitment has expired
  • PaymentTokenNotSupported(IERC20 paymentToken) - If the payment token isn’t supported
  • NotValid(string label) - If the label is not valid per the price oracle rules
Example:
// Generate a secret (keep this private!)
bytes32 secret = keccak256(abi.encodePacked("my-random-secret", msg.sender, block.timestamp));

// Step 1: Commit (wait at least MIN_COMMITMENT_AGE after this)
bytes32 commitment = registrar.makeCommitment(
    "myname",
    msg.sender,
    secret,
    subregistry,
    resolver,
    365 days,
    bytes32(0)
);
registrar.commit(commitment);

// Step 2: Wait for MIN_COMMITMENT_AGE...

// Step 3: Approve payment token
IERC20 paymentToken = IERC20(usdcAddress);
(uint256 base, uint256 premium) = registrar.rentPrice("myname", msg.sender, 365 days, paymentToken);
paymentToken.approve(address(registrar), base + premium);

// Step 4: Register
uint256 tokenId = registrar.register(
    "myname",
    msg.sender,
    secret,
    subregistry,
    resolver,
    365 days,
    paymentToken,
    bytes32(0)
);

renew

function renew(
    string calldata label,
    uint64 duration,
    IERC20 paymentToken,
    bytes32 referrer
) external
Extend the registration period for an existing name. No commitment is required for renewals.
label
string
required
The name to renew (without .eth suffix)
duration
uint64
required
The duration to extend the registration by, in seconds. Must be greater than 0.
paymentToken
IERC20
required
The ERC-20 token to use for payment. Must be supported by the price oracle.
referrer
bytes32
required
A referrer identifier (can be zero bytes if no referrer)
Events Emitted:
  • NameRenewed(uint256 indexed tokenId, string label, uint64 duration, uint64 newExpiry, IERC20 paymentToken, bytes32 referrer, uint256 base)
Reverts:
  • NameIsAvailable(string label) - If the name is available (not registered)
  • PaymentTokenNotSupported(IERC20 paymentToken) - If the payment token isn’t supported
  • NotValid(string label) - If the label is not valid per the price oracle rules
Example:
// Renew "myname.eth" for another year
IERC20 paymentToken = IERC20(usdcAddress);

// Get the renewal price (no premium for current owner)
(uint256 base, ) = registrar.rentPrice(
    "myname",
    currentOwner,
    365 days,
    paymentToken
);

// Approve payment
paymentToken.approve(address(registrar), base);

// Renew
registrar.renew(
    "myname",
    365 days,
    paymentToken,
    bytes32(0)
);

isAvailable

function isAvailable(string memory label) public view returns (bool)
Check if a name is available for registration.
label
string
required
The name to check (without .eth suffix)
available
bool
true if the name is available for registration, false if it’s registered or locked
Note: This function does not validate if the label is normalized or valid according to naming rules. It only checks registry availability. Example:
if (registrar.isAvailable("myname")) {
    // Name is available for registration
}

isValid

function isValid(string calldata label) external view returns (bool)
Check if a label is valid according to the price oracle rules (e.g., minimum length, valid characters).
label
string
required
The name to validate
valid
bool
true if the label is valid for registration
Example:
if (registrar.isValid("myname")) {
    // Label meets validation requirements
}

isPaymentToken

function isPaymentToken(IERC20 paymentToken) external view returns (bool)
Check if a token is supported for payment.
paymentToken
IERC20
required
The ERC-20 token address to check
supported
bool
true if the token is accepted for payments
Example:
if (registrar.isPaymentToken(IERC20(usdcAddress))) {
    // USDC is accepted
}

rentPrice

function rentPrice(
    string memory label,
    address owner,
    uint64 duration,
    IERC20 paymentToken
) public view returns (uint256 base, uint256 premium)
Calculate the registration or renewal price for a name.
label
string
required
The name to price
owner
address
required
The prospective owner address. Set to current owner to exclude premium, or zero address for estimation.
duration
uint64
required
The registration duration in seconds
paymentToken
IERC20
required
The ERC-20 token to use for payment
base
uint256
The base registration price in payment token units
premium
uint256
The premium price (if applicable) in payment token units. Premium applies to recently expired names.
Reverts:
  • PaymentTokenNotSupported(IERC20 paymentToken) - If the payment token isn’t supported
  • NotValid(string label) - If the label is not valid
Example:
// Get price for registering "myname" for 1 year
(uint256 base, uint256 premium) = registrar.rentPrice(
    "myname",
    msg.sender,
    365 days,
    IERC20(usdcAddress)
);

uint256 totalCost = base + premium;

makeCommitment

function makeCommitment(
    string calldata label,
    address owner,
    bytes32 secret,
    IRegistry subregistry,
    address resolver,
    uint64 duration,
    bytes32 referrer
) public pure returns (bytes32)
Generate a commitment hash from registration parameters. This is a pure function that can be called off-chain.
label
string
required
The name to register
owner
address
required
The owner address
secret
bytes32
required
A secret value to prevent commitment guessing
subregistry
IRegistry
required
The initial subregistry contract
resolver
address
required
The initial resolver address
duration
uint64
required
Registration duration in seconds
referrer
bytes32
required
Referrer identifier
commitment
bytes32
The keccak256 hash of all parameters encoded together
Example:
bytes32 secret = keccak256(abi.encodePacked("my-secret", msg.sender));
bytes32 commitment = registrar.makeCommitment(
    "myname",
    msg.sender,
    secret,
    subregistry,
    resolver,
    365 days,
    bytes32(0)
);

Administrative Functions

setRentPriceOracle

function setRentPriceOracle(IRentPriceOracle oracle) external
Update the rent price oracle contract. Requires ROLE_SET_ORACLE permission on the root resource.
oracle
IRentPriceOracle
required
The new price oracle contract address
Events Emitted:
  • RentPriceOracleChanged(IRentPriceOracle oracle)
Access Control: Only callable by accounts with ROLE_SET_ORACLE role.

Events

CommitmentMade

event CommitmentMade(bytes32 commitment)
Emitted when a commitment is recorded.
commitment
bytes32
The commitment hash that was recorded

NameRegistered

event NameRegistered(
    uint256 indexed tokenId,
    string label,
    address owner,
    IRegistry subregistry,
    address resolver,
    uint64 duration,
    IERC20 paymentToken,
    bytes32 referrer,
    uint256 base,
    uint256 premium
)
Emitted when a name is successfully registered.
tokenId
uint256
The registry token ID for the registered name
label
string
The registered name (without .eth)
owner
address
The owner address
subregistry
IRegistry
The initial subregistry contract
resolver
address
The initial resolver address
duration
uint64
Registration duration in seconds
paymentToken
IERC20
The ERC-20 token used for payment
referrer
bytes32
The referrer identifier
base
uint256
The base price paid
premium
uint256
The premium price paid

NameRenewed

event NameRenewed(
    uint256 indexed tokenId,
    string label,
    uint64 duration,
    uint64 newExpiry,
    IERC20 paymentToken,
    bytes32 referrer,
    uint256 base
)
Emitted when a name registration is extended.
tokenId
uint256
The registry token ID for the renewed name
label
string
The renewed name (without .eth)
duration
uint64
The extension duration in seconds
newExpiry
uint64
The new expiration timestamp
paymentToken
IERC20
The ERC-20 token used for payment
referrer
bytes32
The referrer identifier
base
uint256
The base price paid (no premium for renewals)

RentPriceOracleChanged

event RentPriceOracleChanged(IRentPriceOracle oracle)
Emitted when the rent price oracle is updated.
oracle
IRentPriceOracle
The new price oracle contract address

Errors

NameIsAvailable

error NameIsAvailable(string label)
The name is available for registration (cannot be renewed). Error Selector: 0xf7681f14

NameNotAvailable

error NameNotAvailable(string label)
The name is not available for registration (already registered). Error Selector: 0x477707e8

DurationTooShort

error DurationTooShort(uint64 duration, uint64 minDuration)
The registration duration is less than the minimum required. Error Selector: 0xa096b844

MaxCommitmentAgeTooLow

error MaxCommitmentAgeTooLow()
Constructor validation error: max commitment age must be greater than min commitment age. Error Selector: 0x3e5aa838

UnexpiredCommitmentExists

error UnexpiredCommitmentExists(bytes32 commitment)
A valid (unexpired) commitment already exists for this hash. Error Selector: 0x0a059d71

CommitmentTooNew

error CommitmentTooNew(bytes32 commitment, uint64 validFrom, uint64 blockTimestamp)
The commitment has not aged enough. Wait until validFrom timestamp. Error Selector: 0x6be614e3

CommitmentTooOld

error CommitmentTooOld(bytes32 commitment, uint64 validTo, uint64 blockTimestamp)
The commitment has expired. Must create a new commitment. Error Selector: 0x0cb9df3f

Registration Flow

Two-Step Process

  1. Commit: Call makeCommitment() to generate a hash, then call commit() to record it on-chain
  2. Wait: Wait for at least MIN_COMMITMENT_AGE seconds (but less than MAX_COMMITMENT_AGE)
  3. Register: Call register() with the same parameters used in the commitment

Why Commit-Reveal?

The commit-reveal scheme prevents frontrunning attacks. Without it:
  • Attacker sees your registration transaction in the mempool
  • Attacker submits their own transaction with higher gas
  • Attacker registers the name before you
With commit-reveal:
  • Your commitment reveals nothing about the name or parameters
  • Even if someone frontruns your commitment, they can’t guess your secret
  • Your registration transaction can only succeed if you have the matching commitment

Integration Examples

Complete Registration Flow

contract MyDApp {
    ETHRegistrar public registrar;
    IERC20 public paymentToken;
    
    function registerName(
        string memory label,
        IRegistry subregistry,
        address resolver
    ) external {
        // Generate secret
        bytes32 secret = keccak256(abi.encodePacked(
            "secret",
            msg.sender,
            block.timestamp,
            label
        ));
        
        // Create and submit commitment
        bytes32 commitment = registrar.makeCommitment(
            label,
            msg.sender,
            secret,
            subregistry,
            resolver,
            365 days,
            bytes32(0)
        );
        registrar.commit(commitment);
        
        // Store secret and commitment for later
        // User must wait MIN_COMMITMENT_AGE before proceeding
    }
    
    function completeRegistration(
        string memory label,
        bytes32 secret,
        IRegistry subregistry,
        address resolver
    ) external {
        // Get price
        (uint256 base, uint256 premium) = registrar.rentPrice(
            label,
            msg.sender,
            365 days,
            paymentToken
        );
        
        // Approve payment
        paymentToken.approve(address(registrar), base + premium);
        
        // Complete registration
        registrar.register(
            label,
            msg.sender,
            secret,
            subregistry,
            resolver,
            365 days,
            paymentToken,
            bytes32(0)
        );
    }
}

Build docs developers (and LLMs) love