ERC-7828 - Readable Interoperable Addresses using ENS

Created 2024-11-27
Status Draft
Category ERC
Type Standards Track
Authors
Requires

Abstract

This proposal extends ERC-7930 (Interoperable Addresses & Names) by standardizing a human-readable format for chain-specific addresses in the form <address>@<chain>#<checksum>. It introduces:

Motivation

The current Ethereum address landscape is leading to an ecosystem that will have hundreds and eventually thousands of L2s that use the same address format as Ethereum mainnet. This means an address by itself is not enough information to know which chain the address is related to. This can be problematic if funds are sent to an unreachable address on the incorrect chain. From the user account it should be possible to obtain the right chain identifier (chainID) to include in a transaction.

The mapping from chain names to identifiers has, since EIP-155, been maintained off chain using a centralized list. This solution has a few shortcomings: - It does not scale with the growing number of L2s. - The list maintainer is a trusted centralized entity. - It does not (currently) support non-EVM chains, even when naming systems (such as ENS, since ENSIP-9) do.

Instead of using non-human-readable numeric chain identifiers, this specification SHALL require a human-readable chain name resolved on-chain via ENS wildcard resolver. The centralized chain list maintained in ethereum-lists/chains SHALL be superseded by an on-chain registry. This registry provides a single source of truth for mapping chain names to chain identifiers and enables decentralized, extensible chain metadata resolution.

In the same spirit, the address could be a human-readable name as well, which is already a use case for ENS. By coupling the TLD to the resolving method used, this standard could leverage current and future features of ENS as well as other naming registries.

Moreover, the above can be leveraged to allow for a name to represent the same entity corresponding to different addresses on different chains, mitigating the risk of sending funds to an address the intended recipient doesn't actually control.

Desired properties: - Backwards compatibility with [ERC-7930] - The chain portion can be an ERC-7785 domain name when that standard is productive. - The address portion can be either the appropriate type of address for the chain, or a domain name. - The address portion and the chain portion should be resolved separately.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

Format

This standard defines a sub-syntax with extra semantics on top of the Interoperable Names syntax defined in [ERC-7930].

<human readable name>: := <address> "@" <chain> "#" <checksum>
<address>:             := <raw-address> | <ens-name>
<chain>:               := <raw-address> | <chain-name>
<checksum>:            := [0-9A-F]{8}

<raw-address>:         := [-:_%a-zA-Z0-9]+
<raw-chain>:           := [-:_a-zA-Z0-9]+
<chain-name>           := (<label> ".")* "<namespace>.eth"  
<ens-name>:            := (<label> .)+ <label>

Where:

This allows for Interoperable Addresses' text representation to mix and match 'resolved' and 'unresolved' usages in both the chain and address parts.

A few examples below:

Mainnet
- 0x12345...6789@eip155:1#FFFFFFFF
- 0x12345...6789@ethereum#FFFFFFFF
- alice.eth@eth#FFFFFFFF

Testnet (Sepolia)
- 0x12345...6789@eip155:11155111#00000000
- alice.testeth@sepolia#00000000

Rollup
- 0x12345...6789@eip155:4270#AAAAAAAA
- 0x12345...6789@arbitrum-nova#AAAAAAAA
- alice.eth@arbitrum-nova#AAAAAAAA

My ENS name is registered on rollup1 (via ENSIP-10 & ENSIP-16), but I want to receive funds on rollup2
- alice.rollup1.eth@rollup2#BBBBBBBB

Non-evm chain
- bc1..23@bip122:000000000019d6689c085ae165831e93#CCCCCCCC
- alice.eth@bip122:000000000019d6689c085ae165831e93#CCCCCCCC

Assuming the on-chain registry list adds a few other CAIP namespaces
- alice.eth@btc#CCCCCCCC

[!NOTE] This standard explicitly defines the use of an agreed .eth second-level domain for chain names. Beyond this, hierarchical name structures continue to be inherited from ENS, as exemplified in the ENS-in-rollup example.

Checksum

Interoperable Addresses MUST be serialized in the format defined by [ERC-7930]: ChainType, ChainReferenceLength, ChainReference, AddressLength, Address

A 4-byte checksum MUST be included when displaying or sharing addresses, as specified in [ERC-7930]. Wallets MUST compute and verify this checksum silently when parsing or validating addresses.

If a user-provided address includes a checksum, wallets MUST recompute it and compare it to the provided value. On mismatch, wallets MUST alert the user and MUST NOT allow sending funds without explicit override.

Wallets MAY display the checksum when formatting addresses, and MAY accept inputs without a checksum by computing it internally.

Resolving chain names

Chain names without a dot (.) are interpreted as labels under a reserved ENS second-level domain .eth and resolved via a wildcard resolver contract. This resolver acts as a single source of truth for mapping human-readable chain names to their corresponding chain identifiers encoded in the [ERC-7930] binary format.

To enable onchain resolution between chain names and chain identifiers, a minimal L2Resolver contract SHOULD implement the following methods. The examples below assume the .eth name to be l2.eth. - function chainId(bytes32 _node) returns (bytes memory _chainBytes) which resolves a chain l2.eth name to its [ERC-7930] chain identifier representation. - function chainName(bytes calldata _chainIdBytes) returns (string memory _chainName) which resolves an [ERC-7930] chain identifier to a human-readable chain name.

To obtain the human-readable chain name corresponding to a CAIP-2 chain identifier, clients SHALL:

  1. Encode the CAIP-2 identifier in ERC-7930 v1 binary form (AddressLength = 0); name the result chainBytes.
  2. Build resolver calldata: callData = abi.encodeWithSelector(IL2Resolver.chainName.selector, chainBytes).
  3. Query the Universal Resolver: result = UniversalResolver.resolve(dnsEncode("l2.eth"), calldata).
  4. ABI-decode result as (string fullName).
  5. Verify fullName ends with .l2.eth.
  6. Strip the suffix and apply UTS-46 lower-case normalisation. • Use the remaining label (e.g. “arbitrum” instead of “arbitrum.l2.eth”).

To obtain the chain identifier corresponding to a human-readable chain name, clients SHALL:

  1. Apply UTS-46 lower-case normalisation to the chain name.
  2. Append the reserved suffix: full = "arbitrum" + ".l2.eth".
  3. Compute node = namehash(full) as per ENSIP-1.
  4. Build resolver calldata: callData = abi.encodeWithSelector(IL2Resolver.chainId.selector, node).
  5. Query the Universal Resolver: result = UniversalResolver.resolve(dnsEncode(full), callData).
  6. ABI-decode result as (bytes chainBytes). • If chainBytes is empty → no mapping exists; treat the label as unknown.

Supported CASA namespaces

While currently only eip155 chains are supported, the on-chain registry could start listing other chains in order to make human-readable names for non-evm chains possible.

Resolving address names

The entirety of address name resolving of all TLDs is delegated to ENS, as specified in current & future ENSIPs.

Some caveats for ENS support are:

Supported CASA namespaces

Forward resolution (Interoperable Address -> Interoperable Name): Supported via the multichain address resolution standard defined in ERC-2304, which uses BIP-44 registered coin type identifiers to resolve names for various chains, including the eip155 namespace.

Reverse resolution (Interoperable Name -> Interoperable Address): EIP-155 chains explicitly supported by ENS by deployment of L2ReverseRegistrar on them and registration of that contract's address on the ENS registry.

Rationale

Backwards Compatibility

The naming scheme herein defined can represent all names supported by [ERC-7930] by displaying raw addresses without resolution

Reference Implementation

This section is non-normative, as future ENSIPs can modify the way in which ENS is used. Its purpose is to demonstrate how human-readable interoperable names are resolved to the [ERC-7930] binary format used at the protocol level. The following examples assume that chain names are registered as subdomains under l2.eth, and that resolution is performed via L2Resolver capable of mapping those subdomains to [ERC-7930] compliant chain identifiers.

Forward resolution step-by-step example

  1. Let the user input an address name. Assume it's alice.eth.
  2. Let the user select a chain. Assume it's optimism. The l2.eth suffix is implicit and hidden from end users.
  3. Append l2.eth and compute the ENS namehash as per ENSIP-1: namehash("optimism.l2.eth").
  4. Call L2Resolver.chainId(bytes32 _node) with the result of step 3 as input, which returns the chain ID in [ERC-7930] byte format.
  5. Convert the returned chain identifier into the CAIP-2 format, as specified by [ERC-7930].
  6. As per ENSIP-11, convert the EIP-155 chainId into an ENSIP-11-specific coinType: 0x80000000 & 0x0000000A == 0x8000000A.
  7. Compute the namehash of the result of step 1, according to ENSIP-1.
  8. Query the ENS registry for the appropriate resolver by calling resolver(bytes32 node) with the result of the step above.
  9. Call addr(bytes32 node, uint256 coinType) on the contract returned on the step above with the results of steps 6 and 5, respectively. This will return the 20 bytes of alice's OP Mainnet address, assume it's 0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa
    • Failure mode: if result is address zero, that means the name is not registered and resolution can't be finished.
  10. Serialize ChainType, ChainReferenceLength, ChainReference, AddressLength and Address according to [ERC-7930] (Interoperable Address v1): [ 0002 0000 01 0A 14 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ]
  11. Interoperable Address is complete: 0x00020000010A14AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
  12. Compute the checksum as described in [ERC-7930]: 0xC69BEB13 and display it to the user.

Reverse Resolution step-by-step example

Starting from the Interoperable Address serialized above: 0x00010000010A14AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

  1. Pick the first two bytes corresponding to the version: 0x0001. Remaining payload: 0000010A14AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA00010002
  2. Parse the ChainType, ChainReferenceLength, ChainReference, AddressLength and Address according to [ERC-7930]. Remaining payload 00010002
    1. ChainType: 0x0000 -> eip155
    2. ChainReferenceLength: 0x01 -> 1
    3. ChainReference: 0x0A -> 10 (optimism)
    4. AddressLength: 0x14 -> 20 bytes, consistent with eip155
    5. Address: 0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa -> alice's address
  3. Call L2Resolver.chainName(bytes calldata _chainIdBytes) with 0x00000000010A00 as the input. The resolver will return the corresponding ENS name, in this case optimism.l2.eth.
  4. Compute coinTypeAsHex for ENSIP-19:
    • Check if ChainType == 0x0000, as ENSIP-19 does not support non-EVM chains.
    • Check if ChainReference & 0xFFFFFFFF == ChainReference, meaning, all 28 most significant bytes are zero. If this is not the case, fail resolution as ENSIP-19 does not support chainids larger than 4 bytes.
    • Truncate the ChainReference to its 4 least significant bytes: 0x000000A
    • Set the ChainReference's MSB to 1: 0x000000A | 0x8000000: 0x8000000A
    • Convert to a lowercase-hexadecimal string without 0x prefix: 8000000a
  5. Compute the address as ENSIP-19 expects it, by producing the lowercase-hexadecimal string without 0x prefix of Address: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  6. Convert the output from two steps above to a reverse lookup string of the form <address>.<coinType>.reverse for ENSIP-19: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.8000000a.reverse
  7. Find resolver to use for address according to ENS wildcard resolution.
    1. Call resolver(namehash(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.8000000a.reverse)) on the mainnet ENS registry. It'll return the zero address, since there is no resolver registered on that address specifically.
    2. Call resolver(namehash(8000000a.reverse)) on the mainnet ENS registry. It'll return 0x00000beef055f7934784d6d81b6bc86665630dba, the address of the L2ReverseRegistrar on the network with chainid 10.
  8. Verify the return of the coinType method of the L2ReverseRegistrar matches the coinType as serialized in step 7.
  9. Call nameForAddr(bytes address) with the address as serialized in step 8. This will return the human-readable name alice.eth.
  10. Check forward resolution of the name as described in ENSIP-11 and repeated in the section above.
    • If it resolves to the same address, then proceed normally.
    • If forward resolution of the ENS name returns an address different from the original, or returns an empty byte array, the wallet MUST display the raw, human-readable [ERC-7930] serialized address, indicating the address is unresolved. The wallet SHOULD also warn the user of the mismatch to prevent potential spoofing or confusion.
  11. Compute the checksum as described in [ERC-7930]: 0xC69BEB13.
  12. Format the address in the format <address>@<chain>#<checksum> by removing l2.eth from step 3 and display it to the user: alice.eth@optimism#C69BEB13

Security Considerations

Copyright

Copyright and related rights waived via CC0. EIP-155 ERC-7930 ERC-7785 ERC-2304 BIP-44