@ledgerhq/coin-ton
Version:
298 lines • 17 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const operation_1 = require("@ledgerhq/coin-framework/lib/operation");
const bignumber_js_1 = __importDefault(require("bignumber.js"));
// eslint-disable-next-line no-restricted-imports
const core_1 = require("@ton/core");
const flatMap_1 = __importDefault(require("lodash/flatMap"));
const txn_1 = require("../../bridge/bridgeHelpers/txn");
const common_fixtures_1 = require("../fixtures/common.fixtures");
describe("Transaction functions", () => {
describe("mapTxToOps", () => {
it("should map an IN failed ton transaction without total_fees to a ledger operation", async () => {
const { now, lt, hash, in_msg, total_fees, mc_block_seqno } = common_fixtures_1.tonTransactionResponse.transactions[0];
const finalOperation = (0, flatMap_1.default)(common_fixtures_1.tonTransactionResponse.transactions, (0, txn_1.mapTxToOps)(common_fixtures_1.mockAccountId, common_fixtures_1.mockAddress, common_fixtures_1.tonTransactionResponse.address_book));
expect(finalOperation).toEqual([
{
accountId: common_fixtures_1.mockAccountId,
blockHash: null,
blockHeight: mc_block_seqno,
date: new Date(now * 1000), // now is defined in seconds
extra: { comment: { isEncrypted: false, text: "" }, explorerHash: hash, lt },
fee: (0, bignumber_js_1.default)(total_fees),
hasFailed: true,
hash: in_msg?.hash,
id: (0, operation_1.encodeOperationId)(common_fixtures_1.mockAccountId, in_msg?.hash ?? "", "IN"),
recipients: [in_msg?.destination],
senders: ["EQCVnqqL0OOiZi2BQnjVGm-ZeUYgfUhHgAi-vn9F8-94HwrH"],
type: "IN",
value: (0, bignumber_js_1.default)(in_msg?.value ?? 0),
subOperations: undefined,
},
]);
});
it("should map an IN ton transaction with total_fees to a ledger operation", async () => {
const transactions = [{ ...common_fixtures_1.tonTransactionResponse.transactions[0], total_fees: "15" }];
const { now, lt, hash, in_msg, total_fees, mc_block_seqno } = transactions[0];
const finalOperation = (0, flatMap_1.default)(transactions, (0, txn_1.mapTxToOps)(common_fixtures_1.mockAccountId, common_fixtures_1.mockAddress, common_fixtures_1.tonTransactionResponse.address_book));
expect(finalOperation).toEqual([
{
accountId: common_fixtures_1.mockAccountId,
blockHash: null,
blockHeight: mc_block_seqno,
date: new Date(now * 1000), // now is defined in seconds
extra: { comment: { isEncrypted: false, text: "" }, explorerHash: hash, lt },
fee: (0, bignumber_js_1.default)(total_fees),
hasFailed: true,
hash: in_msg?.hash,
id: (0, operation_1.encodeOperationId)(common_fixtures_1.mockAccountId, in_msg?.hash ?? "", "IN"),
recipients: [in_msg?.destination],
senders: ["EQCVnqqL0OOiZi2BQnjVGm-ZeUYgfUhHgAi-vn9F8-94HwrH"],
type: "IN",
value: (0, bignumber_js_1.default)(in_msg?.value ?? 0),
subOperations: [
{
id: (0, operation_1.encodeOperationId)(common_fixtures_1.mockAccountId, in_msg?.hash ?? "", "NONE"),
hash: in_msg?.hash,
type: "NONE",
value: (0, bignumber_js_1.default)(total_fees),
fee: (0, bignumber_js_1.default)(0),
blockHeight: mc_block_seqno,
blockHash: null,
hasFailed: true,
accountId: common_fixtures_1.mockAccountId,
senders: [common_fixtures_1.mockAddress],
recipients: [],
date: new Date(now * 1000),
extra: { comment: { isEncrypted: false, text: "" }, explorerHash: hash, lt },
},
],
},
]);
});
it("should map a failed OUT ton transaction to a ledger operation", async () => {
// The IN transaction will be used as OUT transaction and it will be adjusted
const transactions = [
{
...common_fixtures_1.tonTransactionResponse.transactions[0],
in_msg: null,
},
];
if (common_fixtures_1.tonTransactionResponse.transactions[0].in_msg) {
transactions[0].out_msgs = [
{ ...common_fixtures_1.tonTransactionResponse.transactions[0].in_msg, source: transactions[0].account },
];
}
const { now, lt, hash, out_msgs, total_fees, mc_block_seqno } = transactions[0];
const finalOperation = (0, flatMap_1.default)(transactions, (0, txn_1.mapTxToOps)(common_fixtures_1.mockAccountId, common_fixtures_1.mockAddress, common_fixtures_1.tonTransactionResponse.address_book));
expect(finalOperation).toEqual([
{
id: (0, operation_1.encodeOperationId)(common_fixtures_1.mockAccountId, hash, "OUT"),
hash: out_msgs?.[0].hash,
type: "OUT",
value: (0, bignumber_js_1.default)(out_msgs?.[0].value ?? 0),
fee: (0, bignumber_js_1.default)(total_fees),
blockHeight: mc_block_seqno,
blockHash: null,
hasFailed: true,
accountId: common_fixtures_1.mockAccountId,
senders: [transactions[0].account],
recipients: ["EQDzd8aeBOU-jqYw_ZSuZjceI5p-F4b7HMprAsUJAtRPbJfg"],
date: new Date(now * 1000), // now is defined in seconds
extra: { comment: { isEncrypted: false, text: "" }, explorerHash: hash, lt },
},
]);
});
});
describe("mapJettonToOps", () => {
it("should map an IN ton transaction without total_fees to a ledger operation", async () => {
const { transaction_hash, amount, transaction_now, transaction_lt } = common_fixtures_1.jettonTransferResponse.jetton_transfers[0];
const finalOperation = (0, flatMap_1.default)(common_fixtures_1.jettonTransferResponse.jetton_transfers, (0, txn_1.mapJettonTxToOps)(common_fixtures_1.mockAccountId, common_fixtures_1.mockAddress, common_fixtures_1.tonTransactionResponse.address_book));
const tokenByCurrencyAddress = `${common_fixtures_1.mockAccountId}+ton%2Fjetton%2Feqavlwfdxgf2lxm67y4yzc17wykd9a0guwpkms1gosm~!underscore!~~!underscore!~not`;
expect(finalOperation).toEqual([
{
id: (0, operation_1.encodeOperationId)(tokenByCurrencyAddress, transaction_hash, "IN"),
hash: transaction_hash,
type: "IN",
value: (0, bignumber_js_1.default)(amount),
fee: (0, bignumber_js_1.default)(0),
blockHeight: 1,
blockHash: null,
hasFailed: false,
accountId: tokenByCurrencyAddress,
senders: ["EQDnqcVSV4S9m2Y9gLAQrDerQktKSx2I1uhs6r5o_H8VT9G-"],
recipients: [common_fixtures_1.mockAddress],
date: new Date(transaction_now * 1000), // now is defined in seconds
extra: {
comment: { isEncrypted: false, text: "" },
explorerHash: transaction_hash,
lt: transaction_lt,
},
},
]);
});
it("should map an OUT jetton transaction to a ledger operation", async () => {
// The IN jetton transaction will be used as OUT transaction and it will be adjusted
const jettonTransfers = [
{
...common_fixtures_1.jettonTransferResponse.jetton_transfers[0],
},
];
jettonTransfers[0].source = jettonTransfers[0].destination;
jettonTransfers[0].destination = common_fixtures_1.jettonTransferResponse.jetton_transfers[0].source;
const { transaction_hash, amount, transaction_now, transaction_lt } = jettonTransfers[0];
const finalOperation = (0, flatMap_1.default)(jettonTransfers, (0, txn_1.mapJettonTxToOps)(common_fixtures_1.mockAccountId, common_fixtures_1.mockAddress, common_fixtures_1.tonTransactionResponse.address_book));
const tokenByCurrencyAddress = `${common_fixtures_1.mockAccountId}+ton%2Fjetton%2Feqavlwfdxgf2lxm67y4yzc17wykd9a0guwpkms1gosm~!underscore!~~!underscore!~not`;
expect(finalOperation).toEqual([
{
id: (0, operation_1.encodeOperationId)(tokenByCurrencyAddress, transaction_hash, "OUT"),
hash: transaction_hash,
type: "OUT",
value: (0, bignumber_js_1.default)(amount),
fee: (0, bignumber_js_1.default)(0),
blockHeight: 1,
blockHash: null,
hasFailed: false,
accountId: tokenByCurrencyAddress,
recipients: ["EQDnqcVSV4S9m2Y9gLAQrDerQktKSx2I1uhs6r5o_H8VT9G-"],
senders: [common_fixtures_1.mockAddress],
date: new Date(transaction_now * 1000), // now is defined in seconds
extra: {
comment: { isEncrypted: false, text: "" },
explorerHash: transaction_hash,
lt: transaction_lt,
},
},
]);
});
});
});
describe("TON Payload Processing Functions", () => {
describe("dataToSlice", () => {
it("should convert base64 string to Slice when it's a valid BOC", () => {
// Create a Cell from a string and convert to BOC
const cell = new core_1.Builder().storeUint(123, 32).endCell();
const bocBase64 = cell.toBoc().toString("base64");
const result = (0, txn_1.dataToSlice)(bocBase64);
expect(result).toBeInstanceOf(core_1.Slice);
expect(result?.loadUint(32)).toBe(123);
});
it("should fallback to BitString when the data is not a valid BOC", () => {
const invalidBocBase64 = "aW52YWxpZCB0b24gZGF0YQ=="; // "invalid ton data"
const result = (0, txn_1.dataToSlice)(invalidBocBase64);
expect(result).toBeInstanceOf(core_1.Slice);
});
it("should return undefined for non-string input", () => {
// @ts-expect-error - Testing invalid input
const result = (0, txn_1.dataToSlice)(null);
expect(result).toBeUndefined();
});
});
describe("loadSnakeBytes", () => {
it("should load bytes from a simple slice without refs", () => {
const cell = new core_1.Builder().storeBuffer(Buffer.from("Slice", "utf-8")).endCell();
const slice = cell.beginParse();
const result = (0, txn_1.loadSnakeBytes)(slice);
expect(result.toString("utf-8")).toBe("Slice");
});
it("should load bytes from a slice with refs (snake structure)", () => {
// Create a chain of cells (snake structure)
const cell2 = new core_1.Builder().storeBuffer(Buffer.from(" Data", "utf-8")).endCell();
const cell1 = new core_1.Builder()
.storeBuffer(Buffer.from("Slice", "utf-8"))
.storeRef(cell2)
.endCell();
const slice = cell1.beginParse();
const result = (0, txn_1.loadSnakeBytes)(slice);
expect(result.toString("utf-8")).toBe("Slice Data");
});
it("should handle empty slice", () => {
const cell = new core_1.Builder().endCell();
const slice = cell.beginParse();
const result = (0, txn_1.loadSnakeBytes)(slice);
expect(result.length).toBe(0);
});
it("should handle slice with multiple refs in chain", () => {
// Create a longer chain of cells (snake structure)
const cell3 = new core_1.Builder().storeBuffer(Buffer.from("Part3", "utf-8")).endCell();
const cell2 = new core_1.Builder()
.storeBuffer(Buffer.from("Part2", "utf-8"))
.storeRef(cell3)
.endCell();
const cell1 = new core_1.Builder()
.storeBuffer(Buffer.from("Part1", "utf-8"))
.storeRef(cell2)
.endCell();
const slice = cell1.beginParse();
const result = (0, txn_1.loadSnakeBytes)(slice);
expect(result.toString("utf-8")).toBe("Part1Part2Part3");
});
});
describe("decodeForwardPayload", () => {
it("should return empty string for null payload", () => {
const result = (0, txn_1.decodeForwardPayload)(null);
expect(result).toBe("");
});
it("should decode a valid payload with opcode 0 containing text", () => {
// Create a cell with opcode 0 followed by a text string
const cell = new core_1.Builder()
.storeUint(0, 32) // opcode 0
.storeBuffer(Buffer.from("This is the comment", "utf-8"))
.endCell();
const bocBase64 = cell.toBoc().toString("base64");
const result = (0, txn_1.decodeForwardPayload)(bocBase64);
expect(result).toBe("This is the comment");
});
it("should return empty string for payloads with non-zero opcode", () => {
// Create a cell with opcode 1 followed by some data
const cell = new core_1.Builder()
.storeUint(1, 32) // non-zero opcode
.storeBuffer(Buffer.from("Should be ignored", "utf-8"))
.endCell();
const bocBase64 = cell.toBoc().toString("base64");
const result = (0, txn_1.decodeForwardPayload)(bocBase64);
expect(result).toBe("");
});
it("should handle payload with unicode characters", () => {
// Create a cell with opcode 0 followed by a text with unicode
const cell = new core_1.Builder()
.storeUint(0, 32) // opcode 0
.storeBuffer(Buffer.from("Unicode: 你好, мир, 🚀", "utf-8"))
.endCell();
const bocBase64 = cell.toBoc().toString("base64");
const result = (0, txn_1.decodeForwardPayload)(bocBase64);
expect(result).toBe("Unicode: 你好, мир, 🚀");
});
it("should handle snake format payloads correctly", () => {
// Create a chain of cells with opcode 0 followed by a long message
const cell2 = new core_1.Builder()
.storeBuffer(Buffer.from(" would need multiple cells to store.", "utf-8"))
.endCell();
const cell1 = new core_1.Builder()
.storeUint(0, 32) // opcode 0
.storeBuffer(Buffer.from("This is a very long message that", "utf-8"))
.storeRef(cell2)
.endCell();
const bocBase64 = cell1.toBoc().toString("base64");
const result = (0, txn_1.decodeForwardPayload)(bocBase64);
expect(result).toBe("This is a very long message that would need multiple cells to store.");
});
it("should handle invalid payloads gracefully by returning empty string", () => {
// Create an invalid base64 string
const invalidBase64 = "!@#$%^&*()";
const result = (0, txn_1.decodeForwardPayload)(invalidBase64);
expect(result).toBe("");
});
it("should handle valid base64 but invalid BOC payloads", () => {
// Valid base64 but not a valid BOC
const validBase64NotBoc = "aW52YWxpZCB0b24gZGF0YQ=="; // "invalid ton data" in base64
const result = (0, txn_1.decodeForwardPayload)(validBase64NotBoc);
// Should return empty string as it's not a valid Cell
expect(result).toBe("");
});
});
});
//# sourceMappingURL=txn.unit.test.js.map