From e14641b61a0de0676bc6520622fecc9f9677819e Mon Sep 17 00:00:00 2001 From: Derek Chen Date: Tue, 11 Nov 2025 10:19:50 -0500 Subject: [PATCH] feat(sdk-core): update erc20 token transfer check for walletconnect Ticket: SC-3865 --- .../src/abstractEthLikeNewCoins.ts | 18 +++++++++- .../test/v2/unit/internal/tssUtils/ecdsa.ts | 33 +++++++++++++++++++ .../sdk-core/src/bitgo/baseCoin/iBaseCoin.ts | 1 + 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/modules/abstract-eth/src/abstractEthLikeNewCoins.ts b/modules/abstract-eth/src/abstractEthLikeNewCoins.ts index c77f6e1d0c..20246605ff 100644 --- a/modules/abstract-eth/src/abstractEthLikeNewCoins.ts +++ b/modules/abstract-eth/src/abstractEthLikeNewCoins.ts @@ -2858,7 +2858,23 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin { { address: addHexPrefix(recipientAddress.toString()), amount: amount.toString() }, ]); } - if (expectedDestination.toLowerCase() !== addHexPrefix(recipientAddress.toString()).toLowerCase()) { + + // Check if recipients[0].data exists (WalletConnect flow) + let expectedRecipientAddress: string; + const recipientData = (recipients[0] as any).data; + if (recipientData && recipientData.startsWith('0xa9059cbb')) { + // WalletConnect: decode expected recipient from recipients[0].data + const [expectedRecipient] = getRawDecoded( + ['address', 'uint256'], + getBufferedByteCode('0xa9059cbb', recipientData) + ); + expectedRecipientAddress = addHexPrefix(expectedRecipient.toString()).toLowerCase(); + } else { + // Normal flow: use recipients[0].address + expectedRecipientAddress = expectedDestination.toLowerCase(); + } + + if (expectedRecipientAddress !== addHexPrefix(recipientAddress.toString()).toLowerCase()) { throwRecipientMismatch('destination address does not match with the recipient address', [ { address: addHexPrefix(recipientAddress.toString()), amount: amount.toString() }, ]); diff --git a/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts b/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts index 0c39f62c3d..53c167f496 100644 --- a/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts +++ b/modules/bitgo/test/v2/unit/internal/tssUtils/ecdsa.ts @@ -856,6 +856,39 @@ describe('TSS Ecdsa Utils:', async function () { .should.be.rejectedWith('the transaction amount in txPrebuild does not match the value given by client'); }); + it('signTxRequest should succeed for WalletConnect ERC20 transfer with data field', async function () { + nock.cleanAll(); + // WalletConnect ERC20 transfer: recipients[0].address is token contract, recipients[0].data contains the actual recipient + const signableHex = + '02f86f83088bb00283e1d7dd84768ea6898301e04b94d9327fd36c3312466efed23ff0493453ee32f55180b844a9059cbb0000000000000000000000007d7e63af583ba73ba5c927dbd028153963566bef00000000000000000000000000000000000000000000000000470de4df820000c0'; + const serializedTxHex = + '02f87283088bb00283e1d7dd84768ea6898301e04b94d9327fd36c3312466efed23ff0493453ee32f55180b844a9059cbb0000000000000000000000007d7e63af583ba73ba5c927dbd028153963566bef00000000000000000000000000000000000000000000000000470de4df820000c0808080'; + await setupSignTxRequestNocks(true, userSignShare, aShare, dShare, enterpriseData, { + signableHex, + serializedTxHex, + apiVersion: 'full', + }); + await tssUtils.signTxRequest({ + txRequest: txRequestId, + prv: JSON.stringify({ + pShare: userKeyShare.pShare, + bitgoNShare: bitgoKeyShare.nShares[1], + backupNShare: backupKeyShare.nShares[1], + }), + reqId, + txParams: { + recipients: [ + { + address: '0xd9327fd36c3312466efed23ff0493453ee32f551', // Token contract address + amount: '20000000000000000', + data: '0xa9059cbb0000000000000000000000007d7e63af583ba73ba5c927dbd028153963566bef00000000000000000000000000000000000000000000000000470de4df820000', // ERC20 transfer calldata with actual recipient + }, + ], + type: 'transfer', + }, + }); + }); + it('getOfflineSignerPaillierModulus should succeed', async function () { const paillierModulus = tssUtils.getOfflineSignerPaillierModulus({ prv: JSON.stringify({ diff --git a/modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts b/modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts index 86e8d9ffb6..1404ab5111 100644 --- a/modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts +++ b/modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts @@ -81,6 +81,7 @@ export interface ITransactionRecipient { amount: string | number; tokenName?: string; memo?: string; + data?: string; } /**