Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

ETH Registrar

The ETH Registrar manages the registration and renewal of .eth names in ENSv2. It retains the proven commit-reveal pattern from ENSv1 while introducing new parameters, multi-token payments, and integration with the Permissioned Registry.

What Changed from ENSv1

FeatureENSv1ENSv2
Grace period90-day grace period after expiryNo grace period, names expire immediately
PaymentETH onlyERC20 tokens via safeTransferFrom
PricingStablePriceOracle (USD-denominated)StandardRentPriceOracle with duration discounts and expiry premiums
Name ownershipERC721 token on BaseRegistrarERC1155Singleton token on Permissioned Registry
PermissionsOwner-onlyFixed role set via EAC
makeCommitmentIncludes data, reverseRecord, ownerControlledFusesReplaced with subregistry, referrer; roles are fixed internally
registerIncludes data, reverseRecord, ownerControlledFusesReplaced with subregistry, paymentToken, referrer

Registering a Name

Like ENSv1, 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:

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:

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 rentPrice)
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)
);

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 for details on the role system.

Pricing

The StandardRentPriceOracle provides flexible, configurable pricing:

Base rate: names are priced by character count, with shorter names costing more. Query via rentPrice(label, owner, duration, paymentToken) which returns (base, premium).

Duration discounts: longer registrations receive discounts through a piecewise-linear curve. The discount points are DAO-configurable.

Expiry premium: since ENSv2 has no grace period, recently-expired names enter a temporary premium period to prevent sniping. The premium starts high and decreases over time. The previous owner can re-register during the premium period without paying the premium.

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).

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().

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).

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. It allows the deployer to batch-register or batch-renew names in a single transaction:

  • For AVAILABLE names: registers them as RESERVED (no owner)
  • For RESERVED names with a shorter expiry: extends the expiry via renew()

This contract is owner-only and intended for pre-migration use, not for general public registration.

EAC Integration

The ETH Registrar participates in the Enhanced Access Control system at two levels.

Registrar's Own EAC

The ETH Registrar inherits from EnhancedAccessControl and defines one role for its own governance:

RoleNybbleDescription
ROLE_SET_ORACLE0Authorizes calling setRentPriceOracle() to change the pricing oracle

The deployer receives all roles on ROOT_RESOURCE at construction. Only root-level roles are used; there are no per-name resources on the registrar itself.

Roles on the ETH Registry

To register and renew names, the ETH Registrar needs roles on the .eth Permissioned Registry. At deployment it is granted two registry roles on ROOT_RESOURCE:

RolePurpose
ROLE_REGISTRARAllows calling register() to create new .eth names
ROLE_RENEWAllows calling renew() on any .eth name

Because these are granted at ROOT_RESOURCE, they apply to all names (the EAC OR-combines root and token-level roles). The registrar does not hold admin variants of these roles, so it cannot delegate its own authority to other contracts.

Reference

Write Functions

commit(commitment)Record a commitment hash on-chain.
register(label, owner, secret, subregistry, resolver, duration, paymentToken, referrer)Register a name (consumes a prior commitment).
renew(label, duration, paymentToken, referrer)Extend a name's expiry. Any account can renew any name.
setRentPriceOracle(oracle)Change the pricing oracle. Requires ROLE_SET_ORACLE.

View Functions

makeCommitment(label, owner, secret, subregistry, resolver, duration, referrer)Compute a commitment hash for the given parameters (pure, no state read).
rentPrice(label, owner, duration, paymentToken)Get the registration or renewal price.
isAvailable(label)Check whether a name can be registered.
isValid(label)Check whether a name meets validity requirements.
isPaymentToken(paymentToken)Check whether a token is accepted for payment.
commitmentAt(commitment)Get the timestamp when a commitment was recorded.

Constants

REGISTRYThe Permissioned Registry this registrar writes to.
BENEFICIARYAddress that receives all registration and renewal payments.
MIN_COMMITMENT_AGEMinimum seconds before a commitment can be used.
MAX_COMMITMENT_AGEMaximum seconds before a commitment expires.
MIN_REGISTER_DURATIONShortest allowed registration duration.
rentPriceOracleCurrent pricing oracle.

Events

CommitmentMade(commitment)Commitment submitted.
NameRegistered(tokenId, label, owner, subregistry, resolver, duration, paymentToken, referrer, base, premium)Name registered.
NameRenewed(tokenId, label, duration, newExpiry, paymentToken, referrer, base)Name renewed.
RentPriceOracleChanged(oracle)Pricing oracle updated.

Code Examples

Full Registration Flow

Viem
import {
  createPublicClient,
  createWalletClient,
  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: 'rentPrice',
  args: [label, owner, 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

Viem
const [renewPrice] = await client.readContract({
  address: ethRegistrarAddress,
  abi: ethRegistrarAbi,
  functionName: 'rentPrice',
  args: [label, owner, 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],
})