Skip to content

ENS: Ethereum Name Service for Human-Readable Addresses

DodaTech Updated 2026-06-22 8 min read

In this tutorial, you'll learn the Ethereum Name Service including registering domains, resolving names, subdomains, reverse resolution, and integrating ENS into dApps for better user experience. Why it matters: ENS replaces 42-character hexadecimal addresses with human-readable names like "vitalik.eth", reducing errors and improving user experience across thousands of dApps, wallets, and services. By the end, you'll resolve and register ENS names programmatically.

ENS (Ethereum Name Service) is a distributed, open, and extensible naming system based on the Ethereum blockchain, mapping human-readable names to machine-readable identifiers such as Ethereum addresses, content hashes, and metadata.

How ENS Works

ENS consists of two main components: the registry (stores domain ownership) and resolvers (translate names to addresses).

flowchart LR
  A[User enters
vitalik.eth] --> B[ENS Registry] B --> C{Who owns the name?} C --> D[Resolver Contract] D --> E[addr() function] E --> F[Ethereum Address
0xd8dA6BF2...] D --> G[text() function] G --> H[Twitter handle
"@VitalikButerin"] D --> I[contenthash()] I --> J[IPFS hash
QmXYZ...] B --> K[Reverse Resolver] K --> L[name() function] L --> M["0x123..." -> "name.eth"]
const { ethers } = require("ethers");

async function resolveENS() {
  const provider = new ethers.JsonRpcProvider("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY");
  
  // Forward resolution: name -> address
  const vitalikAddress = await provider.resolveName("vitalik.eth");
  console.log("vitalik.eth resolves to:", vitalikAddress);
  
  // Reverse resolution: address -> name
  const vitalikName = await provider.lookupAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
  console.log("Address resolves to:", vitalikName);
  
  // Check if a name is available
  const resolver = await provider.getResolver("mynewdomain.eth");
  console.log("Has resolver:", resolver !== null);
}

resolveENS();
// Expected output:
// vitalik.eth resolves to: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
// Address resolves to: vitalik.eth
// Has resolver: false (name not registered)

Registering an ENS Name

Register an ENS domain using the ENS app or programmatically.

const { ethers } = require("ethers");

async function registerENS() {
  const provider = new ethers.JsonRpcProvider("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY");
  const signer = new ethers.Wallet("YOUR_PRIVATE_KEY", provider);
  
  // ENS Registry and Registrar addresses
  const registrarAddress = "0x253553366Da8546fC250F225fe3d25d0C782303b";  // ETH Registrar Controller
  const registrar = new ethers.Contract(registrarAddress, [
    "function available(string memory name) public view returns (bool)",
    "function makeCommitment(string memory name, address owner, bytes32 secret) public pure returns (bytes32)",
    "function commit(bytes32 commitment) public",
    "function register(string memory name, address owner, uint256 duration, bytes32 secret) public payable",
    "function rentPrice(string memory name, uint256 duration) public view returns (uint256)]
  ], signer);
  
  const name = "mydomain12345";  // Must be unique
  const duration = 31556952;  // 1 year in seconds
  
  // Check availability
  const available = await registrar.available(name);
  console.log(`${name}.eth available:`, available);
  
  if (available) {
    // Step 1: Commit (reveal after 60 seconds)
    const secret = ethers.randomBytes(32);
    const commitment = await registrar.makeCommitment(name, signer.address, secret);
    const commitTx = await registrar.commit(commitment);
    await commitTx.wait();
    console.log("Commitment made, wait 60 seconds...");
    
    // Step 2: Register after commit delay
    await new Promise(resolve => setTimeout(resolve, 65000));  // Wait 65 seconds
    
    const price = await registrar.rentPrice(name, duration);
    console.log("Registration price:", ethers.formatEther(price), "ETH");
    
    const registerTx = await registrar.register(name, signer.address, duration, secret, {
      value: price,
    });
    const receipt = await registerTx.wait();
    console.log(`${name}.eth registered! Tx: ${receipt.hash}`);
  }
}

// Expected output:
// mydomain12345.eth available: true
// Commitment made, wait 60 seconds...
// Registration price: 0.002 ETH
// mydomain12345.eth registered! Tx: 0x...

Setting Records and Subdomains

Configure your ENS name with addresses, content, and text records.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@ensdomains/ens-contracts/contracts/resolvers/Resolver.sol";

contract ENSManager {
    IENS public ens;
    IResolver public resolver;
    
    constructor(address _ens, address _resolver) {
        ens = IENS(_ens);
        resolver = IResolver(_resolver);
    }
    
    // Set ETH address for a name
    function setAddress(bytes32 _node, address _addr) external {
        address owner = ens.owner(_node);
        require(msg.sender == owner, "Not domain owner");
        resolver.setAddr(_node, _addr);
    }
    
    // Set text record (e.g., twitter handle)
    function setText(bytes32 _node, string calldata _key, string calldata _value) external {
        address owner = ens.owner(_node);
        require(msg.sender == owner, "Not domain owner");
        resolver.setText(_node, _key, _value);
    }
    
    // Create subdomain
    function createSubdomain(bytes32 _parentNode, string calldata _label, address _owner) external {
        address parentOwner = ens.owner(_parentNode);
        require(msg.sender == parentOwner, "Not parent owner");
        
        bytes32 subnode = keccak256(abi.encodePacked(_parentNode, keccak256(bytes(_label))));
        ens.setSubnodeRecord(_parentNode, keccak256(bytes(_label)), _owner, address(resolver), 0);
    }
    
    // Set content hash for IPFS
    function setContenthash(bytes32 _node, bytes calldata _content) external {
        address owner = ens.owner(_node);
        require(msg.sender == owner, "Not domain owner");
        resolver.setContenthash(_node, _content);
    }
}

Expected behavior: Domain owners can set their ETH address (for receiving payments), add text records (social links, email), create subdomains (like pay.mydomain.eth), and set content hashes (linking to IPFS sites).

Integrating ENS into dApps

Add ENS resolution to your dApp for a better user experience.

import { useState, useEffect } from "react";
import { ethers } from "ethers";

function ENSInput({ provider, onChange }) {
  const [input, setInput] = useState("");
  const [resolved, setResolved] = useState(null);
  const [error, setError] = useState(null);
  
  async function handleInput(value) {
    setInput(value);
    setError(null);
    setResolved(null);
    
    if (!value || value.length < 3) return;
    
    try {
      // Check if input is ENS name or address
      if (value.endsWith(".eth")) {
        // Resolve ENS to address
        const address = await provider.resolveName(value);
        if (address) {
          setResolved({ type: "ens", address, name: value });
          onChange?.(address);
        } else {
          setError("ENS name not found");
        }
      } else if (ethers.isAddress(value)) {
        // Try reverse resolution
        let name = await provider.lookupAddress(value);
        setResolved({
          type: "address",
          address: value,
          name: name || "Unknown",
        });
        onChange?.(value);
      }
    } catch (err) {
      setError(err.message);
    }
  }
  
  return (
    <div>
      <input
        type="text"
        value={input}
        onChange={(e) => handleInput(e.target.value)}
        placeholder="Enter ENS name or address"
      />
      {resolved && (
        <div>
          {resolved.type === "ens" ? (
            <span>{resolved.name} = {resolved.address}</span>
          ) : (
            <span>{resolved.address}</span>
          )}
        </div>
      )}
      {error && <div style={{ color: "red" }}>{error}</div>}
    </div>
  );
}

// Expected behavior:
// Input: "vitalik.eth"
// Output: "vitalik.eth = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
// 
// Input: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
// Output: "0xd8dA6B... = vitalik.eth"

Batch Resolution and Subgraph Query

Use The Graph for efficient batch ENS resolution.

async function batchResolveENS(provider, names) {
  // Individual resolution (slow for many names)
  const addressPromises = names.map(name => provider.resolveName(name));
  const addresses = await Promise.all(addressPromises);
  
  return names.map((name, i) => ({
    name,
    address: addresses[i],
  }));
}

// Better: Use ENS Subgraph for batch queries
async function queryENSSubgraph(names) {
  const query = `
    {
      domains(where: { name_in: [${names.map(n => `"${n}"`).join(",")}] }) {
        name
        owner {
          id
        }
        resolvedAddress {
          id
        }
        createdAt
      }
    }
  `;
  
  const response = await fetch("https://api.thegraph.com/subgraphs/name/ensdomains/ens", {
    method: "POST",
    body: JSON.stringify({ query }),
    headers: { "Content-Type": "application/json" },
  });
  
  const data = await response.json();
  return data.data.domains;
}

// Expected output:
// [
//   { name: "vitalik.eth", owner: "0x...", resolvedAddress: { id: "0xd8dA6B..." }, createdAt: "..." },
//   { name: "nick.eth", owner: "0x...", resolvedAddress: { id: "0xb8c2C2..." }, createdAt: "..." }
// ]

Off-Chain Resolution with CCIP-Read

ENS supports off-chain data via EIP-3668 (CCIP-Read), enabling gasless resolution for frequently changing data.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

// CCIP-Read enabled resolver (off-chain data)
contract OffchainResolver {
    address public gateway;
    
    // Forwards resolution to off-chain gateway
    function resolve(bytes memory _name, bytes memory _data) external view returns (bytes memory) {
        // Gateway responds with signed data
        // Client verifies signature and returns result
        return abi.encodeWithSelector(this.resolve.selector, _name, _data);
    }
}

// Client side
async function resolveOffchainENS(provider, name) {
  try {
    // First try on-chain resolution
    const address = await provider.resolveName(name);
    if (address) return address;
  } catch {
    // If CCIP-Read is needed, ethers.js handles it automatically
    console.log("Trying off-chain resolution...");
  }
  
  // ethers.js v6 supports CCIP-Read natively
  const address = await provider.resolveName(name);
  return address;
}

Common Errors and Misunderstandings

1. Confusing Registrar and Registry

The Registry stores ownership data. Registrars are contracts that manage domain registration for specific TLDs (.eth). Different TLDs have different registrars.

2. Forgetting the 60-Second Commit Delay

ENS registration requires a commit-reveal scheme to prevent front-running. Commit first, wait 60 seconds, then register.

3. Not Setting Resolver

Registering a name does not automatically set a resolver. You must set the resolver before the name resolves to anything.

4. Assuming All ENS Names Have Reverse Records

Not all addresses have reverse ENS records. Always handle the case where lookupAddress returns null.

5. Gas Costs for Setting Records

Each record (address, text, contenthash) costs gas to set. Batch record updates are not supported natively by the resolver.

Practice Questions

  1. What are the two main components of ENS? The Registry (stores domain ownership and resolver pointers) and Resolvers (translate names to addresses, content hashes, and other records).

  2. How does forward resolution differ from reverse resolution? Forward resolution converts a name (vitalik.eth) to an address. Reverse resolution converts an address back to a name. Both use the ENS registry and resolvers.

  3. Why does ENS registration use a commit-reveal scheme? To prevent front-running. Without it, bots could watch for someone starting registration of a name and register it first. The commit phase hides the name, then reveals it 60 seconds later.

  4. What is a subdomain in ENS? A subdomain is a name under another domain (pay.mydomain.eth). The parent domain owner can create and manage subdomains with their own records.

  5. How does CCIP-Read improve ENS? CCIP-Read (EIP-3668) allows off-chain storage of ENS records while maintaining on-chain verification. This enables gasless updates for frequently changing data like social media handles.

Challenge

Build a complete ENS management dApp using ethers.js and React that allows users to search ENS names, register available names, set address and text records, create subdomains, and import existing ENS names via the subgraph. Include support for CCIP-Read off-chain resolution.

Real-World Task

Create a dApp that uses ENS for all wallet interactions: inputs accept both ENS names and addresses, transaction recipients shown as ENS names, user profiles display ENS text records (avatar, Twitter, email), and a contact list stores frequently used ENS names. Deploy using Next.js and ethers.js with The Graph for efficient batch resolution.

Frequently Asked Questions

How much does an ENS name cost?

.eth names cost $5+ in ETH per year depending on length. 5+ character names are approximately $5/year. 3-4 character names start at $160/year. Gas costs for registration are additional (typically $10-50 depending on network).

Can I transfer my ENS name?

Yes. ENS names are NFTs (ERC-721 tokens). You can transfer them like any NFT on OpenSea or directly via the ENS app. The new owner gains full control over the name and all its records.

What can I do with an ENS name?

Receive cryptocurrency (ETH, tokens) using your name instead of an address. Host decentralized websites (IPFS + contenthash). Set up subdomains for team members. Link social profiles, email, and avatars. Verify ownership across Web3 platforms.

Next Steps

After mastering ENS, explore Cross-Chain Bridges for interoperability, then build multi-chain dApps with ENS integrated cross-chain names.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro