Skip to content

Commit 28a7df1

Browse files
danielpeng1lcovar
authored andcommitted
refactor: enhance eth-like tx verification with recipient mismatch errors
Ticket: WP-6189
1 parent 5fdb2f0 commit 28a7df1

File tree

1 file changed

+54
-28
lines changed

1 file changed

+54
-28
lines changed

modules/abstract-eth/src/abstractEthLikeNewCoins.ts

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
Recipient,
3030
SignTransactionOptions as BaseSignTransactionOptions,
3131
TxIntentMismatchError,
32+
TxIntentMismatchRecipientError,
3233
TransactionParams,
3334
TransactionPrebuild as BaseTransactionPrebuild,
3435
TransactionRecipient,
@@ -2768,14 +2769,14 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
27682769
* @param {TransactionPrebuild} params.txPrebuild - prebuild object returned by server
27692770
* @param {Wallet} params.wallet - Wallet object to obtain keys to verify against
27702771
* @returns {boolean}
2771-
* @throws {TxIntentMismatchError} if transaction validation fails
2772+
* @throws {TxIntentMismatchRecipientError} if transaction recipients don't match user intent
27722773
*/
27732774
async verifyTssTransaction(params: VerifyEthTransactionOptions): Promise<boolean> {
27742775
const { txParams, txPrebuild, wallet } = params;
27752776

2776-
// Helper to throw TxIntentMismatchError with consistent context
2777-
const throwTxMismatch = (message: string): never => {
2778-
throw new TxIntentMismatchError(message, undefined, [txParams], txPrebuild?.txHex);
2777+
// Helper to throw TxIntentMismatchRecipientError with recipient details
2778+
const throwRecipientMismatch = (message: string, mismatchedRecipients: Recipient[]): never => {
2779+
throw new TxIntentMismatchRecipientError(message, undefined, [txParams], txPrebuild?.txHex, mismatchedRecipients);
27792780
};
27802781

27812782
if (
@@ -2785,13 +2786,13 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
27852786
(txParams.type && ['acceleration', 'fillNonce', 'transferToken', 'tokenApproval'].includes(txParams.type))
27862787
)
27872788
) {
2788-
throwTxMismatch(`missing txParams`);
2789+
throw new Error('missing txParams');
27892790
}
27902791
if (!wallet || !txPrebuild) {
2791-
throwTxMismatch(`missing params`);
2792+
throw new Error('missing params');
27922793
}
27932794
if (txParams.hop && txParams.recipients && txParams.recipients.length > 1) {
2794-
throwTxMismatch(`tx cannot be both a batch and hop transaction`);
2795+
throw new Error('tx cannot be both a batch and hop transaction');
27952796
}
27962797

27972798
if (txParams.type && ['transfer'].includes(txParams.type)) {
@@ -2806,21 +2807,29 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28062807
const txJson = tx.toJson();
28072808
if (txJson.data === '0x') {
28082809
if (expectedAmount !== txJson.value) {
2809-
throwTxMismatch('the transaction amount in txPrebuild does not match the value given by client');
2810+
throwRecipientMismatch('the transaction amount in txPrebuild does not match the value given by client', [
2811+
{ address: txJson.to, amount: txJson.value },
2812+
]);
28102813
}
28112814
if (expectedDestination.toLowerCase() !== txJson.to.toLowerCase()) {
2812-
throwTxMismatch('destination address does not match with the recipient address');
2815+
throwRecipientMismatch('destination address does not match with the recipient address', [
2816+
{ address: txJson.to, amount: txJson.value },
2817+
]);
28132818
}
28142819
} else if (txJson.data.startsWith('0xa9059cbb')) {
28152820
const [recipientAddress, amount] = getRawDecoded(
28162821
['address', 'uint256'],
28172822
getBufferedByteCode('0xa9059cbb', txJson.data)
28182823
);
28192824
if (expectedAmount !== amount.toString()) {
2820-
throwTxMismatch('the transaction amount in txPrebuild does not match the value given by client');
2825+
throwRecipientMismatch('the transaction amount in txPrebuild does not match the value given by client', [
2826+
{ address: addHexPrefix(recipientAddress.toString()), amount: amount.toString() },
2827+
]);
28212828
}
28222829
if (expectedDestination.toLowerCase() !== addHexPrefix(recipientAddress.toString()).toLowerCase()) {
2823-
throwTxMismatch('destination address does not match with the recipient address');
2830+
throwRecipientMismatch('destination address does not match with the recipient address', [
2831+
{ address: addHexPrefix(recipientAddress.toString()), amount: amount.toString() },
2832+
]);
28242833
}
28252834
}
28262835
}
@@ -2838,6 +2847,7 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28382847
* @param {Wallet} params.wallet - Wallet object to obtain keys to verify against
28392848
* @returns {boolean}
28402849
* @throws {TxIntentMismatchError} if transaction validation fails
2850+
* @throws {TxIntentMismatchRecipientError} if transaction recipients don't match user intent
28412851
*/
28422852
async verifyTransaction(params: VerifyEthTransactionOptions): Promise<boolean> {
28432853
const ethNetwork = this.getNetwork();
@@ -2847,29 +2857,29 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28472857
return this.verifyTssTransaction(params);
28482858
}
28492859

2850-
// Helper to throw TxIntentMismatchError with consistent context
2851-
const throwTxMismatch = (message: string): never => {
2852-
throw new TxIntentMismatchError(message, undefined, [txParams], txPrebuild?.txHex);
2860+
// Helper to throw TxIntentMismatchRecipientError with recipient details
2861+
const throwRecipientMismatch = (message: string, mismatchedRecipients: Recipient[]): never => {
2862+
throw new TxIntentMismatchRecipientError(message, undefined, [txParams], txPrebuild?.txHex, mismatchedRecipients);
28532863
};
28542864

28552865
if (!txParams?.recipients || !txPrebuild?.recipients || !wallet) {
2856-
throwTxMismatch(`missing params`);
2866+
throw new Error('missing params');
28572867
}
28582868

28592869
const recipients = txParams.recipients!;
28602870

28612871
if (txParams.hop && recipients.length > 1) {
2862-
throwTxMismatch(`tx cannot be both a batch and hop transaction`);
2872+
throw new Error('tx cannot be both a batch and hop transaction');
28632873
}
28642874
if (txPrebuild.recipients.length > 1) {
2865-
throwTxMismatch(
2875+
throw new Error(
28662876
`${this.getChain()} doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.`
28672877
);
28682878
}
28692879
if (txParams.hop && txPrebuild.hopTransaction) {
28702880
// Check recipient amount for hop transaction
28712881
if (recipients.length !== 1) {
2872-
throwTxMismatch(`hop transaction only supports 1 recipient but ${recipients.length} found`);
2882+
throw new Error(`hop transaction only supports 1 recipient but ${recipients.length} found`);
28732883
}
28742884

28752885
// Check tx sends to hop address
@@ -2879,7 +2889,9 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28792889
const expectedHopAddress = optionalDeps.ethUtil.stripHexPrefix(decodedHopTx.getSenderAddress().toString());
28802890
const actualHopAddress = optionalDeps.ethUtil.stripHexPrefix(txPrebuild.recipients[0].address);
28812891
if (expectedHopAddress.toLowerCase() !== actualHopAddress.toLowerCase()) {
2882-
throwTxMismatch('recipient address of txPrebuild does not match hop address');
2892+
throwRecipientMismatch('recipient address of txPrebuild does not match hop address', [
2893+
{ address: txPrebuild.recipients[0].address, amount: txPrebuild.recipients[0].amount.toString() },
2894+
]);
28832895
}
28842896

28852897
// Convert TransactionRecipient array to Recipient array
@@ -2897,16 +2909,19 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
28972909
if (txParams.tokenName) {
28982910
const expectedTotalAmount = new BigNumber(0);
28992911
if (!expectedTotalAmount.isEqualTo(txPrebuild.recipients[0].amount)) {
2900-
throwTxMismatch('batch token transaction amount in txPrebuild should be zero for token transfers');
2912+
throwRecipientMismatch('batch token transaction amount in txPrebuild should be zero for token transfers', [
2913+
{ address: txPrebuild.recipients[0].address, amount: txPrebuild.recipients[0].amount.toString() },
2914+
]);
29012915
}
29022916
} else {
29032917
let expectedTotalAmount = new BigNumber(0);
29042918
for (let i = 0; i < recipients.length; i++) {
29052919
expectedTotalAmount = expectedTotalAmount.plus(recipients[i].amount);
29062920
}
29072921
if (!expectedTotalAmount.isEqualTo(txPrebuild.recipients[0].amount)) {
2908-
throwTxMismatch(
2909-
'batch transaction amount in txPrebuild received from BitGo servers does not match txParams supplied by client'
2922+
throwRecipientMismatch(
2923+
'batch transaction amount in txPrebuild received from BitGo servers does not match txParams supplied by client',
2924+
[{ address: txPrebuild.recipients[0].address, amount: txPrebuild.recipients[0].amount.toString() }]
29102925
);
29112926
}
29122927
}
@@ -2917,26 +2932,37 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
29172932
!batcherContractAddress ||
29182933
batcherContractAddress.toLowerCase() !== txPrebuild.recipients[0].address.toLowerCase()
29192934
) {
2920-
throwTxMismatch('recipient address of txPrebuild does not match batcher address');
2935+
throwRecipientMismatch('recipient address of txPrebuild does not match batcher address', [
2936+
{ address: txPrebuild.recipients[0].address, amount: txPrebuild.recipients[0].amount.toString() },
2937+
]);
29212938
}
29222939
} else {
29232940
// Check recipient address and amount for normal transaction
29242941
if (recipients.length !== 1) {
2925-
throwTxMismatch(`normal transaction only supports 1 recipient but ${recipients.length} found`);
2942+
throw new Error(`normal transaction only supports 1 recipient but ${recipients.length} found`);
29262943
}
29272944
const expectedAmount = new BigNumber(recipients[0].amount);
29282945
if (!expectedAmount.isEqualTo(txPrebuild.recipients[0].amount)) {
2929-
throwTxMismatch(
2930-
'normal transaction amount in txPrebuild received from BitGo servers does not match txParams supplied by client'
2946+
throwRecipientMismatch(
2947+
'normal transaction amount in txPrebuild received from BitGo servers does not match txParams supplied by client',
2948+
[{ address: txPrebuild.recipients[0].address, amount: txPrebuild.recipients[0].amount.toString() }]
29312949
);
29322950
}
29332951
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');
2952+
throwRecipientMismatch(
2953+
'destination address in normal txPrebuild does not match that in txParams supplied by client',
2954+
[{ address: txPrebuild.recipients[0].address, amount: txPrebuild.recipients[0].amount.toString() }]
2955+
);
29352956
}
29362957
}
29372958
// Check coin is correct for all transaction types
29382959
if (!this.verifyCoin(txPrebuild)) {
2939-
throwTxMismatch(`coin in txPrebuild did not match that in txParams supplied by client`);
2960+
throw new TxIntentMismatchError(
2961+
'coin in txPrebuild did not match that in txParams supplied by client',
2962+
undefined,
2963+
[txParams],
2964+
txPrebuild?.txHex
2965+
);
29402966
}
29412967
return true;
29422968
}

0 commit comments

Comments
 (0)