@@ -28,6 +28,8 @@ import {
2828 PresignTransactionOptions as BasePresignTransactionOptions ,
2929 Recipient ,
3030 SignTransactionOptions as BaseSignTransactionOptions ,
31+ TxIntentMismatchError ,
32+ TxIntentMismatchRecipientError ,
3133 TransactionParams ,
3234 TransactionPrebuild as BaseTransactionPrebuild ,
3335 TransactionRecipient ,
@@ -2799,23 +2801,30 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
27992801 * @param {TransactionPrebuild } params.txPrebuild - prebuild object returned by server
28002802 * @param {Wallet } params.wallet - Wallet object to obtain keys to verify against
28012803 * @returns {boolean }
2804+ * @throws {TxIntentMismatchRecipientError } if transaction recipients don't match user intent
28022805 */
28032806 async verifyTssTransaction ( params : VerifyEthTransactionOptions ) : Promise < boolean > {
28042807 const { txParams, txPrebuild, wallet } = params ;
2808+
2809+ // Helper to throw TxIntentMismatchRecipientError with recipient details
2810+ const throwRecipientMismatch = ( message : string , mismatchedRecipients : Recipient [ ] ) : never => {
2811+ throw new TxIntentMismatchRecipientError ( message , undefined , [ txParams ] , txPrebuild ?. txHex , mismatchedRecipients ) ;
2812+ } ;
2813+
28052814 if (
28062815 ! txParams ?. recipients &&
28072816 ! (
28082817 txParams . prebuildTx ?. consolidateId ||
28092818 ( txParams . type && [ 'acceleration' , 'fillNonce' , 'transferToken' , 'tokenApproval' ] . includes ( txParams . type ) )
28102819 )
28112820 ) {
2812- throw new Error ( ` missing txParams` ) ;
2821+ throw new Error ( ' missing txParams' ) ;
28132822 }
28142823 if ( ! wallet || ! txPrebuild ) {
2815- throw new Error ( ` missing params` ) ;
2824+ throw new Error ( ' missing params' ) ;
28162825 }
28172826 if ( txParams . hop && txParams . recipients && txParams . recipients . length > 1 ) {
2818- throw new Error ( ` tx cannot be both a batch and hop transaction` ) ;
2827+ throw new Error ( ' tx cannot be both a batch and hop transaction' ) ;
28192828 }
28202829
28212830 if ( txParams . type && [ 'transfer' ] . includes ( txParams . type ) ) {
@@ -2830,21 +2839,29 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28302839 const txJson = tx . toJson ( ) ;
28312840 if ( txJson . data === '0x' ) {
28322841 if ( expectedAmount !== txJson . value ) {
2833- throw new Error ( 'the transaction amount in txPrebuild does not match the value given by client' ) ;
2842+ throwRecipientMismatch ( 'the transaction amount in txPrebuild does not match the value given by client' , [
2843+ { address : txJson . to , amount : txJson . value } ,
2844+ ] ) ;
28342845 }
28352846 if ( expectedDestination . toLowerCase ( ) !== txJson . to . toLowerCase ( ) ) {
2836- throw new Error ( 'destination address does not match with the recipient address' ) ;
2847+ throwRecipientMismatch ( 'destination address does not match with the recipient address' , [
2848+ { address : txJson . to , amount : txJson . value } ,
2849+ ] ) ;
28372850 }
28382851 } else if ( txJson . data . startsWith ( '0xa9059cbb' ) ) {
28392852 const [ recipientAddress , amount ] = getRawDecoded (
28402853 [ 'address' , 'uint256' ] ,
28412854 getBufferedByteCode ( '0xa9059cbb' , txJson . data )
28422855 ) ;
28432856 if ( expectedAmount !== amount . toString ( ) ) {
2844- throw new Error ( 'the transaction amount in txPrebuild does not match the value given by client' ) ;
2857+ throwRecipientMismatch ( 'the transaction amount in txPrebuild does not match the value given by client' , [
2858+ { address : addHexPrefix ( recipientAddress . toString ( ) ) , amount : amount . toString ( ) } ,
2859+ ] ) ;
28452860 }
28462861 if ( expectedDestination . toLowerCase ( ) !== addHexPrefix ( recipientAddress . toString ( ) ) . toLowerCase ( ) ) {
2847- throw new Error ( 'destination address does not match with the recipient address' ) ;
2862+ throwRecipientMismatch ( 'destination address does not match with the recipient address' , [
2863+ { address : addHexPrefix ( recipientAddress . toString ( ) ) , amount : amount . toString ( ) } ,
2864+ ] ) ;
28482865 }
28492866 }
28502867 }
@@ -2861,6 +2878,8 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28612878 * @param {TransactionPrebuild } params.txPrebuild - prebuild object returned by server
28622879 * @param {Wallet } params.wallet - Wallet object to obtain keys to verify against
28632880 * @returns {boolean }
2881+ * @throws {TxIntentMismatchError } if transaction validation fails
2882+ * @throws {TxIntentMismatchRecipientError } if transaction recipients don't match user intent
28642883 */
28652884 async verifyTransaction ( params : VerifyEthTransactionOptions ) : Promise < boolean > {
28662885 const ethNetwork = this . getNetwork ( ) ;
@@ -2870,11 +2889,19 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28702889 return this . verifyTssTransaction ( params ) ;
28712890 }
28722891
2892+ // Helper to throw TxIntentMismatchRecipientError with recipient details
2893+ const throwRecipientMismatch = ( message : string , mismatchedRecipients : Recipient [ ] ) : never => {
2894+ throw new TxIntentMismatchRecipientError ( message , undefined , [ txParams ] , txPrebuild ?. txHex , mismatchedRecipients ) ;
2895+ } ;
2896+
28732897 if ( ! txParams ?. recipients || ! txPrebuild ?. recipients || ! wallet ) {
2874- throw new Error ( ` missing params` ) ;
2898+ throw new Error ( ' missing params' ) ;
28752899 }
2876- if ( txParams . hop && txParams . recipients . length > 1 ) {
2877- throw new Error ( `tx cannot be both a batch and hop transaction` ) ;
2900+
2901+ const recipients = txParams . recipients ! ;
2902+
2903+ if ( txParams . hop && recipients . length > 1 ) {
2904+ throw new Error ( 'tx cannot be both a batch and hop transaction' ) ;
28782905 }
28792906 if ( txPrebuild . recipients . length > 1 ) {
28802907 throw new Error (
@@ -2883,8 +2910,8 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28832910 }
28842911 if ( txParams . hop && txPrebuild . hopTransaction ) {
28852912 // Check recipient amount for hop transaction
2886- if ( txParams . recipients . length !== 1 ) {
2887- throw new Error ( `hop transaction only supports 1 recipient but ${ txParams . recipients . length } found` ) ;
2913+ if ( recipients . length !== 1 ) {
2914+ throw new Error ( `hop transaction only supports 1 recipient but ${ recipients . length } found` ) ;
28882915 }
28892916
28902917 // Check tx sends to hop address
@@ -2894,34 +2921,39 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28942921 const expectedHopAddress = optionalDeps . ethUtil . stripHexPrefix ( decodedHopTx . getSenderAddress ( ) . toString ( ) ) ;
28952922 const actualHopAddress = optionalDeps . ethUtil . stripHexPrefix ( txPrebuild . recipients [ 0 ] . address ) ;
28962923 if ( expectedHopAddress . toLowerCase ( ) !== actualHopAddress . toLowerCase ( ) ) {
2897- throw new Error ( 'recipient address of txPrebuild does not match hop address' ) ;
2924+ throwRecipientMismatch ( 'recipient address of txPrebuild does not match hop address' , [
2925+ { address : txPrebuild . recipients [ 0 ] . address , amount : txPrebuild . recipients [ 0 ] . amount . toString ( ) } ,
2926+ ] ) ;
28982927 }
28992928
29002929 // Convert TransactionRecipient array to Recipient array
2901- const recipients : Recipient [ ] = txParams . recipients . map ( ( r ) => {
2930+ const hopRecipients : Recipient [ ] = recipients . map ( ( r ) => {
29022931 return {
29032932 address : r . address ,
29042933 amount : typeof r . amount === 'number' ? r . amount . toString ( ) : r . amount ,
29052934 } ;
29062935 } ) ;
29072936
29082937 // Check destination address and amount
2909- await this . validateHopPrebuild ( wallet , txPrebuild . hopTransaction , { recipients } ) ;
2910- } else if ( txParams . recipients . length > 1 ) {
2938+ await this . validateHopPrebuild ( wallet , txPrebuild . hopTransaction , { recipients : hopRecipients } ) ;
2939+ } else if ( recipients . length > 1 ) {
29112940 // Check total amount for batch transaction
29122941 if ( txParams . tokenName ) {
29132942 const expectedTotalAmount = new BigNumber ( 0 ) ;
29142943 if ( ! expectedTotalAmount . isEqualTo ( txPrebuild . recipients [ 0 ] . amount ) ) {
2915- throw new Error ( 'batch token transaction amount in txPrebuild should be zero for token transfers' ) ;
2944+ throwRecipientMismatch ( 'batch token transaction amount in txPrebuild should be zero for token transfers' , [
2945+ { address : txPrebuild . recipients [ 0 ] . address , amount : txPrebuild . recipients [ 0 ] . amount . toString ( ) } ,
2946+ ] ) ;
29162947 }
29172948 } else {
29182949 let expectedTotalAmount = new BigNumber ( 0 ) ;
2919- for ( let i = 0 ; i < txParams . recipients . length ; i ++ ) {
2920- expectedTotalAmount = expectedTotalAmount . plus ( txParams . recipients [ i ] . amount ) ;
2950+ for ( let i = 0 ; i < recipients . length ; i ++ ) {
2951+ expectedTotalAmount = expectedTotalAmount . plus ( recipients [ i ] . amount ) ;
29212952 }
29222953 if ( ! expectedTotalAmount . isEqualTo ( txPrebuild . recipients [ 0 ] . amount ) ) {
2923- throw new Error (
2924- 'batch transaction amount in txPrebuild received from BitGo servers does not match txParams supplied by client'
2954+ throwRecipientMismatch (
2955+ 'batch transaction amount in txPrebuild received from BitGo servers does not match txParams supplied by client' ,
2956+ [ { address : txPrebuild . recipients [ 0 ] . address , amount : txPrebuild . recipients [ 0 ] . amount . toString ( ) } ]
29252957 ) ;
29262958 }
29272959 }
@@ -2932,29 +2964,37 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
29322964 ! batcherContractAddress ||
29332965 batcherContractAddress . toLowerCase ( ) !== txPrebuild . recipients [ 0 ] . address . toLowerCase ( )
29342966 ) {
2935- throw new Error ( 'recipient address of txPrebuild does not match batcher address' ) ;
2967+ throwRecipientMismatch ( 'recipient address of txPrebuild does not match batcher address' , [
2968+ { address : txPrebuild . recipients [ 0 ] . address , amount : txPrebuild . recipients [ 0 ] . amount . toString ( ) } ,
2969+ ] ) ;
29362970 }
29372971 } else {
29382972 // Check recipient address and amount for normal transaction
2939- if ( txParams . recipients . length !== 1 ) {
2940- throw new Error ( `normal transaction only supports 1 recipient but ${ txParams . recipients . length } found` ) ;
2973+ if ( recipients . length !== 1 ) {
2974+ throw new Error ( `normal transaction only supports 1 recipient but ${ recipients . length } found` ) ;
29412975 }
2942- const expectedAmount = new BigNumber ( txParams . recipients [ 0 ] . amount ) ;
2976+ const expectedAmount = new BigNumber ( recipients [ 0 ] . amount ) ;
29432977 if ( ! expectedAmount . isEqualTo ( txPrebuild . recipients [ 0 ] . amount ) ) {
2944- throw new Error (
2945- 'normal transaction amount in txPrebuild received from BitGo servers does not match txParams supplied by client'
2978+ throwRecipientMismatch (
2979+ 'normal transaction amount in txPrebuild received from BitGo servers does not match txParams supplied by client' ,
2980+ [ { address : txPrebuild . recipients [ 0 ] . address , amount : txPrebuild . recipients [ 0 ] . amount . toString ( ) } ]
29462981 ) ;
29472982 }
2948- if (
2949- this . isETHAddress ( txParams . recipients [ 0 ] . address ) &&
2950- txParams . recipients [ 0 ] . address !== txPrebuild . recipients [ 0 ] . address
2951- ) {
2952- throw new Error ( 'destination address in normal txPrebuild does not match that in txParams supplied by client' ) ;
2983+ if ( this . isETHAddress ( recipients [ 0 ] . address ) && recipients [ 0 ] . address !== txPrebuild . recipients [ 0 ] . address ) {
2984+ throwRecipientMismatch (
2985+ 'destination address in normal txPrebuild does not match that in txParams supplied by client' ,
2986+ [ { address : txPrebuild . recipients [ 0 ] . address , amount : txPrebuild . recipients [ 0 ] . amount . toString ( ) } ]
2987+ ) ;
29532988 }
29542989 }
29552990 // Check coin is correct for all transaction types
29562991 if ( ! this . verifyCoin ( txPrebuild ) ) {
2957- throw new Error ( `coin in txPrebuild did not match that in txParams supplied by client` ) ;
2992+ throw new TxIntentMismatchError (
2993+ 'coin in txPrebuild did not match that in txParams supplied by client' ,
2994+ undefined ,
2995+ [ txParams ] ,
2996+ txPrebuild ?. txHex
2997+ ) ;
29582998 }
29592999 return true ;
29603000 }
0 commit comments