@ton-community/ton-ledger 4.0.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [4.0.0] - 2023-06-09
8
+
9
+ ### Added
10
+
11
+ - Added payload types for NFT and Jetton transfers
12
+ - Added TON Connect 2.0 address proof request
13
+
14
+ ### Removed
15
+
16
+ - Removed old payload types except for comment and unsafe
17
+
18
+ ### Changed
19
+
20
+ - Updated dependencies
21
+ - Changed APDU format to be the same as the latest embedded app version (breaking change)
22
+
23
+ ## [3.0.0] - 2023-01-08
24
+
25
+ ### Changed
26
+
27
+ - Migration to `ton-core`
28
+
29
+ ## [2.3.2]
30
+
31
+ - Update documentation
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # TON Ledger Library
2
+
3
+ This library allows you to connect to a ledger device and with with TON from browser (only Chrome), NodeJS and React Native.
4
+
5
+ ## How to install
6
+
7
+ To add library to your project execute:
8
+
9
+ ```bash
10
+ yarn add ton-ledger
11
+ ```
12
+
13
+ ## Connecting to a Device
14
+
15
+ First you need to select transport library for you environment.
16
+
17
+ Browser:
18
+ * [@ledgerhq/hw-transport-webhid](https://www.npmjs.com/package/@ledgerhq/hw-transport-webhid)
19
+ * [@ledgerhq/hw-transport-webusb](https://www.npmjs.com/package/@ledgerhq/hw-transport-webusb)
20
+
21
+ Node:
22
+ * [@ledgerhq/hw-transport-node-ble](https://www.npmjs.com/package/@ledgerhq/hw-transport-node-ble)
23
+ * [@ledgerhq/hw-transport-node-hid](https://www.npmjs.com/package/@ledgerhq/hw-transport-node-hid)
24
+ * [@ledgerhq/hw-transport-node-hid-noevents](https://www.npmjs.com/package/@ledgerhq/hw-transport-node-hid-noevents)
25
+ * [@ledgerhq/hw-transport-node-hid-singleton](https://www.npmjs.com/package/@ledgerhq/hw-transport-node-hid-singleton)
26
+
27
+ React Native:
28
+ * [@ledgerhq/hw-transport-web-ble](https://www.npmjs.com/package/@ledgerhq/hw-transport-web-ble)
29
+
30
+ After connecting to a device create a TonTransport instance:
31
+ ```typescript
32
+ import { TonTransport } from 'ton-ledger';
33
+ let transport = new TonTransport(device);
34
+ ```
35
+
36
+ ## Deriviation Path
37
+
38
+ For hardware wallets you need to specify deriviation path of your account for TON it is specified as:
39
+
40
+ ```typescript
41
+ function pathForAccount(testnet: boolean, workchain: number, account: number) {
42
+ let network = testnet ? 1 : 0;
43
+ let chain = workchain === -1 ? 255 : 0;
44
+ return [44, 607, network, chain, account, 0]; // Last zero is reserved for alternative wallet contracts
45
+ }
46
+ ```
47
+
48
+ You can specify any path that starts with `[44, 607]`, but it could be incompatible with other apps.
49
+
50
+ ## Get an Address and Public Key
51
+
52
+ To get an address without confimration on device you can perform next things:
53
+
54
+ ```typescript
55
+ let testnet = true;
56
+ let workchain = 0;
57
+ let accountIndex = 0;
58
+ let bounceable = false;
59
+ let path = pathForAccount(testnet, workchain, accountIndex);
60
+ let response = await transport.getAddress(path, { chain, bounceable, testOnly: testnet });
61
+ let publiKey: Buffer = response.publicKey;
62
+ let address: string = response.address;
63
+ ```
64
+
65
+ ## Validate Address
66
+
67
+ The same as getting address, but returns address and key only when user confirms that address on the screen is correct. This method usually used after the non-confirming one and displaying address in dApp ad then requesting address validation.
68
+
69
+ ```typescript
70
+ let testnet = true;
71
+ let workchain = 0;
72
+ let accountIndex = 0;
73
+ let bounceable = false;
74
+ let path = pathForAccount(testnet, workchain, accountIndex);
75
+ let response = await transport.validateAddress(path, { chain, bounceable, testOnly: testnet });
76
+ let publiKey: Buffer = response.publicKey;
77
+ let address: string = response.address;
78
+ ```
79
+
80
+ ## Sign simple transaction
81
+
82
+ Ledger Nanoapp works with Wallet v4 for now, we recommend you to continue to use it:
83
+
84
+ ```typescript
85
+ import { WalletV4Contract, WalletV4Source } from 'ton';
86
+ import { TonPayloadFormat } from 'ton-ledger';
87
+ import { TonClient, Address, SendMode, toNano } from 'ton-core';
88
+
89
+ let client = new TonClient({ endpoint: 'https://toncenter.com/api/v2/jsonRPC' });
90
+ let source = WalletV4Source.create({ workchain: 0, publicKey: deviceAddress.publicKey });
91
+ let contract = new WalletV4Contract(address, source);
92
+ let seqno = await contract.getSeqNo();
93
+
94
+ // Parameters
95
+ let path: number[]; // Account path from above
96
+ let to: Address = Address.parse('...'); // Destination
97
+ let amount: bigint = toNano('100'); // Send 100 TON
98
+ let sendMode = SendMode.IGNORE_ERRORS | SendMode.PAY_GAS_SEPARATLY;
99
+ let timeout = Math.floor((Date.now() / 1000) + 60);
100
+ let bounce = false;
101
+ let payload: TonPayloadFormat | null = null; // See below
102
+
103
+ // Signing on device
104
+ let signed = await transport.signTransaction(path, {
105
+ to,
106
+ sendMode,
107
+ amount,
108
+ seqno,
109
+ timeout: Math.floor((Date.now() / 1000) + 60),
110
+ bounce,
111
+ payload: payload ? payload : undefined
112
+ });
113
+
114
+ // Send transaction to the network
115
+ await c.sendExternalMessage(contract, signed);
116
+
117
+ ```
118
+
119
+ ## Payload formats
120
+
121
+ Usually you want to perform transactions with some payload. Ledger's NanoApp currently supports 2 stable commands, all other are outdated or unstable:
122
+
123
+ ### Transaction with a comment
124
+ Comments are limited to ASCII-only symbols and 127 letters. Anything above would be automatically downgraded to Blind Signing Mode that you want to avoid at all cost.
125
+
126
+ ```typescript
127
+ let payload: TonPayloadFormat = {
128
+ type: 'comment',
129
+ text: 'Deposit'
130
+ };
131
+ ```
132
+
133
+ ### Unsafe with custom payload
134
+
135
+ This payload allows you to send arbitrary message, this is considered as Blind Signing Mode and only hash of your transaction would be shown to a user.
136
+
137
+ ```typescript
138
+ let cell: Cell = ...
139
+ let message = new CellMessage(cell);
140
+ let payload: TonPayloadFormat = {
141
+ type: 'unsafe',
142
+ message
143
+ };
144
+ ```
145
+
146
+ # License
147
+
148
+ MIT
@@ -0,0 +1,74 @@
1
+ /// <reference types="node" />
2
+ import Transport from "@ledgerhq/hw-transport";
3
+ import { Address, Cell, SendMode, StateInit } from "ton-core";
4
+ export type TonPayloadFormat = {
5
+ type: 'unsafe';
6
+ message: Cell;
7
+ } | {
8
+ type: 'comment';
9
+ text: string;
10
+ } | {
11
+ type: 'jetton-transfer';
12
+ queryId: bigint | null;
13
+ amount: bigint;
14
+ decimals: number;
15
+ ticker: string;
16
+ destination: Address;
17
+ responseDestination: Address;
18
+ customPayload: Cell | null;
19
+ forwardAmount: bigint;
20
+ forwardPayload: Cell | null;
21
+ } | {
22
+ type: 'nft-transfer';
23
+ queryId: bigint | null;
24
+ newOwner: Address;
25
+ responseDestination: Address;
26
+ customPayload: Cell | null;
27
+ forwardAmount: bigint;
28
+ forwardPayload: Cell | null;
29
+ };
30
+ export declare class TonTransport {
31
+ #private;
32
+ readonly transport: Transport;
33
+ constructor(transport: Transport);
34
+ isAppOpen(): Promise<boolean>;
35
+ getVersion(): Promise<string>;
36
+ getAddress(path: number[], opts?: {
37
+ testOnly?: boolean;
38
+ bounceable?: boolean;
39
+ chain?: number;
40
+ }): Promise<{
41
+ address: string;
42
+ publicKey: Buffer;
43
+ }>;
44
+ validateAddress(path: number[], opts?: {
45
+ testOnly?: boolean;
46
+ bounceable?: boolean;
47
+ chain?: number;
48
+ }): Promise<{
49
+ address: string;
50
+ publicKey: Buffer;
51
+ }>;
52
+ getAddressProof(path: number[], params: {
53
+ domain: string;
54
+ timestamp: number;
55
+ payload: Buffer;
56
+ }, opts?: {
57
+ testOnly?: boolean;
58
+ bounceable?: boolean;
59
+ chain?: number;
60
+ }): Promise<{
61
+ signature: Buffer;
62
+ hash: Buffer;
63
+ }>;
64
+ signTransaction: (path: number[], transaction: {
65
+ to: Address;
66
+ sendMode: SendMode;
67
+ seqno: number;
68
+ timeout: number;
69
+ bounce: boolean;
70
+ amount: bigint;
71
+ stateInit?: StateInit;
72
+ payload?: TonPayloadFormat;
73
+ }) => Promise<Cell>;
74
+ }
@@ -0,0 +1,408 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TonTransport = void 0;
4
+ const ton_core_1 = require("ton-core");
5
+ const ton_crypto_1 = require("ton-crypto");
6
+ const teslabot_1 = require("teslabot");
7
+ const ledgerWriter_1 = require("./utils/ledgerWriter");
8
+ const getInit_1 = require("./utils/getInit");
9
+ const LEDGER_SYSTEM = 0xB0;
10
+ const LEDGER_CLA = 0xe0;
11
+ const INS_VERSION = 0x03;
12
+ const INS_ADDRESS = 0x05;
13
+ const INS_PROOF = 0x08;
14
+ function chunks(buf, n) {
15
+ const nc = Math.ceil(buf.length / n);
16
+ const cs = [];
17
+ for (let i = 0; i < nc; i++) {
18
+ cs.push(buf.subarray(i * n, (i + 1) * n));
19
+ }
20
+ return cs;
21
+ }
22
+ class TonTransport {
23
+ transport;
24
+ #lock = new teslabot_1.AsyncLock();
25
+ constructor(transport) {
26
+ this.transport = transport;
27
+ }
28
+ //
29
+ // Apps
30
+ //
31
+ async #getCurrentApp() {
32
+ return this.#lock.inLock(async () => {
33
+ let r = await this.transport.send(LEDGER_SYSTEM, 0x01, 0x00, 0x00, undefined, [0x9000]);
34
+ let data = r.slice(0, r.length - 2);
35
+ if (data[0] !== 0x01) {
36
+ throw Error('Invalid response');
37
+ }
38
+ let nameLength = data[1];
39
+ let name = data.slice(2, 2 + nameLength).toString();
40
+ let versionLength = data[2 + nameLength];
41
+ let version = data.slice(3 + nameLength, 3 + nameLength + versionLength).toString();
42
+ return { name, version };
43
+ });
44
+ }
45
+ async isAppOpen() {
46
+ return (await this.#getCurrentApp()).name === 'TON';
47
+ }
48
+ async getVersion() {
49
+ let loaded = await this.#doRequest(INS_VERSION, 0x00, 0x00, Buffer.alloc(0));
50
+ const [major, minor, patch] = loaded;
51
+ return `${major}.${minor}.${patch}`;
52
+ }
53
+ //
54
+ // Operations
55
+ //
56
+ async getAddress(path, opts) {
57
+ // Check path
58
+ validatePath(path);
59
+ // Resolve flags
60
+ let bounceable = true;
61
+ let chain = 0;
62
+ let test = false;
63
+ let flags = 0x00;
64
+ if (opts && opts.bounceable !== undefined && !opts.bounceable) {
65
+ flags |= 0x01;
66
+ bounceable = false;
67
+ }
68
+ if (opts && opts.testOnly) {
69
+ flags |= 0x02;
70
+ test = true;
71
+ }
72
+ if (opts && opts.chain !== undefined) {
73
+ if (opts.chain !== 0 && opts.chain !== -1) {
74
+ throw Error('Invalid chain');
75
+ }
76
+ chain = opts.chain;
77
+ if (opts.chain === -1) {
78
+ flags |= 0x04;
79
+ }
80
+ }
81
+ // Get public key
82
+ let response = await this.#doRequest(INS_ADDRESS, 0x00, 0x00, pathElementsToBuffer(path.map((v) => v + 0x80000000)));
83
+ if (response.length !== 32) {
84
+ throw Error('Invalid response');
85
+ }
86
+ // Contract
87
+ const contract = (0, getInit_1.getInit)(chain, response);
88
+ const address = (0, ton_core_1.contractAddress)(chain, contract);
89
+ return { address: address.toString({ bounceable: bounceable, testOnly: test }), publicKey: response };
90
+ }
91
+ async validateAddress(path, opts) {
92
+ // Check path
93
+ validatePath(path);
94
+ // Resolve flags
95
+ let bounceable = true;
96
+ let chain = 0;
97
+ let test = false;
98
+ let flags = 0x00;
99
+ if (opts && opts.bounceable !== undefined && !opts.bounceable) {
100
+ flags |= 0x01;
101
+ bounceable = false;
102
+ }
103
+ if (opts && opts.testOnly) {
104
+ flags |= 0x02;
105
+ test = true;
106
+ }
107
+ if (opts && opts.chain !== undefined) {
108
+ if (opts.chain !== 0 && opts.chain !== -1) {
109
+ throw Error('Invalid chain');
110
+ }
111
+ chain = opts.chain;
112
+ if (opts.chain === -1) {
113
+ flags |= 0x04;
114
+ }
115
+ }
116
+ // Get public key
117
+ let response = await this.#doRequest(INS_ADDRESS, 0x01, flags, pathElementsToBuffer(path.map((v) => v + 0x80000000)));
118
+ if (response.length !== 32) {
119
+ throw Error('Invalid response');
120
+ }
121
+ // Contract
122
+ const contract = (0, getInit_1.getInit)(chain, response);
123
+ const address = (0, ton_core_1.contractAddress)(chain, contract);
124
+ return { address: address.toString({ bounceable: bounceable, testOnly: test }), publicKey: response };
125
+ }
126
+ async getAddressProof(path, params, opts) {
127
+ // Check path
128
+ validatePath(path);
129
+ let publicKey = (await this.getAddress(path)).publicKey;
130
+ // Resolve flags
131
+ let bounceable = true;
132
+ let chain = 0;
133
+ let test = false;
134
+ let flags = 0x00;
135
+ if (opts && opts.bounceable !== undefined && !opts.bounceable) {
136
+ flags |= 0x01;
137
+ bounceable = false;
138
+ }
139
+ if (opts && opts.testOnly) {
140
+ flags |= 0x02;
141
+ test = true;
142
+ }
143
+ if (opts && opts.chain !== undefined) {
144
+ if (opts.chain !== 0 && opts.chain !== -1) {
145
+ throw Error('Invalid chain');
146
+ }
147
+ chain = opts.chain;
148
+ if (opts.chain === -1) {
149
+ flags |= 0x04;
150
+ }
151
+ }
152
+ const domainBuf = Buffer.from(params.domain, 'utf-8');
153
+ const reqBuf = Buffer.concat([
154
+ pathElementsToBuffer(path.map((v) => v + 0x80000000)),
155
+ (0, ledgerWriter_1.writeUint8)(domainBuf.length),
156
+ domainBuf,
157
+ (0, ledgerWriter_1.writeUint64)(BigInt(params.timestamp)),
158
+ params.payload,
159
+ ]);
160
+ // Get public key
161
+ let res = await this.#doRequest(INS_PROOF, 0x01, flags, reqBuf);
162
+ let signature = res.slice(1, 1 + 64);
163
+ let hash = res.slice(2 + 64, 2 + 64 + 32);
164
+ if (!(0, ton_crypto_1.signVerify)(hash, signature, publicKey)) {
165
+ throw Error('Received signature is invalid');
166
+ }
167
+ return { signature, hash };
168
+ }
169
+ signTransaction = async (path, transaction) => {
170
+ // Check path
171
+ validatePath(path);
172
+ //
173
+ // Fetch key
174
+ //
175
+ let publicKey = (await this.getAddress(path)).publicKey;
176
+ //
177
+ // Create package
178
+ //
179
+ let pkg = Buffer.concat([
180
+ (0, ledgerWriter_1.writeUint8)(0),
181
+ (0, ledgerWriter_1.writeUint32)(transaction.seqno),
182
+ (0, ledgerWriter_1.writeUint32)(transaction.timeout),
183
+ (0, ledgerWriter_1.writeVarUInt)(transaction.amount),
184
+ (0, ledgerWriter_1.writeAddress)(transaction.to),
185
+ (0, ledgerWriter_1.writeUint8)(transaction.bounce ? 1 : 0),
186
+ (0, ledgerWriter_1.writeUint8)(transaction.sendMode),
187
+ ]);
188
+ //
189
+ // State init
190
+ //
191
+ let stateInit = null;
192
+ if (transaction.stateInit) {
193
+ stateInit = (0, ton_core_1.beginCell)()
194
+ .store((0, ton_core_1.storeStateInit)(transaction.stateInit))
195
+ .endCell();
196
+ pkg = Buffer.concat([
197
+ pkg,
198
+ (0, ledgerWriter_1.writeUint8)(1),
199
+ (0, ledgerWriter_1.writeUint16)(stateInit.depth()),
200
+ stateInit.hash()
201
+ ]);
202
+ }
203
+ else {
204
+ pkg = Buffer.concat([
205
+ pkg,
206
+ (0, ledgerWriter_1.writeUint8)(0)
207
+ ]);
208
+ }
209
+ //
210
+ // Payload
211
+ //
212
+ let payload = null;
213
+ let hints = Buffer.concat([(0, ledgerWriter_1.writeUint8)(0)]);
214
+ if (transaction.payload) {
215
+ if (transaction.payload.type === 'comment') {
216
+ hints = Buffer.concat([
217
+ (0, ledgerWriter_1.writeUint8)(1),
218
+ (0, ledgerWriter_1.writeUint32)(0x00),
219
+ (0, ledgerWriter_1.writeUint16)(Buffer.from(transaction.payload.text).length),
220
+ Buffer.from(transaction.payload.text)
221
+ ]);
222
+ payload = (0, ton_core_1.beginCell)()
223
+ .storeUint(0, 32)
224
+ .storeBuffer(Buffer.from(transaction.payload.text))
225
+ .endCell();
226
+ }
227
+ else if (transaction.payload.type === 'unsafe') {
228
+ payload = transaction.payload.message;
229
+ }
230
+ else if (transaction.payload.type === 'jetton-transfer' || transaction.payload.type === 'nft-transfer') {
231
+ hints = Buffer.concat([
232
+ (0, ledgerWriter_1.writeUint8)(1),
233
+ (0, ledgerWriter_1.writeUint32)(transaction.payload.type === 'jetton-transfer' ? 0x01 : 0x02)
234
+ ]);
235
+ let b = (0, ton_core_1.beginCell)()
236
+ .storeUint(transaction.payload.type === 'jetton-transfer' ? 0x0f8a7ea5 : 0x5fcc3d14, 32);
237
+ let d = Buffer.alloc(0);
238
+ if (transaction.payload.queryId !== null) {
239
+ d = Buffer.concat([d, (0, ledgerWriter_1.writeUint8)(1), (0, ledgerWriter_1.writeUint64)(transaction.payload.queryId)]);
240
+ b = b.storeUint(transaction.payload.queryId, 64);
241
+ }
242
+ else {
243
+ d = Buffer.concat([d, (0, ledgerWriter_1.writeUint8)(0)]);
244
+ b = b.storeUint(0, 64);
245
+ }
246
+ if (transaction.payload.type === 'jetton-transfer') {
247
+ d = Buffer.concat([d, (0, ledgerWriter_1.writeVarUInt)(transaction.payload.amount)]);
248
+ b = b.storeCoins(transaction.payload.amount);
249
+ d = Buffer.concat([d, (0, ledgerWriter_1.writeUint8)(transaction.payload.decimals), (0, ledgerWriter_1.writeUint8)(transaction.payload.ticker.length), Buffer.from(transaction.payload.ticker, 'ascii')]);
250
+ d = Buffer.concat([d, (0, ledgerWriter_1.writeAddress)(transaction.payload.destination)]);
251
+ b = b.storeAddress(transaction.payload.destination);
252
+ }
253
+ else {
254
+ d = Buffer.concat([d, (0, ledgerWriter_1.writeAddress)(transaction.payload.newOwner)]);
255
+ b = b.storeAddress(transaction.payload.newOwner);
256
+ }
257
+ d = Buffer.concat([d, (0, ledgerWriter_1.writeAddress)(transaction.payload.responseDestination)]);
258
+ b = b.storeAddress(transaction.payload.responseDestination);
259
+ if (transaction.payload.customPayload !== null) {
260
+ d = Buffer.concat([d, (0, ledgerWriter_1.writeUint8)(1), (0, ledgerWriter_1.writeCellRef)(transaction.payload.customPayload)]);
261
+ b = b.storeMaybeRef(transaction.payload.customPayload);
262
+ }
263
+ else {
264
+ d = Buffer.concat([d, (0, ledgerWriter_1.writeUint8)(0)]);
265
+ b = b.storeMaybeRef(transaction.payload.customPayload);
266
+ }
267
+ d = Buffer.concat([d, (0, ledgerWriter_1.writeVarUInt)(transaction.payload.forwardAmount)]);
268
+ b = b.storeCoins(transaction.payload.forwardAmount);
269
+ if (transaction.payload.forwardPayload !== null) {
270
+ d = Buffer.concat([d, (0, ledgerWriter_1.writeUint8)(1), (0, ledgerWriter_1.writeCellRef)(transaction.payload.forwardPayload)]);
271
+ b = b.storeMaybeRef(transaction.payload.forwardPayload);
272
+ }
273
+ else {
274
+ d = Buffer.concat([d, (0, ledgerWriter_1.writeUint8)(0)]);
275
+ b = b.storeMaybeRef(transaction.payload.forwardPayload);
276
+ }
277
+ payload = b.endCell();
278
+ hints = Buffer.concat([
279
+ hints,
280
+ (0, ledgerWriter_1.writeUint16)(d.length),
281
+ d
282
+ ]);
283
+ }
284
+ }
285
+ //
286
+ // Serialize payload
287
+ //
288
+ if (payload) {
289
+ pkg = Buffer.concat([
290
+ pkg,
291
+ (0, ledgerWriter_1.writeUint8)(1),
292
+ (0, ledgerWriter_1.writeUint16)(payload.depth()),
293
+ payload.hash(),
294
+ hints
295
+ ]);
296
+ }
297
+ else {
298
+ pkg = Buffer.concat([
299
+ pkg,
300
+ (0, ledgerWriter_1.writeUint8)(0),
301
+ (0, ledgerWriter_1.writeUint8)(0)
302
+ ]);
303
+ }
304
+ //
305
+ // Send package
306
+ //
307
+ await this.#doRequest(0x06, 0x00, 0x03, pathElementsToBuffer(path.map((v) => v + 0x80000000)));
308
+ const pkgCs = chunks(pkg, 255);
309
+ for (let i = 0; i < pkgCs.length - 1; i++) {
310
+ await this.#doRequest(0x06, 0x00, 0x02, pkgCs[i]);
311
+ }
312
+ let res = await this.#doRequest(0x06, 0x00, 0x00, pkgCs[pkgCs.length - 1]);
313
+ //
314
+ // Parse response
315
+ //
316
+ let orderBuilder = (0, ton_core_1.beginCell)()
317
+ .storeBit(0)
318
+ .storeBit(true)
319
+ .storeBit(transaction.bounce)
320
+ .storeBit(false)
321
+ .storeAddress(null)
322
+ .storeAddress(transaction.to)
323
+ .storeCoins(transaction.amount)
324
+ .storeBit(false)
325
+ .storeCoins(0)
326
+ .storeCoins(0)
327
+ .storeUint(0, 64)
328
+ .storeUint(0, 32);
329
+ // State Init
330
+ if (stateInit) {
331
+ orderBuilder = orderBuilder
332
+ .storeBit(true)
333
+ .storeBit(true) // Always in reference
334
+ .storeRef(stateInit);
335
+ }
336
+ else {
337
+ orderBuilder = orderBuilder
338
+ .storeBit(false);
339
+ }
340
+ // Payload
341
+ if (payload) {
342
+ orderBuilder = orderBuilder
343
+ .storeBit(true) // Always in reference
344
+ .storeRef(payload);
345
+ }
346
+ else {
347
+ orderBuilder = orderBuilder
348
+ .storeBit(false);
349
+ }
350
+ // Transfer message
351
+ let transfer = (0, ton_core_1.beginCell)()
352
+ .storeUint(698983191, 32)
353
+ .storeUint(transaction.timeout, 32)
354
+ .storeUint(transaction.seqno, 32)
355
+ .storeUint(0, 8)
356
+ .storeUint(transaction.sendMode, 8)
357
+ .storeRef(orderBuilder.endCell())
358
+ .endCell();
359
+ // Parse result
360
+ let signature = res.slice(1, 1 + 64);
361
+ let hash = res.slice(2 + 64, 2 + 64 + 32);
362
+ if (!hash.equals(transfer.hash())) {
363
+ throw Error('Hash mismatch. Expected: ' + transfer.hash().toString('hex') + ', got: ' + hash.toString('hex'));
364
+ }
365
+ if (!(0, ton_crypto_1.signVerify)(hash, signature, publicKey)) {
366
+ throw Error('Received signature is invalid');
367
+ }
368
+ // Build a message
369
+ return (0, ton_core_1.beginCell)()
370
+ .storeBuffer(signature)
371
+ .storeSlice(transfer.beginParse())
372
+ .endCell();
373
+ };
374
+ #doRequest = async (ins, p1, p2, data) => {
375
+ return this.#lock.inLock(async () => {
376
+ let r = await this.transport.send(LEDGER_CLA, ins, p1, p2, data);
377
+ return r.slice(0, r.length - 2);
378
+ });
379
+ };
380
+ }
381
+ exports.TonTransport = TonTransport;
382
+ //
383
+ // Utils
384
+ //
385
+ function validatePath(path) {
386
+ if (path.length < 6) {
387
+ throw Error('Path is too short');
388
+ }
389
+ if (path[0] !== 44) {
390
+ throw Error('First element of a path must be 44');
391
+ }
392
+ if (path[1] !== 607) {
393
+ throw Error('Second element of a path must be 607');
394
+ }
395
+ for (let p of path) {
396
+ if (p >= 0x80000000) {
397
+ throw Error('All path elements must be under 0x80000000');
398
+ }
399
+ }
400
+ }
401
+ function pathElementsToBuffer(paths) {
402
+ const buffer = Buffer.alloc(1 + paths.length * 4);
403
+ buffer[0] = paths.length;
404
+ paths.forEach((element, index) => {
405
+ buffer.writeUInt32BE(element, 1 + 4 * index);
406
+ });
407
+ return buffer;
408
+ }
@@ -0,0 +1 @@
1
+ export { TonPayloadFormat, TonTransport } from './TonTransport';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TonTransport = void 0;
4
+ var TonTransport_1 = require("./TonTransport");
5
+ Object.defineProperty(exports, "TonTransport", { enumerable: true, get: function () { return TonTransport_1.TonTransport; } });
@@ -0,0 +1,6 @@
1
+ /// <reference types="node" />
2
+ import { Cell } from 'ton-core';
3
+ export declare function getInit(workchain: number, publicKey: Buffer): {
4
+ code: Cell;
5
+ data: Cell;
6
+ };
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getInit = void 0;
4
+ const ton_core_1 = require("ton-core");
5
+ function getInit(workchain, publicKey) {
6
+ let code = ton_core_1.Cell.fromBoc(Buffer.from('te6ccgECFAEAAtQAART/APSkE/S88sgLAQIBIAIDAgFIBAUE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8QERITAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNBgcCASAICQB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/JgED7AJJfA+ICASAKCwBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDA0AEbjJftRNDXCx+AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIA4PABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVA==', 'base64'))[0];
7
+ let data = (0, ton_core_1.beginCell)()
8
+ .storeUint(0, 32) // Seqno
9
+ .storeUint(698983191 + workchain, 32)
10
+ .storeBuffer(publicKey)
11
+ .storeBit(0) // Empty plugins dict
12
+ .endCell();
13
+ return { code, data };
14
+ }
15
+ exports.getInit = getInit;
@@ -0,0 +1,9 @@
1
+ /// <reference types="node" />
2
+ import { Address, Cell } from 'ton-core';
3
+ export declare function writeUint32(value: number): Buffer;
4
+ export declare function writeUint16(value: number): Buffer;
5
+ export declare function writeUint64(value: bigint): Buffer;
6
+ export declare function writeVarUInt(value: bigint): Buffer;
7
+ export declare function writeUint8(value: number): Buffer;
8
+ export declare function writeAddress(address: Address): Buffer;
9
+ export declare function writeCellRef(ref: Cell): Buffer;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.writeCellRef = exports.writeAddress = exports.writeUint8 = exports.writeVarUInt = exports.writeUint64 = exports.writeUint16 = exports.writeUint32 = void 0;
4
+ const ton_core_1 = require("ton-core");
5
+ function writeUint32(value) {
6
+ let b = Buffer.alloc(4);
7
+ b.writeUint32BE(value, 0);
8
+ return b;
9
+ }
10
+ exports.writeUint32 = writeUint32;
11
+ function writeUint16(value) {
12
+ let b = Buffer.alloc(2);
13
+ b.writeUint16BE(value, 0);
14
+ return b;
15
+ }
16
+ exports.writeUint16 = writeUint16;
17
+ function writeUint64(value) {
18
+ return (0, ton_core_1.beginCell)().storeUint(value, 64).endCell().beginParse().loadBuffer(8);
19
+ }
20
+ exports.writeUint64 = writeUint64;
21
+ function writeVarUInt(value) {
22
+ const sizeBytes = Math.ceil((value.toString(2).length) / 8);
23
+ return (0, ton_core_1.beginCell)().storeUint(sizeBytes, 8).storeUint(value, sizeBytes * 8).endCell().beginParse().loadBuffer(1 + sizeBytes);
24
+ }
25
+ exports.writeVarUInt = writeVarUInt;
26
+ function writeUint8(value) {
27
+ let b = Buffer.alloc(1);
28
+ b[0] = value;
29
+ return b;
30
+ }
31
+ exports.writeUint8 = writeUint8;
32
+ function writeAddress(address) {
33
+ return Buffer.concat([
34
+ writeUint8(address.workChain === -1 ? 0xff : address.workChain),
35
+ address.hash
36
+ ]);
37
+ }
38
+ exports.writeAddress = writeAddress;
39
+ function writeCellRef(ref) {
40
+ return Buffer.concat([
41
+ writeUint16(ref.depth()),
42
+ ref.hash()
43
+ ]);
44
+ }
45
+ exports.writeCellRef = writeCellRef;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ton_core_1 = require("ton-core");
4
+ const ledgerWriter_1 = require("./ledgerWriter");
5
+ describe('ledgerWriter', () => {
6
+ it('should write ints', () => {
7
+ expect((0, ledgerWriter_1.writeUint8)(0).toString('hex')).toMatchSnapshot();
8
+ expect((0, ledgerWriter_1.writeUint8)(10).toString('hex')).toMatchSnapshot();
9
+ expect((0, ledgerWriter_1.writeUint8)(255).toString('hex')).toMatchSnapshot();
10
+ expect((0, ledgerWriter_1.writeUint16)(0).toString('hex')).toMatchSnapshot();
11
+ expect((0, ledgerWriter_1.writeUint16)(255).toString('hex')).toMatchSnapshot();
12
+ expect((0, ledgerWriter_1.writeUint16)(12312).toString('hex')).toMatchSnapshot();
13
+ expect((0, ledgerWriter_1.writeUint16)(65535).toString('hex')).toMatchSnapshot();
14
+ expect((0, ledgerWriter_1.writeUint32)(0).toString('hex')).toMatchSnapshot();
15
+ expect((0, ledgerWriter_1.writeUint32)(255).toString('hex')).toMatchSnapshot();
16
+ expect((0, ledgerWriter_1.writeUint32)(12312).toString('hex')).toMatchSnapshot();
17
+ expect((0, ledgerWriter_1.writeUint32)(65535).toString('hex')).toMatchSnapshot();
18
+ expect((0, ledgerWriter_1.writeUint32)(123123123).toString('hex')).toMatchSnapshot();
19
+ expect((0, ledgerWriter_1.writeUint32)(4294967295).toString('hex')).toMatchSnapshot();
20
+ expect((0, ledgerWriter_1.writeUint64)(0n).toString('hex')).toMatchSnapshot();
21
+ expect((0, ledgerWriter_1.writeUint64)(255n).toString('hex')).toMatchSnapshot();
22
+ expect((0, ledgerWriter_1.writeUint64)(12312n).toString('hex')).toMatchSnapshot();
23
+ expect((0, ledgerWriter_1.writeUint64)(65535n).toString('hex')).toMatchSnapshot();
24
+ expect((0, ledgerWriter_1.writeUint64)(123123123n).toString('hex')).toMatchSnapshot();
25
+ expect((0, ledgerWriter_1.writeUint64)(4294967295n).toString('hex')).toMatchSnapshot();
26
+ expect((0, ledgerWriter_1.writeUint64)(12312312312312n).toString('hex')).toMatchSnapshot();
27
+ expect((0, ledgerWriter_1.writeUint64)(18446744073709551615n).toString('hex')).toMatchSnapshot();
28
+ });
29
+ it('should write addresses', () => {
30
+ expect((0, ledgerWriter_1.writeAddress)(new ton_core_1.Address(0, Buffer.alloc(32))).toString('hex')).toMatchSnapshot();
31
+ expect((0, ledgerWriter_1.writeAddress)(new ton_core_1.Address(-1, Buffer.alloc(32))).toString('hex')).toMatchSnapshot();
32
+ expect((0, ledgerWriter_1.writeAddress)(ton_core_1.Address.parse('EQBNVUFfKt2QgqKL5vZvnyP50wmniCFP2ASOKAE-g2noRDlR')).toString('hex')).toMatchSnapshot();
33
+ expect((0, ledgerWriter_1.writeAddress)(ton_core_1.Address.parse('Ef87m7_QrVM4uXAPCDM4DuF9Rj5Rwa5nHubwiQG96JmyAjQY')).toString('hex')).toMatchSnapshot();
34
+ });
35
+ it('should write cell refs', () => {
36
+ expect((0, ledgerWriter_1.writeCellRef)((0, ton_core_1.beginCell)().endCell()).toString('hex')).toMatchSnapshot();
37
+ expect((0, ledgerWriter_1.writeCellRef)((0, ton_core_1.beginCell)().storeUint(0, 32).endCell()).toString('hex')).toMatchSnapshot();
38
+ expect((0, ledgerWriter_1.writeCellRef)((0, ton_core_1.beginCell)().storeUint(0, 32).storeRef((0, ton_core_1.beginCell)().endCell()).endCell()).toString('hex')).toMatchSnapshot();
39
+ expect((0, ledgerWriter_1.writeCellRef)((0, ton_core_1.beginCell)().storeUint(0, 32).storeRef((0, ton_core_1.beginCell)().storeRef((0, ton_core_1.beginCell)().endCell()).endCell()).endCell()).toString('hex')).toMatchSnapshot();
40
+ });
41
+ });
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@ton-community/ton-ledger",
3
+ "version": "4.0.0",
4
+ "repository": "https://github.com/ton-community/ton-ledger-ts",
5
+ "author": "Steve Korshakov <steve@korshakov.com>",
6
+ "license": "MIT",
7
+ "main": "dist/index.js",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "rm -fr dist && tsc --declaration",
13
+ "test": "jest",
14
+ "release": "yarn test && yarn build && yarn release-it --npm.yarn1",
15
+ "dev": "ts-node ./test/index.ts"
16
+ },
17
+ "peerDependencies": {
18
+ "ton-core": ">=0.49.1"
19
+ },
20
+ "devDependencies": {
21
+ "@release-it/keep-a-changelog": "^3.1.0",
22
+ "@ledgerhq/hw-transport-node-hid": "^6.27.15",
23
+ "@types/jest": "^29.5.2",
24
+ "@types/node": "^20.2.5",
25
+ "jest": "^29.5.0",
26
+ "release-it": "^15.11.0",
27
+ "ton-core": "^0.49.1",
28
+ "ts-jest": "^29.1.0",
29
+ "ts-node": "^10.9.1",
30
+ "typescript": "^4.9.5"
31
+ },
32
+ "dependencies": {
33
+ "@ledgerhq/hw-transport": "^6.28.4",
34
+ "teslabot": "^1.5.0",
35
+ "ton-crypto": "^3.2.0"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public",
39
+ "registry": "https://registry.npmjs.org/"
40
+ },
41
+ "release-it": {
42
+ "github": {
43
+ "release": true
44
+ },
45
+ "plugins": {
46
+ "@release-it/keep-a-changelog": {
47
+ "filename": "CHANGELOG.md"
48
+ }
49
+ }
50
+ }
51
+ }