Skip to content

Commit 818de8a

Browse files
authored
Merge pull request #5867 from BitGo/COIN-3616
fix(sdk-coin-stx): memo issue in verify transaction of sip10
2 parents 013c145 + 8a6aefc commit 818de8a

File tree

4 files changed

+127
-15
lines changed

4 files changed

+127
-15
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,20 @@ export function findContractTokenNameUsingContract(contractAddress: string, cont
567567
return sip10Token ? sip10Token[0].assetId.split('::')[1] : undefined;
568568
}
569569
}
570+
571+
/**
572+
* Function to get address and memo details from address input
573+
*
574+
* @param address
575+
* @returns {AddressDetails}
576+
*/
577+
export function getMemoIdAndBaseAddressFromAddress(address: string): AddressDetails {
578+
const [baseAddress, queryString] = address.split('?');
579+
const params = new URLSearchParams(queryString);
580+
const memoId = params.get('memoId');
581+
582+
return {
583+
address: baseAddress,
584+
memoId: memoId ? memoId : undefined,
585+
};
586+
}

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

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import _ from 'lodash';
22
import BigNumber from 'bignumber.js';
33

4-
import { BitGoBase, CoinConstructor, NamedCoinConstructor, VerifyTransactionOptions } from '@bitgo/sdk-core';
4+
import { BitGoBase, CoinConstructor, Memo, NamedCoinConstructor, VerifyTransactionOptions } from '@bitgo/sdk-core';
55
import { BaseCoin as StaticsBaseCoin, coins, NetworkType, Sip10TokenConfig, tokens } from '@bitgo/statics';
66

77
import { Stx } from './stx';
88
import { TransactionBuilderFactory } from './lib';
99
import { TransactionBuilder } from './lib/transactionBuilder';
10+
import { getMemoIdAndBaseAddressFromAddress } from './lib/utils';
11+
12+
export interface Sip10VerifyTransactionOptions extends VerifyTransactionOptions {
13+
memo?: Memo;
14+
}
1015

1116
export class Sip10Token extends Stx {
1217
public readonly tokenConfig: Sip10TokenConfig;
@@ -72,8 +77,8 @@ export class Sip10Token extends Stx {
7277
return new TransactionBuilderFactory(coinConfig).getFungibleTokenTransferBuilder();
7378
}
7479

75-
async verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
76-
const { txPrebuild: txPrebuild, txParams: txParams } = params;
80+
async verifyTransaction(params: Sip10VerifyTransactionOptions): Promise<boolean> {
81+
const { txPrebuild: txPrebuild, txParams: txParams, memo: memo } = params;
7782
if (Array.isArray(txParams.recipients) && txParams.recipients.length > 1) {
7883
throw new Error(
7984
`${this.getChain()} doesn't support sending to more than 1 destination address within a single transaction. Try again, using only a single recipient.`
@@ -89,13 +94,11 @@ export class Sip10Token extends Stx {
8994
const explainedTx = await this.explainTransaction({ txHex: rawTx, feeInfo: { fee: '' } });
9095
if (txParams.recipients !== undefined && explainedTx) {
9196
const filteredRecipients = txParams.recipients?.map((recipient) => {
97+
const addressDetails = getMemoIdAndBaseAddressFromAddress(recipient.address);
9298
const recipientData = {
93-
address: recipient.address,
99+
address: addressDetails.address,
94100
amount: BigInt(recipient.amount),
95101
};
96-
if (recipient.memo) {
97-
recipientData['memo'] = recipient.memo;
98-
}
99102
if (recipient.tokenName) {
100103
recipientData['tokenName'] = recipient.tokenName;
101104
}
@@ -106,9 +109,6 @@ export class Sip10Token extends Stx {
106109
address: output.address,
107110
amount: BigInt(output.amount),
108111
};
109-
if (output.memo) {
110-
recipientData['memo'] = output.memo;
111-
}
112112
if (output.tokenName) {
113113
recipientData['tokenName'] = output.tokenName;
114114
}
@@ -117,6 +117,22 @@ export class Sip10Token extends Stx {
117117
if (!_.isEqual(filteredOutputs, filteredRecipients)) {
118118
throw new Error('Tx outputs does not match with expected txParams recipients');
119119
}
120+
// compare memo
121+
let memoInput = '';
122+
let memoOutput = '';
123+
if (memo && memo.value) {
124+
memoInput = memo.value;
125+
} else if (txParams.recipients.length) {
126+
const addressDetails = getMemoIdAndBaseAddressFromAddress(txParams.recipients[0].address);
127+
memoInput = addressDetails.memoId ? addressDetails.memoId : '';
128+
}
129+
if (explainedTx.memo) {
130+
memoOutput = explainedTx.memo;
131+
}
132+
if (!_.isEqual(memoInput, memoOutput)) {
133+
throw new Error('Tx memo does not match with expected txParams recipient memo');
134+
}
135+
// compare send amount
120136
let totalAmount = new BigNumber(0);
121137
for (const recipients of txParams.recipients) {
122138
totalAmount = totalAmount.plus(recipients.amount);

modules/sdk-coin-stx/test/fixtures.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ export const unsignedTxExplainedTransfer = {
3535

3636
export const txForExplainFungibleTokenTransfer =
3737
'808000000004012fe507c09dbb23c3b7e5d166c81fc4b87692510b000000000000000000000000000000b4000000030201e4c98d7687eab5f11d03febc96951301eede336088b06c5a676cc6093f70e16b3f3533ccd8e6f92390a17dcc917f4e828657f17605574a5fcbb3da59f25483dc02019095c2f5217e0f168f27b605b47e9eac2da3c5ea309c42a2677addfb67c1fd471ef20187ce6b85b5b541d4b4f00d2f8ea8f7d42be84327968cb9411730b5217a00038e3c4529395611be9abf6fa3b6987e81d402385e3d605a073f42f407565a4a3d00020302000000010102152fe507c09dbb23c3b7e5d166c81fc4b87692510b1a1500a1c42f0c11bfe3893f479af18904677685be0d747369703664702d746f6b656e0d747369703664702d746f6b656e010000000000002710021a1500a1c42f0c11bfe3893f479af18904677685be0d747369703664702d746f6b656e087472616e73666572000000040100000000000000000000000000002710051a1500a1c42f0c11bfe3893f479af18904677685be0515ab50cac953ac55edc14e2b236854b1ead863fece0a020000000131';
38+
export const txForExplainFungibleTokenTransferWithMemoId10 =
39+
'808000000004012fe507c09dbb23c3b7e5d166c81fc4b87692510b000000000000000000000000000000b400000003020130831ab5d8fc925e088931b2eb86efcd054612293aa8a0156dfa50f44b54b6776683d2463783a10ee358d565ac74e4004c9cd0a96674cb5726ac1fc848e6f00c0201a76e30fa14ca3528ebb3a3aa92cc2e202592747c2b7028d4a3791663c3361f6d2d6fd44617a0775f58076ad119f923b27d1ca3ef63273910e7a8f91d59b5ee0900038e3c4529395611be9abf6fa3b6987e81d402385e3d605a073f42f407565a4a3d00020302000000010102152fe507c09dbb23c3b7e5d166c81fc4b87692510b1a1500a1c42f0c11bfe3893f479af18904677685be0d747369703664702d746f6b656e0d747369703664702d746f6b656e010000000000002710021a1500a1c42f0c11bfe3893f479af18904677685be0d747369703664702d746f6b656e087472616e73666572000000040100000000000000000000000000002710051a1500a1c42f0c11bfe3893f479af18904677685be0515ab50cac953ac55edc14e2b236854b1ead863fece0a02000000023130';
40+
export const txForExplainFungibleTokenTransferWithoutMemo =
41+
'808000000004012fe507c09dbb23c3b7e5d166c81fc4b87692510b000000000000000000000000000000b4000000030200125aadf265dc67bae4e08cffd42795a05745d9ad345ab582a0ad4ceedec79e1c26111d6a5058bb9aac355a007bad9b81c2f90026864fa1f2dbe13a29fbcea60f02013d603fedc49e7040607c6e091e81557a31b1832eef3ca7a6ab10105e95e226fd5fe2c7c97b7ca0dabc17af6ca0dcb7133dac7d24951b4b410ff9092f566a5ca000038e3c4529395611be9abf6fa3b6987e81d402385e3d605a073f42f407565a4a3d00020302000000010102152fe507c09dbb23c3b7e5d166c81fc4b87692510b1a1500a1c42f0c11bfe3893f479af18904677685be0d747369703664702d746f6b656e0d747369703664702d746f6b656e010000000000002710021a1500a1c42f0c11bfe3893f479af18904677685be0d747369703664702d746f6b656e087472616e73666572000000030100000000000000000000000000002710051a1500a1c42f0c11bfe3893f479af18904677685be0515ab50cac953ac55edc14e2b236854b1ead863fece';
42+
export const txForExplainFungibleTokenTransferWithMemoZero =
43+
'808000000004012fe507c09dbb23c3b7e5d166c81fc4b87692510b000000000000000000000000000000b4000000030200ce306873b8a41b66676f0dcf8cc933eedc15b52fb24e41495d43edecfa2e9e3e291a898724a997c223178910405939ffcd57264bdc0d08de6bfc402385a57edc02010e0fd2bae5af96bbbb49de41bf965cd47e2f9d93018189cc7e5642536c309a527c701d0e5f5ae44cd30c411323b09112f6f23f372a0c5d1d7713922c2a275a7500038e3c4529395611be9abf6fa3b6987e81d402385e3d605a073f42f407565a4a3d00020302000000010102152fe507c09dbb23c3b7e5d166c81fc4b87692510b1a1500a1c42f0c11bfe3893f479af18904677685be0d747369703664702d746f6b656e0d747369703664702d746f6b656e010000000000002710021a1500a1c42f0c11bfe3893f479af18904677685be0d747369703664702d746f6b656e087472616e73666572000000040100000000000000000000000000002710051a1500a1c42f0c11bfe3893f479af18904677685be0515ab50cac953ac55edc14e2b236854b1ead863fece0a020000000130';
3844

3945
export const fungibleTokenTransferTx = {
4046
id: '2bf5277ac7cd57741163d85b78905c97e55f05887369e3908450e4050aa4cf01',
@@ -69,7 +75,6 @@ export const recipients: ITransactionRecipient[] = [
6975
{
7076
address: 'SN2NN1JP9AEP5BVE19RNJ6T2MP7NDGRZYST1VDF3M',
7177
amount: '10000',
72-
memo: '1',
7378
tokenName: 'tstx:tsip6dp',
7479
},
7580
];

modules/sdk-coin-stx/test/unit/sip10Token.ts

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import _ from 'lodash';
22
import { BitGoAPI } from '@bitgo/sdk-api';
33
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
4-
import { BaseCoin, ITransactionRecipient, Wallet } from '@bitgo/sdk-core';
4+
import { ITransactionRecipient, Wallet } from '@bitgo/sdk-core';
55

66
import { Sip10Token } from '../../src';
77
import * as testData from '../fixtures';
88

99
describe('Sip10Token:', function () {
1010
const sip10TokenName = 'tstx:tsip6dp';
1111
let bitgo: TestBitGoAPI;
12-
let basecoin: BaseCoin;
12+
let basecoin: Sip10Token;
1313
let newTxPrebuild: () => { txHex: string; txInfo: Record<string, unknown> };
1414
let newTxParams: () => { recipients: ITransactionRecipient[] };
1515
let wallet: Wallet;
@@ -23,6 +23,11 @@ describe('Sip10Token:', function () {
2323
recipients: testData.recipients,
2424
};
2525

26+
const memo = {
27+
type: '',
28+
value: '1',
29+
};
30+
2631
before(function () {
2732
bitgo = TestBitGo.decorate(BitGoAPI, {
2833
env: 'mock',
@@ -37,7 +42,7 @@ describe('Sip10Token:', function () {
3742
newTxParams = () => {
3843
return _.cloneDeep(txParams);
3944
};
40-
basecoin = bitgo.coin(sip10TokenName);
45+
basecoin = bitgo.coin(sip10TokenName) as Sip10Token;
4146
wallet = new Wallet(bitgo, basecoin, {});
4247
});
4348

@@ -51,6 +56,7 @@ describe('Sip10Token:', function () {
5156
txPrebuild,
5257
verification,
5358
wallet,
59+
memo,
5460
});
5561
isTransactionVerified.should.equal(true);
5662
});
@@ -69,6 +75,74 @@ describe('Sip10Token:', function () {
6975
txPrebuild,
7076
verification,
7177
wallet,
78+
memo,
79+
});
80+
isTransactionVerified.should.equal(true);
81+
});
82+
83+
it('should succeed to verify when memo is passed', async function () {
84+
const txPrebuild = newTxPrebuild();
85+
txPrebuild.txHex = testData.txForExplainFungibleTokenTransferWithMemoId10;
86+
const txParams = newTxParams();
87+
const verification = {};
88+
const memo = {
89+
type: '',
90+
value: '10',
91+
};
92+
const isTransactionVerified = await basecoin.verifyTransaction({
93+
txParams: txParams,
94+
txPrebuild,
95+
verification,
96+
wallet,
97+
memo,
98+
});
99+
isTransactionVerified.should.equal(true);
100+
});
101+
102+
it('should succeed to verify when memo is zero', async function () {
103+
const txPrebuild = newTxPrebuild();
104+
const txParams = newTxParams();
105+
txPrebuild.txHex = testData.txForExplainFungibleTokenTransferWithMemoZero;
106+
const memo = {
107+
type: '',
108+
value: '0',
109+
};
110+
const verification = {};
111+
const isTransactionVerified = await basecoin.verifyTransaction({
112+
txParams: txParams,
113+
txPrebuild,
114+
verification,
115+
wallet,
116+
memo,
117+
});
118+
isTransactionVerified.should.equal(true);
119+
});
120+
121+
it('should succeed to verify when memo is passed inside recipient address', async function () {
122+
const txPrebuild = newTxPrebuild();
123+
const txParams = newTxParams();
124+
txParams.recipients[0].address = 'SN2NN1JP9AEP5BVE19RNJ6T2MP7NDGRZYST1VDF3M?memoId=10';
125+
txPrebuild.txHex = testData.txForExplainFungibleTokenTransferWithMemoId10;
126+
const verification = {};
127+
const isTransactionVerified = await basecoin.verifyTransaction({
128+
txParams: txParams,
129+
txPrebuild,
130+
verification,
131+
wallet,
132+
});
133+
isTransactionVerified.should.equal(true);
134+
});
135+
136+
it('should succeed to verify when memo is not passed', async function () {
137+
const txPrebuild = newTxPrebuild();
138+
const txParams = newTxParams();
139+
txPrebuild.txHex = testData.txForExplainFungibleTokenTransferWithoutMemo;
140+
const verification = {};
141+
const isTransactionVerified = await basecoin.verifyTransaction({
142+
txParams: txParams,
143+
txPrebuild,
144+
verification,
145+
wallet,
72146
});
73147
isTransactionVerified.should.equal(true);
74148
});
@@ -148,7 +222,7 @@ describe('Sip10Token:', function () {
148222
verification,
149223
wallet,
150224
})
151-
.should.rejectedWith('Tx outputs does not match with expected txParams recipients');
225+
.should.rejectedWith('Tx memo does not match with expected txParams recipient memo');
152226
});
153227

154228
it('should fail to verify transaction with wrong token', async function () {

0 commit comments

Comments
 (0)