diff --git a/modules/abstract-utxo/src/abstractUtxoCoin.ts b/modules/abstract-utxo/src/abstractUtxoCoin.ts index 57f5de5e52..5739b47c17 100644 --- a/modules/abstract-utxo/src/abstractUtxoCoin.ts +++ b/modules/abstract-utxo/src/abstractUtxoCoin.ts @@ -814,27 +814,6 @@ export abstract class AbstractUtxoCoin extends BaseCoin { return explainTx(this.decodeTransactionFromPrebuild(params), params, this.network); } - /** - * Create a multisig address of a given type from a list of keychains and a signing threshold - * @param addressType - * @param signatureThreshold - * @param keys - */ - createMultiSigAddress(addressType: ScriptType2Of3, signatureThreshold: number, keys: Buffer[]): MultiSigAddress { - const { - scriptPubKey: outputScript, - redeemScript, - witnessScript, - } = utxolib.bitgo.outputScripts.createOutputScript2of3(keys, addressType); - - return { - outputScript, - redeemScript, - witnessScript, - address: utxolib.address.fromOutputScript(outputScript, this.network), - }; - } - /** * @deprecated - use {@see backupKeyRecovery} * Builds a funds recovery transaction without BitGo diff --git a/modules/abstract-utxo/src/address/fixedScript.ts b/modules/abstract-utxo/src/address/fixedScript.ts index 0f927ebc23..3cb9dcfb00 100644 --- a/modules/abstract-utxo/src/address/fixedScript.ts +++ b/modules/abstract-utxo/src/address/fixedScript.ts @@ -9,7 +9,8 @@ import { P2trUnsupportedError, P2wshUnsupportedError, UnsupportedAddressTypeError, - sanitizeLegacyPath, + isTriple, + Triple, } from '@bitgo/sdk-core'; import * as utxolib from '@bitgo/utxo-lib'; import { bitgo } from '@bitgo/utxo-lib'; @@ -52,7 +53,7 @@ function supportsAddressType(network: utxolib.Network, addressType: ScriptType2O export function generateAddressWithChainAndIndex( network: utxolib.Network, - keychains: { pub: string }[], + keychains: bitgo.RootWalletKeys | Triple, chain: bitgo.ChainCode, index: number, format: CreateAddressFormat | undefined @@ -61,24 +62,19 @@ export function generateAddressWithChainAndIndex( // Convert CreateAddressFormat to AddressFormat for wasm-utxo // 'base58' -> 'default', 'cashaddr' -> 'cashaddr' const wasmFormat = format === 'base58' ? 'default' : format; - return wasmUtxo.fixedScriptWallet.address( - keychains.map((k) => k.pub) as [string, string, string], - chain, - index, - network, - wasmFormat - ); + return wasmUtxo.fixedScriptWallet.address(keychains, chain, index, network, wasmFormat); + } + + if (!(keychains instanceof bitgo.RootWalletKeys)) { + const hdNodes = keychains.map((pub) => bip32.fromBase58(pub)); + keychains = new bitgo.RootWalletKeys(hdNodes as Triple); } - const path = '0/0/' + chain + '/' + index; - const hdNodes = keychains.map(({ pub }) => bip32.fromBase58(pub)); - const derivedKeys = hdNodes.map((hdNode) => hdNode.derivePath(sanitizeLegacyPath(path)).publicKey); const addressType = bitgo.scriptTypeForChain(chain); + const derivedKeys = keychains.deriveForChainAndIndex(chain, index).publicKeys; const { scriptPubKey: outputScript } = utxolib.bitgo.outputScripts.createOutputScript2of3(derivedKeys, addressType); - const address = utxolib.address.fromOutputScript(outputScript, network); - return canonicalAddress(network, address, format); } @@ -143,7 +139,17 @@ export function generateAddress(network: utxolib.Network, params: GenerateFixedS } } - return generateAddressWithChainAndIndex(network, keychains, derivationChain, derivationIndex, params.format); + if (!isTriple(keychains)) { + throw new Error('keychains must be a triple'); + } + + return generateAddressWithChainAndIndex( + network, + keychains.map((k) => k.pub) as Triple, + derivationChain, + derivationIndex, + params.format + ); } type Keychain = { diff --git a/modules/abstract-utxo/src/recovery/backupKeyRecovery.ts b/modules/abstract-utxo/src/recovery/backupKeyRecovery.ts index 0676158a7f..daaa2d0add 100644 --- a/modules/abstract-utxo/src/recovery/backupKeyRecovery.ts +++ b/modules/abstract-utxo/src/recovery/backupKeyRecovery.ts @@ -15,8 +15,9 @@ import { } from '@bitgo/sdk-core'; import { getMainnet, networks } from '@bitgo/utxo-lib'; -import { AbstractUtxoCoin, MultiSigAddress } from '../abstractUtxoCoin'; +import { AbstractUtxoCoin } from '../abstractUtxoCoin'; import { signAndVerifyPsbt } from '../sign'; +import { generateAddressWithChainAndIndex } from '../address'; import { forCoin, RecoveryProvider } from './RecoveryProvider'; import { MempoolApi } from './mempoolApi'; @@ -125,12 +126,27 @@ export interface RecoverParams { feeRate?: number; } -function getFormattedAddress(coin: AbstractUtxoCoin, address: MultiSigAddress) { - // Blockchair uses cashaddr format when querying the API for address information. Convert legacy addresses to cashaddr - // before querying the API. - return coin.getChain() === 'bch' || coin.getChain() === 'bcha' - ? coin.canonicalAddress(address.address, 'cashaddr').split(':')[1] - : address.address; +/** + * Generate an address and format it for API queries + * @param coin - The coin instance + * @param network - The network to use + * @param walletKeys - The wallet keys + * @param chain - The chain code + * @param addrIndex - The address index + * @returns The formatted address (with cashaddr prefix stripped for BCH/BCHA) + */ +function getFormattedAddress( + coin: AbstractUtxoCoin, + network: utxolib.Network, + walletKeys: RootWalletKeys, + chain: ChainCode, + addrIndex: number +): string { + const format = coin.getChain() === 'bch' || coin.getChain() === 'bcha' ? 'cashaddr' : undefined; + const address = generateAddressWithChainAndIndex(network, walletKeys, chain, addrIndex, format); + + // Blockchair uses cashaddr format when querying the API for address information. Strip the prefix for BCH/BCHA. + return format === 'cashaddr' ? address.split(':')[1] : address; } async function queryBlockchainUnspentsPath( @@ -157,10 +173,7 @@ async function queryBlockchainUnspentsPath( } async function gatherUnspents(addrIndex: number) { - const walletKeysForUnspent = walletKeys.deriveForChainAndIndex(chain, addrIndex); - const address = coin.createMultiSigAddress(scriptType, 2, walletKeysForUnspent.publicKeys); - - const formattedAddress = getFormattedAddress(coin, address); + const formattedAddress = getFormattedAddress(coin, coin.network, walletKeys, chain, addrIndex); const addrInfo = await recoveryProvider.getAddressInfo(formattedAddress); // we use txCount here because it implies usage - having tx'es means the addr was generated and used if (addrInfo.txCount === 0) { @@ -169,7 +182,7 @@ async function queryBlockchainUnspentsPath( numSequentialAddressesWithoutTxs = 0; if (addrInfo.balance > 0) { - console.log(`Found an address with balance: ${address.address} with balance ${addrInfo.balance}`); + console.log(`Found an address with balance: ${formattedAddress} with balance ${addrInfo.balance}`); const addressUnspents = await recoveryProvider.getUnspentsForAddresses([formattedAddress]); const processedUnspents = await Promise.all( addressUnspents.map(async (u): Promise> => { @@ -375,9 +388,9 @@ export async function backupKeyRecovery( const recoveryAmount = totalInputAmount - approximateFee - krsFee; if (recoveryAmount < BigInt(0)) { - throw new Error(`this wallet\'s balance is too low to pay the fees specified by the KRS provider. + throw new Error(`this wallet\'s balance is too low to pay the fees specified by the KRS provider. Existing balance on wallet: ${totalInputAmount.toString()}. Estimated network fee for the recovery transaction - : ${approximateFee.toString()}, KRS fee to pay: ${krsFee.toString()}. After deducting fees, your total + : ${approximateFee.toString()}, KRS fee to pay: ${krsFee.toString()}. After deducting fees, your total recoverable balance is ${recoveryAmount.toString()}`); }