Skip to content

Commit 6b3505d

Browse files
feat: fixed verify transaction flow for tao and polyx
ticket: CECHO-991 Signed-off-by: B.Prasad <bhaveshprasad584@bitgo.com>
1 parent df08211 commit 6b3505d

3 files changed

Lines changed: 210 additions & 7 deletions

File tree

modules/abstract-substrate/src/abstractSubstrateCoin.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ import {
2020
RecoveryTxRequest,
2121
SignedTransaction,
2222
TssVerifyAddressOptions,
23+
TxIntentMismatchRecipientError,
2324
UnexpectedAddressError,
2425
verifyEddsaTssWalletAddress,
2526
VerifyTransactionOptions,
2627
} from '@bitgo/sdk-core';
2728
import { CoinFamily, BaseCoin as StaticsBaseCoin } from '@bitgo/statics';
2829
import { KeyPair as SubstrateKeyPair, Transaction } from './lib';
30+
import { NativeTransferBuilder } from './lib/nativeTransferBuilder';
2931
import { DEFAULT_SUBSTRATE_PREFIX } from './lib/constants';
3032
import { SignTransactionOptions, VerifiedTransactionParameters, Material } from './lib/iface';
3133
import utils from './lib/utils';
@@ -132,12 +134,55 @@ export class SubstrateCoin extends BaseCoin {
132134

133135
/** @inheritDoc **/
134136
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
135-
const { txParams } = params;
136-
if (Array.isArray(txParams.recipients) && txParams.recipients.length > 1) {
137-
throw new Error(
138-
`${this.getChain()} doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.`
139-
);
137+
const { txParams, txPrebuild } = params;
138+
139+
if (!txParams) {
140+
throw new Error('missing txParams');
141+
}
142+
if (!txPrebuild) {
143+
throw new Error('missing txPrebuild');
140144
}
145+
if (!txPrebuild.txHex) {
146+
throw new Error('missing txHex in txPrebuild');
147+
}
148+
149+
const factory = this.getBuilder();
150+
const txBuilder = factory.from(txPrebuild.txHex) as unknown as NativeTransferBuilder;
151+
const txTo: string = txBuilder['_to'];
152+
const txAmount: string = txBuilder['_amount'];
153+
const isSweep: boolean = txBuilder['_sweepFreeBalance'] === true;
154+
155+
if (txParams.recipients !== undefined) {
156+
if (txParams.recipients.length === 0) {
157+
throw new Error('missing recipients in txParams');
158+
}
159+
if (Array.isArray(txParams.recipients) && txParams.recipients.length > 1) {
160+
throw new Error(
161+
`${this.getChain()} doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.`
162+
);
163+
}
164+
165+
if (txParams.recipients[0].address !== txTo) {
166+
throw new TxIntentMismatchRecipientError(
167+
`Recipient address ${txParams.recipients[0].address} does not match transaction destination address ${txTo}`,
168+
undefined,
169+
[txParams],
170+
txPrebuild.txHex,
171+
[{ address: txTo, amount: txAmount }]
172+
);
173+
}
174+
175+
if (!isSweep && txParams.recipients[0].amount !== txAmount) {
176+
throw new TxIntentMismatchRecipientError(
177+
`Recipient amount ${txParams.recipients[0].amount} does not match transaction amount ${txAmount}`,
178+
undefined,
179+
[txParams],
180+
txPrebuild.txHex,
181+
[{ address: txTo, amount: txAmount }]
182+
);
183+
}
184+
}
185+
141186
return true;
142187
}
143188

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

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { POLYX_ADDRESS_FORMAT, TPOLYX_ADDRESS_FORMAT } from '../../src/lib/const
66
import * as sinon from 'sinon';
77
import * as testData from '../resources/wrwUsers';
88
import { afterEach } from 'mocha';
9-
import { genesisHash, specVersion, txVersion } from '../resources';
9+
import { genesisHash, specVersion, txVersion, rawTx } from '../resources';
10+
import { TxIntentMismatchRecipientError } from '@bitgo/sdk-core';
1011

1112
describe('Polyx:', function () {
1213
let bitgo: TestBitGoAPI;
@@ -199,4 +200,72 @@ describe('Polyx:', function () {
199200
);
200201
});
201202
});
203+
204+
describe('verifyTransaction', function () {
205+
const transferTo = '5F8jxKE81GhFrphyfMFr5UjeAz5wS4AaZFmeFPnf8wTetD72';
206+
const transferAmount = '2000000000';
207+
const wrongAddress = '5GhbC6n2pUFrX98DwyPit67fB5AwQvVCwZ4j2HKA7a4dUK4y';
208+
209+
describe('transfer transaction', function () {
210+
it('should return true when address and amount match', async function () {
211+
const result = await baseCoin.verifyTransaction({
212+
txPrebuild: { txHex: rawTx.transfer.signed },
213+
txParams: { recipients: [{ address: transferTo, amount: transferAmount }] },
214+
});
215+
result.should.be.true();
216+
});
217+
218+
it('should throw TxIntentMismatchRecipientError for address mismatch', async function () {
219+
await baseCoin
220+
.verifyTransaction({
221+
txPrebuild: { txHex: rawTx.transfer.signed },
222+
txParams: { recipients: [{ address: wrongAddress, amount: transferAmount }] },
223+
})
224+
.should.be.rejectedWith(TxIntentMismatchRecipientError);
225+
});
226+
227+
it('should throw TxIntentMismatchRecipientError for amount mismatch', async function () {
228+
await baseCoin
229+
.verifyTransaction({
230+
txPrebuild: { txHex: rawTx.transfer.signed },
231+
txParams: { recipients: [{ address: transferTo, amount: '1' }] },
232+
})
233+
.should.be.rejectedWith(TxIntentMismatchRecipientError);
234+
});
235+
});
236+
237+
describe('guard cases', function () {
238+
it('should throw when txHex is missing', async function () {
239+
await baseCoin
240+
.verifyTransaction({
241+
txPrebuild: {},
242+
txParams: { recipients: [{ address: transferTo, amount: transferAmount }] },
243+
})
244+
.should.be.rejectedWith('missing txHex in txPrebuild');
245+
});
246+
247+
it('should throw when recipients has more than 1 entry', async function () {
248+
await baseCoin
249+
.verifyTransaction({
250+
txPrebuild: { txHex: rawTx.transfer.signed },
251+
txParams: {
252+
recipients: [
253+
{ address: transferTo, amount: transferAmount },
254+
{ address: wrongAddress, amount: transferAmount },
255+
],
256+
},
257+
})
258+
.should.be.rejectedWith(/support sending to more than 1 destination address/);
259+
});
260+
261+
it('should throw when recipients is an empty array', async function () {
262+
await baseCoin
263+
.verifyTransaction({
264+
txPrebuild: { txHex: rawTx.transfer.signed },
265+
txParams: { recipients: [] },
266+
})
267+
.should.be.rejectedWith('missing recipients in txParams');
268+
});
269+
});
270+
});
202271
});

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

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { BitGoAPI } from '@bitgo/sdk-api';
44
import { Tao, Ttao } from '../../src';
55
import * as sinon from 'sinon';
66
import * as testData from './fixtures';
7-
import { txVersion, genesisHash, specVersion } from '../resources';
7+
import { txVersion, genesisHash, specVersion, rawTx } from '../resources';
88
import { afterEach } from 'mocha';
9+
import { TxIntentMismatchRecipientError } from '@bitgo/sdk-core';
910

1011
describe('Tao:', function () {
1112
let bitgo: TestBitGoAPI;
@@ -508,4 +509,92 @@ describe('Tao:', function () {
508509
);
509510
});
510511
});
512+
513+
describe('verifyTransaction', function () {
514+
const transferTo = '5EQZSJmHuFH8asYYJruSRwpJmE5aqSdhdiX9oxRbxujKUkTe';
515+
const transferAmount = '2';
516+
const sweepTo = '5EQZSJmHuFH8asYYJruSRwpJmE5aqSdhdiX9oxRbxujKUkTe';
517+
const wrongAddress = '5Ffp1wJCPu4hzVDTo7XaMLqZSvSadyUQmxWPDw74CBjECSoq';
518+
519+
describe('transfer transaction', function () {
520+
it('should return true when address and amount match', async function () {
521+
const result = await baseCoin.verifyTransaction({
522+
txPrebuild: { txHex: rawTx.transfer.signed },
523+
txParams: { recipients: [{ address: transferTo, amount: transferAmount }] },
524+
});
525+
result.should.be.true();
526+
});
527+
528+
it('should throw TxIntentMismatchRecipientError for address mismatch', async function () {
529+
await baseCoin
530+
.verifyTransaction({
531+
txPrebuild: { txHex: rawTx.transfer.signed },
532+
txParams: { recipients: [{ address: wrongAddress, amount: transferAmount }] },
533+
})
534+
.should.be.rejectedWith(TxIntentMismatchRecipientError);
535+
});
536+
537+
it('should throw TxIntentMismatchRecipientError for amount mismatch', async function () {
538+
await baseCoin
539+
.verifyTransaction({
540+
txPrebuild: { txHex: rawTx.transfer.signed },
541+
txParams: { recipients: [{ address: transferTo, amount: '9999' }] },
542+
})
543+
.should.be.rejectedWith(TxIntentMismatchRecipientError);
544+
});
545+
});
546+
547+
describe('sweep transaction', function () {
548+
it('should return true when address matches (amount check skipped for sweep)', async function () {
549+
const result = await baseCoin.verifyTransaction({
550+
txPrebuild: { txHex: rawTx.transferAll.signed },
551+
txParams: { recipients: [{ address: sweepTo, amount: '9999999' }] },
552+
});
553+
result.should.be.true();
554+
});
555+
556+
it('should throw TxIntentMismatchRecipientError when sweep address does not match', async function () {
557+
await baseCoin
558+
.verifyTransaction({
559+
txPrebuild: { txHex: rawTx.transferAll.signed },
560+
txParams: { recipients: [{ address: wrongAddress, amount: '0' }] },
561+
})
562+
.should.be.rejectedWith(TxIntentMismatchRecipientError);
563+
});
564+
});
565+
566+
describe('guard cases', function () {
567+
it('should throw when txHex is missing', async function () {
568+
await baseCoin
569+
.verifyTransaction({
570+
txPrebuild: {},
571+
txParams: { recipients: [{ address: transferTo, amount: transferAmount }] },
572+
})
573+
.should.be.rejectedWith('missing txHex in txPrebuild');
574+
});
575+
576+
it('should throw when recipients has more than 1 entry', async function () {
577+
await baseCoin
578+
.verifyTransaction({
579+
txPrebuild: { txHex: rawTx.transfer.signed },
580+
txParams: {
581+
recipients: [
582+
{ address: transferTo, amount: transferAmount },
583+
{ address: wrongAddress, amount: transferAmount },
584+
],
585+
},
586+
})
587+
.should.be.rejectedWith(/doesn't support sending to more than 1 destination address/);
588+
});
589+
590+
it('should throw when recipients is an empty array', async function () {
591+
await baseCoin
592+
.verifyTransaction({
593+
txPrebuild: { txHex: rawTx.transfer.signed },
594+
txParams: { recipients: [] },
595+
})
596+
.should.be.rejectedWith('missing recipients in txParams');
597+
});
598+
});
599+
});
511600
});

0 commit comments

Comments
 (0)