Skip to content

feat(ens): add address_bytes() for raw ENSIP-9 multichain resolution#3854

Open
yashgo0018 wants to merge 1 commit into
ApeWorX:mainfrom
ens-integrations:feat/ens-address-bytes-multichain
Open

feat(ens): add address_bytes() for raw ENSIP-9 multichain resolution#3854
yashgo0018 wants to merge 1 commit into
ApeWorX:mainfrom
ens-integrations:feat/ens-address-bytes-multichain

Conversation

@yashgo0018

Copy link
Copy Markdown

Summary

Adds ENS.address_bytes() and AsyncENS.address_bytes() to return resolver addr records as raw bytes (per ENSIP-9), without applying EIP-55 checksumming. Refactors address(coin_type=…) to resolve via address_bytes() and then encode with to_checksum_address() for Ethereum-shaped records.

This lets integrators resolve Bitcoin, Solana, and other coin types without copying Universal Resolver logic or hitting ValueError from to_checksum_address() on non–20-byte payloads.

Motivation

When coin_type is set, address() decodes resolver bytes and unconditionally calls to_checksum_address(). That works for Ethereum (coin_type=60, 20 bytes) but fails for typical ENSIP-9 encodings (e.g. Bitcoin 25-byte scriptPubKey) with ValueError: Unknown format ….

Integrators currently have to override AsyncENS.address to encode non-EVM addresses themselves. The requested API shape is:

  1. address_bytes() — resolution only, returns raw bytes.
  2. address() — calls address_bytes() when coin_type is set, then EIP-55 encoding (unchanged behavior for ETH; still raises for non-EVM bytes).

For display encoding of non-EVM bytes, ensdomains/address-encoder remains the recommended approach (same split as viem: resolve bytes → encode per coin type).

ENSIP-9 and ENSIP-11

ENSIP-9 defines addr(node, coinType) → bytes using each chain’s native binary encoding (Bitcoin scriptPubKey, Ethereum 20 bytes, Bech32 payloads, etc.).

ENSIP-11 amends ENSIP-9 for EVM-compatible chains: coin types with the high bit set encode an EVM chainId:

  • coinType = 0x80000000 | chainId (e.g. mainnet ETH remains SLIP-44 60; Arbitrum 421612147483657)
  • Reverse: chainId = 0x7fffffff & coinType
  • On-chain form is still 20-byte ChecksummedHex per ENSIP-9 — so address(coin_type=0x80000000 | chainId) can work when the record is standard EVM bytes

Non-EVM SLIP-44 types (e.g. Bitcoin 0, Solana) use variable-length encodings; address() is not appropriate there — use address_bytes() plus address-encoder (which implements ENSIP-9/11 conversion helpers such as convertEVMChainIdToCoinType).

Changes

  • ens/ens.py, ens/async_ens.py: new address_bytes(name, coin_type=None); address() delegates to it when coin_type is provided.
  • ens/utils.py: resolved_address_to_bytes() helper for legacy addr(node)address ABI decoding.
  • docs/ens_overview.rst: document address_bytes() for multichain resolution.
  • tests/ens/test_ens.py: coverage for Bitcoin ENSIP-9 bytes, delegation from address(), and async parity.
  • newsfragments/3854.feature.rst: release note fragment.

Behavior notes

Method coin_type=None / 60 (ETH) coin_type=0x80000000 | chainId (ENSIP-11 EVM) coin_type=0 (BTC, etc., ENSIP-9)
address_bytes() 20-byte payload 20-byte payload Variable-length script / chain bytes
address() Checksum ETH via _resolve or address_bytes + EIP-55 Checksum ETH (if 20 bytes) ValueError — not valid for EIP-55
  • EVM L2 / sidechain records (ENSIP-11 coin types): address() remains valid when the resolver stores 20-byte EVM addresses.
  • Non-EVM records (Bitcoin, Solana, …): use address_bytes() + address-encoder (or a custom encoder), not address().

Test plan

  • pytest tests/ens/test_ens.py — 32 passed
  • pytest tests/ens/ — full ENS suite passed locally
  • Bitcoin ENSIP-9 fixture (76a914…88ac) returns bytes via address_bytes() without error
  • address(coin_type=0) still raises ValueError on Bitcoin bytes (documents current ETH-only encoding)
  • Existing coin_type=60 and multichain setup tests unchanged

…lution

Introduces the address_bytes() method in both ENS and AsyncENS classes to retrieve raw address bytes per ENSIP-9 without EIP-55 encoding. This allows for proper handling of non-Ethereum coin types. The existing address() method is updated to delegate to address_bytes() for coin type lookups. Tests are added to ensure functionality for various coin types, including Bitcoin.
@fubuloubu fubuloubu marked this pull request as ready for review June 2, 2026 15:02
@fubuloubu fubuloubu self-assigned this Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants