UNPKG

@enclave-hq/chain-utils

Version:

Multi-chain utilities for Enclave - SLIP-44 mappings, universal address encoding, and chain conversions

442 lines (437 loc) 12.9 kB
// src/types/index.ts var ChainType = /* @__PURE__ */ ((ChainType2) => { ChainType2["EVM"] = "evm"; ChainType2["TRON"] = "tron"; ChainType2["SOLANA"] = "solana"; ChainType2["COSMOS"] = "cosmos"; return ChainType2; })(ChainType || {}); // src/chain/slip44.ts var SLIP44_CHAIN_MAP = { // Ethereum (SLIP-44: 60) 60: { nativeChainId: 1, slip44: 60, name: "Ethereum Mainnet", chainType: "evm" /* EVM */, symbol: "ETH" }, // Tron (SLIP-44: 195) 195: { nativeChainId: 195, slip44: 195, name: "Tron Mainnet", chainType: "tron" /* TRON */, symbol: "TRX" }, // BSC (SLIP-44: 714) 714: { nativeChainId: 56, slip44: 714, name: "BNB Smart Chain", chainType: "evm" /* EVM */, symbol: "BNB" }, // Polygon (SLIP-44: 966) 966: { nativeChainId: 137, slip44: 966, name: "Polygon Mainnet", chainType: "evm" /* EVM */, symbol: "MATIC" }, // Solana (SLIP-44: 501) - Reserved 501: { nativeChainId: "mainnet-beta", // Solana uses string ID slip44: 501, name: "Solana Mainnet", chainType: "solana" /* SOLANA */, symbol: "SOL" }, // Avalanche C-Chain (SLIP-44: 9000) 9e3: { nativeChainId: 43114, slip44: 9e3, name: "Avalanche C-Chain", chainType: "evm" /* EVM */, symbol: "AVAX" }, // === Custom SLIP-44 IDs (Layer 2 and chains without official SLIP-44) === // Range: 1000000-1999999 (avoids conflicts with official SLIP-44) // Rule: 1000000 + nativeChainId // Arbitrum One (Custom SLIP-44: 1042161) 1042161: { nativeChainId: 42161, slip44: 1042161, name: "Arbitrum One", chainType: "evm" /* EVM */, symbol: "ETH" }, // Optimism (Custom SLIP-44: 1000010) 1000010: { nativeChainId: 10, slip44: 1000010, name: "Optimism", chainType: "evm" /* EVM */, symbol: "ETH" }, // Base (Custom SLIP-44: 1008453) 1008453: { nativeChainId: 8453, slip44: 1008453, name: "Base", chainType: "evm" /* EVM */, symbol: "ETH" }, // zkSync Era (Custom SLIP-44: 1000324) 1000324: { nativeChainId: 324, slip44: 1000324, name: "zkSync Era", chainType: "evm" /* EVM */, symbol: "ETH" } }; var NATIVE_TO_SLIP44_MAP = new Map( Object.entries(SLIP44_CHAIN_MAP).map(([slip44, info]) => [ String(info.nativeChainId), Number(slip44) ]) ); function nativeToSlip44(nativeChainId) { return NATIVE_TO_SLIP44_MAP.get(String(nativeChainId)) ?? null; } function slip44ToNative(slip44) { const info = SLIP44_CHAIN_MAP[slip44]; return info ? info.nativeChainId : null; } function getChainInfoBySlip44(slip44) { return SLIP44_CHAIN_MAP[slip44] ?? null; } function getChainInfoByNative(nativeChainId) { const slip44 = nativeToSlip44(nativeChainId); return slip44 ? getChainInfoBySlip44(slip44) : null; } function getChainType(slip44) { return SLIP44_CHAIN_MAP[slip44]?.chainType ?? null; } function isSupportedChain(nativeChainId) { return NATIVE_TO_SLIP44_MAP.has(String(nativeChainId)); } function isSupportedSlip44(slip44) { return slip44 in SLIP44_CHAIN_MAP; } function registerChain(chainInfo) { SLIP44_CHAIN_MAP[chainInfo.slip44] = chainInfo; NATIVE_TO_SLIP44_MAP.set(String(chainInfo.nativeChainId), chainInfo.slip44); } function getAllSupportedChains() { return Object.values(SLIP44_CHAIN_MAP); } function getAllSupportedSlip44s() { return Object.keys(SLIP44_CHAIN_MAP).map(Number); } // src/address/evm.ts var EVMAddressConverter = class { /** * Convert EVM address to 32 bytes (left-padded) * * @param nativeAddress - EVM address (0x...) * @returns 32 bytes Uint8Array * * @example * toBytes('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0') * // => Uint8Array(32) [0,0,0,0,0,0,0,0,0,0,0,0,116,45,53,204,...] */ toBytes(nativeAddress) { const cleaned = nativeAddress.toLowerCase().replace(/^0x/, ""); if (cleaned.length !== 40) { throw new Error(`Invalid EVM address length: ${nativeAddress}`); } const addressBytes = new Uint8Array(20); for (let i = 0; i < 20; i++) { addressBytes[i] = parseInt(cleaned.substring(i * 2, i * 2 + 2), 16); } const result = new Uint8Array(32); result.set(addressBytes, 12); return result; } /** * Convert 32 bytes back to EVM address * * @param bytes - 32 bytes Uint8Array * @returns EVM address (0x...) * * @example * fromBytes(new Uint8Array(32)) * // => '0x742d35cc6634c0532925a3b844bc9e7595f0beb0' */ fromBytes(bytes) { if (bytes.length !== 32) { throw new Error(`Invalid bytes length for EVM address: ${bytes.length}`); } const addressBytes = bytes.slice(12, 32); const hex = Array.from(addressBytes).map((b) => b.toString(16).padStart(2, "0")).join(""); return `0x${hex}`; } /** * Validate EVM address format * * @param nativeAddress - EVM address * @returns Whether it is valid */ isValid(nativeAddress) { return /^0x[0-9a-fA-F]{40}$/.test(nativeAddress); } /** * Format EVM address (normalize to lowercase) */ format(nativeAddress) { if (!this.isValid(nativeAddress)) { throw new Error(`Invalid EVM address: ${nativeAddress}`); } return nativeAddress.toLowerCase(); } }; var evmConverter = new EVMAddressConverter(); // src/address/tron.ts var _TronAddressConverter = class _TronAddressConverter { /** * Convert Tron address to 32 bytes (first to hex, then left-pad) * * @param nativeAddress - Tron address (T...) * @returns 32 bytes Uint8Array * * @example * toBytes('TRX9hash...') * // => Uint8Array(32) [0,0,0,0,0,0,0,0,0,0,0,0,...] */ toBytes(nativeAddress) { if (!this.isValid(nativeAddress)) { throw new Error(`Invalid Tron address: ${nativeAddress}`); } const decoded = this.base58Decode(nativeAddress); if (decoded.length !== 25) { throw new Error(`Invalid Tron address length after decoding: ${decoded.length}`); } const addressBytes = decoded.slice(1, 21); const result = new Uint8Array(32); result.set(addressBytes, 12); return result; } /** * Convert 32 bytes back to Tron address * * @param bytes - 32 bytes Uint8Array * @returns Tron address (T...) */ fromBytes(bytes) { if (bytes.length !== 32) { throw new Error(`Invalid bytes length for Tron address: ${bytes.length}`); } const addressBytes = bytes.slice(12, 32); const prefixed = new Uint8Array(21); prefixed[0] = 65; prefixed.set(addressBytes, 1); const checksum = this.calculateChecksum(prefixed); const full = new Uint8Array(25); full.set(prefixed, 0); full.set(checksum, 21); return this.base58Encode(full); } /** * Validate Tron address format */ isValid(nativeAddress) { if (!/^T[1-9A-HJ-NP-Za-km-z]{33}$/.test(nativeAddress)) { return false; } try { const decoded = this.base58Decode(nativeAddress); if (decoded.length !== 25) { return false; } const payload = decoded.slice(0, 21); const checksum = decoded.slice(21, 25); const calculatedChecksum = this.calculateChecksum(payload); return this.arraysEqual(checksum, calculatedChecksum); } catch { return false; } } /** * Base58 decode */ base58Decode(input) { const bytes = []; for (const char of input) { let value = _TronAddressConverter.BASE58_ALPHABET.indexOf(char); if (value < 0) { throw new Error(`Invalid Base58 character: ${char}`); } for (let i = 0; i < bytes.length; i++) { value += bytes[i] * 58; bytes[i] = value & 255; value >>= 8; } while (value > 0) { bytes.push(value & 255); value >>= 8; } } for (const char of input) { if (char !== "1") break; bytes.push(0); } return new Uint8Array(bytes.reverse()); } /** * Base58 encode */ base58Encode(bytes) { const digits = []; for (const byte of bytes) { let carry = byte; for (let i = 0; i < digits.length; i++) { carry += digits[i] << 8; digits[i] = carry % 58; carry = Math.floor(carry / 58); } while (carry > 0) { digits.push(carry % 58); carry = Math.floor(carry / 58); } } for (const byte of bytes) { if (byte !== 0) break; digits.push(0); } return digits.reverse().map((d) => _TronAddressConverter.BASE58_ALPHABET[d]).join(""); } /** * Calculate checksum (first 4 bytes of double SHA256) */ calculateChecksum(payload) { let hash = 0; for (const byte of payload) { hash = (hash << 5) - hash + byte | 0; } const checksum = new Uint8Array(4); checksum[0] = hash >>> 24 & 255; checksum[1] = hash >>> 16 & 255; checksum[2] = hash >>> 8 & 255; checksum[3] = hash & 255; return checksum; } /** * Compare two Uint8Arrays for equality */ arraysEqual(a, b) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false; } return true; } }; _TronAddressConverter.BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; var TronAddressConverter = _TronAddressConverter; var tronConverter = new TronAddressConverter(); // src/address/universal-address.ts function encodeUniversalAddress(slip44, nativeAddress) { const chainType = getChainType(slip44); if (!chainType) { throw new Error(`Unsupported SLIP-44 ID: ${slip44}`); } let addressBytes; switch (chainType) { case "evm" /* EVM */: addressBytes = evmConverter.toBytes(nativeAddress); break; case "tron" /* TRON */: addressBytes = tronConverter.toBytes(nativeAddress); break; case "solana" /* SOLANA */: throw new Error("Solana address conversion not implemented yet"); default: throw new Error(`Unsupported chain type: ${chainType}`); } const result = new Uint8Array(36); result[0] = slip44 >>> 24 & 255; result[1] = slip44 >>> 16 & 255; result[2] = slip44 >>> 8 & 255; result[3] = slip44 & 255; result.set(addressBytes, 4); return result; } function decodeUniversalAddress(universalAddress) { if (universalAddress.length !== 36) { throw new Error(`Invalid Universal Address length: ${universalAddress.length}`); } const slip44 = (universalAddress[0] << 24 | universalAddress[1] << 16 | universalAddress[2] << 8 | universalAddress[3]) >>> 0; const chainType = getChainType(slip44); if (!chainType) { throw new Error(`Unknown SLIP-44 ID: ${slip44}`); } const addressBytes = universalAddress.slice(4, 36); let nativeAddress; switch (chainType) { case "evm" /* EVM */: nativeAddress = evmConverter.fromBytes(addressBytes); break; case "tron" /* TRON */: nativeAddress = tronConverter.fromBytes(addressBytes); break; case "solana" /* SOLANA */: throw new Error("Solana address conversion not implemented yet"); default: throw new Error(`Unsupported chain type: ${chainType}`); } return { slip44, nativeAddress, nativeChainId: slip44ToNative(slip44) }; } function bytesToHex(bytes) { if (bytes.length !== 36) { throw new Error(`Invalid bytes length: ${bytes.length}`); } const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join(""); return `0x${hex}`; } function hexToBytes(hex) { const cleaned = hex.replace(/^0x/, ""); if (cleaned.length !== 72) { throw new Error(`Invalid hex length: ${hex}`); } const bytes = new Uint8Array(36); for (let i = 0; i < 36; i++) { bytes[i] = parseInt(cleaned.substring(i * 2, i * 2 + 2), 16); } return bytes; } function createUniversalAddress(nativeChainId, nativeAddress) { const slip44 = nativeToSlip44(nativeChainId); if (slip44 === null) { throw new Error(`Unsupported native chain ID: ${nativeChainId}`); } return encodeUniversalAddress(slip44, nativeAddress); } function createUniversalAddressHex(nativeChainId, nativeAddress) { const bytes = createUniversalAddress(nativeChainId, nativeAddress); return bytesToHex(bytes); } function isValidUniversalAddress(address) { try { const bytes = typeof address === "string" ? hexToBytes(address) : address; decodeUniversalAddress(bytes); return true; } catch { return false; } } export { ChainType, EVMAddressConverter, TronAddressConverter, bytesToHex, createUniversalAddress, createUniversalAddressHex, decodeUniversalAddress, encodeUniversalAddress, evmConverter, getAllSupportedChains, getAllSupportedSlip44s, getChainInfoByNative, getChainInfoBySlip44, getChainType, hexToBytes, isSupportedChain, isSupportedSlip44, isValidUniversalAddress, nativeToSlip44, registerChain, slip44ToNative, tronConverter }; //# sourceMappingURL=index.mjs.map //# sourceMappingURL=index.mjs.map