Skip to content

Commit 8bf301d

Browse files
fix: override verify tss txn for xdc token
Ticket: COIN-7060
1 parent dbb9215 commit 8bf301d

File tree

3 files changed

+216
-1
lines changed

3 files changed

+216
-1
lines changed

modules/sdk-coin-xdc/src/xdcToken.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
*/
44
import { EthLikeTokenConfig, coins } from '@bitgo/statics';
55
import { BitGoBase, CoinConstructor, NamedCoinConstructor, common, MPCAlgorithm } from '@bitgo/sdk-core';
6-
import { CoinNames, EthLikeToken, recoveryBlockchainExplorerQuery } from '@bitgo/abstract-eth';
6+
import {
7+
CoinNames,
8+
EthLikeToken,
9+
recoveryBlockchainExplorerQuery,
10+
VerifyEthTransactionOptions,
11+
} from '@bitgo/abstract-eth';
712

813
import { TransactionBuilder } from './lib';
914
export { EthLikeTokenConfig };
@@ -52,4 +57,34 @@ export class XdcToken extends EthLikeToken {
5257
getMPCAlgorithm(): MPCAlgorithm {
5358
return 'ecdsa';
5459
}
60+
61+
/**
62+
* Verify if a tss transaction is valid
63+
*
64+
* @param {VerifyEthTransactionOptions} params
65+
* @param {TransactionParams} params.txParams - params object passed to send
66+
* @param {TransactionPrebuild} params.txPrebuild - prebuild object returned by server
67+
* @param {Wallet} params.wallet - Wallet object to obtain keys to verify against
68+
* @returns {boolean}
69+
*/
70+
async verifyTssTransaction(params: VerifyEthTransactionOptions): Promise<boolean> {
71+
const { txParams, txPrebuild, wallet } = params;
72+
if (
73+
!txParams?.recipients &&
74+
!(
75+
txParams.prebuildTx?.consolidateId ||
76+
(txParams.type && ['acceleration', 'fillNonce', 'transferToken'].includes(txParams.type))
77+
)
78+
) {
79+
throw new Error(`missing txParams`);
80+
}
81+
if (!wallet || !txPrebuild) {
82+
throw new Error(`missing params`);
83+
}
84+
if (txParams.hop && txParams.recipients && txParams.recipients.length > 1) {
85+
throw new Error(`tx cannot be both a batch and hop transaction`);
86+
}
87+
88+
return true;
89+
}
5590
}

modules/sdk-coin-xdc/test/resources.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,34 @@ const getBalanceResponseNonBitGoRecovery: Record<string, unknown> = {
7777
message: 'OK',
7878
};
7979

80+
// Mock data for txdc:tmt token transfer TSS transaction
81+
export const mockTokenTransferData = {
82+
txRequestId: '2475368d-f604-46e3-a743-e32f663fa350',
83+
walletId: '695e1ca4fb4a739c8c6f9b49120c55c7',
84+
serializedTxHex:
85+
'f86a0485045d964b8083061a8094b283ec8dad644effc5c4c50bb7bb21442ac3c2db80b844a9059cbb000000000000000000000000421cdf5e890070c28db0fd8e4bf87deac0cd0ffc00000000000000000000000000000000000000000000000000000000000f4240808080',
86+
signableHex:
87+
'f86a0485045d964b8083061a8094b283ec8dad644effc5c4c50bb7bb21442ac3c2db80b844a9059cbb000000000000000000000000421cdf5e890070c28db0fd8e4bf87deac0cd0ffc00000000000000000000000000000000000000000000000000000000000f4240338080',
88+
tokenContractAddress: '0xb283ec8dad644effc5c4c50bb7bb21442ac3c2db',
89+
recipientAddress: '0x421cdf5e890070c28db0fd8e4bf87deac0cd0ffc',
90+
senderAddress: '0x6aafaddf545f96772140f0008190c176a065df9a',
91+
tokenAmount: '1000000',
92+
feeInfo: {
93+
fee: 7500000000000000,
94+
feeString: '7500000000000000',
95+
},
96+
txPrebuild: {
97+
txHex:
98+
'f86a0485045d964b8083061a8094b283ec8dad644effc5c4c50bb7bb21442ac3c2db80b844a9059cbb000000000000000000000000421cdf5e890070c28db0fd8e4bf87deac0cd0ffc00000000000000000000000000000000000000000000000000000000000f4240808080',
99+
recipients: [
100+
{
101+
address: '0x421cdf5e890070c28db0fd8e4bf87deac0cd0ffc',
102+
amount: '1000000',
103+
},
104+
],
105+
},
106+
};
107+
80108
export const mockDataNonBitGoRecovery = {
81109
recoveryDestination: '0xd76b586901850f2c656db0cbef795c0851bbec35',
82110
userKeyData:

modules/sdk-coin-xdc/test/unit/xdcToken.ts

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import 'should';
22
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
33
import { BitGoAPI } from '@bitgo/sdk-api';
4+
import { IWallet } from '@bitgo/sdk-core';
45

56
import { register, XdcToken } from '../../src';
7+
import { mockTokenTransferData } from '../resources';
68

79
describe('XDC Token:', function () {
810
let bitgo: TestBitGoAPI;
@@ -117,4 +119,154 @@ describe('XDC Token:', function () {
117119
});
118120
});
119121
});
122+
123+
describe('verifyTssTransaction', function () {
124+
it('should return true for valid token transfer params', async function () {
125+
const token = bitgo.coin('txdc:tmt') as XdcToken;
126+
const mockWallet = {} as unknown as IWallet;
127+
128+
const result = await token.verifyTssTransaction({
129+
txParams: {
130+
recipients: [
131+
{
132+
address: mockTokenTransferData.recipientAddress,
133+
amount: mockTokenTransferData.tokenAmount,
134+
},
135+
],
136+
},
137+
txPrebuild: mockTokenTransferData.txPrebuild as unknown as Parameters<
138+
typeof token.verifyTssTransaction
139+
>[0]['txPrebuild'],
140+
wallet: mockWallet,
141+
});
142+
143+
result.should.equal(true);
144+
});
145+
146+
it('should return true for transferToken type without recipients', async function () {
147+
const token = bitgo.coin('txdc:tmt') as XdcToken;
148+
const mockWallet = {} as unknown as IWallet;
149+
150+
const result = await token.verifyTssTransaction({
151+
txParams: {
152+
type: 'transferToken',
153+
},
154+
txPrebuild: mockTokenTransferData.txPrebuild as unknown as Parameters<
155+
typeof token.verifyTssTransaction
156+
>[0]['txPrebuild'],
157+
wallet: mockWallet,
158+
});
159+
160+
result.should.equal(true);
161+
});
162+
163+
it('should throw error when txParams.recipients is missing and no valid type', async function () {
164+
const token = bitgo.coin('txdc:tmt') as XdcToken;
165+
const mockWallet = {} as unknown as IWallet;
166+
167+
await token
168+
.verifyTssTransaction({
169+
txParams: {},
170+
txPrebuild: mockTokenTransferData.txPrebuild as unknown as Parameters<
171+
typeof token.verifyTssTransaction
172+
>[0]['txPrebuild'],
173+
wallet: mockWallet,
174+
})
175+
.should.be.rejectedWith('missing txParams');
176+
});
177+
178+
it('should throw error when wallet is missing', async function () {
179+
const token = bitgo.coin('txdc:tmt') as XdcToken;
180+
181+
await token
182+
.verifyTssTransaction({
183+
txParams: {
184+
recipients: [
185+
{
186+
address: mockTokenTransferData.recipientAddress,
187+
amount: mockTokenTransferData.tokenAmount,
188+
},
189+
],
190+
},
191+
txPrebuild: mockTokenTransferData.txPrebuild as unknown as Parameters<
192+
typeof token.verifyTssTransaction
193+
>[0]['txPrebuild'],
194+
wallet: undefined as unknown as IWallet,
195+
})
196+
.should.be.rejectedWith('missing params');
197+
});
198+
199+
it('should throw error when txPrebuild is missing', async function () {
200+
const token = bitgo.coin('txdc:tmt') as XdcToken;
201+
const mockWallet = {} as unknown as IWallet;
202+
203+
await token
204+
.verifyTssTransaction({
205+
txParams: {
206+
recipients: [
207+
{
208+
address: mockTokenTransferData.recipientAddress,
209+
amount: mockTokenTransferData.tokenAmount,
210+
},
211+
],
212+
},
213+
txPrebuild: undefined as unknown as Parameters<typeof token.verifyTssTransaction>[0]['txPrebuild'],
214+
wallet: mockWallet,
215+
})
216+
.should.be.rejectedWith('missing params');
217+
});
218+
219+
it('should throw error for batch + hop transaction', async function () {
220+
const token = bitgo.coin('txdc:tmt') as XdcToken;
221+
const mockWallet = {} as unknown as IWallet;
222+
223+
await token
224+
.verifyTssTransaction({
225+
txParams: {
226+
hop: true,
227+
recipients: [
228+
{ address: '0x1111111111111111111111111111111111111111', amount: '1000' },
229+
{ address: '0x2222222222222222222222222222222222222222', amount: '2000' },
230+
],
231+
},
232+
txPrebuild: mockTokenTransferData.txPrebuild as unknown as Parameters<
233+
typeof token.verifyTssTransaction
234+
>[0]['txPrebuild'],
235+
wallet: mockWallet,
236+
})
237+
.should.be.rejectedWith('tx cannot be both a batch and hop transaction');
238+
});
239+
240+
it('should not throw EIP155 error when verifying token transaction', async function () {
241+
// This test ensures that verifyTssTransaction does NOT parse the txHex
242+
// which would fail with "Incompatible EIP155-based V" error
243+
const token = bitgo.coin('txdc:tmt') as XdcToken;
244+
const mockWallet = {} as unknown as IWallet;
245+
246+
// Use the signableHex (with v=51) which would fail if parsed
247+
const txPrebuildWithSignableHex = {
248+
...mockTokenTransferData.txPrebuild,
249+
txHex: mockTokenTransferData.signableHex,
250+
};
251+
252+
// This should NOT throw EIP155 error because verifyTssTransaction
253+
// does not parse the transaction
254+
const result = await token.verifyTssTransaction({
255+
txParams: {
256+
recipients: [
257+
{
258+
address: mockTokenTransferData.recipientAddress,
259+
amount: mockTokenTransferData.tokenAmount,
260+
},
261+
],
262+
},
263+
txPrebuild: txPrebuildWithSignableHex as unknown as Parameters<
264+
typeof token.verifyTssTransaction
265+
>[0]['txPrebuild'],
266+
wallet: mockWallet,
267+
});
268+
269+
result.should.equal(true);
270+
});
271+
});
120272
});

0 commit comments

Comments
 (0)