Repository Public
1import { base58, bech32, bech32m } from '@scure/base';2import { getAddress, isAddress } from 'viem';3import { Address as BtcAddress } from '@scure/btc-signer';4import { pluginFor } from './families.ts';5import { btcParams } from './build-btc.ts';6import { getNetwork } from './networks.ts';78/** Returns the normalized address or throws. EVM → EIP-55 checksum form. */9export function normalizeAddress(network: string, addr: string): string {10 const n = getNetwork(network);11 const a = addr.trim();12 if (n.family === 'evm') {13 if (!/^0x[0-9a-fA-F]{40}$/.test(a)) throw new Error('invalid EVM address');14 // Mixed-case input must carry a valid checksum; all-lower / all-upper is accepted and checksummed.15 const body = a.slice(2);16 const mixed = body !== body.toLowerCase() && body !== body.toUpperCase();17 if (mixed && !isAddress(a, { strict: true })) throw new Error('bad EVM checksum');18 const out = getAddress(a.toLowerCase());19 if (out === '0x0000000000000000000000000000000000000000') throw new Error('zero address');20 return out;21 }22 if (n.family === 'btc') {23 // this network only; decode validates checksum, prefix/version and program length24 const d = BtcAddress(btcParams(n)).decode(a);25 if (!['wpkh', 'wsh', 'tr', 'pkh', 'sh'].includes(d.type)) throw new Error(`unsupported ${n.name} address type`);26 if (n.utxo?.kind === 'pkh' && !['pkh', 'sh'].includes(d.type)) throw new Error(`unsupported ${n.name} address type`);27 const hrp = n.utxo?.bech32;28 if (hrp && a.toLowerCase().startsWith(hrp + '1')) {29 if (a !== a.toLowerCase() && a !== a.toUpperCase()) throw new Error('mixed-case bech32');30 const lower = a.toLowerCase();31 (d.type === 'tr' ? bech32m : bech32).decode(lower as `${string}1${string}`);32 return lower;33 }34 return a;35 }36 if (n.family !== 'sol') return pluginFor(n).normalizeAddress(n, a);37 const raw = base58.decode(a);38 if (raw.length !== 32) throw new Error('invalid Solana address');39 if (base58.encode(raw) !== a) throw new Error('non-canonical Solana address');40 return a;41}4243export function isValidAddress(network: string, addr: string): boolean {44 try { normalizeAddress(network, addr); return true; } catch { return false; }45}46