Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions modules/sdk-coin-ton/test/resources/ton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ export const signedTokenSendTransaction = {
},
};

export const signedTokenSendTransactionForMemoId = {
tx: 'te6cckECGgEABB0AAuGIAVSGb+UGjjP3lvt+zFA8wouI3McEd6CKbO2TwcZ3OfLKGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACmpoxdJlgLSAAAAAAADgEXAgE0AhYBFP8A9KQT9LzyyAsDAgEgBBECAUgFCALm0AHQ0wMhcbCSXwTgItdJwSCSXwTgAtMfIYIQcGx1Z70ighBkc3RyvbCSXwXgA/pAMCD6RAHIygfL/8nQ7UTQgQFA1yH0BDBcgQEI9ApvoTGzkl8H4AXTP8glghBwbHVnupI4MOMNA4IQZHN0crqSXwbjDQYHAHgB+gD0BDD4J28iMFAKoSG+8uBQghBwbHVngx6xcIAYUATLBSbPFlj6Ahn0AMtpF8sfUmDLPyDJgED7AAYAilAEgQEI9Fkw7UTQgQFA1yDIAc8W9ADJ7VQBcrCOI4IQZHN0coMesXCAGFAFywVQA88WI/oCE8tqyx/LP8mAQPsAkl8D4gIBIAkQAgEgCg8CAVgLDAA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA0OABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AABG4yX7UTQ1wsfgAWb0kK29qJoQICga5D6AhhHDUCAhHpJN9KZEM5pA+n/mDeBKAG3gQFImHFZ8xhAT48oMI1xgg0x/TH9MfAvgju/Jk7UTQ0x/TH9P/9ATRUUO68qFRUbryogX5AVQQZPkQ8qP4ACSkyMsfUkDLH1Iwy/9SEPQAye1U+A8B0wchwACfbFGTINdKltMH1AL7AOgw4CHAAeMAIcAC4wABwAORMOMNA6TIyx8Syx/L/xITFBUAbtIH+gDU1CL5AAXIygcVy//J0Hd0gBjIywXLAiLPFlAF+gIUy2sSzMzJc/sAyEAUgQEI9FHypwIAcIEBCNcY+gDTP8hUIEeBAQj0UfKnghBub3RlcHSAGMjLBcsCUAbPFlAE+gIUy2oSyx/LP8lz+wACAGyBAQjXGPoA0z8wUiSBAQj0WfKnghBkc3RycHSAGMjLBcsCUAXPFlAD+gITy2rLHxLLP8lz+wAACvQAye1UAFEAAAAAKamjF9NTAQHUHhbX00VGZ3d2r8hbJxuz7PaxmuCOJ6kgckppQAFmQgABT9LR3Iqffskp0J9gWYO8Azlnb33BCMj8FqIUIGxGOZpiWgAAAAAAAAAAAAAAAAABGAGuD4p+pQAAAAAAAAAAQ7msoAgA/BGdBi/R01erquxJOvPgGKclBawUs3MAi0/IdctKQz8AKpDN/KDRxn7y32/ZigeYUXEbmOCO9BFNnbJ4OM7nPllGHoSBGQAkAAAAAGpldHRvbiB0ZXN0aW5nwHtw7A==',
recipient: {
address: 'UQB-CM6DF-jpq9XVdiSdefAMU5KC1gpZuYBFp-Q65aUhn0OP?memoId=1234',
amount: '1000000000',
},
};

export const signedSendTransactionForMemoId = {
tx: 'te6cckEBAgEAqQAB4YgBJAxo7vqHF++LJ4bC/kJ8A1uVRskrKlrKJZ8rIB0tF+gCadlSX+hPo2mmhZyi0p3zTVUYVRkcmrCm97cSUFSa2vzvCArM3APg+ww92r3IcklNjnzfKOgysJVQXiCvj9SAaU1NGLsotvRwAAAAMAAcAQBmQgAaRefBOjTi/hwqDjv+7I6nGj9WEAe3ls/rFuBEQvggr5zEtAAAAAAAAAAAAAAAAAAAdfZO7w==',
recipient: {
Expand Down
291 changes: 288 additions & 3 deletions modules/sdk-coin-ton/test/unit/jettonToken.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import 'should';

import Tonweb from 'tonweb';
import should from 'should';
import * as sinon from 'sinon';
import { TransactionExplanation } from '@bitgo/sdk-core';
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
import { BitGoAPI } from '@bitgo/sdk-api';
import { JettonToken } from '../../src';
import { JettonToken, Ton, TonParseTransactionOptions } from '../../src';
import * as testData from '../resources/ton';

describe('Jetton Tokens', function () {
let bitgo: TestBitGoAPI;
Expand All @@ -11,6 +14,31 @@ describe('Jetton Tokens', function () {
const testnetTokenName = 'tton:ukwny-us';
const mainnetTokenName = 'ton:usdt';

const txPrebuildList = [
{
txHex: Buffer.from(testData.signedTokenSendTransaction.tx, 'base64').toString('hex'),
txInfo: {},
},
];
const txPrebuildBounceableList = [
{
txHex: Buffer.from(testData.signedTokenSendTransaction.txBounceable, 'base64').toString('hex'),
txInfo: {},
},
];

const txParamsList = [
{
recipients: [testData.signedTokenSendTransaction.recipient],
},
];

const txParamsBounceableList = [
{
recipients: [testData.signedTokenSendTransaction.recipientBounceable],
},
];

before(function () {
bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
JettonToken.createTokenConstructors().forEach(({ name, coinConstructor }) => {
Expand Down Expand Up @@ -46,4 +74,261 @@ describe('Jetton Tokens', function () {
mainnetJettonToken.contractAddress.should.equal('EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs');
mainnetJettonToken.decimalPlaces.should.equal(6);
});

describe('Verify Jetton transaction: ', () => {
txParamsList.forEach((_, index) => {
const txParams = txParamsList[index];
const txPrebuild = txPrebuildList[index];
const txParamsBounceable = txParamsBounceableList[index];
const txPrebuildBounceable = txPrebuildBounceableList[index];

it('should succeed to verify transaction', async function () {
const verification = {};
const isTransactionVerified = await testnetJettonToken.verifyTransaction({
txParams,
txPrebuild,
verification,
} as any);
isTransactionVerified.should.equal(true);

const isBounceableTransactionVerified = await testnetJettonToken.verifyTransaction({
txParams: txParamsBounceable,
txPrebuild: txPrebuildBounceable,
verification: {},
} as any);
isBounceableTransactionVerified.should.equal(true);
});

it('should succeed to verify transaction when recipients amount are numbers', async function () {
const txParamsWithNumberAmounts = JSON.parse(JSON.stringify(txParams));
txParamsWithNumberAmounts.recipients[0].amount = 20000000;
const verification = {};
await testnetJettonToken
.verifyTransaction({
txParams: txParamsWithNumberAmounts,
txPrebuild,
verification,
} as any)
.should.rejectedWith('Tx outputs does not match with expected txParams recipients');
});

it('should succeed to verify transaction when recipients amount are strings', async function () {
const txParamsWithNumberAmounts = JSON.parse(JSON.stringify(txParams));
txParamsWithNumberAmounts.recipients[0].amount = '20000000';
const verification = {};
await testnetJettonToken
.verifyTransaction({
txParams: txParamsWithNumberAmounts,
txPrebuild,
verification,
} as any)
.should.rejectedWith('Tx outputs does not match with expected txParams recipients');
});

it('should succeed to verify transaction when recipients amounts are number and amount is same', async function () {
const verification = {};
await testnetJettonToken
.verifyTransaction({
txParams,
txPrebuild,
verification,
} as any)
.should.resolvedWith(true);
});

it('should succeed to verify transaction when recipients amounts are string and amount is same', async function () {
const verification = {};
await testnetJettonToken
.verifyTransaction({
txParams,
txPrebuild,
verification,
} as any)
.should.resolvedWith(true);
});

it('should succeed to verify transaction when recipient address are non bounceable', async function () {
const txParamsWithNumberAmounts = JSON.parse(JSON.stringify(txParams));
txParamsWithNumberAmounts.recipients[0].address = new Tonweb.Address(
txParamsWithNumberAmounts.recipients[0].address
).toString(true, true, false);
const verification = {};
const isVerified = await testnetJettonToken.verifyTransaction({
txParams: txParamsWithNumberAmounts,
txPrebuild,
verification,
} as any);
isVerified.should.equal(true);
});

it('should fail to verify transaction with invalid param', async function () {
const txPrebuild = {};
await testnetJettonToken
.verifyTransaction({
txParams,
txPrebuild,
} as any)
.should.rejectedWith('missing required tx prebuild property txHex');
});
});

it('should succeed to verify transaction with recipient having memo', async function () {
const txParams = {
recipients: [testData.signedTokenSendTransactionForMemoId.recipient],
};
const txPrebuild = {
txHex: Buffer.from(testData.signedTokenSendTransactionForMemoId.tx, 'base64').toString('hex'),
txInfo: {},
};
const verification = {};
const isTransactionVerified = await testnetJettonToken.verifyTransaction({
txParams,
txPrebuild,
verification,
} as any);
isTransactionVerified.should.equal(true);
});
});

describe('Explain Jetton Transaction: ', () => {
it('should explain a jetton transfer transaction', async function () {
const explainedTransaction = (await testnetJettonToken.explainTransaction({
txHex: Buffer.from(testData.signedTokenSendTransaction.tx, 'base64').toString('hex'),
})) as TransactionExplanation;
explainedTransaction.should.deepEqual({
displayOrder: ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'withdrawAmount'],
id: testData.signedTokenSendTransaction.txId,
outputs: [
{
address: testData.signedTokenSendTransaction.recipient.address,
amount: testData.signedTokenSendTransaction.recipient.amount,
},
],
outputAmount: testData.signedTokenSendTransaction.recipient.amount,
changeOutputs: [],
changeAmount: '0',
fee: { fee: 'UNKNOWN' },
withdrawAmount: undefined,
});
});

it('should explain a non-bounceable jetton transfer transaction', async function () {
const explainedTransaction = (await testnetJettonToken.explainTransaction({
txHex: Buffer.from(testData.signedTokenSendTransaction.txBounceable, 'base64').toString('hex'),
toAddressBounceable: false,
fromAddressBounceable: false,
})) as TransactionExplanation;
explainedTransaction.should.deepEqual({
displayOrder: ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'withdrawAmount'],
id: testData.signedTokenSendTransaction.txIdBounceable,
outputs: [
{
address: testData.signedTokenSendTransaction.recipientBounceable.address,
amount: testData.signedTokenSendTransaction.recipientBounceable.amount,
},
],
outputAmount: testData.signedTokenSendTransaction.recipientBounceable.amount,
changeOutputs: [],
changeAmount: '0',
fee: { fee: 'UNKNOWN' },
withdrawAmount: undefined,
});
});

it('should fail to explain transaction with missing params', async function () {
try {
await testnetJettonToken.explainTransaction({});
} catch (error) {
should.equal(error.message, 'Invalid transaction');
}
});

it('should fail to explain transaction with invalid params', async function () {
try {
await testnetJettonToken.explainTransaction({ txHex: 'randomString' });
} catch (error) {
should.equal(error.message, 'Invalid transaction');
}
});
});

describe('Parse Jetton Transactions: ', () => {
const transactionsList = [testData.signedTokenSendTransaction.tx];
const transactionInputsResponseList = [
[
{
address: 'EQCqQzfyg0cZ-8t9v2YoHmFFxG5jgjvQRTZ2yeDjO5z5ZRy9',
amount: testData.tokenRecipients[0].amount,
},
],
];

const transactionInputsResponseBounceableList = [
[
{
address: testData.tokenSender.address,
amount: testData.tokenRecipients[0].amount,
},
],
];

const transactionOutputsResponseList = [
[
{
address: 'EQB-CM6DF-jpq9XVdiSdefAMU5KC1gpZuYBFp-Q65aUhnx5K',
amount: testData.tokenRecipients[0].amount,
},
],
];

const transactionOutputsResponseBounceableList = [
[
{
address: testData.tokenRecipients[0].address,
amount: testData.tokenRecipients[0].amount,
},
],
];

transactionsList.forEach((_, index) => {
const transaction = transactionsList[index];
const transactionInputsResponse = transactionInputsResponseList[index];
const transactionInputsResponseBounceable = transactionInputsResponseBounceableList[index];
const transactionOutputsResponse = transactionOutputsResponseList[index];
const transactionOutputsResponseBounceable = transactionOutputsResponseBounceableList[index];

it('should parse a TON transaction', async function () {
const parsedTransaction = await testnetJettonToken.parseTransaction({
txHex: Buffer.from(transaction, 'base64').toString('hex'),
});

parsedTransaction.should.deepEqual({
inputs: transactionInputsResponse,
outputs: transactionOutputsResponse,
});
});

it('should parse a non-bounceable TON transaction', async function () {
const parsedTransaction = await testnetJettonToken.parseTransaction({
txHex: Buffer.from(transaction, 'base64').toString('hex'),
toAddressBounceable: false,
fromAddressBounceable: false,
} as TonParseTransactionOptions);

parsedTransaction.should.deepEqual({
inputs: transactionInputsResponseBounceable,
outputs: transactionOutputsResponseBounceable,
});
});

it('should fail to parse a TON transaction when explainTransaction response is undefined', async function () {
const stub = sinon.stub(Ton.prototype, 'explainTransaction');
stub.resolves(undefined);
await testnetJettonToken
.parseTransaction({ txHex: transaction })
.should.be.rejectedWith('invalid raw transaction');
stub.restore();
});
});
});
});
Loading