Quickstart
This guide will get you up and running with ENS v2 contracts in minutes. You’ll learn how to deploy the contracts locally, register a name, and resolve it.
Prerequisites
Before you begin, ensure you have:
Node.js v24+
Foundry v1.3.2+
Bun v1.2+
Quick Setup
Clone and Install
Clone the repository and install dependencies: git clone https://github.com/ensdomains/contracts-v2
cd contracts-v2/contracts
bun i
forge i
Build Contracts
Compile both Foundry and Hardhat contracts: forge build
bun run compile:hardhat
Start the Devnet
Launch a local development network with all contracts deployed: This starts a local chain at http://localhost:8545 (Chain ID: 31337) with:
Root Registry deployed
ETH Registry (.eth TLD)
Universal Resolver
ETH Registrar with pricing oracle
Pre-configured test accounts
Register Your First Name
ENS v2 uses a commit-reveal scheme to prevent front-running. Here’s how to register a name:
Create a Commitment
First, create a commitment hash for your registration: // Calculate commitment
bytes32 commitment = ethRegistrar. makeCommitment (
"myname" , // label
owner, // owner address
secret, // random bytes32 secret
IRegistry ( address ( 0 )), // subregistry (optional)
resolver, // resolver address
duration, // registration duration in seconds
bytes32 ( 0 ) // referrer (optional)
);
Submit the Commitment
Submit the commitment and wait for the minimum commitment age: // Submit commitment
ethRegistrar. commit (commitment);
// Wait for MIN_COMMITMENT_AGE (typically 60 seconds)
// This prevents front-running attacks
The commitment must be consumed before MAX_COMMITMENT_AGE expires (typically 24 hours).
Register the Name
After the waiting period, register the name: // Approve payment token
paymentToken. approve ( address (ethRegistrar), price);
// Register the name
uint256 tokenId = ethRegistrar. register (
"myname" , // label
owner, // owner address
secret, // same secret from commitment
IRegistry ( address ( 0 )), // subregistry
resolver, // resolver address
duration, // duration in seconds (e.g., 365 days)
paymentToken, // payment ERC20 token
bytes32 ( 0 ) // referrer
);
The registrar automatically grants these roles to the owner:
ROLE_SET_SUBREGISTRY + admin
ROLE_SET_RESOLVER + admin
ROLE_CAN_TRANSFER_ADMIN
Set Resolver Records
Once you own a name, set its resolver records:
Set Address Record
Set Text Records
Set Contenthash
// Set Ethereum address
resolver. setAddr (
namehash ( "myname.eth" ),
0x1234567890123456789012345678901234567890
);
// Set address for another chain
resolver. setAddr (
namehash ( "myname.eth" ),
60 , // coinType for Ethereum
abi . encodePacked ( address ( 0x1234567890123456789012345678901234567890 ))
);
Resolve Names
Use the Universal Resolver to query any ENS name:
Basic Resolution
Resolve Text Records
Wildcard Resolution
// Resolve an address
( bytes memory result, address resolverAddress) = universalResolver. resolve (
dnsEncode ( "myname.eth" ),
abi . encodeWithSelector (IAddrResolver.addr.selector, namehash ( "myname.eth" ))
);
address owner = abi . decode (result, ( address ));
Manage Subdomains
Create and manage subdomains under your name:
// Register a subdomain
uint256 subTokenId = registry. register (
"sub" , // subdomain label
subOwner, // subdomain owner
IRegistry ( address ( 0 )), // no sub-registry
subResolver, // resolver for subdomain
ROLE_SET_RESOLVER | ROLE_SET_RESOLVER_ADMIN, // roles granted to owner
expiryTimestamp // expiry (must be <= parent expiry)
);
You need ROLE_REGISTRAR on the registry to create subdomains. This role is automatically granted at the root level when you own the parent name.
Grant Permissions
Delegate specific permissions to other addresses:
Grant Resolver Permission
Grant Multiple Roles
Create Soulbound Name
// Allow someone to manage resolver settings
registry. grantRoles (
tokenId,
RegistryRolesLib.ROLE_SET_RESOLVER,
operatorAddress
);
Renew a Name
Extend the registration period of a name:
// Calculate renewal price
( uint256 price, ) = ethRegistrar. rentPrice (
"myname" ,
currentOwner,
365 days , // renewal duration
paymentToken
);
// Approve payment
paymentToken. approve ( address (ethRegistrar), price);
// Renew the name
ethRegistrar. renew (
"myname" ,
365 days , // duration to add
paymentToken,
bytes32 ( 0 ) // referrer
);
Check Name Availability
Before registering, check if a name is available:
// Check if name is available
bool available = ethRegistrar. isAvailable ( "myname" );
// Get detailed status
IPermissionedRegistry.Status status = registry. getStatus (labelId);
// Status can be: AVAILABLE, RESERVED, or REGISTERED
// Get full state including expiry and token ID
IPermissionedRegistry.State memory state = registry. getState (labelId);
// state.status, state.expiry, state.tokenId, state.latestOwner
Working with TypeScript
Here’s a complete TypeScript example using viem:
import { createPublicClient , createWalletClient , http } from 'viem' ;
import { privateKeyToAccount } from 'viem/accounts' ;
import { localhost } from 'viem/chains' ;
// Setup client
const client = createWalletClient ({
chain: localhost ,
transport: http ( 'http://localhost:8545' ),
});
// Load contracts (from deployment)
const ethRegistrar = {
address: '0x...' , // deployed address
abi: [ ... ], // ETHRegistrar ABI
};
// Generate secret
const secret = '0x' + crypto . randomBytes ( 32 ). toString ( 'hex' );
// Create commitment
const commitment = await client . readContract ({
... ethRegistrar ,
functionName: 'makeCommitment' ,
args: [
'myname' ,
account . address ,
secret ,
'0x0000000000000000000000000000000000000000' ,
resolverAddress ,
BigInt ( 365 * 24 * 60 * 60 ),
'0x0000000000000000000000000000000000000000000000000000000000000000' ,
],
});
// Submit commitment
await client . writeContract ({
... ethRegistrar ,
functionName: 'commit' ,
args: [ commitment ],
});
// Wait 60 seconds...
// Register name
await client . writeContract ({
... ethRegistrar ,
functionName: 'register' ,
args: [
'myname' ,
account . address ,
secret ,
'0x0000000000000000000000000000000000000000' ,
resolverAddress ,
BigInt ( 365 * 24 * 60 * 60 ),
paymentTokenAddress ,
'0x0000000000000000000000000000000000000000000000000000000000000000' ,
],
});
Next Steps
Core Concepts Deep dive into ENS v2 architecture
Access Control Learn about the role system
Universal Resolver Advanced resolution patterns
Migration Guide Migrate from ENS v1 to v2
Common Issues
You need to wait for MIN_COMMITMENT_AGE (typically 60 seconds) after submitting a commitment before registering.
Your commitment has expired. Commitments are valid for MAX_COMMITMENT_AGE (typically 24 hours). Submit a new commitment.
NameAlreadyRegistered error
The name is already registered and not expired. Check getExpiry() to see when it expires.
Token ID changed after granting roles
This is expected behavior. Token IDs are regenerated when roles change to prevent NFT marketplace griefing attacks. Use getTokenId() to always get the current token ID.