@ledgerhq/coin-ton 0.8.2-nightly.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +8 -6
- package/lib/__tests__/unit/txn.unit.test.js +32 -161
- package/lib/__tests__/unit/txn.unit.test.js.map +1 -1
- package/lib/bridge/bridgeHelpers/txn.d.ts +0 -6
- package/lib/bridge/bridgeHelpers/txn.d.ts.map +1 -1
- package/lib/bridge/bridgeHelpers/txn.js +10 -46
- package/lib/bridge/bridgeHelpers/txn.js.map +1 -1
- package/lib-es/__tests__/unit/txn.unit.test.js +28 -157
- package/lib-es/__tests__/unit/txn.unit.test.js.map +1 -1
- package/lib-es/bridge/bridgeHelpers/txn.d.ts +0 -6
- package/lib-es/bridge/bridgeHelpers/txn.d.ts.map +1 -1
- package/lib-es/bridge/bridgeHelpers/txn.js +11 -44
- package/lib-es/bridge/bridgeHelpers/txn.js.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/unit/txn.unit.test.ts +28 -206
- package/src/bridge/bridgeHelpers/txn.ts +13 -49
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
import { encodeOperationId } from "@ledgerhq/coin-framework/lib/operation";
|
|
2
2
|
import BigNumber from "bignumber.js";
|
|
3
3
|
// eslint-disable-next-line no-restricted-imports
|
|
4
|
-
import {
|
|
5
|
-
import flatMap from "lodash/flatMap";
|
|
4
|
+
import { flatMap } from "lodash";
|
|
6
5
|
import { TonJettonTransfer, TonTransaction } from "../../bridge/bridgeHelpers/api.types";
|
|
7
|
-
import {
|
|
8
|
-
dataToSlice,
|
|
9
|
-
decodeForwardPayload,
|
|
10
|
-
loadSnakeBytes,
|
|
11
|
-
mapJettonTxToOps,
|
|
12
|
-
mapTxToOps,
|
|
13
|
-
} from "../../bridge/bridgeHelpers/txn";
|
|
6
|
+
import { mapJettonTxToOps, mapTxToOps } from "../../bridge/bridgeHelpers/txn";
|
|
14
7
|
import {
|
|
15
8
|
jettonTransferResponse,
|
|
16
9
|
mockAccountId,
|
|
@@ -20,7 +13,7 @@ import {
|
|
|
20
13
|
|
|
21
14
|
describe("Transaction functions", () => {
|
|
22
15
|
describe("mapTxToOps", () => {
|
|
23
|
-
it("should map an IN
|
|
16
|
+
it.skip("should map an IN ton transaction without total_fees to a ledger operation", async () => {
|
|
24
17
|
const { now, lt, hash, in_msg, total_fees, mc_block_seqno } =
|
|
25
18
|
tonTransactionResponse.transactions[0];
|
|
26
19
|
|
|
@@ -37,21 +30,20 @@ describe("Transaction functions", () => {
|
|
|
37
30
|
date: new Date(now * 1000), // now is defined in seconds
|
|
38
31
|
extra: { comment: { isEncrypted: false, text: "" }, explorerHash: hash, lt },
|
|
39
32
|
fee: BigNumber(total_fees),
|
|
40
|
-
hasFailed:
|
|
33
|
+
hasFailed: false,
|
|
41
34
|
hash: in_msg?.hash,
|
|
42
35
|
id: encodeOperationId(mockAccountId, in_msg?.hash ?? "", "IN"),
|
|
43
36
|
recipients: [in_msg?.destination],
|
|
44
37
|
senders: ["EQCVnqqL0OOiZi2BQnjVGm-ZeUYgfUhHgAi-vn9F8-94HwrH"],
|
|
45
38
|
type: "IN",
|
|
46
39
|
value: BigNumber(in_msg?.value ?? 0),
|
|
47
|
-
subOperations: undefined,
|
|
48
40
|
},
|
|
49
41
|
]);
|
|
50
42
|
});
|
|
51
43
|
|
|
52
|
-
it("should map an IN ton transaction with total_fees to a ledger operation", async () => {
|
|
44
|
+
it.skip("should map an IN ton transaction with total_fees to a ledger operation", async () => {
|
|
53
45
|
const transactions = [{ ...tonTransactionResponse.transactions[0], total_fees: "15" }];
|
|
54
|
-
const { now, lt, hash, in_msg, total_fees, mc_block_seqno } = transactions[0];
|
|
46
|
+
const { now, lt, hash, in_msg, total_fees, mc_block_seqno, account } = transactions[0];
|
|
55
47
|
|
|
56
48
|
const finalOperation = flatMap(
|
|
57
49
|
transactions,
|
|
@@ -59,6 +51,21 @@ describe("Transaction functions", () => {
|
|
|
59
51
|
);
|
|
60
52
|
|
|
61
53
|
expect(finalOperation).toEqual([
|
|
54
|
+
{
|
|
55
|
+
id: encodeOperationId(mockAccountId, in_msg?.hash ?? "", "NONE"),
|
|
56
|
+
hash: in_msg?.hash,
|
|
57
|
+
type: "NONE",
|
|
58
|
+
value: BigNumber(total_fees),
|
|
59
|
+
fee: BigNumber(0),
|
|
60
|
+
blockHash: null,
|
|
61
|
+
blockHeight: mc_block_seqno,
|
|
62
|
+
hasFailed: false,
|
|
63
|
+
accountId: mockAccountId,
|
|
64
|
+
senders: [account],
|
|
65
|
+
recipients: [],
|
|
66
|
+
date: new Date(now * 1000), // now is defined in seconds
|
|
67
|
+
extra: { comment: { isEncrypted: false, text: "" }, explorerHash: hash, lt },
|
|
68
|
+
},
|
|
62
69
|
{
|
|
63
70
|
accountId: mockAccountId,
|
|
64
71
|
blockHash: null,
|
|
@@ -66,35 +73,18 @@ describe("Transaction functions", () => {
|
|
|
66
73
|
date: new Date(now * 1000), // now is defined in seconds
|
|
67
74
|
extra: { comment: { isEncrypted: false, text: "" }, explorerHash: hash, lt },
|
|
68
75
|
fee: BigNumber(total_fees),
|
|
69
|
-
hasFailed:
|
|
76
|
+
hasFailed: false,
|
|
70
77
|
hash: in_msg?.hash,
|
|
71
78
|
id: encodeOperationId(mockAccountId, in_msg?.hash ?? "", "IN"),
|
|
72
79
|
recipients: [in_msg?.destination],
|
|
73
80
|
senders: ["EQCVnqqL0OOiZi2BQnjVGm-ZeUYgfUhHgAi-vn9F8-94HwrH"],
|
|
74
81
|
type: "IN",
|
|
75
82
|
value: BigNumber(in_msg?.value ?? 0),
|
|
76
|
-
subOperations: [
|
|
77
|
-
{
|
|
78
|
-
id: encodeOperationId(mockAccountId, in_msg?.hash ?? "", "NONE"),
|
|
79
|
-
hash: in_msg?.hash,
|
|
80
|
-
type: "NONE",
|
|
81
|
-
value: BigNumber(total_fees),
|
|
82
|
-
fee: BigNumber(0),
|
|
83
|
-
blockHeight: mc_block_seqno,
|
|
84
|
-
blockHash: null,
|
|
85
|
-
hasFailed: true,
|
|
86
|
-
accountId: mockAccountId,
|
|
87
|
-
senders: [mockAddress],
|
|
88
|
-
recipients: [],
|
|
89
|
-
date: new Date(now * 1000),
|
|
90
|
-
extra: { comment: { isEncrypted: false, text: "" }, explorerHash: hash, lt },
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
83
|
},
|
|
94
84
|
]);
|
|
95
85
|
});
|
|
96
86
|
|
|
97
|
-
it("should map
|
|
87
|
+
it.skip("should map an OUT ton transaction to a ledger operation", async () => {
|
|
98
88
|
// The IN transaction will be used as OUT transaction and it will be adjusted
|
|
99
89
|
const transactions: TonTransaction[] = [
|
|
100
90
|
{
|
|
@@ -107,7 +97,7 @@ describe("Transaction functions", () => {
|
|
|
107
97
|
{ ...tonTransactionResponse.transactions[0].in_msg, source: transactions[0].account },
|
|
108
98
|
];
|
|
109
99
|
}
|
|
110
|
-
const { now, lt, hash, out_msgs, total_fees, mc_block_seqno } = transactions[0];
|
|
100
|
+
const { now, lt, hash, out_msgs, total_fees, mc_block_seqno, account } = transactions[0];
|
|
111
101
|
|
|
112
102
|
const finalOperation = flatMap(
|
|
113
103
|
transactions,
|
|
@@ -116,16 +106,16 @@ describe("Transaction functions", () => {
|
|
|
116
106
|
|
|
117
107
|
expect(finalOperation).toEqual([
|
|
118
108
|
{
|
|
119
|
-
id: encodeOperationId(mockAccountId, hash, "OUT"),
|
|
109
|
+
id: encodeOperationId(mockAccountId, hash ?? "", "OUT"),
|
|
120
110
|
hash: out_msgs?.[0].hash,
|
|
121
111
|
type: "OUT",
|
|
122
|
-
value: BigNumber(out_msgs
|
|
112
|
+
value: BigNumber(out_msgs[0].value ?? 0).plus(BigNumber(total_fees)),
|
|
123
113
|
fee: BigNumber(total_fees),
|
|
124
114
|
blockHeight: mc_block_seqno,
|
|
125
115
|
blockHash: null,
|
|
126
|
-
hasFailed:
|
|
116
|
+
hasFailed: false,
|
|
127
117
|
accountId: mockAccountId,
|
|
128
|
-
senders: [
|
|
118
|
+
senders: [account],
|
|
129
119
|
recipients: ["EQDzd8aeBOU-jqYw_ZSuZjceI5p-F4b7HMprAsUJAtRPbJfg"],
|
|
130
120
|
date: new Date(now * 1000), // now is defined in seconds
|
|
131
121
|
extra: { comment: { isEncrypted: false, text: "" }, explorerHash: hash, lt },
|
|
@@ -210,171 +200,3 @@ describe("Transaction functions", () => {
|
|
|
210
200
|
});
|
|
211
201
|
});
|
|
212
202
|
});
|
|
213
|
-
|
|
214
|
-
describe("TON Payload Processing Functions", () => {
|
|
215
|
-
describe("dataToSlice", () => {
|
|
216
|
-
it("should convert base64 string to Slice when it's a valid BOC", () => {
|
|
217
|
-
// Create a Cell from a string and convert to BOC
|
|
218
|
-
const cell = new Builder().storeUint(123, 32).endCell();
|
|
219
|
-
const bocBase64 = cell.toBoc().toString("base64");
|
|
220
|
-
|
|
221
|
-
const result = dataToSlice(bocBase64);
|
|
222
|
-
|
|
223
|
-
expect(result).toBeInstanceOf(Slice);
|
|
224
|
-
expect(result?.loadUint(32)).toBe(123);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it("should fallback to BitString when the data is not a valid BOC", () => {
|
|
228
|
-
const invalidBocBase64 = "aW52YWxpZCB0b24gZGF0YQ=="; // "invalid ton data"
|
|
229
|
-
|
|
230
|
-
const result = dataToSlice(invalidBocBase64);
|
|
231
|
-
|
|
232
|
-
expect(result).toBeInstanceOf(Slice);
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
it("should return undefined for non-string input", () => {
|
|
236
|
-
// @ts-expect-error - Testing invalid input
|
|
237
|
-
const result = dataToSlice(null);
|
|
238
|
-
|
|
239
|
-
expect(result).toBeUndefined();
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
describe("loadSnakeBytes", () => {
|
|
244
|
-
it("should load bytes from a simple slice without refs", () => {
|
|
245
|
-
const cell = new Builder().storeBuffer(Buffer.from("Slice", "utf-8")).endCell();
|
|
246
|
-
const slice = cell.beginParse();
|
|
247
|
-
|
|
248
|
-
const result = loadSnakeBytes(slice);
|
|
249
|
-
|
|
250
|
-
expect(result.toString("utf-8")).toBe("Slice");
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it("should load bytes from a slice with refs (snake structure)", () => {
|
|
254
|
-
// Create a chain of cells (snake structure)
|
|
255
|
-
const cell2 = new Builder().storeBuffer(Buffer.from(" Data", "utf-8")).endCell();
|
|
256
|
-
const cell1 = new Builder()
|
|
257
|
-
.storeBuffer(Buffer.from("Slice", "utf-8"))
|
|
258
|
-
.storeRef(cell2)
|
|
259
|
-
.endCell();
|
|
260
|
-
|
|
261
|
-
const slice = cell1.beginParse();
|
|
262
|
-
|
|
263
|
-
const result = loadSnakeBytes(slice);
|
|
264
|
-
|
|
265
|
-
expect(result.toString("utf-8")).toBe("Slice Data");
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it("should handle empty slice", () => {
|
|
269
|
-
const cell = new Builder().endCell();
|
|
270
|
-
const slice = cell.beginParse();
|
|
271
|
-
|
|
272
|
-
const result = loadSnakeBytes(slice);
|
|
273
|
-
|
|
274
|
-
expect(result.length).toBe(0);
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it("should handle slice with multiple refs in chain", () => {
|
|
278
|
-
// Create a longer chain of cells (snake structure)
|
|
279
|
-
const cell3 = new Builder().storeBuffer(Buffer.from("Part3", "utf-8")).endCell();
|
|
280
|
-
const cell2 = new Builder()
|
|
281
|
-
.storeBuffer(Buffer.from("Part2", "utf-8"))
|
|
282
|
-
.storeRef(cell3)
|
|
283
|
-
.endCell();
|
|
284
|
-
const cell1 = new Builder()
|
|
285
|
-
.storeBuffer(Buffer.from("Part1", "utf-8"))
|
|
286
|
-
.storeRef(cell2)
|
|
287
|
-
.endCell();
|
|
288
|
-
|
|
289
|
-
const slice = cell1.beginParse();
|
|
290
|
-
|
|
291
|
-
const result = loadSnakeBytes(slice);
|
|
292
|
-
|
|
293
|
-
expect(result.toString("utf-8")).toBe("Part1Part2Part3");
|
|
294
|
-
});
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
describe("decodeForwardPayload", () => {
|
|
298
|
-
it("should return empty string for null payload", () => {
|
|
299
|
-
const result = decodeForwardPayload(null);
|
|
300
|
-
|
|
301
|
-
expect(result).toBe("");
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
it("should decode a valid payload with opcode 0 containing text", () => {
|
|
305
|
-
// Create a cell with opcode 0 followed by a text string
|
|
306
|
-
const cell = new Builder()
|
|
307
|
-
.storeUint(0, 32) // opcode 0
|
|
308
|
-
.storeBuffer(Buffer.from("This is the comment", "utf-8"))
|
|
309
|
-
.endCell();
|
|
310
|
-
const bocBase64 = cell.toBoc().toString("base64");
|
|
311
|
-
|
|
312
|
-
const result = decodeForwardPayload(bocBase64);
|
|
313
|
-
|
|
314
|
-
expect(result).toBe("This is the comment");
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
it("should return empty string for payloads with non-zero opcode", () => {
|
|
318
|
-
// Create a cell with opcode 1 followed by some data
|
|
319
|
-
const cell = new Builder()
|
|
320
|
-
.storeUint(1, 32) // non-zero opcode
|
|
321
|
-
.storeBuffer(Buffer.from("Should be ignored", "utf-8"))
|
|
322
|
-
.endCell();
|
|
323
|
-
const bocBase64 = cell.toBoc().toString("base64");
|
|
324
|
-
|
|
325
|
-
const result = decodeForwardPayload(bocBase64);
|
|
326
|
-
|
|
327
|
-
expect(result).toBe("");
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
it("should handle payload with unicode characters", () => {
|
|
331
|
-
// Create a cell with opcode 0 followed by a text with unicode
|
|
332
|
-
const cell = new Builder()
|
|
333
|
-
.storeUint(0, 32) // opcode 0
|
|
334
|
-
.storeBuffer(Buffer.from("Unicode: 你好, мир, 🚀", "utf-8"))
|
|
335
|
-
.endCell();
|
|
336
|
-
const bocBase64 = cell.toBoc().toString("base64");
|
|
337
|
-
|
|
338
|
-
const result = decodeForwardPayload(bocBase64);
|
|
339
|
-
|
|
340
|
-
expect(result).toBe("Unicode: 你好, мир, 🚀");
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
it("should handle snake format payloads correctly", () => {
|
|
344
|
-
// Create a chain of cells with opcode 0 followed by a long message
|
|
345
|
-
const cell2 = new Builder()
|
|
346
|
-
.storeBuffer(Buffer.from(" would need multiple cells to store.", "utf-8"))
|
|
347
|
-
.endCell();
|
|
348
|
-
const cell1 = new Builder()
|
|
349
|
-
.storeUint(0, 32) // opcode 0
|
|
350
|
-
.storeBuffer(Buffer.from("This is a very long message that", "utf-8"))
|
|
351
|
-
.storeRef(cell2)
|
|
352
|
-
.endCell();
|
|
353
|
-
|
|
354
|
-
const bocBase64 = cell1.toBoc().toString("base64");
|
|
355
|
-
|
|
356
|
-
const result = decodeForwardPayload(bocBase64);
|
|
357
|
-
|
|
358
|
-
expect(result).toBe("This is a very long message that would need multiple cells to store.");
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
it("should handle invalid payloads gracefully by returning empty string", () => {
|
|
362
|
-
// Create an invalid base64 string
|
|
363
|
-
const invalidBase64 = "!@#$%^&*()";
|
|
364
|
-
|
|
365
|
-
const result = decodeForwardPayload(invalidBase64);
|
|
366
|
-
|
|
367
|
-
expect(result).toBe("");
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
it("should handle valid base64 but invalid BOC payloads", () => {
|
|
371
|
-
// Valid base64 but not a valid BOC
|
|
372
|
-
const validBase64NotBoc = "aW52YWxpZCB0b24gZGF0YQ=="; // "invalid ton data" in base64
|
|
373
|
-
|
|
374
|
-
const result = decodeForwardPayload(validBase64NotBoc);
|
|
375
|
-
|
|
376
|
-
// Should return empty string as it's not a valid Cell
|
|
377
|
-
expect(result).toBe("");
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
});
|
|
@@ -2,7 +2,7 @@ import { decodeAccountId, encodeTokenAccountId } from "@ledgerhq/coin-framework/
|
|
|
2
2
|
import { encodeOperationId } from "@ledgerhq/coin-framework/operation";
|
|
3
3
|
import { findTokenByAddressInCurrency } from "@ledgerhq/cryptoassets/tokens";
|
|
4
4
|
import { Operation } from "@ledgerhq/types-live";
|
|
5
|
-
import { Address,
|
|
5
|
+
import { Address, Cell } from "@ton/core";
|
|
6
6
|
import BigNumber from "bignumber.js";
|
|
7
7
|
import { TonOperation } from "../../types";
|
|
8
8
|
import { addressesAreEqual, isAddressValid } from "../../utils";
|
|
@@ -281,61 +281,25 @@ export function mapJettonTxToOps(
|
|
|
281
281
|
},
|
|
282
282
|
});
|
|
283
283
|
}
|
|
284
|
+
|
|
284
285
|
return ops;
|
|
285
286
|
};
|
|
286
287
|
}
|
|
287
288
|
|
|
288
|
-
|
|
289
|
-
export function dataToSlice(data: string): Slice | undefined {
|
|
290
|
-
let buffer: Buffer;
|
|
291
|
-
if (typeof data === "string") {
|
|
292
|
-
buffer = Buffer.from(data, "base64");
|
|
293
|
-
|
|
294
|
-
try {
|
|
295
|
-
return Cell.fromBoc(buffer)[0].beginParse();
|
|
296
|
-
} catch (err) {
|
|
297
|
-
return new Slice(new BitReader(new BitString(buffer, 0, buffer.length * 8)), []);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return undefined;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
export function loadSnakeBytes(slice: Slice): Buffer {
|
|
305
|
-
let buffer = Buffer.alloc(0);
|
|
306
|
-
|
|
307
|
-
while (slice.remainingBits >= 8) {
|
|
308
|
-
buffer = Buffer.concat([buffer, slice.loadBuffer(slice.remainingBits / 8)]);
|
|
309
|
-
if (slice.remainingRefs) {
|
|
310
|
-
slice = slice.loadRef().beginParse();
|
|
311
|
-
} else {
|
|
312
|
-
break;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return buffer;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
export function decodeForwardPayload(payload: string | null): string {
|
|
289
|
+
function decodeForwardPayload(payload: string | null): string {
|
|
320
290
|
if (!payload) return "";
|
|
321
291
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if (!slice) return "";
|
|
292
|
+
const decodedBuffer = Buffer.from(payload, "base64");
|
|
293
|
+
const cell = Cell.fromBoc(decodedBuffer)[0];
|
|
294
|
+
const slice = cell.beginParse();
|
|
326
295
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (opcode !== 0) {
|
|
331
|
-
return "";
|
|
332
|
-
}
|
|
333
|
-
const buffer = loadSnakeBytes(slice);
|
|
334
|
-
const comment = buffer.toString("utf-8");
|
|
335
|
-
|
|
336
|
-
return comment;
|
|
337
|
-
} catch (error) {
|
|
338
|
-
// Silent failure, returning empty string
|
|
296
|
+
// Read the opcode
|
|
297
|
+
const opcode = slice.loadUint(32);
|
|
298
|
+
if (opcode !== 0) {
|
|
339
299
|
return "";
|
|
340
300
|
}
|
|
301
|
+
|
|
302
|
+
// Read the comment
|
|
303
|
+
const comment = slice.loadStringTail();
|
|
304
|
+
return comment;
|
|
341
305
|
}
|