/// HD address derivation using bitcore-lib family. /// Each coin uses its own xpub from env vars. /// Derives child addresses at m/0/{index} (external receive chain). // @ts-ignore — bitcore libs don't have perfect types import bitcore from "bitcore-lib"; import bs58check from "bs58check"; // @ts-ignore import bitcoreCash from "bitcore-lib-cash"; // @ts-ignore import bitcoreLtc from "bitcore-lib-ltc"; // @ts-ignore import bitcoreDoge from "bitcore-lib-doge"; // @ts-ignore import dashcore from "@dashevo/dashcore-lib"; interface CoinLib { HDPublicKey: any; } const LIBS: Record = { btc: bitcore, bch: bitcoreCash, ltc: bitcoreLtc, doge: bitcoreDoge, dash: dashcore, }; function getXpub(coin: string): string { const key = `XPUB_${coin.toUpperCase()}`; const val = process.env[key]; if (!val) throw new Error(`Missing env var: ${key}`); return val; } /** * Derive a receive address for the given coin at the given index. * Uses the standard BIP44 external chain: m/0/{index} */ export function deriveAddress(coin: string, index: number): string { if (coin === "xec") { // XEC uses the same address format as BCH but with ecash: prefix // Derive using bitcore-lib-cash, then convert prefix const xpub = getXpub("xec"); const hdPub = new bitcoreCash.HDPublicKey(xpub); const child = hdPub.deriveChild(0).deriveChild(index); const addr = new bitcoreCash.Address(child.publicKey, bitcoreCash.Networks.mainnet); // bitcore-lib-cash gives "bitcoincash:q..." — replace prefix with "ecash:" const cashAddr = addr.toCashAddress(); return cashAddr.replace(/^bitcoincash:/, "ecash:"); } const lib = LIBS[coin]; if (!lib) throw new Error(`Unsupported coin: ${coin}`); const xpub = getXpub(coin); const hdPub = new lib.HDPublicKey(xpub); const child = hdPub.deriveChild(0).deriveChild(index); const addr = new lib.HDPublicKey.prototype.constructor.Address ? new (lib as any).Address(child.publicKey) : child.publicKey.toAddress(); return addr.toString(); } /** * Simpler approach — derive using each lib's built-in methods. */ export function deriveAddressSafe(coin: string, index: number): string { const xpub = getXpub(coin === "xec" ? "xec" : coin); if (coin === "btc") { const hd = new bitcore.HDPublicKey(xpub); return hd.deriveChild(0).deriveChild(index).publicKey.toAddress().toString(); } if (coin === "bch") { const hd = new bitcoreCash.HDPublicKey(xpub); return hd.deriveChild(0).deriveChild(index).publicKey.toAddress().toCashAddress(); } if (coin === "xec") { const hd = new bitcoreCash.HDPublicKey(xpub); const addr = hd.deriveChild(0).deriveChild(index).publicKey.toAddress().toCashAddress(); return addr.replace(/^bitcoincash:/, "ecash:"); } if (coin === "ltc") { // All zpub/Ltub/Mtub/xpub variants share the same key data — only version bytes differ. // Remap to the standard xpub version (0x0488b21e) so bitcore-lib-ltc can parse it, // then derive a native segwit P2WPKH (bech32 ltc1q...) address. const decoded = Buffer.from(bs58check.decode(xpub)); decoded[0] = 0x04; decoded[1] = 0x88; decoded[2] = 0xb2; decoded[3] = 0x1e; const normalized = bs58check.encode(decoded); const hd = new bitcoreLtc.HDPublicKey(normalized); const pubkey = hd.deriveChild(0).deriveChild(index).publicKey; // Derive as P2WPKH (native segwit, bech32 ltc1q...) return new bitcoreLtc.Address(pubkey, bitcoreLtc.Networks.mainnet, bitcoreLtc.Address.PayToWitnessPublicKeyHash).toString(); } if (coin === "doge") { const hd = new bitcoreDoge.HDPublicKey(xpub); return hd.deriveChild(0).deriveChild(index).publicKey.toAddress().toString(); } if (coin === "dash") { const hd = new dashcore.HDPublicKey(xpub); return hd.deriveChild(0).deriveChild(index).publicKey.toAddress().toString(); } throw new Error(`Unsupported coin: ${coin}`); } export { deriveAddressSafe as derive };