Skip to content

unruggable-labs/chain-resolver

Repository files navigation

Unified Chain Resolver (Registry + Resolver)

This repo contains a single contract — ChainResolver.sol — that lets clients look up chain IDs and ENS records in one place. Read operations use the ENSIP‑10 extended resolver entrypoint resolve(bytes name, bytes data) (https://docs.ens.domains/ensip/10). Only the contract owner (ideally a multisig) can make changes to the label–chain ID pairs.

Why this structure works

  • Everything is keyed by labelhash (computed as labelhash = keccak256(bytes(label)), for example with label = "optimism"). This keeps the contract agnostic to the final namespace (cid.eth, on.eth, l2.eth, etc.), so we can change hierarchies later without migrating fields.
  • One source of truth: Ownership, 7930 chain IDs (ERC‑7930), ENS records, and reverse lookups live in one place.
  • ENSIP‑10 Extended Resolver: Once registered, the chain owner (or an authorised operator) can set ENS fields like addresses, contenthash and other text/data fields. Reads go through the extended resolver entrypoint resolve(bytes name, bytes data) (see ENSIP‑10), so clients can call the standard ENS fields - addr, contenthash, and text - to pull chain metadata directly from an ENS name like optimism.cid.eth. For available fields and examples, see Contract Interfaces.
  • Clear forward and reverse: Forward returns a chain’s 7930 identifier; reverse maps 7930 bytes back to the chain name.

ChainResolver.sol

  • The chainId bytes follow the 7930 chain identifier format; see 7930 Chain Identifier.
  • Forward mapping: labelhash → chainId (bytes)
  • Reverse mapping: chainId (bytes) → chainName (string)
  • Per‑label ENS records: addr(coinType), contenthash, text, and data.
  • Ownership and operator permissions per label owner.

Resolution flow:

Resolution flow

Forward resolution (label → 7930): The ENS field text(..., "chain-id") (per ENSIP‑5) returns the chain’s 7930 ID as a hex string. The field data(..., "chain-id") returns the raw 7930 bytes (per ENSIP‑TBD‑19). This value is written at registration by the contract owner (e.g., a multisig) and the resolver ignores any user‑set text under that key. To resolve a chain ID:

  • DNS‑encode the ENS name (e.g., optimism.cid.eth).
  • Compute the node of the ENS name (e.g., using ethers: namehash(name))
  • Calls:
    • resolve(name, abi.encodeWithSelector(text(node, "chain-id"))) → returns a hex string.
    • resolve(name, abi.encodeWithSelector(data(node, "chain-id"))) → returns raw bytes.

Reverse resolution (7930 → name):

  • Reverse lookups are performed via the ENS text interface. Pass a key prefixed with "chain-name:" and suffixed with the 7930 hex using text(bytes32 node,string key) (per ENSIP‑5). This follows the chain-name: text key parameter standard (per ENSIP‑TBD‑17). For example:

    • Text key parameter (string): "chain-name:<7930-hex>"
    • Call:
      • resolve(name, encode(text(node, serviceKey)))

Contract Interfaces

Core reads and admin (see src/interfaces/IChainResolver.sol):

function chainId(bytes32 labelhash) external view returns (bytes memory);
function chainName(bytes calldata chainIdBytes) external view returns (string memory);
function register(string calldata chainName, address owner, bytes calldata chainId) external; // owner-only
function setLabelOwner(bytes32 labelhash, address owner) external; // label owner or operator
function setOperator(address operator, bool isOperator) external;   // per-owner operator

ENS fields available via IExtendedResolver.resolve(name,data):

  • addr(bytes32 node) → address (ETH, coin type 60) — per ENSIP‑1
  • addr(bytes32 node,uint256 coinType) → bytes (raw multi‑coin value) — per ENSIP‑9
  • contenthash(bytes32 node) → bytes — per ENSIP‑7
  • text(bytes32 node,string key) → string — per ENSIP‑5 (with special handling for "chain-id" and "chain-name:")
  • data(bytes32 node,string key) → bytes — per ENSIP‑TBD‑19 (with special handling for "chain-id")

7930 Chain Identifier

We use the chain identifier variant of ERC‑7930. Examples:

  • Optimism (chain 10): 0x000000010001010a00
  • Arbitrum (chain 102): 0x000000010001016600

See ERC‑7930: Universal Chain Identifier for the full specification.

Development

forge install
bun install

Foundry tests

forge test -vv

Blocksmith test

bun run test/ChainResolver.blocksmith.ts

Deploy & Resolve Workflow

  1. Deploy unified resolver
bun run deploy/DeployChainResolver.ts -- --chain=sepolia
  1. Capture deployed address in .env:
# Unified ChainResolver deployed address
CHAIN_RESOLVER_ADDRESS=0x...
  1. Register a chain and set records
bun run deploy/RegisterChainAndSetRecords.ts -- --chain=sepolia
  1. Set records for an existing label
bun run deploy/SetRecords.ts -- --chain=sepolia
  1. Resolve records (addr, contenthash, text, data)
bun run deploy/ResolveRecords.ts -- --chain=sepolia
  1. Resolve chain-id by label
bun run deploy/ResolveByLabel.ts -- --chain=sepolia
  1. Reverse resolve by chain‑id
bun run deploy/ReverseResolveByChainId.ts -- --chain=sepolia

References

About

Ethereum chain registry contracts

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published