@@ -46,9 +46,9 @@ describe('WalletTxSignTSS codec tests', function () {
4646 apiVersion : 'lite' as const ,
4747 } ;
4848
49- // Create mock wallet with signTransaction method
49+ // Create mock wallet with ensureCleanSigSharesAndSignTransaction method
5050 const mockWallet = {
51- signTransaction : sinon . stub ( ) . resolves ( mockFullySignedResponse ) ,
51+ ensureCleanSigSharesAndSignTransaction : sinon . stub ( ) . resolves ( mockFullySignedResponse ) ,
5252 } ;
5353
5454 // Stub the wallets().get() chain
@@ -85,7 +85,7 @@ describe('WalletTxSignTSS codec tests', function () {
8585 assert . strictEqual ( coinStub . calledOnceWith ( coin ) , true ) ;
8686 assert . strictEqual ( mockCoin . wallets . calledOnce , true ) ;
8787 assert . strictEqual ( walletsGetStub . calledOnceWith ( { id : walletId } ) , true ) ;
88- assert . strictEqual ( mockWallet . signTransaction . calledOnce , true ) ;
88+ assert . strictEqual ( mockWallet . ensureCleanSigSharesAndSignTransaction . calledOnce , true ) ;
8989 } ) ;
9090
9191 it ( 'should successfully sign a half-signed TSS wallet transaction' , async function ( ) {
@@ -106,9 +106,9 @@ describe('WalletTxSignTSS codec tests', function () {
106106 } ,
107107 } ;
108108
109- // Create mock wallet with signTransaction method
109+ // Create mock wallet with ensureCleanSigSharesAndSignTransaction method
110110 const mockWallet = {
111- signTransaction : sinon . stub ( ) . resolves ( mockHalfSignedResponse ) ,
111+ ensureCleanSigSharesAndSignTransaction : sinon . stub ( ) . resolves ( mockHalfSignedResponse ) ,
112112 } ;
113113
114114 // Stub the wallets().get() chain
@@ -146,7 +146,7 @@ describe('WalletTxSignTSS codec tests', function () {
146146 assert . strictEqual ( coinStub . calledOnceWith ( coin ) , true ) ;
147147 assert . strictEqual ( mockCoin . wallets . calledOnce , true ) ;
148148 assert . strictEqual ( walletsGetStub . calledOnceWith ( { id : walletId } ) , true ) ;
149- assert . strictEqual ( mockWallet . signTransaction . calledOnce , true ) ;
149+ assert . strictEqual ( mockWallet . ensureCleanSigSharesAndSignTransaction . calledOnce , true ) ;
150150 } ) ;
151151
152152 it ( 'should successfully sign a half-signed UTXO transaction' , async function ( ) {
@@ -165,9 +165,9 @@ describe('WalletTxSignTSS codec tests', function () {
165165 '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f00000000484730440220abc123def456...' ,
166166 } ;
167167
168- // Create mock wallet with signTransaction method
168+ // Create mock wallet with ensureCleanSigSharesAndSignTransaction method
169169 const mockWallet = {
170- signTransaction : sinon . stub ( ) . resolves ( mockHalfSignedUtxoResponse ) ,
170+ ensureCleanSigSharesAndSignTransaction : sinon . stub ( ) . resolves ( mockHalfSignedUtxoResponse ) ,
171171 } ;
172172
173173 // Stub the wallets().get() chain
@@ -204,7 +204,7 @@ describe('WalletTxSignTSS codec tests', function () {
204204 assert . strictEqual ( coinStub . calledOnceWith ( coin ) , true ) ;
205205 assert . strictEqual ( mockCoin . wallets . calledOnce , true ) ;
206206 assert . strictEqual ( walletsGetStub . calledOnceWith ( { id : walletId } ) , true ) ;
207- assert . strictEqual ( mockWallet . signTransaction . calledOnce , true ) ;
207+ assert . strictEqual ( mockWallet . ensureCleanSigSharesAndSignTransaction . calledOnce , true ) ;
208208 } ) ;
209209
210210 it ( 'should successfully return a TSS transaction request ID' , async function ( ) {
@@ -222,9 +222,9 @@ describe('WalletTxSignTSS codec tests', function () {
222222 txRequestId : '5a1341e7c8421dc90710673b3166bbd5' ,
223223 } ;
224224
225- // Create mock wallet with signTransaction method
225+ // Create mock wallet with ensureCleanSigSharesAndSignTransaction method
226226 const mockWallet = {
227- signTransaction : sinon . stub ( ) . resolves ( mockTxRequestResponse ) ,
227+ ensureCleanSigSharesAndSignTransaction : sinon . stub ( ) . resolves ( mockTxRequestResponse ) ,
228228 } ;
229229
230230 // Stub the wallets().get() chain
@@ -261,7 +261,7 @@ describe('WalletTxSignTSS codec tests', function () {
261261 assert . strictEqual ( coinStub . calledOnceWith ( coin ) , true ) ;
262262 assert . strictEqual ( mockCoin . wallets . calledOnce , true ) ;
263263 assert . strictEqual ( walletsGetStub . calledOnceWith ( { id : walletId } ) , true ) ;
264- assert . strictEqual ( mockWallet . signTransaction . calledOnce , true ) ;
264+ assert . strictEqual ( mockWallet . ensureCleanSigSharesAndSignTransaction . calledOnce , true ) ;
265265 } ) ;
266266
267267 it ( 'should successfully return a full TSS transaction request (TxRequestResponse)' , async function ( ) {
@@ -322,9 +322,9 @@ describe('WalletTxSignTSS codec tests', function () {
322322 latest : true ,
323323 } ;
324324
325- // Create mock wallet with signTransaction method
325+ // Create mock wallet with ensureCleanSigSharesAndSignTransaction method
326326 const mockWallet = {
327- signTransaction : sinon . stub ( ) . resolves ( mockTxRequestFullResponse ) ,
327+ ensureCleanSigSharesAndSignTransaction : sinon . stub ( ) . resolves ( mockTxRequestFullResponse ) ,
328328 } ;
329329
330330 // Stub the wallets().get() chain
@@ -385,7 +385,170 @@ describe('WalletTxSignTSS codec tests', function () {
385385 assert . strictEqual ( coinStub . calledOnceWith ( coin ) , true ) ;
386386 assert . strictEqual ( mockCoin . wallets . calledOnce , true ) ;
387387 assert . strictEqual ( walletsGetStub . calledOnceWith ( { id : walletId } ) , true ) ;
388- assert . strictEqual ( mockWallet . signTransaction . calledOnce , true ) ;
388+ assert . strictEqual ( mockWallet . ensureCleanSigSharesAndSignTransaction . calledOnce , true ) ;
389+ } ) ;
390+
391+ // ==========================================
392+ // SIGNATURE CLEANUP TESTS
393+ // ==========================================
394+
395+ describe ( 'Signature Cleanup (ensureCleanSigSharesAndSignTransaction)' , function ( ) {
396+ it ( 'should cleanup partial signature shares before signing' , async function ( ) {
397+ const requestBody = {
398+ txPrebuild : {
399+ txHex :
400+ '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000' ,
401+ walletId : walletId ,
402+ txRequestId : 'tx-req-with-partial-sigs' ,
403+ } ,
404+ walletPassphrase : 'test_passphrase_12345' ,
405+ apiVersion : 'full' as const ,
406+ } ;
407+
408+ const partiallySignedTxRequest = {
409+ txRequestId : 'tx-req-with-partial-sigs' ,
410+ walletId : walletId ,
411+ walletType : 'hot' ,
412+ version : 1 ,
413+ state : 'pendingSignature' ,
414+ date : '2025-10-22' ,
415+ userId : 'user-123' ,
416+ intent : { } ,
417+ policiesChecked : false ,
418+ unsignedTxs : [ ] ,
419+ apiVersion : 'full' ,
420+ latest : true ,
421+ transactions : [
422+ {
423+ state : 'pendingSignature' ,
424+ unsignedTx : {
425+ serializedTxHex : 'abc123' ,
426+ signableHex : 'def456' ,
427+ derivationPath : 'm/0' ,
428+ } ,
429+ signatureShares : [
430+ {
431+ from : 'user' ,
432+ to : 'bitgo' ,
433+ share : 'stale-partial-sig' ,
434+ } ,
435+ ] ,
436+ } ,
437+ ] ,
438+ } ;
439+
440+ const mockWallet = {
441+ ensureCleanSigSharesAndSignTransaction : sinon . stub ( ) . resolves ( partiallySignedTxRequest ) ,
442+ } ;
443+
444+ const walletsGetStub = sinon . stub ( ) . resolves ( mockWallet ) ;
445+ const mockWallets = { get : walletsGetStub } ;
446+ const mockCoin = { wallets : sinon . stub ( ) . returns ( mockWallets ) } ;
447+ sinon . stub ( BitGo . prototype , 'coin' ) . returns ( mockCoin as any ) ;
448+
449+ const result = await agent
450+ . post ( `/api/v2/${ coin } /wallet/${ walletId } /signtxtss` )
451+ . set ( 'Authorization' , 'Bearer test_access_token_12345' )
452+ . set ( 'Content-Type' , 'application/json' )
453+ . send ( requestBody ) ;
454+
455+ assert . strictEqual ( result . status , 200 ) ;
456+ assert . strictEqual ( mockWallet . ensureCleanSigSharesAndSignTransaction . calledOnce , true ) ;
457+
458+ const callArgs = mockWallet . ensureCleanSigSharesAndSignTransaction . firstCall . args [ 0 ] ;
459+ assert . strictEqual ( callArgs . txPrebuild . txRequestId , 'tx-req-with-partial-sigs' ) ;
460+ assert . strictEqual ( callArgs . walletPassphrase , 'test_passphrase_12345' ) ;
461+ } ) ;
462+
463+ it ( 'should handle message-based TxRequest with partial signatures' , async function ( ) {
464+ const requestBody = {
465+ txPrebuild : {
466+ txHex : '0xabc123' ,
467+ walletId : walletId ,
468+ txRequestId : 'msg-req-with-partial-sigs' ,
469+ } ,
470+ walletPassphrase : 'test_passphrase_12345' ,
471+ apiVersion : 'full' as const ,
472+ } ;
473+
474+ const partiallySignedMessageRequest = {
475+ txRequestId : 'msg-req-with-partial-sigs' ,
476+ walletId : walletId ,
477+ walletType : 'hot' ,
478+ version : 1 ,
479+ state : 'pendingSignature' ,
480+ date : '2025-10-22' ,
481+ userId : 'user-123' ,
482+ intent : { intentType : 'signMessage' } ,
483+ policiesChecked : false ,
484+ unsignedTxs : [ ] ,
485+ apiVersion : 'full' ,
486+ latest : true ,
487+ messages : [
488+ {
489+ state : 'pendingSignature' ,
490+ messageRaw : 'hello world' ,
491+ derivationPath : 'm/0' ,
492+ signatureShares : [
493+ {
494+ from : 'user' ,
495+ to : 'bitgo' ,
496+ share : 'stale-msg-sig' ,
497+ } ,
498+ ] ,
499+ } ,
500+ ] ,
501+ } ;
502+
503+ const mockWallet = {
504+ ensureCleanSigSharesAndSignTransaction : sinon . stub ( ) . resolves ( partiallySignedMessageRequest ) ,
505+ } ;
506+
507+ const walletsGetStub = sinon . stub ( ) . resolves ( mockWallet ) ;
508+ const mockWallets = { get : walletsGetStub } ;
509+ const mockCoin = { wallets : sinon . stub ( ) . returns ( mockWallets ) } ;
510+ sinon . stub ( BitGo . prototype , 'coin' ) . returns ( mockCoin as any ) ;
511+
512+ const result = await agent
513+ . post ( `/api/v2/${ coin } /wallet/${ walletId } /signtxtss` )
514+ . set ( 'Authorization' , 'Bearer test_access_token_12345' )
515+ . set ( 'Content-Type' , 'application/json' )
516+ . send ( requestBody ) ;
517+
518+ assert . strictEqual ( result . status , 200 ) ;
519+ assert . strictEqual ( mockWallet . ensureCleanSigSharesAndSignTransaction . calledOnce , true ) ;
520+ } ) ;
521+
522+ it ( 'should not perform cleanup for Lite TxRequest' , async function ( ) {
523+ const requestBody = {
524+ txPrebuild : {
525+ txHex :
526+ '0100000001c7dad3d9607a23c45a6c1c5ad7bce02acff71a0f21eb4a72a59d0c0e19402d0f0000000000ffffffff0180a21900000000001976a914c918e1b36f2c72b1aaef94dbb7f578a4b68b542788ac00000000' ,
527+ walletId : walletId ,
528+ txRequestId : 'lite-tx-req' ,
529+ } ,
530+ walletPassphrase : 'test_passphrase_12345' ,
531+ apiVersion : 'lite' as const ,
532+ } ;
533+
534+ const mockWallet = {
535+ ensureCleanSigSharesAndSignTransaction : sinon . stub ( ) . resolves ( mockFullySignedResponse ) ,
536+ } ;
537+
538+ const walletsGetStub = sinon . stub ( ) . resolves ( mockWallet ) ;
539+ const mockWallets = { get : walletsGetStub } ;
540+ const mockCoin = { wallets : sinon . stub ( ) . returns ( mockWallets ) } ;
541+ sinon . stub ( BitGo . prototype , 'coin' ) . returns ( mockCoin as any ) ;
542+
543+ const result = await agent
544+ . post ( `/api/v2/${ coin } /wallet/${ walletId } /signtxtss` )
545+ . set ( 'Authorization' , 'Bearer test_access_token_12345' )
546+ . set ( 'Content-Type' , 'application/json' )
547+ . send ( requestBody ) ;
548+
549+ assert . strictEqual ( result . status , 200 ) ;
550+ assert . strictEqual ( mockWallet . ensureCleanSigSharesAndSignTransaction . calledOnce , true ) ;
551+ } ) ;
389552 } ) ;
390553
391554 // ==========================================
0 commit comments