@@ -4,6 +4,7 @@ import { EXPORT_IN_P as testData } from '../../resources/transactionData/exportI
44import { TransactionBuilderFactory } from '../../../src/lib' ;
55import { coins } from '@bitgo/statics' ;
66import signFlowTest from './signFlowTestSuit' ;
7+ import { pvmSerial , UnsignedTx , TransferOutput } from '@flarenetwork/flarejs' ;
78
89describe ( 'Flrp Export In P Tx Builder' , ( ) => {
910 const coinConfig = coins . get ( 'tflrp' ) ;
@@ -364,4 +365,141 @@ describe('Flrp Export In P Tx Builder', () => {
364365 fullSignedTx . toBroadcastFormat ( ) . should . be . a . String ( ) ;
365366 } ) ;
366367 } ) ;
368+
369+ describe ( 'Change output threshold fix' , ( ) => {
370+ /**
371+ * This test suite verifies the fix for the change output threshold bug.
372+ *
373+ * The issue: FlareJS's pvm.e.newExportTx() defaults change outputs to threshold=1,
374+ * but for multisig wallets we need threshold=2 to maintain proper security.
375+ *
376+ * The fix: After building the transaction, we correct the change outputs to use
377+ * the wallet's threshold (typically 2 for multisig).
378+ */
379+
380+ it ( 'should create change output with threshold=2 for multisig wallets' , async ( ) => {
381+ const txBuilder = factory
382+ . getExportInPBuilder ( )
383+ . threshold ( testData . threshold )
384+ . locktime ( testData . locktime )
385+ . fromPubKey ( testData . pAddresses )
386+ . amount ( testData . amount )
387+ . externalChainId ( testData . sourceChainId )
388+ . feeState ( testData . feeState )
389+ . context ( testData . context )
390+ . decodedUtxos ( testData . utxos ) ;
391+
392+ const tx = await txBuilder . build ( ) ;
393+
394+ const flareTransaction = ( tx as any ) . _flareTransaction as UnsignedTx ;
395+ const innerTx = flareTransaction . getTx ( ) as pvmSerial . ExportTx ;
396+ const changeOutputs = innerTx . baseTx . outputs ;
397+
398+ changeOutputs . length . should . be . greaterThan ( 0 ) ;
399+
400+ changeOutputs . forEach ( ( output , index ) => {
401+ const transferOut = output . output as TransferOutput ;
402+ const threshold = transferOut . outputOwners . threshold . value ( ) ;
403+
404+ threshold . should . equal (
405+ testData . threshold ,
406+ `Change output ${ index } should have threshold=${ testData . threshold } , but has threshold=${ threshold } `
407+ ) ;
408+ } ) ;
409+ } ) ;
410+
411+ it ( 'should create change output with correct locktime for multisig wallets' , async ( ) => {
412+ const txBuilder = factory
413+ . getExportInPBuilder ( )
414+ . threshold ( testData . threshold )
415+ . locktime ( testData . locktime )
416+ . fromPubKey ( testData . pAddresses )
417+ . amount ( testData . amount )
418+ . externalChainId ( testData . sourceChainId )
419+ . feeState ( testData . feeState )
420+ . context ( testData . context )
421+ . decodedUtxos ( testData . utxos ) ;
422+
423+ const tx = await txBuilder . build ( ) ;
424+
425+ const flareTransaction = ( tx as any ) . _flareTransaction as UnsignedTx ;
426+ const innerTx = flareTransaction . getTx ( ) as pvmSerial . ExportTx ;
427+ const changeOutputs = innerTx . baseTx . outputs ;
428+
429+ changeOutputs . length . should . be . greaterThan ( 0 ) ;
430+
431+ changeOutputs . forEach ( ( output , index ) => {
432+ const transferOut = output . output as TransferOutput ;
433+ const locktime = transferOut . outputOwners . locktime . value ( ) ;
434+
435+ locktime . should . equal (
436+ BigInt ( testData . locktime ) ,
437+ `Change output ${ index } should have locktime=${ testData . locktime } , but has locktime=${ locktime } `
438+ ) ;
439+ } ) ;
440+ } ) ;
441+
442+ it ( 'should maintain threshold=2 after parsing and rebuilding' , async ( ) => {
443+ const txBuilder = factory
444+ . getExportInPBuilder ( )
445+ . threshold ( testData . threshold )
446+ . locktime ( testData . locktime )
447+ . fromPubKey ( testData . pAddresses )
448+ . amount ( testData . amount )
449+ . externalChainId ( testData . sourceChainId )
450+ . feeState ( testData . feeState )
451+ . context ( testData . context )
452+ . decodedUtxos ( testData . utxos ) ;
453+
454+ const tx = await txBuilder . build ( ) ;
455+ const txHex = tx . toBroadcastFormat ( ) ;
456+
457+ const parsedTxBuilder = factory . from ( txHex ) ;
458+ const parsedTx = await parsedTxBuilder . build ( ) ;
459+
460+ const flareTransaction = ( parsedTx as any ) . _flareTransaction as UnsignedTx ;
461+ const innerTx = flareTransaction . getTx ( ) as pvmSerial . ExportTx ;
462+ const changeOutputs = innerTx . baseTx . outputs ;
463+
464+ changeOutputs . length . should . be . greaterThan ( 0 ) ;
465+
466+ changeOutputs . forEach ( ( output , index ) => {
467+ const transferOut = output . output as TransferOutput ;
468+ const threshold = transferOut . outputOwners . threshold . value ( ) ;
469+
470+ threshold . should . equal (
471+ testData . threshold ,
472+ `After parsing, change output ${ index } should have threshold=${ testData . threshold } , but has threshold=${ threshold } `
473+ ) ;
474+ } ) ;
475+ } ) ;
476+
477+ it ( 'should have change output addresses matching wallet addresses' , async ( ) => {
478+ const txBuilder = factory
479+ . getExportInPBuilder ( )
480+ . threshold ( testData . threshold )
481+ . locktime ( testData . locktime )
482+ . fromPubKey ( testData . pAddresses )
483+ . amount ( testData . amount )
484+ . externalChainId ( testData . sourceChainId )
485+ . feeState ( testData . feeState )
486+ . context ( testData . context )
487+ . decodedUtxos ( testData . utxos ) ;
488+
489+ const tx = await txBuilder . build ( ) ;
490+
491+ const flareTransaction = ( tx as any ) . _flareTransaction as UnsignedTx ;
492+ const innerTx = flareTransaction . getTx ( ) as pvmSerial . ExportTx ;
493+ const changeOutputs = innerTx . baseTx . outputs ;
494+
495+ changeOutputs . length . should . be . greaterThan ( 0 ) ;
496+
497+ changeOutputs . forEach ( ( output ) => {
498+ const transferOut = output . output as TransferOutput ;
499+ const addresses = transferOut . outputOwners . addrs ;
500+
501+ addresses . length . should . equal ( 3 , 'Change output should have 3 addresses for multisig wallet' ) ;
502+ } ) ;
503+ } ) ;
504+ } ) ;
367505} ) ;
0 commit comments