Skip to main content
Reverse resolution allows you to look up the ENS name associated with an Ethereum address. This enables applications to display human-readable names instead of addresses.

Overview

Reverse resolution uses a special .addr.reverse domain where each Ethereum address has a corresponding subdomain:
  • Address 0x1234...5678 maps to 1234...5678.addr.reverse
  • The resolver for this reverse record stores the associated ENS name

How Reverse Resolution Works

1

Address to Reverse Node

Convert an Ethereum address to its reverse node:
import { namehash } from 'viem'

function getReverseNode(address) {
  // Remove 0x prefix and convert to lowercase
  const addr = address.slice(2).toLowerCase()
  // Create reverse domain: <address>.addr.reverse
  const reverseName = `${addr}.addr.reverse`
  return namehash(reverseName)
}

const reverseNode = getReverseNode('0x1234567890123456789012345678901234567890')
2

Query Reverse Resolver

Get the resolver for the reverse node and query the name:
const ensRegistry = await viem.getContractAt(
  'ENSRegistry',
  ENS_REGISTRY_ADDRESS
)

const resolverAddress = await ensRegistry.read.resolver([reverseNode])

if (resolverAddress !== zeroAddress) {
  const resolver = await viem.getContractAt(
    'PublicResolver',
    resolverAddress
  )
  const name = await resolver.read.name([reverseNode])
  console.log('Reverse resolved name:', name)
}
3

Forward Resolution Check

Always verify that the name resolves back to the address:
// Forward resolution to verify
const forwardNode = namehash(name)
const forwardResolver = await ensRegistry.read.resolver([forwardNode])

if (forwardResolver !== zeroAddress) {
  const resolver = await viem.getContractAt(
    'PublicResolver',
    forwardResolver
  )
  const resolvedAddress = await resolver.read.addr([forwardNode])
  
  if (resolvedAddress.toLowerCase() === originalAddress.toLowerCase()) {
    console.log('Verified! Name:', name)
  } else {
    console.log('Warning: Reverse record does not match forward resolution')
  }
}
Always perform forward resolution to verify the reverse record. Anyone can set their reverse record to any name, so you must confirm the name actually points back to the address.

Setting Up Reverse Resolution

Using ReverseRegistrar

The ReverseRegistrar contract manages reverse records:
const REVERSE_REGISTRAR_ADDRESS = '0x...' // Get from deployment

const reverseRegistrar = await viem.getContractAt(
  'ReverseRegistrar',
  REVERSE_REGISTRAR_ADDRESS
)

Claim Reverse Record

1

Simple Claim

Claim your reverse record with the default resolver:
// Claims reverse record for msg.sender
const tx = await reverseRegistrar.write.claim([ownerAddress])
await tx.wait()

console.log('Reverse record claimed!')
This sets you as the owner of your reverse record using the default resolver.Source: ~/workspace/source/contracts/reverseRegistrar/ReverseRegistrar.sol:64-66
2

Claim with Custom Resolver

Specify a custom resolver when claiming:
const tx = await reverseRegistrar.write.claimWithResolver([
  ownerAddress,
  customResolverAddress
])
await tx.wait()
Source: ~/workspace/source/contracts/reverseRegistrar/ReverseRegistrar.sol:93-98
3

Set the Name

Set the actual name for your reverse record:
// Sets name for msg.sender's reverse record
const tx = await reverseRegistrar.write.setName(['myname.eth'])
await tx.wait()

console.log('Reverse record set to myname.eth')
This combines claiming the reverse record and setting the name in one transaction.Source: ~/workspace/source/contracts/reverseRegistrar/ReverseRegistrar.sol:105-113

Setting Reverse Records for Other Addresses

Controllers and authorized accounts can set reverse records for other addresses:
// Only works if you're a controller or authorized
const tx = await reverseRegistrar.write.setNameForAddr([
  targetAddress,      // Address to set reverse record for
  ownerAddress,       // Owner of the reverse record
  resolverAddress,    // Resolver to use
  'example.eth'       // Name to set
])

await tx.wait()
Source: ~/workspace/source/contracts/reverseRegistrar/ReverseRegistrar.sol:123-132
This function requires authorization. You must be:
  • The address itself
  • A controller on the ReverseRegistrar
  • Approved for all by the address in the ENS registry
  • The owner of the address if it’s a contract

Setting Reverse Records During Registration

You can set reverse records when registering a new .eth name:
import { namehash, encodeFunctionData } from 'viem'

const registration = {
  label: 'myname',
  owner: ownerAddress,
  duration: 31536000,
  secret: secretBytes,
  resolver: publicResolverAddress,
  data: [],
  reverseRecord: 1, // 1 = Ethereum, 2 = default, 3 = both
  referrer: zeroHash
}

const tx = await ethRegistrarController.write.register(
  [registration],
  { value: price }
)

Reverse Record Options

  • 0 - Don’t set reverse record
  • 1 - Set Ethereum reverse record (addr.reverse)
  • 2 - Set default reverse record (default.reverse)
  • 3 - Set both Ethereum and default reverse records
Source: ~/workspace/source/contracts/ethregistrar/ETHRegistrarController.sol:319-330
Setting the reverse record during registration saves a separate transaction and ensures your name is immediately visible when your address is reverse resolved.

Calculating Reverse Node

The ReverseRegistrar provides a helper to calculate the reverse node:
const reverseNode = await reverseRegistrar.read.node([address])
console.log('Reverse node:', reverseNode)
Source: ~/workspace/source/contracts/reverseRegistrar/ReverseRegistrar.sol:137-142

Manual Calculation

You can also calculate it manually:
import { keccak256, encodePacked, namehash } from 'viem'

function calculateReverseNode(address) {
  // Remove 0x prefix and convert to lowercase
  const addr = address.slice(2).toLowerCase()
  
  // addr.reverse node
  const ADDR_REVERSE_NODE = 
    '0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2'
  
  // Calculate sha3 of hex address
  const labelHash = keccak256(encodePacked(['string'], [addr]))
  
  // Combine with addr.reverse node
  return keccak256(
    encodePacked(
      ['bytes32', 'bytes32'],
      [ADDR_REVERSE_NODE, labelHash]
    )
  )
}
Source: ~/workspace/source/contracts/reverseRegistrar/ReverseRegistrar.sol:13-15

Complete Reverse Resolution Example

import { namehash, zeroAddress } from 'viem'

async function reverseResolve(address) {
  try {
    // 1. Get reverse node
    const reverseRegistrar = await viem.getContractAt(
      'ReverseRegistrar',
      REVERSE_REGISTRAR_ADDRESS
    )
    const reverseNode = await reverseRegistrar.read.node([address])
    
    // 2. Get resolver for reverse node
    const ensRegistry = await viem.getContractAt(
      'ENSRegistry',
      ENS_REGISTRY_ADDRESS
    )
    const resolverAddress = await ensRegistry.read.resolver([reverseNode])
    
    if (resolverAddress === zeroAddress) {
      return { error: 'No reverse record set' }
    }
    
    // 3. Get name from resolver
    const resolver = await viem.getContractAt(
      'PublicResolver',
      resolverAddress
    )
    const name = await resolver.read.name([reverseNode])
    
    if (!name) {
      return { error: 'No name set in reverse record' }
    }
    
    // 4. Verify forward resolution
    const forwardNode = namehash(name)
    const forwardResolverAddress = await ensRegistry.read.resolver([forwardNode])
    
    if (forwardResolverAddress === zeroAddress) {
      return { 
        name,
        verified: false,
        error: 'Name has no resolver'
      }
    }
    
    const forwardResolver = await viem.getContractAt(
      'PublicResolver',
      forwardResolverAddress
    )
    const resolvedAddress = await forwardResolver.read.addr([forwardNode])
    
    const verified = resolvedAddress.toLowerCase() === address.toLowerCase()
    
    return {
      name,
      verified,
      resolvedAddress
    }
    
  } catch (error) {
    console.error('Reverse resolution failed:', error)
    return { error: error.message }
  }
}

// Usage
const result = await reverseResolve('0x1234567890123456789012345678901234567890')
if (result.verified) {
  console.log('Verified name:', result.name)
} else {
  console.log('Unverified or no name')
}

Authorization

The ReverseRegistrar checks authorization before allowing changes:
modifier authorised(address addr) {
    require(
        addr == msg.sender ||
            controllers[msg.sender] ||
            ens.isApprovedForAll(addr, msg.sender) ||
            ownsContract(addr),
        "ReverseRegistrar: Caller is not a controller or authorised by address or the address itself"
    );
    _;
}
You can set a reverse record for an address if:
  • You are the address itself
  • You are a controller on the ReverseRegistrar
  • You are approved for all by the address in ENS
  • You own the contract at that address
Source: ~/workspace/source/contracts/reverseRegistrar/ReverseRegistrar.sol:40-49

Events

ReverseClaimed

Emitted when a reverse record is claimed:
event ReverseClaimed(
    address indexed addr,
    bytes32 indexed node
)
Source: ~/workspace/source/contracts/reverseRegistrar/ReverseRegistrar.sol:23

DefaultResolverChanged

Emitted when the default resolver is updated:
event DefaultResolverChanged(
    NameResolver indexed resolver
)
Source: ~/workspace/source/contracts/reverseRegistrar/ReverseRegistrar.sol:24

Default Resolver

The ReverseRegistrar has a default resolver that’s used for simple claims:
// Get current default resolver
const defaultResolver = await reverseRegistrar.read.defaultResolver()

// Set default resolver (owner only)
const tx = await reverseRegistrar.write.setDefaultResolver([
  newResolverAddress
])
Source: ~/workspace/source/contracts/reverseRegistrar/ReverseRegistrar.sol:51-58

Best Practices

Always verify reverse records with forward resolution. Anyone can claim their reverse record points to any name, so you must verify the name actually resolves back to the address.
Cache reverse resolution results but with a reasonable TTL (e.g., 5-10 minutes). Users may update their reverse records at any time.
Display addresses with unverified reverse records differently in your UI to indicate they haven’t been validated.

UI Display Pattern

async function getDisplayName(address) {
  const result = await reverseResolve(address)
  
  if (result.verified) {
    return {
      display: result.name,
      verified: true,
      showCheckmark: true
    }
  } else if (result.name) {
    return {
      display: `${result.name} (unverified)`,
      verified: false,
      showWarning: true
    }
  } else {
    return {
      display: `${address.slice(0, 6)}...${address.slice(-4)}`,
      verified: false
    }
  }
}

Contract Addresses

Mainnet

  • ReverseRegistrar: Check latest deployment
  • Default Resolver: PublicResolver (check deployment)

Sepolia

  • ReverseRegistrar: Check latest deployment
  • Default Resolver: PublicResolver (check deployment)

Next Steps

Build docs developers (and LLMs) love