Skip to content

Commit 67c754e

Browse files
Marzooqacursoragent
andcommitted
fix(sdk-coin-xdc): remove weak verifyTssTransaction override
Drop the unconditional-return-true stubs from Xdc/XdcToken so they inherit AbstractEthLikeNewCoins transfer validation. Relies on the sdk-core serializedTxHex verify path for legacy EIP-155. Ticket: WCI-1169 Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 8c5562e commit 67c754e

4 files changed

Lines changed: 186 additions & 234 deletions

File tree

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

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
1-
import {
2-
BaseCoin,
3-
BitGoBase,
4-
common,
5-
MPCAlgorithm,
6-
MultisigType,
7-
multisigTypes,
8-
NO_RECIPIENT_TX_TYPES,
9-
} from '@bitgo/sdk-core';
1+
import { BaseCoin, BitGoBase, common, MPCAlgorithm, MultisigType, multisigTypes } from '@bitgo/sdk-core';
102
import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics';
113
import {
124
AbstractEthLikeNewCoins,
135
recoveryBlockchainExplorerQuery,
146
UnsignedSweepTxMPCv2,
157
RecoverOptions,
168
OfflineVaultTxInfo,
17-
VerifyEthTransactionOptions,
189
} from '@bitgo/abstract-eth';
1910
import { TransactionBuilder } from './lib';
2011

@@ -55,35 +46,4 @@ export class Xdc extends AbstractEthLikeNewCoins {
5546
const explorerUrl = common.Environments[this.bitgo.getEnv()].xdcExplorerBaseUrl;
5647
return await recoveryBlockchainExplorerQuery(query, explorerUrl as string, apiToken);
5748
}
58-
/**
59-
* Verify if a tss transaction is valid
60-
*
61-
* @param {VerifyEthTransactionOptions} params
62-
* @param {TransactionParams} params.txParams - params object passed to send
63-
* @param {TransactionPrebuild} params.txPrebuild - prebuild object returned by server
64-
* @param {Wallet} params.wallet - Wallet object to obtain keys to verify against
65-
* @returns {boolean}
66-
*/
67-
async verifyTssTransaction(params: VerifyEthTransactionOptions): Promise<boolean> {
68-
const { txParams, txPrebuild, wallet } = params;
69-
if (
70-
!txParams?.recipients &&
71-
!(
72-
txParams.prebuildTx?.consolidateId ||
73-
txParams.stakingRequestId ||
74-
txParams.prebuildTx?.stakingRequestId ||
75-
(txParams.type && NO_RECIPIENT_TX_TYPES.has(txParams.type))
76-
)
77-
) {
78-
throw new Error(`missing txParams`);
79-
}
80-
if (!wallet || !txPrebuild) {
81-
throw new Error(`missing params`);
82-
}
83-
if (txParams.hop && txParams.recipients && txParams.recipients.length > 1) {
84-
throw new Error(`tx cannot be both a batch and hop transaction`);
85-
}
86-
87-
return true;
88-
}
8949
}

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

Lines changed: 2 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,8 @@
22
* @prettier
33
*/
44
import { EthLikeTokenConfig, coins } from '@bitgo/statics';
5-
import {
6-
BitGoBase,
7-
CoinConstructor,
8-
NamedCoinConstructor,
9-
common,
10-
MPCAlgorithm,
11-
NO_RECIPIENT_TX_TYPES,
12-
} from '@bitgo/sdk-core';
13-
import {
14-
CoinNames,
15-
EthLikeToken,
16-
recoveryBlockchainExplorerQuery,
17-
VerifyEthTransactionOptions,
18-
} from '@bitgo/abstract-eth';
5+
import { BitGoBase, CoinConstructor, NamedCoinConstructor, common, MPCAlgorithm } from '@bitgo/sdk-core';
6+
import { CoinNames, EthLikeToken, recoveryBlockchainExplorerQuery } from '@bitgo/abstract-eth';
197

208
import { TransactionBuilder } from './lib';
219
export { EthLikeTokenConfig };
@@ -64,36 +52,4 @@ export class XdcToken extends EthLikeToken {
6452
getMPCAlgorithm(): MPCAlgorithm {
6553
return 'ecdsa';
6654
}
67-
68-
/**
69-
* Verify if a tss transaction is valid
70-
*
71-
* @param {VerifyEthTransactionOptions} params
72-
* @param {TransactionParams} params.txParams - params object passed to send
73-
* @param {TransactionPrebuild} params.txPrebuild - prebuild object returned by server
74-
* @param {Wallet} params.wallet - Wallet object to obtain keys to verify against
75-
* @returns {boolean}
76-
*/
77-
async verifyTssTransaction(params: VerifyEthTransactionOptions): Promise<boolean> {
78-
const { txParams, txPrebuild, wallet } = params;
79-
if (
80-
!txParams?.recipients &&
81-
!(
82-
txParams.prebuildTx?.consolidateId ||
83-
txParams.stakingRequestId ||
84-
txParams.prebuildTx?.stakingRequestId ||
85-
(txParams.type && NO_RECIPIENT_TX_TYPES.has(txParams.type))
86-
)
87-
) {
88-
throw new Error(`missing txParams`);
89-
}
90-
if (!wallet || !txPrebuild) {
91-
throw new Error(`missing params`);
92-
}
93-
if (txParams.hop && txParams.recipients && txParams.recipients.length > 1) {
94-
throw new Error(`tx cannot be both a batch and hop transaction`);
95-
}
96-
97-
return true;
98-
}
9955
}

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

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,20 @@ import { Xdc, Txdc } from '../../src/index';
77
import { UnsignedSweepTxMPCv2 } from '@bitgo/abstract-eth';
88
import { mockDataUnsignedSweep, mockDataNonBitGoRecovery } from '../resources';
99
import nock from 'nock';
10-
import { common } from '@bitgo/sdk-core';
10+
import { common, TransactionType, Wallet } from '@bitgo/sdk-core';
1111
import { Transaction } from '@ethereumjs/tx';
1212
import { stripHexPrefix } from '@ethereumjs/util';
1313

14+
import { TransactionBuilder } from '../../src/lib';
15+
import { getBuilder } from './getBuilder';
16+
17+
/** Encode ERC-20 transfer(address,uint256) calldata without ethereumjs-abi. */
18+
function encodeErc20Transfer(to: string, amount: string): string {
19+
const address = to.toLowerCase().replace(/^0x/, '').padStart(64, '0');
20+
const value = BigInt(amount).toString(16).padStart(64, '0');
21+
return `0xa9059cbb${address}${value}`;
22+
}
23+
1424
const bitgo: TestBitGoAPI = TestBitGo.decorate(BitGoAPI, { env: 'test' });
1525

1626
describe('xdc', function () {
@@ -45,6 +55,119 @@ describe('xdc', function () {
4555
txdc.allowsAccountConsolidations().should.equal(false);
4656
});
4757
});
58+
59+
describe('verifyTssTransaction', function () {
60+
const recipientAddress = '0x174cfd823af8ce27ed0afee3fcf3c3ba259116be';
61+
const wrongAddress = '0x7e85bdc27c050e3905ebf4b8e634d9ad6edd0de6';
62+
const tokenContractAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48';
63+
const transferAmount = '1000000000000000000';
64+
65+
it('should accept a native XDC transfer where txHex matches declared recipient', async function () {
66+
const coin = bitgo.coin('txdc') as Txdc;
67+
68+
const txBuilder = getBuilder('txdc') as TransactionBuilder;
69+
txBuilder.type(TransactionType.SingleSigSend);
70+
txBuilder.fee({ fee: '10', gasLimit: '21000' });
71+
txBuilder.counter(1);
72+
txBuilder.contract(recipientAddress);
73+
txBuilder.value(transferAmount);
74+
const tx = await txBuilder.build();
75+
const txHex = tx.toBroadcastFormat();
76+
77+
const wallet = new Wallet(bitgo, coin, { coinSpecific: { baseAddress: recipientAddress } });
78+
79+
const result = await coin.verifyTssTransaction({
80+
txParams: {
81+
type: 'transfer',
82+
recipients: [{ address: recipientAddress, amount: transferAmount }],
83+
} as any,
84+
txPrebuild: { txHex, coin: 'txdc', walletId: 'fakeWalletId' } as any,
85+
wallet,
86+
});
87+
result.should.equal(true);
88+
});
89+
90+
it('should reject a native XDC transfer when txHex recipient does not match declared recipient', async function () {
91+
const coin = bitgo.coin('txdc') as Txdc;
92+
93+
const txBuilder = getBuilder('txdc') as TransactionBuilder;
94+
txBuilder.type(TransactionType.SingleSigSend);
95+
txBuilder.fee({ fee: '10', gasLimit: '21000' });
96+
txBuilder.counter(1);
97+
txBuilder.contract(wrongAddress);
98+
txBuilder.value(transferAmount);
99+
const tx = await txBuilder.build();
100+
const txHex = tx.toBroadcastFormat();
101+
102+
const wallet = new Wallet(bitgo, coin, { coinSpecific: { baseAddress: recipientAddress } });
103+
104+
await coin
105+
.verifyTssTransaction({
106+
txParams: {
107+
type: 'transfer',
108+
recipients: [{ address: recipientAddress, amount: transferAmount }],
109+
} as any,
110+
txPrebuild: { txHex, coin: 'txdc', walletId: 'fakeWalletId' } as any,
111+
wallet,
112+
})
113+
.should.be.rejectedWith('destination address does not match with the recipient address');
114+
});
115+
116+
it('should accept an ERC-20 token transfer where calldata matches declared recipient', async function () {
117+
const coin = bitgo.coin('txdc') as Txdc;
118+
119+
const erc20TransferData = encodeErc20Transfer(recipientAddress, '10000000');
120+
121+
const txBuilder = getBuilder('txdc') as TransactionBuilder;
122+
txBuilder.type(TransactionType.ContractCall);
123+
txBuilder.fee({ fee: '10', gasLimit: '60000' });
124+
txBuilder.counter(1);
125+
txBuilder.contract(tokenContractAddress);
126+
txBuilder.data(erc20TransferData);
127+
const tx = await txBuilder.build();
128+
const txHex = tx.toBroadcastFormat();
129+
130+
const wallet = new Wallet(bitgo, coin, { coinSpecific: { baseAddress: recipientAddress } });
131+
132+
const result = await coin.verifyTssTransaction({
133+
txParams: {
134+
type: 'transfer',
135+
recipients: [{ address: recipientAddress, amount: '10000000' }],
136+
} as any,
137+
txPrebuild: { txHex, coin: 'txdc', walletId: 'fakeWalletId' } as any,
138+
wallet,
139+
});
140+
result.should.equal(true);
141+
});
142+
143+
it('should reject an ERC-20 token transfer when calldata recipient does not match declared recipient', async function () {
144+
const coin = bitgo.coin('txdc') as Txdc;
145+
146+
const erc20TransferData = encodeErc20Transfer(wrongAddress, '10000000');
147+
148+
const txBuilder = getBuilder('txdc') as TransactionBuilder;
149+
txBuilder.type(TransactionType.ContractCall);
150+
txBuilder.fee({ fee: '10', gasLimit: '60000' });
151+
txBuilder.counter(1);
152+
txBuilder.contract(tokenContractAddress);
153+
txBuilder.data(erc20TransferData);
154+
const tx = await txBuilder.build();
155+
const txHex = tx.toBroadcastFormat();
156+
157+
const wallet = new Wallet(bitgo, coin, { coinSpecific: { baseAddress: recipientAddress } });
158+
159+
await coin
160+
.verifyTssTransaction({
161+
txParams: {
162+
type: 'transfer',
163+
recipients: [{ address: recipientAddress, amount: '10000000' }],
164+
} as any,
165+
txPrebuild: { txHex, coin: 'txdc', walletId: 'fakeWalletId' } as any,
166+
wallet,
167+
})
168+
.should.be.rejectedWith('destination address does not match with the recipient address');
169+
});
170+
});
48171
});
49172

50173
describe('Build Unsigned Sweep for Self-Custody Cold Wallets - (MPCv2)', function () {

0 commit comments

Comments
 (0)