@@ -28,6 +28,7 @@ import {
2828 PresignTransactionOptions as BasePresignTransactionOptions ,
2929 Recipient ,
3030 SignTransactionOptions as BaseSignTransactionOptions ,
31+ TxIntentMismatchError ,
3132 TransactionParams ,
3233 TransactionPrebuild as BaseTransactionPrebuild ,
3334 TransactionRecipient ,
@@ -2767,23 +2768,30 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
27672768 * @param {TransactionPrebuild } params.txPrebuild - prebuild object returned by server
27682769 * @param {Wallet } params.wallet - Wallet object to obtain keys to verify against
27692770 * @returns {boolean }
2771+ * @throws {TxIntentMismatchError } if transaction validation fails
27702772 */
27712773 async verifyTssTransaction ( params : VerifyEthTransactionOptions ) : Promise < boolean > {
27722774 const { txParams, txPrebuild, wallet } = params ;
2775+
2776+ // Helper to throw TxIntentMismatchError with consistent context
2777+ const throwTxMismatch = ( message : string ) : never => {
2778+ throw new TxIntentMismatchError ( message , '' , [ txParams ] , txPrebuild ?. txHex || '' ) ;
2779+ } ;
2780+
27732781 if (
27742782 ! txParams ?. recipients &&
27752783 ! (
27762784 txParams . prebuildTx ?. consolidateId ||
27772785 ( txParams . type && [ 'acceleration' , 'fillNonce' , 'transferToken' , 'tokenApproval' ] . includes ( txParams . type ) )
27782786 )
27792787 ) {
2780- throw new Error ( `missing txParams` ) ;
2788+ throwTxMismatch ( `missing txParams` ) ;
27812789 }
27822790 if ( ! wallet || ! txPrebuild ) {
2783- throw new Error ( `missing params` ) ;
2791+ throwTxMismatch ( `missing params` ) ;
27842792 }
27852793 if ( txParams . hop && txParams . recipients && txParams . recipients . length > 1 ) {
2786- throw new Error ( `tx cannot be both a batch and hop transaction` ) ;
2794+ throwTxMismatch ( `tx cannot be both a batch and hop transaction` ) ;
27872795 }
27882796
27892797 if ( txParams . type && [ 'transfer' ] . includes ( txParams . type ) ) {
@@ -2798,21 +2806,21 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
27982806 const txJson = tx . toJson ( ) ;
27992807 if ( txJson . data === '0x' ) {
28002808 if ( expectedAmount !== txJson . value ) {
2801- throw new Error ( 'the transaction amount in txPrebuild does not match the value given by client' ) ;
2809+ throwTxMismatch ( 'the transaction amount in txPrebuild does not match the value given by client' ) ;
28022810 }
28032811 if ( expectedDestination . toLowerCase ( ) !== txJson . to . toLowerCase ( ) ) {
2804- throw new Error ( 'destination address does not match with the recipient address' ) ;
2812+ throwTxMismatch ( 'destination address does not match with the recipient address' ) ;
28052813 }
28062814 } else if ( txJson . data . startsWith ( '0xa9059cbb' ) ) {
28072815 const [ recipientAddress , amount ] = getRawDecoded (
28082816 [ 'address' , 'uint256' ] ,
28092817 getBufferedByteCode ( '0xa9059cbb' , txJson . data )
28102818 ) ;
28112819 if ( expectedAmount !== amount . toString ( ) ) {
2812- throw new Error ( 'the transaction amount in txPrebuild does not match the value given by client' ) ;
2820+ throwTxMismatch ( 'the transaction amount in txPrebuild does not match the value given by client' ) ;
28132821 }
28142822 if ( expectedDestination . toLowerCase ( ) !== addHexPrefix ( recipientAddress . toString ( ) ) . toLowerCase ( ) ) {
2815- throw new Error ( 'destination address does not match with the recipient address' ) ;
2823+ throwTxMismatch ( 'destination address does not match with the recipient address' ) ;
28162824 }
28172825 }
28182826 }
@@ -2829,6 +2837,7 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28292837 * @param {TransactionPrebuild } params.txPrebuild - prebuild object returned by server
28302838 * @param {Wallet } params.wallet - Wallet object to obtain keys to verify against
28312839 * @returns {boolean }
2840+ * @throws {TxIntentMismatchError } if transaction validation fails
28322841 */
28332842 async verifyTransaction ( params : VerifyEthTransactionOptions ) : Promise < boolean > {
28342843 const ethNetwork = this . getNetwork ( ) ;
@@ -2838,21 +2847,29 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28382847 return this . verifyTssTransaction ( params ) ;
28392848 }
28402849
2850+ // Helper to throw TxIntentMismatchError with consistent context
2851+ const throwTxMismatch = ( message : string ) : never => {
2852+ throw new TxIntentMismatchError ( message , '' , [ txParams ] , txPrebuild ?. txHex || '' ) ;
2853+ } ;
2854+
28412855 if ( ! txParams ?. recipients || ! txPrebuild ?. recipients || ! wallet ) {
2842- throw new Error ( `missing params` ) ;
2856+ throwTxMismatch ( `missing params` ) ;
28432857 }
2844- if ( txParams . hop && txParams . recipients . length > 1 ) {
2845- throw new Error ( `tx cannot be both a batch and hop transaction` ) ;
2858+
2859+ const recipients = txParams . recipients ! ;
2860+
2861+ if ( txParams . hop && recipients . length > 1 ) {
2862+ throwTxMismatch ( `tx cannot be both a batch and hop transaction` ) ;
28462863 }
28472864 if ( txPrebuild . recipients . length > 1 ) {
2848- throw new Error (
2865+ throwTxMismatch (
28492866 `${ this . getChain ( ) } doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.`
28502867 ) ;
28512868 }
28522869 if ( txParams . hop && txPrebuild . hopTransaction ) {
28532870 // Check recipient amount for hop transaction
2854- if ( txParams . recipients . length !== 1 ) {
2855- throw new Error ( `hop transaction only supports 1 recipient but ${ txParams . recipients . length } found` ) ;
2871+ if ( recipients . length !== 1 ) {
2872+ throwTxMismatch ( `hop transaction only supports 1 recipient but ${ recipients . length } found` ) ;
28562873 }
28572874
28582875 // Check tx sends to hop address
@@ -2862,33 +2879,33 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28622879 const expectedHopAddress = optionalDeps . ethUtil . stripHexPrefix ( decodedHopTx . getSenderAddress ( ) . toString ( ) ) ;
28632880 const actualHopAddress = optionalDeps . ethUtil . stripHexPrefix ( txPrebuild . recipients [ 0 ] . address ) ;
28642881 if ( expectedHopAddress . toLowerCase ( ) !== actualHopAddress . toLowerCase ( ) ) {
2865- throw new Error ( 'recipient address of txPrebuild does not match hop address' ) ;
2882+ throwTxMismatch ( 'recipient address of txPrebuild does not match hop address' ) ;
28662883 }
28672884
28682885 // Convert TransactionRecipient array to Recipient array
2869- const recipients : Recipient [ ] = txParams . recipients . map ( ( r ) => {
2886+ const hopRecipients : Recipient [ ] = recipients . map ( ( r ) => {
28702887 return {
28712888 address : r . address ,
28722889 amount : typeof r . amount === 'number' ? r . amount . toString ( ) : r . amount ,
28732890 } ;
28742891 } ) ;
28752892
28762893 // Check destination address and amount
2877- await this . validateHopPrebuild ( wallet , txPrebuild . hopTransaction , { recipients } ) ;
2878- } else if ( txParams . recipients . length > 1 ) {
2894+ await this . validateHopPrebuild ( wallet , txPrebuild . hopTransaction , { recipients : hopRecipients } ) ;
2895+ } else if ( recipients . length > 1 ) {
28792896 // Check total amount for batch transaction
28802897 if ( txParams . tokenName ) {
28812898 const expectedTotalAmount = new BigNumber ( 0 ) ;
28822899 if ( ! expectedTotalAmount . isEqualTo ( txPrebuild . recipients [ 0 ] . amount ) ) {
2883- throw new Error ( 'batch token transaction amount in txPrebuild should be zero for token transfers' ) ;
2900+ throwTxMismatch ( 'batch token transaction amount in txPrebuild should be zero for token transfers' ) ;
28842901 }
28852902 } else {
28862903 let expectedTotalAmount = new BigNumber ( 0 ) ;
2887- for ( let i = 0 ; i < txParams . recipients . length ; i ++ ) {
2888- expectedTotalAmount = expectedTotalAmount . plus ( txParams . recipients [ i ] . amount ) ;
2904+ for ( let i = 0 ; i < recipients . length ; i ++ ) {
2905+ expectedTotalAmount = expectedTotalAmount . plus ( recipients [ i ] . amount ) ;
28892906 }
28902907 if ( ! expectedTotalAmount . isEqualTo ( txPrebuild . recipients [ 0 ] . amount ) ) {
2891- throw new Error (
2908+ throwTxMismatch (
28922909 'batch transaction amount in txPrebuild received from BitGo servers does not match txParams supplied by client'
28932910 ) ;
28942911 }
@@ -2900,29 +2917,26 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
29002917 ! batcherContractAddress ||
29012918 batcherContractAddress . toLowerCase ( ) !== txPrebuild . recipients [ 0 ] . address . toLowerCase ( )
29022919 ) {
2903- throw new Error ( 'recipient address of txPrebuild does not match batcher address' ) ;
2920+ throwTxMismatch ( 'recipient address of txPrebuild does not match batcher address' ) ;
29042921 }
29052922 } else {
29062923 // Check recipient address and amount for normal transaction
2907- if ( txParams . recipients . length !== 1 ) {
2908- throw new Error ( `normal transaction only supports 1 recipient but ${ txParams . recipients . length } found` ) ;
2924+ if ( recipients . length !== 1 ) {
2925+ throwTxMismatch ( `normal transaction only supports 1 recipient but ${ recipients . length } found` ) ;
29092926 }
2910- const expectedAmount = new BigNumber ( txParams . recipients [ 0 ] . amount ) ;
2927+ const expectedAmount = new BigNumber ( recipients [ 0 ] . amount ) ;
29112928 if ( ! expectedAmount . isEqualTo ( txPrebuild . recipients [ 0 ] . amount ) ) {
2912- throw new Error (
2929+ throwTxMismatch (
29132930 'normal transaction amount in txPrebuild received from BitGo servers does not match txParams supplied by client'
29142931 ) ;
29152932 }
2916- if (
2917- this . isETHAddress ( txParams . recipients [ 0 ] . address ) &&
2918- txParams . recipients [ 0 ] . address !== txPrebuild . recipients [ 0 ] . address
2919- ) {
2920- throw new Error ( 'destination address in normal txPrebuild does not match that in txParams supplied by client' ) ;
2933+ if ( this . isETHAddress ( recipients [ 0 ] . address ) && recipients [ 0 ] . address !== txPrebuild . recipients [ 0 ] . address ) {
2934+ throwTxMismatch ( 'destination address in normal txPrebuild does not match that in txParams supplied by client' ) ;
29212935 }
29222936 }
29232937 // Check coin is correct for all transaction types
29242938 if ( ! this . verifyCoin ( txPrebuild ) ) {
2925- throw new Error ( `coin in txPrebuild did not match that in txParams supplied by client` ) ;
2939+ throwTxMismatch ( `coin in txPrebuild did not match that in txParams supplied by client` ) ;
29262940 }
29272941 return true ;
29282942 }
0 commit comments