Skip to content

Commit 5c970c2

Browse files
fix: call verifytransaction coin method after fetching txrequest from server for eddsa coins
TICKET: WIN-5204
1 parent 0f0826e commit 5c970c2

File tree

10 files changed

+287
-8
lines changed

10 files changed

+287
-8
lines changed

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

+38
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,44 @@ export class Algo extends BaseCoin {
579579
}
580580

581581
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
582+
const coinConfig = coins.get(this.getChain());
583+
const { txPrebuild, txParams } = params;
584+
585+
// Validate the presence of txHex
586+
const rawTx = txPrebuild.txHex;
587+
if (!rawTx) {
588+
throw new Error('Missing required tx prebuild property: txHex');
589+
}
590+
591+
// Parse the transaction
592+
const transaction = new AlgoLib.Transaction(coinConfig);
593+
transaction.fromRawTransaction(Buffer.from(rawTx, 'hex').toString('base64'));
594+
const explainedTx = transaction.explainTransaction();
595+
596+
// Validate recipients
597+
if (txParams.recipients) {
598+
const filteredRecipients = txParams.recipients.map((recipient) => ({
599+
address: recipient.address,
600+
amount: BigInt(recipient.amount),
601+
}));
602+
603+
const filteredOutputs = explainedTx.outputs.map((output) => ({
604+
address: output.address,
605+
amount: BigInt(output.amount),
606+
}));
607+
608+
if (!_.isEqual(filteredOutputs, filteredRecipients)) {
609+
throw new Error('Transaction outputs do not match the expected recipients in txParams.');
610+
}
611+
612+
// Validate total amount
613+
const totalAmount = txParams.recipients.reduce((sum, recipient) => sum.plus(recipient.amount), new BigNumber(0));
614+
615+
if (!totalAmount.isEqualTo(explainedTx.outputAmount)) {
616+
throw new Error('Transaction total amount does not match the expected total amount.');
617+
}
618+
}
619+
582620
return true;
583621
}
584622

modules/sdk-coin-algo/src/lib/transaction.ts

+19
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,25 @@ export class Transaction extends BaseTransaction {
252252
return result;
253253
}
254254

255+
/**
256+
* Sets this transaction payload
257+
*
258+
* @param rawTx - The raw transaction in hex format
259+
*/
260+
fromRawTransaction(rawTransaction: string): void {
261+
try {
262+
// Decode the raw transaction using Algorand SDK
263+
const decodedTx = algosdk.decodeUnsignedTransaction(Buffer.from(rawTransaction, 'hex'));
264+
265+
// Extract and set transaction details
266+
this._algoTransaction = decodedTx;
267+
this._sender = algosdk.encodeAddress(decodedTx.from.publicKey);
268+
this._signatures = []; // Reset signatures as this is a raw transaction
269+
} catch (e) {
270+
throw new Error('Invalid raw transaction: ' + e.message);
271+
}
272+
}
273+
255274
/**
256275
* Load the input and output data on this transaction.
257276
*/

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

+39-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
VerifyAddressOptions,
2626
VerifyTransactionOptions,
2727
} from '@bitgo/sdk-core';
28+
import _ from 'lodash';
2829

2930
interface SignTransactionOptions extends BaseSignTransactionOptions {
3031
txPrebuild: TransactionPrebuild;
@@ -110,7 +111,44 @@ export class Cspr extends BaseCoin {
110111
}
111112

112113
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
113-
// TODO: Implement when available on the SDK.
114+
const coinConfig = coins.get(this.getChain());
115+
const { txPrebuild, txParams } = params;
116+
117+
// Validate the presence of txHex
118+
const rawTx = txPrebuild.txHex;
119+
if (!rawTx) {
120+
throw new Error('Missing required tx prebuild property: txHex');
121+
}
122+
123+
// Parse the transaction
124+
const transaction = new CsprLib.Transaction(coinConfig);
125+
transaction.fromRawTransaction(Buffer.from(rawTx, 'hex').toString('base64'));
126+
const explainedTx = transaction.explainTransaction();
127+
128+
// Validate recipients
129+
if (txParams.recipients) {
130+
const filteredRecipients = txParams.recipients.map((recipient) => ({
131+
address: recipient.address,
132+
amount: BigInt(recipient.amount),
133+
}));
134+
135+
const filteredOutputs = explainedTx.outputs.map((output) => ({
136+
address: output.address,
137+
amount: BigInt(output.amount),
138+
}));
139+
140+
if (!_.isEqual(filteredOutputs, filteredRecipients)) {
141+
throw new Error('Transaction outputs do not match the expected recipients in txParams.');
142+
}
143+
144+
// Validate total amount
145+
const totalAmount = txParams.recipients.reduce((sum, recipient) => sum.plus(recipient.amount), new BigNumber(0));
146+
147+
if (!totalAmount.isEqualTo(explainedTx.outputAmount)) {
148+
throw new Error('Transaction total amount does not match the expected total amount.');
149+
}
150+
}
151+
114152
return true;
115153
}
116154

modules/sdk-coin-cspr/src/lib/transaction.ts

+26
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,32 @@ export class Transaction extends BaseTransaction {
254254
}
255255
}
256256

257+
/**
258+
* Sets this transaction payload
259+
*
260+
* @param rawTx - The raw transaction in hex format
261+
*/
262+
async fromRawTransaction(rawTransaction: string): Promise<void> {
263+
try {
264+
// Decode the raw transaction using Casper's DeployUtil
265+
const deploy = DeployUtil.deployFromJson(JSON.parse(Buffer.from(rawTransaction, 'hex').toString()));
266+
267+
if (!deploy) {
268+
throw new Error('Failed to decode raw transaction');
269+
}
270+
271+
// Extract and set transaction details
272+
if (deploy.ok) {
273+
this._deploy = deploy.val;
274+
} else {
275+
throw new Error('Failed to decode raw transaction: ' + (deploy.err as any)?.message || 'Unknown error');
276+
}
277+
this._signatures = []; // Reset signatures as this is a raw transaction
278+
} catch (e) {
279+
throw new Error('Invalid raw transaction: ' + e.message);
280+
}
281+
}
282+
257283
get casperTx(): DeployUtil.Deploy {
258284
return this._deploy;
259285
}

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

+41-2
Original file line numberDiff line numberDiff line change
@@ -646,12 +646,51 @@ export class Dot extends BaseCoin {
646646
}
647647

648648
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
649-
const { txParams } = params;
649+
const coinConfig = coins.get(this.getChain());
650+
const { txPrebuild, txParams } = params;
651+
652+
// Ensure only one recipient is allowed
650653
if (Array.isArray(txParams.recipients) && txParams.recipients.length > 1) {
651654
throw new Error(
652-
`${this.getChain()} doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.`
655+
`${this.getChain()} doesn't support sending to more than one destination address within a single transaction. Use only a single recipient.`
653656
);
654657
}
658+
659+
// Validate the presence of txHex
660+
const rawTx = txPrebuild.txHex;
661+
if (!rawTx) {
662+
throw new Error('Missing required tx prebuild property: txHex');
663+
}
664+
665+
// Parse the transaction
666+
const transaction = new Transaction(coinConfig);
667+
transaction.fromRawTransaction(Buffer.from(rawTx, 'hex').toString('base64'));
668+
const explainedTx = transaction.explainTransaction();
669+
670+
// Validate recipients
671+
if (txParams.recipients) {
672+
const filteredRecipients = txParams.recipients.map((recipient) => ({
673+
address: recipient.address,
674+
amount: BigInt(recipient.amount),
675+
}));
676+
677+
const filteredOutputs = explainedTx.outputs.map((output) => ({
678+
address: output.address,
679+
amount: BigInt(output.amount),
680+
}));
681+
682+
if (!_.isEqual(filteredOutputs, filteredRecipients)) {
683+
throw new Error('Transaction outputs do not match the expected recipients in txParams.');
684+
}
685+
686+
// Validate total amount
687+
const totalAmount = txParams.recipients.reduce((sum, recipient) => sum.plus(recipient.amount), new BigNumber(0));
688+
689+
if (!totalAmount.isEqualTo(explainedTx.outputAmount)) {
690+
throw new Error('Transaction total amount does not match the expected total amount.');
691+
}
692+
}
693+
655694
return true;
656695
}
657696

modules/sdk-coin-dot/src/lib/transaction.ts

+23
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,29 @@ export class Transaction extends BaseTransaction {
448448
];
449449
}
450450

451+
/**
452+
* Sets this transaction payload
453+
*
454+
* @param rawTx - The raw transaction in hex format
455+
*/
456+
fromRawTransaction(rawTransaction: string): void {
457+
try {
458+
// Decode the raw transaction using the Polkadot txwrapper
459+
const decodedTx = decode(rawTransaction, {
460+
metadataRpc: this._dotTransaction.metadataRpc,
461+
registry: this._registry,
462+
isImmortalEra: utils.isZeroHex(this._dotTransaction.era),
463+
}) as unknown as DecodedTx;
464+
465+
// Extract and set transaction details
466+
this._sender = decodedTx.address;
467+
this._dotTransaction = decodedTx as unknown as UnsignedTransaction;
468+
this._signatures = []; // Reset signatures as this is a raw transaction
469+
} catch (e) {
470+
throw new Error('Invalid raw transaction: ' + e.message);
471+
}
472+
}
473+
451474
private decodeInputsAndOutputsForBatch(decodedTx: DecodedTx) {
452475
const sender = decodedTx.address;
453476
this._inputs = [];

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

+35-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ import { ClarityType, cvToString, cvToValue } from '@stacks/transactions';
1515

1616
import { ExplainTransactionOptions, StxSignTransactionOptions, StxTransactionExplanation } from './types';
1717
import { StxLib } from '.';
18-
import { TransactionBuilderFactory } from './lib';
18+
import { Transaction, TransactionBuilderFactory } from './lib';
1919
import { TransactionBuilder } from './lib/transactionBuilder';
2020
import { findTokenNameByContract } from './lib/utils';
21+
import _ from 'lodash';
22+
import BigNumber from 'bignumber.js';
2123

2224
export class Stx extends BaseCoin {
2325
protected readonly _staticsCoin: Readonly<StaticsBaseCoin>;
@@ -67,12 +69,43 @@ export class Stx extends BaseCoin {
6769
}
6870

6971
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
70-
const { txParams } = params;
72+
const coinConfig = coins.get(this.getChain());
73+
const { txPrebuild: txPrebuild, txParams: txParams } = params;
7174
if (Array.isArray(txParams.recipients) && txParams.recipients.length > 1) {
7275
throw new Error(
7376
`${this.getChain()} doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.`
7477
);
7578
}
79+
const transaction = new Transaction(coinConfig);
80+
const rawTx = txPrebuild.txHex;
81+
if (!rawTx) {
82+
throw new Error('missing required tx prebuild property txHex');
83+
}
84+
85+
transaction.fromRawTransaction(Buffer.from(rawTx, 'hex').toString('base64'));
86+
const explainedTx = transaction.explainTransaction();
87+
if (txParams.recipients !== undefined) {
88+
const filteredRecipients = txParams.recipients.map((recipient) => ({
89+
address: recipient.address,
90+
amount: BigInt(recipient.amount),
91+
}));
92+
93+
const filteredOutputs = explainedTx.outputs.map((output) => ({
94+
address: output.address,
95+
amount: BigInt(output.amount),
96+
}));
97+
98+
if (!_.isEqual(filteredOutputs, filteredRecipients)) {
99+
throw new Error('Transaction outputs do not match the expected recipients in txParams.');
100+
}
101+
102+
// Validate total amount
103+
const totalAmount = txParams.recipients.reduce((sum, recipient) => sum.plus(recipient.amount), new BigNumber(0));
104+
105+
if (!totalAmount.isEqualTo(explainedTx.outputAmount)) {
106+
throw new Error('Transaction total amount does not match the expected total amount.');
107+
}
108+
}
76109
return true;
77110
}
78111

modules/sdk-coin-xtz/src/lib/transaction.ts

+20
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,26 @@ export class Transaction extends BaseTransaction {
158158
});
159159
}
160160

161+
/**
162+
* Sets this transaction payload
163+
*
164+
* @param rawTx - The raw transaction in hex format
165+
*/
166+
async fromRawTransaction(rawTransaction: string): Promise<void> {
167+
try {
168+
// Decode the raw transaction using Taquito's local-forging library
169+
const decodedTx = localForger.parse(rawTransaction);
170+
171+
// Extract and set transaction details
172+
this._encodedTransaction = rawTransaction;
173+
this._parsedTransaction = await decodedTx;
174+
this._source = (await decodedTx).contents[0]?.source || '';
175+
this._signatures = []; // Reset signatures as this is a raw transaction
176+
} catch (e) {
177+
throw new Error('Invalid raw transaction: ' + e.message);
178+
}
179+
}
180+
161181
/**
162182
* Record the most important fields for a Transaction operation.
163183
*

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

+39-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
import { bip32 } from '@bitgo/secp256k1';
1717
import { CoinFamily, coins, BaseCoin as StaticsBaseCoin } from '@bitgo/statics';
1818
import BigNumber from 'bignumber.js';
19-
import { Interface, KeyPair, TransactionBuilder, Utils } from './lib';
19+
import { Interface, KeyPair, Transaction, TransactionBuilder, Utils } from './lib';
20+
import _ from 'lodash';
2021

2122
export class Xtz extends BaseCoin {
2223
protected readonly _staticsCoin: Readonly<StaticsBaseCoin>;
@@ -116,12 +117,48 @@ export class Xtz extends BaseCoin {
116117
}
117118

118119
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
119-
const { txParams } = params;
120+
const coinConfig = coins.get(this.getChain());
121+
const { txPrebuild, txParams } = params;
120122
if (Array.isArray(txParams.recipients) && txParams.recipients.length > 1) {
121123
throw new Error(
122124
`${this.getChain()} doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.`
123125
);
124126
}
127+
// Validate the presence of txHex
128+
const rawTx = txPrebuild.txHex;
129+
if (!rawTx) {
130+
throw new Error('Missing required tx prebuild property: txHex');
131+
}
132+
133+
// Parse the transaction
134+
const transaction = new Transaction(coinConfig);
135+
transaction.fromRawTransaction(Buffer.from(rawTx, 'hex').toString('base64'));
136+
const explainedTx = transaction.explainTransaction();
137+
138+
// Validate recipients
139+
if (txParams.recipients) {
140+
const filteredRecipients = txParams.recipients.map((recipient) => ({
141+
address: recipient.address,
142+
amount: BigInt(recipient.amount),
143+
}));
144+
145+
const filteredOutputs = explainedTx.outputs.map((output) => ({
146+
address: output.address,
147+
amount: BigInt(output.amount),
148+
}));
149+
150+
if (!_.isEqual(filteredOutputs, filteredRecipients)) {
151+
throw new Error('Transaction outputs do not match the expected recipients in txParams.');
152+
}
153+
154+
// Validate total amount
155+
const totalAmount = txParams.recipients.reduce((sum, recipient) => sum.plus(recipient.amount), new BigNumber(0));
156+
157+
if (!totalAmount.isEqualTo(explainedTx.outputAmount)) {
158+
throw new Error('Transaction total amount does not match the expected total amount.');
159+
}
160+
}
161+
125162
return true;
126163
}
127164

0 commit comments

Comments
 (0)