Chain Registry-Resolver
Overview
The Chain Registry-Resolver is a smart contract that acts as a canonical, on-chain registry for blockchain metadata. It serves as the resolver for the on.eth namespace and enables applications and users to retrieve metadata for any blockchain using a single human-readable identifier, such as base or solana.
Historically, blockchain metadata has been stored in centralized, fragmented repositories maintained by third parties. The Chain Registry-Resolver brings this metadata on-chain into a single, extensible registry, where control and update authority are delegated to the relevant chain operators.
Architecture
Authorization Model
The Chain Registry-Resolver uses a two-tier authorization model:
Contract Owner
The contract owner can:
- Register new chains
- Update existing chain registrations
- Upgrade the contract implementation
- Set the default contenthash for the namespace
Chain Admin
When an address is registered by the contract owner, an administrator address is specified. This is an address provided by the chain operators.
The chain admin address can:
- Modify text, data, contenthash, and address records for their chain
- Transfer admin rights to another address
Upgrades
The Chain Registry-Resolver is deployed behind a UUPS (Universal Upgradeable Proxy Standard) proxy. This allows the contract logic to be upgraded while preserving all stored chain data and maintaining a consistent contract address.
Namespace Structure
on.eth (root namespace)
├── optimism.on.eth → Chain record storage
├── base.on.eth → Chain record storage
├── arbitrum.on.eth → Chain record storage
├── ... → Any number of chains can be registered
└── reverse.on.eth → Reserved for reverse resolutionResolver Profiles
All established ENS Resolver profiles are implemented by the contract to allow for the resolution of:
- Text Records (ENSIP-5)
- Contenthash (ENSIP-7)
- Addresses (ENSIP-9 / ENSIP-11)
- Arbitrary Data (ENSIP-24)
Immutable Records
For a given chain, the interoperable-address data key is set upon chain registration, and is immutable.
It references the ERC-7930 Interoperable Address for the chain in question.
As an example, for optimism.on.eth the value is set to 0x00010000010a00.
Aliasing
Chains can have multiple aliases that point to the canonical label.
Aliases are transparent - resolution through an alias returns the same underlying data as the canonical label.
For example:
op.on.eth→ resolves the same underlying metadata asoptimism.on.etharb.on.eth→ resolves the same underlying metadata asarbitrum.on.eth
Usage Guide
Chain Discovery
The registry is fully enumerable, allowing applications to discover all registered chains.
interface IChainResolver {
function chainCount() external view returns (uint256);
function getChainAtIndex(uint256 index) external view returns (
string memory label,
string memory name,
bytes memory interoperableAddress
);
}Chain Metadata Discovery
Data Records
The resolver implements the optional discoverability mechanism outlined in ENSIP-24.
/// @dev Interface selector: `0x29fb1892`
interface ISupportedDataKeys {
/// @notice For a specific `node`, get an array of supported data keys.
/// @param node The node (namehash).
/// @return The keys for which we have associated data.
function supportedDataKeys(bytes32 node) external view returns (string[] memory);
}Calling supportedDataKeys for a given chain e.g. optimism.on.eth will return an array of keys for which data is defined.
Text Records
The resolver also implements the discoverability mechanism for text records:
interface ISupportedTextKeys {
/// @notice For a specific `node`, get an array of supported text keys.
/// @param node The node (namehash).
/// @return The keys for which we have associated text records.
function supportedTextKeys(bytes32 node) external view returns (string[] memory);
}Calling supportedTextKeys for a given chain e.g. optimism.on.eth will return an array of keys for which text records are set.
Resolving Chain Metadata
As the resolver is set on the on.eth name, resolution for subnames is subject to the resolution process outlined in ENSIP-10: Wildcard Resolution.
The calldata that you will pass to the resolve method of the IExtendedResolver interface is dependent on whether the metadata that you are fetching is stored as a data record or a text record.
For data records (e.g. fetching an ERC-7930 Interoperable Address), use the data(bytes32 node, string calldata key) getter function defined in ENSIP-24.
import { createPublicClient, http, encodeFunctionData, decodeFunctionResult, parseAbi, toHex } from 'viem'
import { mainnet } from 'viem/chains'
import { namehash, packetToBytes } from 'viem/ens'
const client = createPublicClient({
chain: mainnet,
transport: http(),
})
const dataAbi = parseAbi([
'function data(bytes32 node, string key) view returns (bytes)',
])
const resolveAbi = parseAbi([
'function resolve(bytes name, bytes data) view returns (bytes)',
])
const name = 'optimism.on.eth'
const dnsEncodedName = toHex(packetToBytes(name))
const node = namehash(name)
// Encode the data() call for the interoperable-address key
const calldata = encodeFunctionData({
abi: dataAbi,
functionName: 'data',
args: [node, 'interoperable-address'],
})
// Call resolve()
const result = await client.readContract({
address: '0x...', // Chain Registry-Resolver address
abi: resolveAbi,
functionName: 'resolve',
args: [dnsEncodedName, calldata],
})
// Decode the result
const interopAddr = decodeFunctionResult({
abi: dataAbi,
functionName: 'data',
data: result,
})
// Returns: 0x00010000010a00For text records (e.g. fetching an X handle), use the text(bytes32 node, string key) function defined in ENSIP-5 instead:
const textAbi = parseAbi([
'function text(bytes32 node, string key) view returns (string)',
])
const resolveAbi = parseAbi([
'function resolve(bytes name, bytes data) view returns (bytes)',
])
const name = 'optimism.on.eth'
const dnsEncodedName = toHex(packetToBytes(name))
const node = namehash(name)
// Encode the text() call for the com.x key
const calldata = encodeFunctionData({
abi: textAbi,
functionName: 'text',
args: [node, 'com.x'],
})
// Call resolve()
const result = await client.readContract({
address: '0x...', // Chain Registry-Resolver address
abi: resolveAbi,
functionName: 'resolve',
args: [dnsEncodedName, calldata],
})
// Decode the result
const xHandle = decodeFunctionResult({
abi: textAbi,
functionName: 'text',
data: result,
})
// Returns: "https://x.com/optimism"Using Direct Getters
The resolver exposes helper functions that allow you to fetch frequently accessed chain metadata directly using the chain label, without needing to go through the ENSIP-10 resolution process.
interface IChainResolver {
function interoperableAddress(string calldata label) external view returns (bytes memory);
function chainName(string calldata label) external view returns (string memory);
}For other record types, use the generic getters:
interface IChainResolver {
function getText(string calldata label, string calldata key) external view returns (string memory);
function getData(string calldata label, string calldata key) external view returns (bytes memory);
function getAddr(string calldata label, uint256 coinType) external view returns (bytes memory);
function getContenthash(string calldata label) external view returns (bytes memory);
}Reverse Resolution
Reverse resolution maps an ERC-7930 Interoperable Address back to its human-readable chain label.
Reverse resolution data is stored as text records on the special subdomain, reverse.on.eth. The key format is chain-label: appended with the Interoperable Address you want to reverse.
const textAbi = parseAbi([
'function text(bytes32 node, string key) view returns (string)',
])
const reverseName = 'reverse.on.eth'
const dnsEncodedReverse = toHex(packetToBytes(reverseName))
const reverseNode = namehash(reverseName)
// The key is "chain-label:" + Interoperable Address without 0x prefix
const textKey = 'chain-label:00010000010a00'
const calldata = encodeFunctionData({
abi: textAbi,
functionName: 'text',
args: [reverseNode, textKey],
})
const result = await client.readContract({
address: '0x...',
abi: resolveAbi,
functionName: 'resolve',
args: [dnsEncodedReverse, calldata],
})
const label = decodeFunctionResult({
abi: textAbi,
functionName: 'text',
data: result,
})
// Returns: "optimism"Using Direct Getters
The resolver exposes a helper function to achieve the same result.
interface IChainResolver {
function chainLabel(bytes calldata interoperableAddress) external view returns (string memory);
}Interface Reference
Core Resolution
interface IChainResolver {
// Container for canonical label information
struct CanonicalLabelInfo {
string label;
bytes32 labelhash;
}
// Forward resolution
function interoperableAddress(string calldata label) external view returns (bytes memory);
function chainName(string calldata label) external view returns (string memory);
// Reverse resolution
function chainLabel(bytes calldata interoperableAddress) external view returns (string memory);
// Discovery
function chainCount() external view returns (uint256);
function getChainAtIndex(uint256 index) external view returns (
string memory label,
string memory name,
bytes memory interoperableAddress
);
// Record getters
function getText(string calldata label, string calldata key) external view returns (string memory);
function getData(string calldata label, string calldata key) external view returns (bytes memory);
function getAddr(string calldata label, uint256 coinType) external view returns (bytes memory);
function getContenthash(string calldata label) external view returns (bytes memory);
// Alias resolution
function getCanonicalLabel(string calldata _label) external view returns (CanonicalLabelInfo memory info);
// ENSIP-10 wildcard resolution
function resolve(bytes calldata name, bytes calldata data) external view returns (bytes memory);
// Interface detection
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}Record management
These functions are available to the respective chain operators for setting metadata about their blockchain.
interface IChainResolverAdmin {
// Text records
function setText(bytes32 labelhash, string calldata key, string calldata value) external;
function batchSetText(bytes32 labelhash, string[] calldata keys, string[] calldata values) external;
// Data records
function setData(bytes32 labelhash, string calldata key, bytes calldata value) external;
function batchSetData(bytes32 labelhash, string[] calldata keys, bytes[] calldata values) external;
// Address records
function setAddr(bytes32 labelhash, uint256 coinType, bytes calldata value) external;
// Contenthash
function setContenthash(bytes32 labelhash, bytes calldata contenthash) external;
// Admin transfer
function setChainAdmin(bytes32 labelhash, address newAdmin) external;
}Browsing Chain Metadata
The default contenthash for the on.eth namespace references a simple decentralized website that resolves data from the on-chain registry-resolver.
Using a service like eth.limo, you can interface with the registry by visiting on.eth.limo.
The metadata for a specific chain can be viewed through this user interface by accessing the specific subname directly. For example base.on.eth.limo, or optimism.on.eth.limo.

Metadata that is supported by the ENS App will also be displayed when the domain is looked up. For example: https://app.ens.domains/optimism.on.eth
Source Code
The Chain Registry-Resolver source code is available on GitHub.