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 adding multi-year registration discounts, ERC20 payment support, and integration with the Permissioned Registry.

What Changed from ENSv1

FeatureENSv1ENSv2
Grace period90-day grace period after expiry28-day grace period
PaymentETH onlyERC20 tokens via safeTransferFrom
PricingUSD-denominated, no duration discountsUSD-denominated, multi-year duration discounts
Name ownershipERC721 on BaseRegistrar (optionally wrapped to ERC1155 via NameWrapper)ERC1155Singleton token on Permissioned Registry
PermissionsOwner has full control; no granular rolesRegistrant receives a fixed set of EAC roles

Pricing

Base Rate

Names are priced annually by character count:

Name LengthAnnual 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:

TermDiscount5+ char /yr5+ char total4-char /yr4-char total3-char /yr3-char total
1 yr0%$8.00$8.00$160$160$640$640
2 yr12.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.

Premium (USD)Days after grace period ends$100M$0021

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.

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

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. 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 checks both 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.

Code Examples

Full Registration Flow

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

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

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. Callable by the contract owner only.

View Functions

makeCommitment(label, owner, secret, subregistry, resolver, duration, referrer)Compute a commitment hash for the given parameters (pure, no state read).
getRegisterPrice(label, duration, paymentToken)Get the registration price for a name.
isAvailable(label)Check whether a name can be registered.
commitmentAt(commitment)Get the timestamp when a commitment was recorded.
getRenewPrice(label, duration, paymentToken)Get the renewal price for a name (base rate only, no premium).
isRenewable(label)Check whether a name can be renewed (registered or within grace period).
getRemainingGracePeriod(label)Get the remaining grace period for an expired name.

Constants

ETH_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.
MIN_RENEW_DURATIONShortest allowed renewal duration (1 second).
GRACE_PERIODPost-expiry window (in seconds) during which a name is renewable but not re-registerable.
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, amount)Name renewed.
RentPriceOracleUpdated(oracle)Pricing oracle updated.