---
title: Multi-Chain Addressing
description: Textual representation of an address and a chain.
author: Sam Wilson (@SamWilsn) <sam@binarycake.ca>
discussions-to: <URL>
status: Draft
type: Standards Track
category: ERC
created: 2024-08-30
requires: 55, 137, 155, 165, 2304
---
## Abstract
This proposal introduces a chain-specific address format that allows specifying both an account and the chain on which that account intends to transact. These chain-specific addresses take the form of `(example.eth:optimism)`, `6A10161835a36302BfD39bDA9B44f5734442234e:ethereum:11155111`, and so on. The target chain is resolved using a registry stored on ENS.
## Motivation
The Ethereum ecosystem is becoming steadily more fragmented. This means a 20 byte address by itself is not enough information to fully specify an account. This can be problematic if funds are sent to an unreachable address on the incorrect chain.
Instead of using chain identifiers, which are not human readable, the address could be extended with a human-readable chain name, which can then be resolved to a chain identifier. The mapping from chain names to identifiers has, since [EIP-155], been maintained off chain using a centralized list. This solution has two main shortcomings:
- It does not scale with the growing number of L2s.
- The list maintainer is a trusted centralized entity.
This ERC proposes the use of ENS to map chain names to identifiers, while still allowing maximum flexibility by changing the root chain.
### Why not ENS with [ERC-2304]?
While ERC-2034 allows registrants to specify per-chain addresses, it does not provide a default chain to receive assets on (nor should it.) The choice of receiving chain depends too much on off-chain factors to require a transaction to change.
## 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].
Grammar snippets in this proposal are given in Augmented Backus-Naur form (ABNF) as defined in [RFC 5234] and [RFC 7405].
### Definitions
The following terms are used throughout this proposal:
- **agent** - software/tool responsible for resolving a chain-specific address to its exact account and chain.
- **bridge** - contract that connects the root chain to the target chain (eg. to transfer tokens, proxy function calls.)
- **root chain** - blockchain containing bridge and name resolver contracts.
- **target chain** - blockchain where the identified account intends to transact; can be any chain with a bridge on the root chain.
### Syntax
At a high level, a chain-specific address is made of three components separated by colons (`:`) ordered from most specific on the left to most general on the right:
- a `local-part`, that identifies the account on the target chain;
- a `chain-part`, that identifies the target chain; and
- _optionally_, a `root-part` that identifies the root chain.
These components may be enclosed by parentheses (`(` and `)`) to resolve parsing ambiguities.
More formally, valid chain-specific addresses MUST adhere to the following grammar:
```abnf
address = OPEN bare-address CLOSE /
bare-address
bare-address = local-part SEP chain-part [SEP root-part]
OPEN = '('
CLOSE = ')'
SEP = ':'
```
#### Local Part
The `local-part` is the most specific section of a chain-specific address. It identifies the account on the target chain. It can be either a hexadecimal string (`hex-address`) or an ENS-like name (`ens-like`).
Valid `local-part` fragments MUST match the following grammar:
```abnf
local-part = hex-address /
ens-like
```
##### Hexadecimal Address
When `local-part` is a hexadecimal string, it MUST include checksum letter casing (see [Checksum](#Checksum)), and it MAY omit leading zeros. It MUST NOT include a leading `0x` prefix.
Note that `local-part` may encode an address longer or shorter than 20 bytes (40 hexadecimal digits.) Implementations MUST support `local-part` lengths of 1 hexadecimal digit up to 40 digits. Implementations SHOULD support arbitrarily sized (within some reasonable limit) `local-part` components.
Formally, `hex-address` MUST match the following grammar:
```abnf
hex-address = 1*HEXDIG
```
##### ENS-Like Names
To disambiguate an ENS name from a hexadecimal string—and unlike standard ENS names—names used in the `local-part` of a chain-specific address MUST contain at least one dot (`.`). If present, a dot placed at the rightmost position (eg. `eth.` or `example.eth.`) SHALL be removed before resolving the name. Chain-specific addresses SHOULD NOT contain a dot in the rightmost position unless no other dot is present.
The following grammar is illustrative only. See [ERC-137] for the definition of an ENS name.
```abnf
; Rough approximation of ENS names, with the additional requirement that it
; contain at least one "."
ens-like = 1*NOTSEP DOT *(1*NOTSEP [DOT])
NOTSEP = %x01-39 / %x3b-ff
```
#### Chain Part
The `chain-part` identifies the target chain. It MUST be a valid ENS name as defined in [ERC-137].
The following grammar is illustrative only. See [ERC-137] for the definition of an ENS name.
```abnf
chain-part = ens-name
; Rough approximation of ENS names, with no additional requirements
ens-name = 1*NOTSEP
```
#### Root Part
The `root-part` identifies the root chain against which other names are resolved. When present, the `root-part` SHALL be the [EIP-155] `chainid` of the root chain in decimal format. `root-part` SHOULD NOT be present when `chainid == 1`, and MUST be present when `chainid != 1`.
```abnf
root-part = 1*DIGIT
```
### Resolution
Resolving a chain-specific address begins on the right, and moves leftward.
#### Root Chain
If `root-part` is not present, assume it is `1`. Set the root's `chainid` to the value of `root-part`. The agent MUST be able to resolve ENS names against this chain. This likely means it has RPC access and a known ENS Resolver address, but any method of resolving addresses is sufficient.
Note that this makes `example.eth:optimism` distinct from `example.eth:optimism:10`. In the former case, both `example.eth` and `optimism` are resolved using ENS deployed on mainnet. In the second case, the two names would be resolved against an ENS deployed on the Optimism chain—an unusual situation.
The assignment of chain identifiers is defined in [EIP-155].
#### Target Chain
Next, construct an ENS name for the target chain by concatenating the value of `chain-part` with `.tbd.eth` <!-- TODO -->(such that `example.eth:foobar` would have a target chain of `foobar.tbd.eth`<!-- TODO -->.) Resolve the target chain's address (i.e. with [ERC-137]'s `addr`) against the ENS deployment on the root chain. The contract at this address is the "bridge contract."
The agent has to verify that the bridge contract supports the `chain-part` in the address. First, the agent MUST call [ERC-165]'s `supportsInterface` on the bridge contract using `ChainMetadata`'s interface identifier (see [Bridge Interface](#Bridge-Interface)) and the agent SHALL fail resolution if it returns false. Next, the agent MUST call the bridge contract's `acceptsName` function with the same namehash (see [ERC-137]) used in the above call to `addr`. The agent SHALL fail resolution if `acceptsName` returns false.
A bridge contract SHALL provide functionality/metadata enabling the agent to interact with the target chain. Further, it SHALL support the [ERC-165] mechanism for interface discovery, and MAY support other methods to accomplish the same. Bridge contracts MUST implement `ChainMetadata` (see [Bridge Interface](#Bridge-Interface).) Further specifics of bridging are left for future proposals.
<!-- TODO: Should we ensure that each chain id is one-to-one mapped to a name? -->
#### Local Address
##### Hexadecimal
Verify the checksum (see [Checksum](#Checksum).)
The local address is the hexadecimal encoding of the binary representation of the target chain's native address. For example, for the native Ethereum address `0x6A10161835a36302BfD39bDA9B44f5734442234e`, the local address would be `6A10161835a36302BfD39bDA9B44f5734442234e`.
##### ENS-Like
If the local address ends in a dot (`.`), remove it. Resolve the address using [ERC-2304]'s `addr` against the ENS deployment on the root chain with a `coinType` derived from the `chainid` of the target chain (retrieved from the bridge contract.)
### Bridge Interface
Bridge contracts MUST implement the following interface:
```solidity
interface ChainMetadata {
function chainId() external view returns (uint64);
function coinType() external view returns (uint256);
function acceptsName(bytes32 keccak) external view returns (bool);
}
```
When queried using [ERC-165]'s `supportsInterface`, bridge contracts MUST return true for `0x00000000`<!-- TODO -->.
### Checksum
<!-- TODO: Get someone smarter than me to verify that this is a reasonable extension of ERC-55 -->
<!-- TODO: Decide if we want ERC-1191. Would we use the root chain id, the target chain id, or even crazier—use the full chain-specific address as the input to the keccak? -->
Hexadecimal strings are cased according to a slightly modified [ERC-55] algorithm. The algorithm is modified by wrapping `nibble_index` to fit within the keccak hash.
<details>
<summary>Python implementation of the modified ERC-55 algorithm</summary>
```python
from Crypto.Hash import keccak # from pycryptodome
def checksum_encode(addr):
hex_addr = addr.hex()
checksummed_buffer = ""
# Treat the hex address as ascii/utf-8 for keccak256 hashing
k = keccak.new(digest_bits=256)
k.update(hex_addr.encode("utf-8"))
hashed_address = k.hexdigest()
# Iterate over each character in the hex address
for nibble_index, character in enumerate(hex_addr):
if character in "0123456789":
# We can't upper-case the decimal digits
checksummed_buffer += character
elif character in "abcdef":
# Check if the corresponding hex digit (nibble) in the hash is 8 or higher
nibble_index_wrapped = nibble_index % len(hashed_address)
hashed_address_nibble = int(hashed_address[nibble_index_wrapped], 16)
if hashed_address_nibble > 7:
checksummed_buffer += character.upper()
else:
checksummed_buffer += character
else:
raise Exception(
f"Unrecognized hex character {character!r} at position {nibble_index}"
)
return "0x" + checksummed_buffer
def test(addr_str: str):
padded_addr_str = addr_str.removeprefix("0x")
if len(padded_addr_str) % 2 == 1:
# Pad to an even number of nibbles.
padded_addr_str = "0" + padded_addr_str
addr_bytes = bytes.fromhex(padded_addr_str)
checksum_encoded = checksum_encode(addr_bytes)
if checksum_encoded != addr_str:
print(f"{checksum_encoded} != expected {addr_str}")
test("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
test("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359")
test("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB")
test("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb")
test("0x004f67dAbb603AAA58eD52641CCafF09C559704A")
test("0x4F67dABB603aAa58Ed52641cCAff09C559704A")
test(
"0x"
"5aaeB6053f3e94c9B9a09f33669435E7"
"ef1BEaED5AaEB6053f3e94C9B9a09F33"
"669435e7ef1bEAed5aaEb6053f3e94C9"
"b9a09f33669435E7ef1beAED5aaEB605"
"3f3e94c9b9a09F33669435e7ef1beAED"
)
```
</details>
For example, these strings are correctly cased:
* `004f67dAbb603AAA58eD52641CCafF09C559704A`
* `04f67dAbb603AAA58eD52641CCafF09C559704A`
* `4F67dABB603aAa58Ed52641cCAff09C559704A`
[RFC 2119]: https://www.rfc-editor.org/rfc/rfc2119
[RFC 8174]: https://www.rfc-editor.org/rfc/rfc8174
[RFC 5234]: https://www.rfc-editor.org/rfc/rfc5234
[RFC 7405]: https://www.rfc-editor.org/rfc/rfc7405
[ERC-55]: ./eip-55.md
[ERC-137]: ./eip-137.md
[EIP-155]: ./eip-155.md
[ERC-165]: ./eip-165.md
[ERC-2304]: ./eip-2304.md
## Rationale
### Component Order
The components are ordered from most specific to most general because... <!-- TODO -->
### Separator Choice
The colon (`:`) is a reasonable choice for separator because it is not an allowed character in ENS names, it is familiar (eg. IPv6), and isn't as overloaded as the `@` symbol.
#### Alternative: `@`
The `@` symbol is a common choice for addresses, and finds use in email and several federated communication protocols. The English reading (foo-**AT**-example-DOT-com) is natural and implies a hierarchy between the left and the right components.
Unfortunately, because the `@` symbol is so widely used, including it in a chain-specific address would make all those protocol identifiers more confusing (or even invalid.) For example, `foo@foo.eth@ethereum` is not a valid email address.
#### Alternative: `/`
<!-- TODO -->
### Target Chain as Subdomain
While it would be technically possible to resolve `chain-part` against a root ENS name (eg. `ethereum.eth` instead of `ethereum.tbd.eth`<!-- TODO -->), using a subdomain allows the pre-registration of well-known chain names for an initial distribution of names before switching to open registration.
Without such a pre-registration, an attacker could register well-known names before the legitimate project.
After the pre-registration period, open registration is acceptable because new chains can register their names before announcing publicly.
## Backwards Compatibility
It is always possible to determine whether a particular string is a chain-specific address, a plain address, or a plain ENS name. Because of this property, there is little opportunity for backwards incompatibility: chain-specific addresses are not valid legacy addresses or ENS names, so tools without support will simply reject them.
## Test Cases
<!--
-- TODO: Test Case Ideas
--
-- * Longer than 20-byte hex local-part
-->
### ENS Configuration
#### Mainnet (1)
| Name | Coin Type | Record |
| ------------- | --------------- | -------------------------------------------- |
| `ethereum.tbd.eth`<!-- TODO --> | - | a bridge contract to mainnet (1) |
| `rollup1.tbd.eth`<!-- TODO --> | - | a bridge contract to rollup1 (1608) |
| `example.eth` | `2147483649` | `0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa` |
| `example.eth` | `2147485256` | `0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB` |
#### Sepolia (11155111)
| Name | Coin Type | Record |
| ---- | --------- | ----- |
| `ethereum.tbd.eth`<!-- TODO --> | - | a bridge contract to sepolia (11155111) |
| `example.eth` | <!-- TODO --> | <!-- TODO --> |
### Inputs & Expected Outputs
#### Valid
| Input | Target Chain | Local Address |
| ------------------------ | -------------- | -------------------------------------------- |
| `(example.eth:ethereum)` | mainnet (1) | `0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa` |
| `example.eth:rollup1` | rollup1 (1608) | `0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB` |
| `example.eth.:ethereum` | mainnet (1) | `0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa` |
| `0:ethereum` | mainnet (1) | `0x0000000000000000000000000000000000000000` |
#### Invalid
| Input | Failure Reason |
| ------------------------------------------------------- | -------------------------- |
| `(0xaAaAaAaaAaAaAaaAaAAAAAAAAaaaAaAaAaaAaaAa:ethereum)` | Invalid hexadecimal |
| `(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:ethereum)` | Invalid checksum |
| `(AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:ethereum)` | Invalid checksum |
| `(eth:ethereum)` | Invalid hexadecimal |
| `(:ethereum)` | Missing `local-part` |
## Reference Implementation
<!--
This section is optional.
The Reference Implementation section should include a minimal implementation that assists in understanding or implementing this specification. It should not include project build files. The reference implementation is not a replacement for the Specification section, and the proposal should still be understandable without it.
If the reference implementation is too large to reasonably be included inline, then consider adding it as one or more files in `../assets/eip-####/`. External links will not be allowed.
TODO: Remove this comment before submitting
-->
## Security Considerations
### Unicode & Typosquatting Attacks
An attacker could register ENS names that resemble well-known chain names. For example, `etherium` and `ehtereum` are reasonably close to `ethereum`. While many unicode homoglyphs are caught by ENS libraries, agents should still be aware of the risk they pose.
## Copyright
Copyright and related rights waived via [CC0](../LICENSE.md).