Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 52 additions & 127 deletions modules/abstract-utxo/src/recovery/crossChainRecovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ import { BitGoBase, IWallet, Keychain, Triple, Wallet } from '@bitgo/sdk-core';
import { decrypt } from '@bitgo/sdk-api';

import { AbstractUtxoCoin, TransactionInfo } from '../abstractUtxoCoin';
import { signAndVerifyWalletTransaction } from '../transaction/fixedScript/signLegacyTransaction';
import { signAndVerifyPsbt } from '../transaction/fixedScript/signPsbt';

const { unspentSum, scriptTypeForChain, outputScripts } = utxolib.bitgo;
const { unspentSum } = utxolib.bitgo;
type RootWalletKeys = utxolib.bitgo.RootWalletKeys;
type Unspent<TNumber extends number | bigint = number> = utxolib.bitgo.Unspent<TNumber>;
type WalletUnspent<TNumber extends number | bigint = number> = utxolib.bitgo.WalletUnspent<TNumber>;
type WalletUnspentLegacy<TNumber extends number | bigint = number> = utxolib.bitgo.WalletUnspentLegacy<TNumber>;

export interface BuildRecoveryTransactionOptions {
wallet: string;
Expand All @@ -28,17 +27,17 @@ type FeeInfo = {

export interface CrossChainRecoveryUnsigned<TNumber extends number | bigint = number> {
txHex: string;
txInfo: TransactionInfo<TNumber>;
txInfo?: TransactionInfo<TNumber>;
walletId: string;
feeInfo: FeeInfo;
feeInfo?: FeeInfo;
address: string;
coin: string;
}

export interface CrossChainRecoverySigned<TNumber extends number | bigint = number> {
version: 1 | 2;
txHex: string;
txInfo: TransactionInfo<TNumber>;
txInfo?: TransactionInfo<TNumber>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we might to fully get rid of these

walletId: string;
sourceCoin: string;
recoveryCoin: string;
Expand Down Expand Up @@ -326,117 +325,51 @@ async function getPrv(xprv?: string, passphrase?: string, wallet?: IWallet | Wal
}

/**
* Create a sweep transaction for cross-chain recovery using PSBT
* @param network
* @param walletKeys
* @param unspents
* @param targetAddress
* @param feeRateSatVB
* @param signer - if set, sign transaction
* @param amountType
* @return transaction spending full input amount to targetAddress
* @return unsigned PSBT
*/
function createSweepTransaction<TNumber extends number | bigint = number>(
network: utxolib.Network,
walletKeys: RootWalletKeys,
unspents: WalletUnspent<TNumber>[],
targetAddress: string,
feeRateSatVB: number,
signer?: utxolib.bitgo.WalletUnspentSigner<RootWalletKeys>,
amountType: 'number' | 'bigint' = 'number'
): utxolib.bitgo.UtxoTransaction<TNumber> {
const inputValue = unspentSum<TNumber>(unspents, amountType);
feeRateSatVB: number
): utxolib.bitgo.UtxoPsbt {
const inputValue = unspentSum<bigint>(
unspents.map((u) => ({ ...u, value: BigInt(u.value) })),
'bigint'
);
const vsize = Dimensions.fromUnspents(unspents, {
p2tr: { scriptPathLevel: 1 },
p2trMusig2: { scriptPathLevel: undefined },
})
.plus(Dimensions.fromOutput({ script: utxolib.address.toOutputScript(targetAddress, network) }))
.getVSize();
const fee = vsize * feeRateSatVB;
const fee = BigInt(Math.round(vsize * feeRateSatVB));

const psbt = utxolib.bitgo.createPsbtForNetwork({ network });
utxolib.bitgo.addXpubsToPsbt(psbt, walletKeys);

const transactionBuilder = utxolib.bitgo.createTransactionBuilderForNetwork<TNumber>(network);
transactionBuilder.addOutput(
targetAddress,
utxolib.bitgo.toTNumber<TNumber>(BigInt(inputValue) - BigInt(fee), amountType)
);
unspents.forEach((unspent) => {
utxolib.bitgo.addToTransactionBuilder(transactionBuilder, unspent);
utxolib.bitgo.addWalletUnspentToPsbt(
psbt,
{ ...unspent, value: BigInt(unspent.value) },
walletKeys,
'user',
'backup',
{ skipNonWitnessUtxo: true }
);
});
let transaction = transactionBuilder.buildIncomplete();
if (signer) {
transaction = signAndVerifyWalletTransaction<TNumber>(transactionBuilder, unspents, signer, {
isLastSignature: false,
});
}
return transaction;
}

function getTxInfo<TNumber extends number | bigint = number>(
transaction: utxolib.bitgo.UtxoTransaction<TNumber>,
unspents: WalletUnspent<TNumber>[],
walletId: string,
walletKeys: RootWalletKeys,
amountType: 'number' | 'bigint' = 'number'
): TransactionInfo<TNumber> {
const inputAmount = utxolib.bitgo.unspentSum<TNumber>(unspents, amountType);
const outputAmount = utxolib.bitgo.toTNumber<TNumber>(
transaction.outs.reduce((sum, o) => sum + BigInt(o.value), BigInt(0)),
amountType
);
const outputs = transaction.outs.map((o) => ({
address: utxolib.address.fromOutputScript(o.script, transaction.network),
valueString: o.value.toString(),
change: false,
}));
const inputs = unspents.map((u) => {
// NOTE:
// The `redeemScript` and `walletScript` properties are required for legacy versions of BitGoJS
// which might require these scripts for signing. The Wallet Recovery Wizard (WRW) can create
// unsigned prebuilds that are submitted to BitGoJS instances which are not necessarily the same
// version.
const addressKeys = walletKeys.deriveForChainAndIndex(u.chain, u.index);
const scriptType = scriptTypeForChain(u.chain);
const { redeemScript, witnessScript } = outputScripts.createOutputScript2of3(addressKeys.publicKeys, scriptType);
const recoveryOutputScript = utxolib.address.toOutputScript(targetAddress, network);
psbt.addOutput({ script: recoveryOutputScript, value: inputValue - fee });

return {
...u,
wallet: walletId,
fromWallet: walletId,
redeemScript: redeemScript?.toString('hex'),
witnessScript: witnessScript?.toString('hex'),
} as WalletUnspentLegacy<TNumber>;
});
return {
inputAmount,
outputAmount,
minerFee: inputAmount - outputAmount,
spendAmount: outputAmount,
inputs,
unspents: inputs,
outputs,
externalOutputs: outputs,
changeOutputs: [],
payGoFee: 0,
} /* cast to TransactionInfo to allow extra fields may be required by legacy consumers of this data */ as TransactionInfo<TNumber>;
}

function getFeeInfo<TNumber extends number | bigint = number>(
transaction: utxolib.bitgo.UtxoTransaction<TNumber>,
unspents: WalletUnspent<TNumber>[],
amountType: 'number' | 'bigint' = 'number'
): FeeInfo {
const vsize = Dimensions.fromUnspents(unspents, {
p2tr: { scriptPathLevel: 1 },
p2trMusig2: { scriptPathLevel: undefined },
})
.plus(Dimensions.fromOutputs(transaction.outs))
.getVSize();
const inputAmount = utxolib.bitgo.unspentSum<TNumber>(unspents, amountType);
const outputAmount = transaction.outs.reduce((sum, o) => sum + BigInt(o.value), BigInt(0));
const fee = Number(BigInt(inputAmount) - outputAmount);
return {
size: vsize,
fee,
feeRate: fee / vsize,
payGoFee: 0,
};
return psbt;
}

type RecoverParams = {
Expand Down Expand Up @@ -484,45 +417,37 @@ export async function recoverCrossChain<TNumber extends number | bigint = number
const walletKeys = await getWalletKeys(params.recoveryCoin, wallet);
const prv =
params.xprv || params.walletPassphrase ? await getPrv(params.xprv, params.walletPassphrase, wallet) : undefined;
const signer = prv
? new utxolib.bitgo.WalletUnspentSigner<RootWalletKeys>(walletKeys, prv, walletKeys.bitgo)
: undefined;
const feeRateSatVB = await getFeeRateSatVB(params.sourceCoin);
const transaction = createSweepTransaction<TNumber>(

// Create PSBT for both signed and unsigned recovery
const psbt = createSweepTransaction<TNumber>(
params.sourceCoin.network,
walletKeys,
walletUnspents,
params.recoveryAddress,
feeRateSatVB,
signer,
params.sourceCoin.amountType
);
const recoveryAmount = transaction.outs[0].value;
const txHex = transaction.toBuffer().toString('hex');
const txInfo = getTxInfo<TNumber>(
transaction,
walletUnspents,
params.walletId,
walletKeys,
params.sourceCoin.amountType
feeRateSatVB
);
if (prv) {
return {
version: wallet instanceof Wallet ? 2 : 1,
walletId: params.walletId,
txHex,
txInfo,
sourceCoin: params.sourceCoin.getChain(),
recoveryCoin: params.recoveryCoin.getChain(),
recoveryAmount,
};
} else {

// For unsigned recovery, return unsigned PSBT hex
if (!prv) {
return {
txHex,
txInfo,
txHex: psbt.toHex(),
walletId: params.walletId,
feeInfo: getFeeInfo(transaction, walletUnspents, params.sourceCoin.amountType),
address: params.recoveryAddress,
coin: params.sourceCoin.getChain(),
};
}

// For signed recovery, sign the PSBT with user key and return half-signed PSBT
signAndVerifyPsbt(psbt, prv, { isLastSignature: false });
const recoveryAmount = utxolib.bitgo.toTNumber<TNumber>(psbt.txOutputs[0].value, params.sourceCoin.amountType);

return {
version: wallet instanceof Wallet ? 2 : 1,
walletId: params.walletId,
txHex: psbt.toHex(),
sourceCoin: params.sourceCoin.getChain(),
recoveryCoin: params.recoveryCoin.getChain(),
recoveryAmount,
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"version": 2,
"walletId": "5abacebe28d72fbd07e0b8cbba0ff39e",
"txHex": "70736274ff0100530200000001526eda15b11314f6ca3ee949a43e3d0e045eb86cdb38515c573359fcdac94ce60000000000ffffffff0188c2f5050000000017a9149c4525e9e9fc92cdda2043d35ad699c343dbab0f87000000004f010488b21e0000000000000000004b256d3cf3524c8d7086e295a1923d6fa2f99b686699ed50084bb114495c982403a86864862a9e315221809501f2a4200cd9e057a70f9164d485d4cfbeb8e47c74048374ad864f010488b21e000000000000000000914cc440157319de14126a1a2e87ea86f3b983f923fb17693a157b721220d74c02e81e105716179975cc47afd117cae272519aafdd6bfff688e4280d384e13184f04e15f6f214f010488b21e000000000000000000da28679577f7faf0ed86164da220aa4a29c7edfb0de8bdabd97f19fd15e74bed03db2b42af97f60db6ec5a1500e246ef2107660c4fc02699ed69b82c2f3e9324ae0403a823910001012018ddf5050000000017a9141e57a925dd863a86af341037e700862bf66bf7b6872202037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e47483045022100baffdbce5b48a4744604041d3ac2ca021cad8124fb0779aa20e329d66d02fa500220381a4afa1d142a7ade7799d779e8b5216ec5ab9852ac79ae2773335a47cb510741010304410000000104695221037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e472102658831a87322b3583515ca8725841335505755ada53ee133c70a6b4b8d3978702102641ee6557561c9038242cafa7f538070d7646a969bcf6169f9950abfcfefd6b853ae220602641ee6557561c9038242cafa7f538070d7646a969bcf6169f9950abfcfefd6b8148374ad8600000000000000000000000000000000220602658831a87322b3583515ca8725841335505755ada53ee133c70a6b4b8d39787014e15f6f21000000000000000000000000000000002206037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e471403a82391000000000000000000000000000000000000",
"sourceCoin": "bch",
"recoveryCoin": "bsv",
"recoveryAmount": 99992200
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"txHex": "70736274ff0100530200000001526eda15b11314f6ca3ee949a43e3d0e045eb86cdb38515c573359fcdac94ce60000000000ffffffff0188c2f5050000000017a9149c4525e9e9fc92cdda2043d35ad699c343dbab0f87000000004f010488b21e0000000000000000004b256d3cf3524c8d7086e295a1923d6fa2f99b686699ed50084bb114495c982403a86864862a9e315221809501f2a4200cd9e057a70f9164d485d4cfbeb8e47c74048374ad864f010488b21e000000000000000000914cc440157319de14126a1a2e87ea86f3b983f923fb17693a157b721220d74c02e81e105716179975cc47afd117cae272519aafdd6bfff688e4280d384e13184f04e15f6f214f010488b21e000000000000000000da28679577f7faf0ed86164da220aa4a29c7edfb0de8bdabd97f19fd15e74bed03db2b42af97f60db6ec5a1500e246ef2107660c4fc02699ed69b82c2f3e9324ae0403a823910001012018ddf5050000000017a9141e57a925dd863a86af341037e700862bf66bf7b687010304410000000104695221037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e472102658831a87322b3583515ca8725841335505755ada53ee133c70a6b4b8d3978702102641ee6557561c9038242cafa7f538070d7646a969bcf6169f9950abfcfefd6b853ae220602641ee6557561c9038242cafa7f538070d7646a969bcf6169f9950abfcfefd6b8148374ad8600000000000000000000000000000000220602658831a87322b3583515ca8725841335505755ada53ee133c70a6b4b8d39787014e15f6f21000000000000000000000000000000002206037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e471403a82391000000000000000000000000000000000000",
"walletId": "5abacebe28d72fbd07e0b8cbba0ff39e",
"address": "3FwJAxqdqfhe4esUv4smhM3zzbE3KAyD88",
"coin": "bch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"version": 2,
"walletId": "5abacebe28d72fbd07e0b8cbba0ff39e",
"txHex": "70736274ff0100530200000001526eda15b11314f6ca3ee949a43e3d0e045eb86cdb38515c573359fcdac94ce60000000000ffffffff0188c2f5050000000017a9149c4525e9e9fc92cdda2043d35ad699c343dbab0f87000000004f010488b21e0000000000000000004b256d3cf3524c8d7086e295a1923d6fa2f99b686699ed50084bb114495c982403a86864862a9e315221809501f2a4200cd9e057a70f9164d485d4cfbeb8e47c74048374ad864f010488b21e000000000000000000914cc440157319de14126a1a2e87ea86f3b983f923fb17693a157b721220d74c02e81e105716179975cc47afd117cae272519aafdd6bfff688e4280d384e13184f04e15f6f214f010488b21e000000000000000000da28679577f7faf0ed86164da220aa4a29c7edfb0de8bdabd97f19fd15e74bed03db2b42af97f60db6ec5a1500e246ef2107660c4fc02699ed69b82c2f3e9324ae0403a823910001012018ddf5050000000017a9141e57a925dd863a86af341037e700862bf66bf7b6872202037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e47483045022100baffdbce5b48a4744604041d3ac2ca021cad8124fb0779aa20e329d66d02fa500220381a4afa1d142a7ade7799d779e8b5216ec5ab9852ac79ae2773335a47cb510741010304410000000104695221037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e472102658831a87322b3583515ca8725841335505755ada53ee133c70a6b4b8d3978702102641ee6557561c9038242cafa7f538070d7646a969bcf6169f9950abfcfefd6b853ae220602641ee6557561c9038242cafa7f538070d7646a969bcf6169f9950abfcfefd6b8148374ad8600000000000000000000000000000000220602658831a87322b3583515ca8725841335505755ada53ee133c70a6b4b8d39787014e15f6f21000000000000000000000000000000002206037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e471403a82391000000000000000000000000000000000000",
"sourceCoin": "bch",
"recoveryCoin": "btc",
"recoveryAmount": 99992200
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"txHex": "70736274ff0100530200000001526eda15b11314f6ca3ee949a43e3d0e045eb86cdb38515c573359fcdac94ce60000000000ffffffff0188c2f5050000000017a9149c4525e9e9fc92cdda2043d35ad699c343dbab0f87000000004f010488b21e0000000000000000004b256d3cf3524c8d7086e295a1923d6fa2f99b686699ed50084bb114495c982403a86864862a9e315221809501f2a4200cd9e057a70f9164d485d4cfbeb8e47c74048374ad864f010488b21e000000000000000000914cc440157319de14126a1a2e87ea86f3b983f923fb17693a157b721220d74c02e81e105716179975cc47afd117cae272519aafdd6bfff688e4280d384e13184f04e15f6f214f010488b21e000000000000000000da28679577f7faf0ed86164da220aa4a29c7edfb0de8bdabd97f19fd15e74bed03db2b42af97f60db6ec5a1500e246ef2107660c4fc02699ed69b82c2f3e9324ae0403a823910001012018ddf5050000000017a9141e57a925dd863a86af341037e700862bf66bf7b687010304410000000104695221037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e472102658831a87322b3583515ca8725841335505755ada53ee133c70a6b4b8d3978702102641ee6557561c9038242cafa7f538070d7646a969bcf6169f9950abfcfefd6b853ae220602641ee6557561c9038242cafa7f538070d7646a969bcf6169f9950abfcfefd6b8148374ad8600000000000000000000000000000000220602658831a87322b3583515ca8725841335505755ada53ee133c70a6b4b8d39787014e15f6f21000000000000000000000000000000002206037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e471403a82391000000000000000000000000000000000000",
"walletId": "5abacebe28d72fbd07e0b8cbba0ff39e",
"address": "3FwJAxqdqfhe4esUv4smhM3zzbE3KAyD88",
"coin": "bch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"version": 2,
"walletId": "5abacebe28d72fbd07e0b8cbba0ff39e",
"txHex": "70736274ff0100530200000001526eda15b11314f6ca3ee949a43e3d0e045eb86cdb38515c573359fcdac94ce60000000000ffffffff0188c2f5050000000017a9149c4525e9e9fc92cdda2043d35ad699c343dbab0f87000000004f010488b21e0000000000000000004b256d3cf3524c8d7086e295a1923d6fa2f99b686699ed50084bb114495c982403a86864862a9e315221809501f2a4200cd9e057a70f9164d485d4cfbeb8e47c74048374ad864f010488b21e000000000000000000914cc440157319de14126a1a2e87ea86f3b983f923fb17693a157b721220d74c02e81e105716179975cc47afd117cae272519aafdd6bfff688e4280d384e13184f04e15f6f214f010488b21e000000000000000000da28679577f7faf0ed86164da220aa4a29c7edfb0de8bdabd97f19fd15e74bed03db2b42af97f60db6ec5a1500e246ef2107660c4fc02699ed69b82c2f3e9324ae0403a823910001012018ddf5050000000017a9141e57a925dd863a86af341037e700862bf66bf7b6872202037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e47483045022100baffdbce5b48a4744604041d3ac2ca021cad8124fb0779aa20e329d66d02fa500220381a4afa1d142a7ade7799d779e8b5216ec5ab9852ac79ae2773335a47cb510741010304410000000104695221037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e472102658831a87322b3583515ca8725841335505755ada53ee133c70a6b4b8d3978702102641ee6557561c9038242cafa7f538070d7646a969bcf6169f9950abfcfefd6b853ae220602641ee6557561c9038242cafa7f538070d7646a969bcf6169f9950abfcfefd6b8148374ad8600000000000000000000000000000000220602658831a87322b3583515ca8725841335505755ada53ee133c70a6b4b8d39787014e15f6f21000000000000000000000000000000002206037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e471403a82391000000000000000000000000000000000000",
"sourceCoin": "bch",
"recoveryCoin": "doge",
"recoveryAmount": 99992200
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"txHex": "70736274ff0100530200000001526eda15b11314f6ca3ee949a43e3d0e045eb86cdb38515c573359fcdac94ce60000000000ffffffff0188c2f5050000000017a9149c4525e9e9fc92cdda2043d35ad699c343dbab0f87000000004f010488b21e0000000000000000004b256d3cf3524c8d7086e295a1923d6fa2f99b686699ed50084bb114495c982403a86864862a9e315221809501f2a4200cd9e057a70f9164d485d4cfbeb8e47c74048374ad864f010488b21e000000000000000000914cc440157319de14126a1a2e87ea86f3b983f923fb17693a157b721220d74c02e81e105716179975cc47afd117cae272519aafdd6bfff688e4280d384e13184f04e15f6f214f010488b21e000000000000000000da28679577f7faf0ed86164da220aa4a29c7edfb0de8bdabd97f19fd15e74bed03db2b42af97f60db6ec5a1500e246ef2107660c4fc02699ed69b82c2f3e9324ae0403a823910001012018ddf5050000000017a9141e57a925dd863a86af341037e700862bf66bf7b687010304410000000104695221037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e472102658831a87322b3583515ca8725841335505755ada53ee133c70a6b4b8d3978702102641ee6557561c9038242cafa7f538070d7646a969bcf6169f9950abfcfefd6b853ae220602641ee6557561c9038242cafa7f538070d7646a969bcf6169f9950abfcfefd6b8148374ad8600000000000000000000000000000000220602658831a87322b3583515ca8725841335505755ada53ee133c70a6b4b8d39787014e15f6f21000000000000000000000000000000002206037acffd52bb7c39a4ac3d4c01af33ce0367afec45347e332edca63a38d1fb2e471403a82391000000000000000000000000000000000000",
"walletId": "5abacebe28d72fbd07e0b8cbba0ff39e",
"address": "3FwJAxqdqfhe4esUv4smhM3zzbE3KAyD88",
"coin": "bch"
}
Loading