110 lines
3.9 KiB
TypeScript
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 };
|