npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@wonderland/interop-addresses

v0.3.0

Published

Interoperable address utilities for cross-chain operations

Readme

@wonderland/interop-addresses

A TypeScript library for handling interoperable blockchain addresses across different networks.

This package provides methods to convert between interoperable names (ERC-7828), structured objects with CAIP-350 text-encoded fields, and binary addresses (EIP-7930), following a clean two-layer architecture.

Installation

pnpm add @wonderland/interop-addresses

Architecture

The package follows a clean two-layer architecture:

1. Address Layer (EIP-7930 + CAIP-350)

Discriminated union address representation - either binary or text, but not both. Functions automatically convert between representations as needed. Synchronous encoding/decoding operations.

Note on "binary" terminology: In the ERC-7930 context, "binary" refers to the serialized format (as opposed to the text format with CAIP-350 encoding). In JavaScript, the binary format can be represented as either a hex string (Hex, the default) or Uint8Array bytes. The default hex string format aligns with common JavaScript ecosystem conventions (viem, ethers, JSON-RPC), where addresses are typically represented as hex strings rather than raw bytes.

Key Functions:

  • decodeAddress(value: Uint8Array | Hex, opts?: { representation?: "binary" | "text" }): InteroperableAddress - Decodes binary to address (defaults to "text" representation). TypeScript overloads ensure that representation: "binary" returns InteroperableAddressBinary, otherwise InteroperableAddressText.
  • encodeAddress(addr: InteroperableAddress, opts?: { format?: "hex" | "bytes" }): Hex | Uint8Array - Encodes address to binary (accepts either representation)
  • toBinaryRepresentation(addr: InteroperableAddress): InteroperableAddress - Converts text representation to binary
  • toTextRepresentation(addr: InteroperableAddress): InteroperableAddress - Converts binary representation to text
  • calculateChecksum(addr: InteroperableAddress): Checksum - Calculates checksum (accepts either representation)
  • validateInteroperableAddress(addr: InteroperableAddress): InteroperableAddress - Validates address (accepts either representation)
  • validateChecksum(addr: InteroperableAddress, checksum: Checksum, options?: ValidateChecksumOptions): void - Validates checksum (accepts either representation)

2. Name Layer (ERC-7828)

Human-readable names with ENS resolution. Async operations for resolution.

Key Functions:

  • parseName(input: string | ParsedInteropNameComponents, opts?: { representation?: "binary" | "text" }): Promise<ParsedInteroperableNameResult> (Name → Address, defaults to "text" representation). TypeScript overloads ensure that representation: "binary" returns ParsedInteroperableNameResult<InteroperableAddressBinary>, otherwise ParsedInteroperableNameResult<InteroperableAddressText>.
  • formatName(addr: InteroperableAddress, opts?: { includeChecksum?: boolean }): InteroperableName (Address → Name, accepts either representation, calculates checksum automatically)

Usage

High-Level Convenience Methods

The InteropAddressProvider class provides convenient async methods for common operations:

// Convert binary to text representation (synchronous)
import {
    binaryToName,
    decodeAddress,
    encodeAddress,
    nameToBinary,
} from "@wonderland/interop-addresses";

// Convert name to binary (async - may resolve ENS)
const binary = await nameToBinary("vitalik.eth@eip155:1#4CA88C9C", { format: "hex" });

// Convert binary to name (synchronous)
const name = binaryToName("0x00010000010114d8da6bf26964af9d7eed9e03e53415D37aa96045");

// Convert text representation to binary (synchronous)
const textAddr = {
    version: 1,
    chainType: "eip155",
    chainReference: "1",
    address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
};
const binaryFromText = encodeAddress(textAddr, { format: "hex" });

const textFromBinary = decodeAddress("0x00010000010114d8da6bf26964af9d7eed9e03e53415D37aa96045", {
    representation: "text",
});

Direct Layer Functions

You can also use the layer functions directly for more control:

import {
    calculateChecksum,
    decodeAddress,
    encodeAddress,
    formatName,
    isTextAddress,
    parseName,
    toBinaryRepresentation,
    toTextRepresentation,
} from "@wonderland/interop-addresses";

// Parse name with full result (includes metadata) - defaults to text representation
const result = await parseName("vitalik.eth@eip155:1#4CA88C9C");
// result.name - original parsed components
// result.interoperableAddress - address in text representation (default)
//   - result.interoperableAddress.chainType - "eip155" (string)
//   - result.interoperableAddress.chainReference - "1" (string)
//   - result.interoperableAddress.address - "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" (string)
// result.meta.checksum - calculated checksum
// result.meta.checksumMismatch - if provided checksum didn't match
// result.meta.isENS - whether address was ENS
// result.meta.isChainLabel - whether chain reference was a label

// Parse to binary representation
const resultBinary = await parseName("vitalik.eth@eip155:1#4CA88C9C", { representation: "binary" });
// resultBinary.interoperableAddress.chainType - Uint8Array
// resultBinary.interoperableAddress.chainReference - Uint8Array
// resultBinary.interoperableAddress.address - Uint8Array

// Decode binary to text representation (default)
const textAddr = decodeAddress("0x00010000010114d8da6bf26964af9d7eed9e03e53415D37aa96045");
// textAddr.chainType - "eip155" (string)
// textAddr.chainReference - "1" (string)
// textAddr.address - "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" (string)

// Decode binary to binary representation
const binaryAddr = decodeAddress("0x00010000010114d8da6bf26964af9d7eed9e03e53415D37aa96045", {
    representation: "binary",
});
// binaryAddr.chainType - Uint8Array
// binaryAddr.chainReference - Uint8Array
// binaryAddr.address - Uint8Array

// Use type guards to narrow the type
if (isTextAddress(textAddr)) {
    console.log(textAddr.chainType); // TypeScript knows this is a string
}

// Convert between representations
const textToBinary = toBinaryRepresentation(textAddr);
const binaryToText = toTextRepresentation(binaryAddr);

// Encode address to binary (accepts either representation)
const textAddr2 = {
    version: 1,
    chainType: "eip155",
    chainReference: "1",
    address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
};
const hex = encodeAddress(textAddr2, { format: "hex" }); // Automatically converts text to binary

// Format name (accepts either representation, calculates checksum automatically)
const name = formatName(textAddr); // Automatically converts if needed and includes checksum

Extracting Components

import { getAddress, getChainId } from "@wonderland/interop-addresses";

// Get address from binary or name
const address = await getAddress("vitalik.eth@eip155:1#4CA88C9C");
// Returns: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"

// Get chain ID from binary or name
const chainId = await getChainId("vitalik.eth@eip155:1#4CA88C9C");
// Returns: "1"

API Reference

High-Level Methods (InteropAddressProvider)

All methods are available as static methods on InteropAddressProvider or as standalone exported functions.

Async Methods (Name Layer)

  • nameToBinary(name: string | ParsedInteropNameComponents, opts?: { format?: "hex" | "bytes" }): Promise<Hex | Uint8Array>

    • Converts an interoperable name to binary. May resolve ENS names or chain labels.
  • getAddress(address: string): Promise<string>

    • Extracts the address component from a binary address or interoperable name as a CAIP-350-encoded string.
  • getChainId(address: string): Promise<string>

    • Extracts the chain reference from a binary address or interoperable name as a CAIP-350-encoded string.
  • computeChecksum(interoperableName: string): Promise<Checksum>

    • Computes the checksum for an interoperable name.
  • isValidInteropAddress(address: string, options?: ParseInteroperableNameOptions): Promise<boolean>

    • Validates if an address (binary or name) is a valid interop address.
  • isValidInteroperableName(interoperableName: string, options?: ParseInteroperableNameOptions): Promise<boolean>

    • Validates if an interoperable name is valid.

Synchronous Methods

  • binaryToName(binaryAddress: Hex | Uint8Array): InteroperableName

    • Converts a binary address to an interoperable name (synchronous).
  • isValidBinaryAddress(binaryAddress: Hex): boolean

    • Checks if a binary address is valid (synchronous).

Direct Layer Functions

Address Layer

  • decodeAddress(value: Uint8Array | Hex, opts?: { representation?: "binary" | "text" }): InteroperableAddress - Decodes binary to address (defaults to "text")
  • encodeAddress(addr: InteroperableAddress, opts?: { format?: "hex" | "bytes" }): Hex | Uint8Array - Encodes address to binary (accepts either representation)
  • toBinaryRepresentation(addr: InteroperableAddress): InteroperableAddress - Converts text representation to binary
  • toTextRepresentation(addr: InteroperableAddress): InteroperableAddress - Converts binary representation to text
  • calculateChecksum(addr: InteroperableAddress): Checksum - Calculates checksum (accepts either representation)
  • validateInteroperableAddress(addr: InteroperableAddress): InteroperableAddress - Validates address (accepts either representation)
  • validateChecksum(addr: InteroperableAddress, checksum: Checksum, options?: ValidateChecksumOptions): void - Validates checksum (accepts either representation)

Name Layer

  • parseName(input: string | ParsedInteropNameComponents, opts?: { representation?: "binary" | "text" }): Promise<ParsedInteroperableNameResult> - Parses name to address (defaults to "text")
  • formatName(addr: InteroperableAddress, opts?: { includeChecksum?: boolean }): InteroperableName - Formats address to name (accepts either representation, calculates checksum automatically)
  • isValidChain(chainType: ChainTypeName, chainReference: string): boolean
  • isValidChainType(chainType: string): chainType is ChainTypeName
  • resolveAddress(address: string, chainType: ChainTypeName, chainReference: string | undefined): Promise<ResolvedAddress>
  • resolveChain(input: { chainType?: string; chainReference?: string }): Promise<ResolvedChain>
  • shortnameToChainId(shortname: string): number | null

Types

InteroperableAddress

A discriminated union type that represents either binary or text fields, but not both. TypeScript narrows based on the chainType field type:

type InteroperableAddress =
    | {
          version: number;
          chainType: Uint8Array; // Binary variant
          chainReference?: Uint8Array;
          address?: Uint8Array;
      }
    | {
          version: number;
          chainType: "eip155" | "solana"; // Text variant
          chainReference?: string;
          address?: string;
      };

Type Guards:

  • isTextAddress(addr: InteroperableAddress): boolean - Check if address is text variant
  • isBinaryAddress(addr: InteroperableAddress): boolean - Check if address is binary variant

Usage:

import { isTextAddress } from "@wonderland/interop-addresses";

const addr = decodeAddress("0x00010000010114d8da6bf26964af9d7eed9e03e53415D37aa96045");

if (isTextAddress(addr)) {
    // TypeScript knows addr.chainType is "eip155" | "solana"
    console.log(addr.chainType); // "eip155"
    console.log(addr.chainReference); // "1" (string)
    console.log(addr.address); // "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" (string)
} else {
    // TypeScript knows addr.chainType is Uint8Array
    console.log(addr.chainType); // Uint8Array
    console.log(addr.chainReference); // Uint8Array | undefined
    console.log(addr.address); // Uint8Array | undefined
}

The text variant uses CAIP-350's text encoding rules, which are chainType-specific:

  • eip155: Chain references as decimal strings, addresses as hex strings with EIP-55 checksumming
  • solana: Chain references and addresses as base58-encoded strings
  • Other chain types follow their respective CAIP-350 encoding rules

InteroperableName

The ERC-7828-style human-readable name string:

type InteroperableName = string; // e.g., "vitalik.eth@eip155:1#4CA88C9C"

ParsedInteropNameComponents

The raw parsed components from an Interoperable Name string:

{
    address?: string;
    chainType: string | undefined;
    chainReference: string;
    checksum: string | undefined;
}

This type represents the raw components extracted from parsing an interoperable name string. It can be used directly as input to parseName or nameToBinary instead of a string.

ParsedInteroperableNameResult

The result from parseName:

{
  name: ParsedInteropNameComponents;      // Original parsed components
  address: InteroperableAddress;          // Address in specified representation (defaults to "text")
  meta: {
    checksum: Checksum;                    // Calculated checksum (always present)
    checksumMismatch?: {                   // Present if provided checksum didn't match
      provided: Checksum;
      calculated: Checksum;
    };
    isENS: boolean;                        // Whether address was resolved via ENS
    isChainLabel: boolean;                 // Whether chain reference was a label
  };
}

The address field contains the InteroperableAddress type in the requested representation (defaults to "text"). Use type guards to access fields:

import { isTextAddress } from "@wonderland/interop-addresses";

const result = await parseName("vitalik.eth@eip155:1#4CA88C9C");

if (isTextAddress(result.interoperableAddress)) {
    // Access text fields directly
    console.log(result.interoperableAddress.chainType); // "eip155"
    console.log(result.interoperableAddress.chainReference); // "1"
    console.log(result.interoperableAddress.address); // "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
}

Checksum Handling

  • Always Calculated: Checksums are always calculated from the binary address, even if not provided in the name.
  • Mismatch Detection: If a checksum is provided in the name but doesn't match the calculated checksum, it's stored in meta.checksumMismatch but doesn't throw an error.
  • Validation: Use validateChecksum from the binary layer to explicitly validate checksums.

ERC Compliance

This package implements:

EIP-7930 (Interoperable Addresses)

  • ✅ Binary format with version, chain type, chain reference, and address
  • ✅ Text serialization using CAIP-350 encoding rules: <address>@<chainType>:<chainReference>
  • ✅ Checksum calculation (first 4 bytes of keccak256 hash)
  • ✅ Support for zero-length addresses and chain references
  • ✅ Versioning support

ERC-7828 (Interoperable Names using ENS)

  • ✅ Human-readable name format: <address>@<chainType>:<chainReference>#<checksum>
  • ✅ ENS name resolution for addresses (e.g., alice.eth@eip155:1)
  • ✅ Validation: ENS names MUST include chain reference
  • ✅ Context-aware error handling for ENS vs raw addresses
  • ⚠️ ENS chain registry integration (pending - registry not yet deployed)

Not Yet Implemented

  • ⏳ ENS chain label resolution (e.g., address@ethereum → resolves via ENS)
  • ⏳ Reverse chain name lookup via ENS
  • ⏳ Chain discovery via ENS registry (chainCount, getChainAtIndex)

Local Development

  1. Install dependencies: pnpm install

Available Scripts

| Script | Description | | ------------- | ------------------------------------------------------- | | build | Build library using tsc | | check-types | Check types issues using tsc | | clean | Remove dist folder | | lint | Run ESLint to check for coding standards | | lint:fix | Run linter and automatically fix code formatting issues | | format | Check code formatting and style using Prettier | | format:fix | Run formatter and automatically fix issues | | test | Run tests using vitest | | test:cov | Run tests with coverage report |

References