The ETH Registrar Controller manages the registration and renewal of .eth second-level domains. This guide covers the commit-reveal registration process and best practices.
Overview
Registering a .eth name involves a two-step commit-reveal process:
- Commit - Submit a commitment hash to prevent frontrunning
- Register - After a waiting period, reveal your registration details
This process protects against frontrunning attacks where someone could see your pending transaction and register the name before you.
Prerequisites
Before registering a name:
- The name must be at least 3 characters long
- The name must be available (not already registered or expired)
- You need ETH to pay the registration fee
- You must wait at least
minCommitmentAge (typically 60 seconds) after committing
- You must register before
maxCommitmentAge (typically 24 hours) expires
Source: ~/workspace/source/contracts/ethregistrar/ETHRegistrarController.sol:33-52
Name Validation
Valid Names
// Check if a name is valid (3+ characters)
const isValid = await ethRegistrarController.read.valid(['myname'])
// Valid examples:
'alice' // true - 5 characters
'bob' // true - 3 characters
'你好吗' // true - 3 characters (Chinese)
'💩💩💩' // true - 3 emoji
// Invalid examples:
'ab' // false - only 2 characters
'a' // false - only 1 character
'' // false - empty string
Source: ~/workspace/source/contracts/ethregistrar/ETHRegistrarController.sol:191-193
Check Availability
// Check if a name is available for registration
const isAvailable = await ethRegistrarController.read.available(['myname'])
A name is available if it’s valid (3+ characters) and not currently registered or in the grace period.
Source: ~/workspace/source/contracts/ethregistrar/ETHRegistrarController.sol:199-204
Registration Process
Check Price
Get the registration price for your desired duration.import { parseEther } from 'viem'
const label = 'myname'
const duration = 365 * 24 * 60 * 60 // 1 year in seconds
const price = await ethRegistrarController.read.rentPrice([
label,
duration
])
console.log('Base cost:', price.base)
console.log('Premium:', price.premium)
console.log('Total:', price.base + price.premium)
The minimum registration duration is 28 days (2,419,200 seconds).
Source: ~/workspace/source/contracts/ethregistrar/ETHRegistrarController.sol:179-185 Prepare Registration Parameters
Create the registration struct with all required parameters.import { namehash, encodeFunctionData } from 'viem'
const registration = {
label: 'myname',
owner: '0x1234...', // Address that will own the name
duration: 31536000, // 1 year in seconds
secret: '0xabcd...', // Random 32-byte secret for commit-reveal
resolver: '0x5678...', // PublicResolver address
data: [], // Optional: resolver setup calls
reverseRecord: 0, // 0 = none, 1 = Ethereum, 2 = default, 3 = both
referrer: '0x0000000000000000000000000000000000000000000000000000000000000000'
}
Keep your secret value safe and random. If someone discovers it before you register, they could potentially frontrun your registration.
Source: ~/workspace/source/contracts/ethregistrar/IETHRegistrarController.sol:7-16 Make Commitment
Generate a commitment hash from your registration parameters.const commitment = await ethRegistrarController.read.makeCommitment([
registration
])
console.log('Commitment hash:', commitment)
The commitment is a keccak256 hash of all registration parameters, preventing others from seeing your intended registration.Source: ~/workspace/source/contracts/ethregistrar/ETHRegistrarController.sol:210-225 Submit Commitment
Send the commitment transaction to the blockchain.const commitTx = await ethRegistrarController.write.commit([
commitment
])
await commitTx.wait()
console.log('Commitment submitted!')
// Wait for minCommitmentAge (usually 60 seconds)
await new Promise(resolve => setTimeout(resolve, 60000))
You must wait at least minCommitmentAge (typically 60 seconds) before registering. The commitment expires after maxCommitmentAge (typically 24 hours).
Source: ~/workspace/source/contracts/ethregistrar/ETHRegistrarController.sol:230-235 Register the Name
After the waiting period, register the name by revealing your registration details.const price = await ethRegistrarController.read.rentPrice([
registration.label,
registration.duration
])
const totalPrice = price.base + price.premium
const registerTx = await ethRegistrarController.write.register(
[registration],
{ value: totalPrice }
)
await registerTx.wait()
console.log('Name registered successfully!')
Any excess ETH sent will be automatically refunded to your address.
Source: ~/workspace/source/contracts/ethregistrar/ETHRegistrarController.sol:247-345
Setting Up Resolver Records During Registration
You can configure resolver records as part of the registration transaction:
import { namehash, encodeFunctionData } from 'viem'
const node = namehash('myname.eth')
// Prepare resolver setup calls
const resolverCalls = [
// Set ETH address
encodeFunctionData({
abi: publicResolverAbi,
functionName: 'setAddr',
args: [node, ownerAddress]
}),
// Set text records
encodeFunctionData({
abi: publicResolverAbi,
functionName: 'setText',
args: [node, 'email', '[email protected]']
}),
encodeFunctionData({
abi: publicResolverAbi,
functionName: 'setText',
args: [node, 'url', 'https://example.com']
})
]
const registration = {
label: 'myname',
owner: ownerAddress,
duration: 31536000,
secret: secretBytes,
resolver: publicResolverAddress,
data: resolverCalls, // Setup calls executed during registration
reverseRecord: 1, // Set Ethereum reverse record
referrer: zeroHash
}
Source: Test example from ~/workspace/source/test/ethregistrar/TestEthRegistrarController.test.ts:120-131
Reverse Record Options
The reverseRecord parameter controls which reverse records are set:
0 - No reverse record
1 - Ethereum reverse record only (addr.reverse)
2 - Default reverse record only (default.reverse)
3 - Both Ethereum and default reverse records
const registration = {
// ... other parameters
reverseRecord: 1, // Set Ethereum reverse record
// ...
}
Source: ~/workspace/source/contracts/ethregistrar/ETHRegistrarController.sol:26-30
A resolver must be specified if you want to set a reverse record or provide data.
Renewing Names
Any account can renew a name, not just the owner:
const label = 'myname'
const duration = 31536000 // 1 year
const referrer = '0x0000000000000000000000000000000000000000000000000000000000000000'
const price = await ethRegistrarController.read.rentPrice([label, duration])
const renewTx = await ethRegistrarController.write.renew(
[label, duration, referrer],
{ value: price.base }
)
await renewTx.wait()
console.log('Name renewed!')
Renewals only require paying the base price, not any premium that may apply to expired names.
Source: ~/workspace/source/contracts/ethregistrar/ETHRegistrarController.sol:352-372
Error Handling
Common Errors
CommitmentTooNew
// Error: Tried to register before minCommitmentAge elapsed
// Solution: Wait longer after committing (at least 60 seconds)
CommitmentTooOld
// Error: Commitment expired (older than maxCommitmentAge)
// Solution: Create a new commitment and try again
CommitmentNotFound
// Error: No commitment found for these parameters
// Solution: Ensure you committed and are using the same parameters
NameNotAvailable
// Error: Name is already registered or in grace period
// Solution: Choose a different name or wait for expiry
DurationTooShort
// Error: Duration less than MIN_REGISTRATION_DURATION (28 days)
// Solution: Increase duration to at least 28 days
InsufficientValue
// Error: Not enough ETH sent to cover registration cost
// Solution: Send at least price.base + price.premium
Source: ~/workspace/source/contracts/ethregistrar/ETHRegistrarController.sol:67-99
Events
NameRegistered
Emitted when a name is successfully registered:
event NameRegistered(
string label,
bytes32 indexed labelhash,
address indexed owner,
uint256 baseCost,
uint256 premium,
uint256 expires,
bytes32 referrer
)
Source: ~/workspace/source/contracts/ethregistrar/ETHRegistrarController.sol:116-124
NameRenewed
Emitted when a name is renewed:
event NameRenewed(
string label,
bytes32 indexed labelhash,
uint256 cost,
uint256 expires,
bytes32 referrer
)
Source: ~/workspace/source/contracts/ethregistrar/ETHRegistrarController.sol:133-139
Best Practices
Always use a cryptographically secure random value for the secret parameter. Never reuse secrets across registrations.
Consider registering for multiple years to save on gas costs and ensure you don’t lose your name to expiration.
Names have a 90-day grace period after expiration. During this time, only the owner can renew, but the name doesn’t resolve.
Complete Example
Here’s a complete registration flow:
import { createPublicClient, createWalletClient, http, namehash } from 'viem'
import { mainnet } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
async function registerENSName(label, ownerAddress, durationYears = 1) {
// 1. Check availability
const available = await ethRegistrarController.read.available([label])
if (!available) throw new Error('Name not available')
// 2. Generate secret
const secret = `0x${crypto.randomBytes(32).toString('hex')}`
// 3. Calculate duration and price
const duration = durationYears * 365 * 24 * 60 * 60
const price = await ethRegistrarController.read.rentPrice([label, duration])
const totalPrice = price.base + price.premium
// 4. Prepare registration
const registration = {
label,
owner: ownerAddress,
duration,
secret,
resolver: PUBLIC_RESOLVER_ADDRESS,
data: [],
reverseRecord: 1,
referrer: zeroHash
}
// 5. Create and submit commitment
const commitment = await ethRegistrarController.read.makeCommitment([registration])
await ethRegistrarController.write.commit([commitment])
console.log('Commitment submitted, waiting 60 seconds...')
// 6. Wait for minCommitmentAge
await new Promise(resolve => setTimeout(resolve, 60000))
// 7. Register the name
const tx = await ethRegistrarController.write.register(
[registration],
{ value: totalPrice }
)
await tx.wait()
console.log(`Successfully registered ${label}.eth!`)
return tx
}
Next Steps