@@ -5,13 +5,12 @@ import { BitGoBase, IWallet, Keychain, Triple, Wallet } from '@bitgo/sdk-core';
55import { decrypt } from '@bitgo/sdk-api' ;
66
77import { AbstractUtxoCoin , TransactionInfo } from '../abstractUtxoCoin' ;
8- import { signAndVerifyWalletTransaction } from '../transaction/fixedScript/signLegacyTransaction ' ;
8+ import { signAndVerifyPsbt } from '../transaction/fixedScript/signPsbt ' ;
99
10- const { unspentSum, scriptTypeForChain , outputScripts } = utxolib . bitgo ;
10+ const { unspentSum } = utxolib . bitgo ;
1111type RootWalletKeys = utxolib . bitgo . RootWalletKeys ;
1212type Unspent < TNumber extends number | bigint = number > = utxolib . bitgo . Unspent < TNumber > ;
1313type WalletUnspent < TNumber extends number | bigint = number > = utxolib . bitgo . WalletUnspent < TNumber > ;
14- type WalletUnspentLegacy < TNumber extends number | bigint = number > = utxolib . bitgo . WalletUnspentLegacy < TNumber > ;
1514
1615export interface BuildRecoveryTransactionOptions {
1716 wallet : string ;
@@ -28,17 +27,17 @@ type FeeInfo = {
2827
2928export interface CrossChainRecoveryUnsigned < TNumber extends number | bigint = number > {
3029 txHex : string ;
31- txInfo : TransactionInfo < TNumber > ;
30+ txInfo ? : TransactionInfo < TNumber > ;
3231 walletId : string ;
33- feeInfo : FeeInfo ;
32+ feeInfo ? : FeeInfo ;
3433 address : string ;
3534 coin : string ;
3635}
3736
3837export interface CrossChainRecoverySigned < TNumber extends number | bigint = number > {
3938 version : 1 | 2 ;
4039 txHex : string ;
41- txInfo : TransactionInfo < TNumber > ;
40+ txInfo ? : TransactionInfo < TNumber > ;
4241 walletId : string ;
4342 sourceCoin : string ;
4443 recoveryCoin : string ;
@@ -326,117 +325,51 @@ async function getPrv(xprv?: string, passphrase?: string, wallet?: IWallet | Wal
326325}
327326
328327/**
328+ * Create a sweep transaction for cross-chain recovery using PSBT
329329 * @param network
330+ * @param walletKeys
330331 * @param unspents
331332 * @param targetAddress
332333 * @param feeRateSatVB
333- * @param signer - if set, sign transaction
334- * @param amountType
335- * @return transaction spending full input amount to targetAddress
334+ * @return unsigned PSBT
336335 */
337336function createSweepTransaction < TNumber extends number | bigint = number > (
338337 network : utxolib . Network ,
338+ walletKeys : RootWalletKeys ,
339339 unspents : WalletUnspent < TNumber > [ ] ,
340340 targetAddress : string ,
341- feeRateSatVB : number ,
342- signer ?: utxolib . bitgo . WalletUnspentSigner < RootWalletKeys > ,
343- amountType : 'number' | 'bigint' = 'number'
344- ) : utxolib . bitgo . UtxoTransaction < TNumber > {
345- const inputValue = unspentSum < TNumber > ( unspents , amountType ) ;
341+ feeRateSatVB : number
342+ ) : utxolib . bitgo . UtxoPsbt {
343+ const inputValue = unspentSum < bigint > (
344+ unspents . map ( ( u ) => ( { ...u , value : BigInt ( u . value ) } ) ) ,
345+ 'bigint'
346+ ) ;
346347 const vsize = Dimensions . fromUnspents ( unspents , {
347348 p2tr : { scriptPathLevel : 1 } ,
348349 p2trMusig2 : { scriptPathLevel : undefined } ,
349350 } )
350351 . plus ( Dimensions . fromOutput ( { script : utxolib . address . toOutputScript ( targetAddress , network ) } ) )
351352 . getVSize ( ) ;
352- const fee = vsize * feeRateSatVB ;
353+ const fee = BigInt ( Math . round ( vsize * feeRateSatVB ) ) ;
354+
355+ const psbt = utxolib . bitgo . createPsbtForNetwork ( { network } ) ;
356+ utxolib . bitgo . addXpubsToPsbt ( psbt , walletKeys ) ;
353357
354- const transactionBuilder = utxolib . bitgo . createTransactionBuilderForNetwork < TNumber > ( network ) ;
355- transactionBuilder . addOutput (
356- targetAddress ,
357- utxolib . bitgo . toTNumber < TNumber > ( BigInt ( inputValue ) - BigInt ( fee ) , amountType )
358- ) ;
359358 unspents . forEach ( ( unspent ) => {
360- utxolib . bitgo . addToTransactionBuilder ( transactionBuilder , unspent ) ;
359+ utxolib . bitgo . addWalletUnspentToPsbt (
360+ psbt ,
361+ { ...unspent , value : BigInt ( unspent . value ) } ,
362+ walletKeys ,
363+ 'user' ,
364+ 'backup' ,
365+ { skipNonWitnessUtxo : true }
366+ ) ;
361367 } ) ;
362- let transaction = transactionBuilder . buildIncomplete ( ) ;
363- if ( signer ) {
364- transaction = signAndVerifyWalletTransaction < TNumber > ( transactionBuilder , unspents , signer , {
365- isLastSignature : false ,
366- } ) ;
367- }
368- return transaction ;
369- }
370368
371- function getTxInfo < TNumber extends number | bigint = number > (
372- transaction : utxolib . bitgo . UtxoTransaction < TNumber > ,
373- unspents : WalletUnspent < TNumber > [ ] ,
374- walletId : string ,
375- walletKeys : RootWalletKeys ,
376- amountType : 'number' | 'bigint' = 'number'
377- ) : TransactionInfo < TNumber > {
378- const inputAmount = utxolib . bitgo . unspentSum < TNumber > ( unspents , amountType ) ;
379- const outputAmount = utxolib . bitgo . toTNumber < TNumber > (
380- transaction . outs . reduce ( ( sum , o ) => sum + BigInt ( o . value ) , BigInt ( 0 ) ) ,
381- amountType
382- ) ;
383- const outputs = transaction . outs . map ( ( o ) => ( {
384- address : utxolib . address . fromOutputScript ( o . script , transaction . network ) ,
385- valueString : o . value . toString ( ) ,
386- change : false ,
387- } ) ) ;
388- const inputs = unspents . map ( ( u ) => {
389- // NOTE:
390- // The `redeemScript` and `walletScript` properties are required for legacy versions of BitGoJS
391- // which might require these scripts for signing. The Wallet Recovery Wizard (WRW) can create
392- // unsigned prebuilds that are submitted to BitGoJS instances which are not necessarily the same
393- // version.
394- const addressKeys = walletKeys . deriveForChainAndIndex ( u . chain , u . index ) ;
395- const scriptType = scriptTypeForChain ( u . chain ) ;
396- const { redeemScript, witnessScript } = outputScripts . createOutputScript2of3 ( addressKeys . publicKeys , scriptType ) ;
369+ const recoveryOutputScript = utxolib . address . toOutputScript ( targetAddress , network ) ;
370+ psbt . addOutput ( { script : recoveryOutputScript , value : inputValue - fee } ) ;
397371
398- return {
399- ...u ,
400- wallet : walletId ,
401- fromWallet : walletId ,
402- redeemScript : redeemScript ?. toString ( 'hex' ) ,
403- witnessScript : witnessScript ?. toString ( 'hex' ) ,
404- } as WalletUnspentLegacy < TNumber > ;
405- } ) ;
406- return {
407- inputAmount,
408- outputAmount,
409- minerFee : inputAmount - outputAmount ,
410- spendAmount : outputAmount ,
411- inputs,
412- unspents : inputs ,
413- outputs,
414- externalOutputs : outputs ,
415- changeOutputs : [ ] ,
416- payGoFee : 0 ,
417- } /* cast to TransactionInfo to allow extra fields may be required by legacy consumers of this data */ as TransactionInfo < TNumber > ;
418- }
419-
420- function getFeeInfo < TNumber extends number | bigint = number > (
421- transaction : utxolib . bitgo . UtxoTransaction < TNumber > ,
422- unspents : WalletUnspent < TNumber > [ ] ,
423- amountType : 'number' | 'bigint' = 'number'
424- ) : FeeInfo {
425- const vsize = Dimensions . fromUnspents ( unspents , {
426- p2tr : { scriptPathLevel : 1 } ,
427- p2trMusig2 : { scriptPathLevel : undefined } ,
428- } )
429- . plus ( Dimensions . fromOutputs ( transaction . outs ) )
430- . getVSize ( ) ;
431- const inputAmount = utxolib . bitgo . unspentSum < TNumber > ( unspents , amountType ) ;
432- const outputAmount = transaction . outs . reduce ( ( sum , o ) => sum + BigInt ( o . value ) , BigInt ( 0 ) ) ;
433- const fee = Number ( BigInt ( inputAmount ) - outputAmount ) ;
434- return {
435- size : vsize ,
436- fee,
437- feeRate : fee / vsize ,
438- payGoFee : 0 ,
439- } ;
372+ return psbt ;
440373}
441374
442375type RecoverParams = {
@@ -484,45 +417,37 @@ export async function recoverCrossChain<TNumber extends number | bigint = number
484417 const walletKeys = await getWalletKeys ( params . recoveryCoin , wallet ) ;
485418 const prv =
486419 params . xprv || params . walletPassphrase ? await getPrv ( params . xprv , params . walletPassphrase , wallet ) : undefined ;
487- const signer = prv
488- ? new utxolib . bitgo . WalletUnspentSigner < RootWalletKeys > ( walletKeys , prv , walletKeys . bitgo )
489- : undefined ;
490420 const feeRateSatVB = await getFeeRateSatVB ( params . sourceCoin ) ;
491- const transaction = createSweepTransaction < TNumber > (
421+
422+ // Create PSBT for both signed and unsigned recovery
423+ const psbt = createSweepTransaction < TNumber > (
492424 params . sourceCoin . network ,
425+ walletKeys ,
493426 walletUnspents ,
494427 params . recoveryAddress ,
495- feeRateSatVB ,
496- signer ,
497- params . sourceCoin . amountType
498- ) ;
499- const recoveryAmount = transaction . outs [ 0 ] . value ;
500- const txHex = transaction . toBuffer ( ) . toString ( 'hex' ) ;
501- const txInfo = getTxInfo < TNumber > (
502- transaction ,
503- walletUnspents ,
504- params . walletId ,
505- walletKeys ,
506- params . sourceCoin . amountType
428+ feeRateSatVB
507429 ) ;
508- if ( prv ) {
509- return {
510- version : wallet instanceof Wallet ? 2 : 1 ,
511- walletId : params . walletId ,
512- txHex,
513- txInfo,
514- sourceCoin : params . sourceCoin . getChain ( ) ,
515- recoveryCoin : params . recoveryCoin . getChain ( ) ,
516- recoveryAmount,
517- } ;
518- } else {
430+
431+ // For unsigned recovery, return unsigned PSBT hex
432+ if ( ! prv ) {
519433 return {
520- txHex,
521- txInfo,
434+ txHex : psbt . toHex ( ) ,
522435 walletId : params . walletId ,
523- feeInfo : getFeeInfo ( transaction , walletUnspents , params . sourceCoin . amountType ) ,
524436 address : params . recoveryAddress ,
525437 coin : params . sourceCoin . getChain ( ) ,
526438 } ;
527439 }
440+
441+ // For signed recovery, sign the PSBT with user key and return half-signed PSBT
442+ signAndVerifyPsbt ( psbt , prv , { isLastSignature : false } ) ;
443+ const recoveryAmount = utxolib . bitgo . toTNumber < TNumber > ( psbt . txOutputs [ 0 ] . value , params . sourceCoin . amountType ) ;
444+
445+ return {
446+ version : wallet instanceof Wallet ? 2 : 1 ,
447+ walletId : params . walletId ,
448+ txHex : psbt . toHex ( ) ,
449+ sourceCoin : params . sourceCoin . getChain ( ) ,
450+ recoveryCoin : params . recoveryCoin . getChain ( ) ,
451+ recoveryAmount,
452+ } ;
528453}
0 commit comments