@@ -3,10 +3,18 @@ import { BIP32Interface, bip32 } from '@bitgo/secp256k1';
33import { Dimensions } from '@bitgo/unspents' ;
44import { BitGoBase , IWallet , Keychain , Triple , Wallet } from '@bitgo/sdk-core' ;
55import { decrypt } from '@bitgo/sdk-api' ;
6+ import { fixedScriptWallet , utxolibCompat } from '@bitgo/wasm-utxo' ;
67
78import { AbstractUtxoCoin , TransactionInfo } from '../abstractUtxoCoin' ;
89import { signAndVerifyPsbt } from '../transaction/fixedScript/signPsbt' ;
910
11+ /**
12+ * Backend to use for PSBT creation.
13+ * - 'wasm-utxo': Use wasm-utxo for PSBT creation (default)
14+ * - 'utxolib': Use utxolib for PSBT creation (legacy)
15+ */
16+ export type PsbtBackend = 'wasm-utxo' | 'utxolib' ;
17+
1018const { unspentSum } = utxolib . bitgo ;
1119type RootWalletKeys = utxolib . bitgo . RootWalletKeys ;
1220type Unspent < TNumber extends number | bigint = number > = utxolib . bitgo . Unspent < TNumber > ;
@@ -324,16 +332,40 @@ async function getPrv(xprv?: string, passphrase?: string, wallet?: IWallet | Wal
324332 return getPrv ( decrypt ( passphrase , encryptedPrv ) ) ;
325333}
326334
335+ const { chainCodesP2tr, chainCodesP2trMusig2 } = utxolib . bitgo ;
336+
337+ type ChainCode = utxolib . bitgo . ChainCode ;
338+
327339/**
328- * Create a sweep transaction for cross-chain recovery using PSBT
340+ * Check if a chain code is for a taproot script type
341+ */
342+ function isTaprootChain ( chain : ChainCode ) : boolean {
343+ return (
344+ ( chainCodesP2tr as readonly number [ ] ) . includes ( chain ) || ( chainCodesP2trMusig2 as readonly number [ ] ) . includes ( chain )
345+ ) ;
346+ }
347+
348+ /**
349+ * Convert utxolib Network to wasm-utxo network name
350+ */
351+ function toNetworkName ( network : utxolib . Network ) : utxolibCompat . UtxolibName {
352+ const networkName = utxolib . getNetworkName ( network ) ;
353+ if ( ! networkName ) {
354+ throw new Error ( `Invalid network` ) ;
355+ }
356+ return networkName ;
357+ }
358+
359+ /**
360+ * Create a sweep transaction for cross-chain recovery using PSBT (utxolib implementation)
329361 * @param network
330362 * @param walletKeys
331363 * @param unspents
332364 * @param targetAddress
333365 * @param feeRateSatVB
334366 * @return unsigned PSBT
335367 */
336- function createSweepTransaction < TNumber extends number | bigint = number > (
368+ function createSweepTransactionUtxolib < TNumber extends number | bigint = number > (
337369 network : utxolib . Network ,
338370 walletKeys : RootWalletKeys ,
339371 unspents : WalletUnspent < TNumber > [ ] ,
@@ -372,6 +404,91 @@ function createSweepTransaction<TNumber extends number | bigint = number>(
372404 return psbt ;
373405}
374406
407+ /**
408+ * Create a sweep transaction for cross-chain recovery using wasm-utxo
409+ * @param network
410+ * @param walletKeys
411+ * @param unspents
412+ * @param targetAddress
413+ * @param feeRateSatVB
414+ * @return unsigned PSBT
415+ */
416+ function createSweepTransactionWasm < TNumber extends number | bigint = number > (
417+ network : utxolib . Network ,
418+ walletKeys : RootWalletKeys ,
419+ unspents : WalletUnspent < TNumber > [ ] ,
420+ targetAddress : string ,
421+ feeRateSatVB : number
422+ ) : utxolib . bitgo . UtxoPsbt {
423+ const inputValue = unspentSum < bigint > (
424+ unspents . map ( ( u ) => ( { ...u , value : BigInt ( u . value ) } ) ) ,
425+ 'bigint'
426+ ) ;
427+
428+ const networkName = toNetworkName ( network ) ;
429+
430+ // Create PSBT with wasm-utxo and add wallet inputs
431+ const wasmPsbt = fixedScriptWallet . BitGoPsbt . createEmpty ( networkName , walletKeys ) ;
432+
433+ unspents . forEach ( ( unspent ) => {
434+ const { txid, vout } = utxolib . bitgo . parseOutputId ( unspent . id ) ;
435+ const signPath : fixedScriptWallet . SignPath | undefined = isTaprootChain ( unspent . chain )
436+ ? { signer : 'user' , cosigner : 'backup' }
437+ : undefined ;
438+
439+ wasmPsbt . addWalletInput (
440+ {
441+ txid,
442+ vout,
443+ value : BigInt ( unspent . value ) ,
444+ } ,
445+ walletKeys ,
446+ {
447+ scriptId : { chain : unspent . chain , index : unspent . index } ,
448+ signPath,
449+ }
450+ ) ;
451+ } ) ;
452+
453+ // Convert wasm-utxo PSBT to utxolib PSBT for dimension calculation and output addition
454+ const psbt = utxolib . bitgo . createPsbtFromBuffer ( Buffer . from ( wasmPsbt . serialize ( ) ) , network ) ;
455+
456+ const vsize = Dimensions . fromPsbt ( psbt )
457+ . plus ( Dimensions . fromOutput ( { script : utxolib . address . toOutputScript ( targetAddress , network ) } ) )
458+ . getVSize ( ) ;
459+ const fee = BigInt ( Math . round ( vsize * feeRateSatVB ) ) ;
460+
461+ const recoveryOutputScript = utxolib . address . toOutputScript ( targetAddress , network ) ;
462+ psbt . addOutput ( { script : recoveryOutputScript , value : inputValue - fee } ) ;
463+
464+ return psbt ;
465+ }
466+
467+ /**
468+ * Create a sweep transaction for cross-chain recovery using PSBT
469+ * @param network
470+ * @param walletKeys
471+ * @param unspents
472+ * @param targetAddress
473+ * @param feeRateSatVB
474+ * @param backend - Which backend to use for PSBT creation (default: 'wasm-utxo')
475+ * @return unsigned PSBT
476+ */
477+ function createSweepTransaction < TNumber extends number | bigint = number > (
478+ network : utxolib . Network ,
479+ walletKeys : RootWalletKeys ,
480+ unspents : WalletUnspent < TNumber > [ ] ,
481+ targetAddress : string ,
482+ feeRateSatVB : number ,
483+ backend : PsbtBackend = 'wasm-utxo'
484+ ) : utxolib . bitgo . UtxoPsbt {
485+ if ( backend === 'wasm-utxo' ) {
486+ return createSweepTransactionWasm ( network , walletKeys , unspents , targetAddress , feeRateSatVB ) ;
487+ } else {
488+ return createSweepTransactionUtxolib ( network , walletKeys , unspents , targetAddress , feeRateSatVB ) ;
489+ }
490+ }
491+
375492type RecoverParams = {
376493 /** Wallet ID (can be v1 wallet or v2 wallet) */
377494 walletId : string ;
@@ -420,12 +537,15 @@ export async function recoverCrossChain<TNumber extends number | bigint = number
420537 const feeRateSatVB = await getFeeRateSatVB ( params . sourceCoin ) ;
421538
422539 // Create PSBT for both signed and unsigned recovery
540+ // Use wasm-utxo for testnet coins only, utxolib for mainnet
541+ const backend : PsbtBackend = utxolib . isTestnet ( params . sourceCoin . network ) ? 'wasm-utxo' : 'utxolib' ;
423542 const psbt = createSweepTransaction < TNumber > (
424543 params . sourceCoin . network ,
425544 walletKeys ,
426545 walletUnspents ,
427546 params . recoveryAddress ,
428- feeRateSatVB
547+ feeRateSatVB ,
548+ backend
429549 ) ;
430550
431551 // For unsigned recovery, return unsigned PSBT hex
0 commit comments