Skip to main content

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+
See the Installation page for detailed setup instructions.

Quick Setup

1

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
2

Build Contracts

Compile both Foundry and Hardhat contracts:
forge build
bun run compile:hardhat
3

Start the Devnet

Launch a local development network with all contracts deployed:
bun run devnet
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:
1

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

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

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 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:
// 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:
// 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.
The name is already registered and not expired. Check getExpiry() to see when it expires.
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.

Build docs developers (and LLMs) love