diff --git a/modules/sdk-coin-stx/src/stx.ts b/modules/sdk-coin-stx/src/stx.ts index eee65ca6b4..2b0abe0bd2 100644 --- a/modules/sdk-coin-stx/src/stx.ts +++ b/modules/sdk-coin-stx/src/stx.ts @@ -40,7 +40,12 @@ import { ExplainTransactionOptions, StxSignTransactionOptions, StxTransactionExp import { StxLib } from '.'; import { TransactionBuilderFactory } from './lib'; import { TransactionBuilder } from './lib/transactionBuilder'; -import { findContractTokenNameUsingContract, findTokenNameByContract, getAddressDetails } from './lib/utils'; +import { + findContractTokenNameUsingContract, + findTokenNameByContract, + getAddressDetails, + getMemoIdAndBaseAddressFromAddress, +} from './lib/utils'; import { AddressDetails, NativeStxBalance, @@ -98,12 +103,49 @@ export class Stx extends BaseCoin { } async verifyTransaction(params: VerifyTransactionOptions): Promise { - const { txParams } = params; + const { txPrebuild, txParams } = params; + const { memo } = txParams; if (Array.isArray(txParams.recipients) && txParams.recipients.length > 1) { throw new Error( `${this.getChain()} doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.` ); } + const rawTx = txPrebuild?.txHex; + if (!rawTx) { + throw new Error('missing required tx prebuild property txHex'); + } + const explainedTx = await this.explainTransaction({ txHex: rawTx, feeInfo: { fee: '' } }); + const recipient = txParams.recipients?.[0]; + if (recipient !== undefined && explainedTx) { + const txOutput = explainedTx.outputs[0]; + const recipientAddress = getMemoIdAndBaseAddressFromAddress(recipient.address).address; + if (txOutput?.address !== recipientAddress) { + throw new Error( + `Tx destination does not match with expected txParams recipient: expected ${recipientAddress} but got ${txOutput?.address}` + ); + } + if (BigInt(txOutput?.amount) !== BigInt(recipient.amount)) { + throw new Error( + `Tx amount does not match with expected txParams recipient amount: expected ${recipient.amount} but got ${txOutput?.amount}` + ); + } + // compare memo + let memoInput = ''; + if (memo && memo.value) { + memoInput = memo.value; + } else { + const addressDetails = getMemoIdAndBaseAddressFromAddress(recipient.address); + memoInput = addressDetails.memoId ?? ''; + } + const memoOutput = explainedTx.memo ?? ''; + if (memoInput !== memoOutput) { + throw new Error('Tx memo does not match with expected txParams recipient memo'); + } + // compare amount + if (!new BigNumber(recipient.amount).isEqualTo(explainedTx.outputAmount)) { + throw new Error('Tx total amount does not match with expected total amount field'); + } + } return true; } diff --git a/modules/sdk-coin-stx/test/unit/stx.ts b/modules/sdk-coin-stx/test/unit/stx.ts index 7e6d95efc2..df7d6903d1 100644 --- a/modules/sdk-coin-stx/test/unit/stx.ts +++ b/modules/sdk-coin-stx/test/unit/stx.ts @@ -324,6 +324,8 @@ describe('STX:', function () { describe('Verify Transaction', function () { const address1 = '0x174cfd823af8ce27ed0afee3fcf3c3ba259116be'; const address2 = '0x7e85bdc27c050e3905ebf4b8e634d9ad6edd0de6'; + const txPrebuild = { txHex: testData.txForExplainTransfer, txInfo: {} }; + it('should reject a txPrebuild with more than one recipient', async function () { const wallet = new Wallet(bitgo, basecoin, {}); @@ -342,6 +344,91 @@ describe('STX:', function () { `tstx doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.` ); }); + + it('should reject when txPrebuild is missing txHex', async function () { + const wallet = new Wallet(bitgo, basecoin, {}); + const txParams = { + recipients: [{ amount: '1000', address: testData.txExplainedTransfer.recipient }], + wallet, + walletPassphrase: 'fakeWalletPassphrase', + }; + await basecoin + .verifyTransaction({ txPrebuild: {}, txParams }) + .should.be.rejectedWith('missing required tx prebuild property txHex'); + }); + + it('should succeed to verify a native STX transfer with matching recipient and amount', async function () { + const wallet = new Wallet(bitgo, basecoin, {}); + const txParams = { + recipients: [ + { + address: testData.txExplainedTransfer.recipient, + amount: testData.txExplainedTransfer.outputAmount, + }, + ], + memo: { type: '', value: testData.txExplainedTransfer.memo }, + wallet, + }; + const result = await basecoin.verifyTransaction({ txPrebuild, txParams }); + result.should.equal(true); + }); + + it('should fail to verify transaction with wrong recipient address', async function () { + const wallet = new Wallet(bitgo, basecoin, {}); + const requestedAddress = 'ST11NJTTKGVT6D1HY4NJRVQWMQM7TVAR091EJ8P2Y'; + const txParams = { + recipients: [ + { + address: requestedAddress, + amount: testData.txExplainedTransfer.outputAmount, + }, + ], + memo: { type: '', value: testData.txExplainedTransfer.memo }, + wallet, + }; + await basecoin + .verifyTransaction({ txPrebuild, txParams }) + .should.be.rejectedWith( + `Tx destination does not match with expected txParams recipient: expected ${requestedAddress} but got ${testData.txExplainedTransfer.recipient}` + ); + }); + + it('should fail to verify transaction with wrong amount', async function () { + const wallet = new Wallet(bitgo, basecoin, {}); + const requestedAmount = '9999'; + const txParams = { + recipients: [ + { + address: testData.txExplainedTransfer.recipient, + amount: requestedAmount, + }, + ], + memo: { type: '', value: testData.txExplainedTransfer.memo }, + wallet, + }; + await basecoin + .verifyTransaction({ txPrebuild, txParams }) + .should.be.rejectedWith( + `Tx amount does not match with expected txParams recipient amount: expected ${requestedAmount} but got ${testData.txExplainedTransfer.outputAmount}` + ); + }); + + it('should fail to verify transaction with wrong memo', async function () { + const wallet = new Wallet(bitgo, basecoin, {}); + const txParams = { + recipients: [ + { + address: testData.txExplainedTransfer.recipient, + amount: testData.txExplainedTransfer.outputAmount, + }, + ], + memo: { type: '', value: 'wrong memo' }, + wallet, + }; + await basecoin + .verifyTransaction({ txPrebuild, txParams }) + .should.be.rejectedWith('Tx memo does not match with expected txParams recipient memo'); + }); }); describe('Recover Transaction STX', function () {