pingql/apps/pay/src/address.ts

110 lines
3.9 KiB
TypeScript

/// 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<string, CoinLib> = {
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 };