@ton-community/ton-ledger 7.4.0-pre.0 → 7.4.0-pre.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.
@@ -121,6 +121,29 @@ export type SignDataRequest = {
121
121
  data: Cell;
122
122
  ext?: Cell;
123
123
  };
124
+ export type SignDataNewRequestCommon = {
125
+ domain: string;
126
+ };
127
+ export type SignDataNewRequestPartial = {
128
+ type: 'plaintext';
129
+ text: string;
130
+ } | {
131
+ type: 'binary';
132
+ data: Buffer;
133
+ } | {
134
+ type: 'app-data';
135
+ schemaCrc: number;
136
+ data: Cell;
137
+ };
138
+ export type SignDataNewRequest = SignDataNewRequestCommon & SignDataNewRequestPartial;
139
+ export type LedgerMessage = {
140
+ to: Address;
141
+ sendMode: SendMode;
142
+ bounce: boolean;
143
+ amount: bigint;
144
+ stateInit?: StateInit;
145
+ payload?: TonPayloadFormat;
146
+ };
124
147
  export declare class TonTransport {
125
148
  #private;
126
149
  readonly transport: Transport;
@@ -168,6 +191,28 @@ export declare class TonTransport {
168
191
  cell: Cell;
169
192
  timestamp: number;
170
193
  }>;
194
+ signDataNew(path: number[], req: SignDataNewRequest, opts?: {
195
+ timestamp?: number;
196
+ testOnly?: boolean;
197
+ chain?: number;
198
+ subwalletId?: number;
199
+ walletVersion?: 'v3r2' | 'v4';
200
+ }): Promise<{
201
+ signature: Buffer;
202
+ address: Address;
203
+ signedData: Buffer | Cell;
204
+ signedDataHash: Buffer;
205
+ timestamp: number;
206
+ }>;
207
+ signMultiTransaction: (path: number[], transaction: {
208
+ seqno: number;
209
+ timeout: number;
210
+ walletSpecifiers?: {
211
+ subwalletId?: number;
212
+ includeWalletOp: boolean;
213
+ expectedPublicKey?: Buffer;
214
+ };
215
+ }, messages: LedgerMessage[]) => Promise<Cell>;
171
216
  signTransaction: (path: number[], transaction: {
172
217
  to: Address;
173
218
  sendMode: SendMode;
@@ -180,6 +225,7 @@ export declare class TonTransport {
180
225
  walletSpecifiers?: {
181
226
  subwalletId?: number;
182
227
  includeWalletOp: boolean;
228
+ expectedPublicKey?: Buffer;
183
229
  };
184
230
  extraCurrency?: {
185
231
  index: number;
@@ -190,4 +236,5 @@ export declare class TonTransport {
190
236
  blindSigningEnabled: boolean;
191
237
  expertMode: boolean;
192
238
  }>;
239
+ launchApp(): Promise<void>;
193
240
  }
@@ -1068,6 +1068,286 @@ class TonTransport {
1068
1068
  timestamp,
1069
1069
  };
1070
1070
  }
1071
+ async signDataNew(path, req, opts) {
1072
+ validatePath(path);
1073
+ const { publicKey, address: addressString } = (await this.getAddress(path, opts));
1074
+ const expectedAddress = core_1.Address.parse(addressString);
1075
+ const { flags, specifiers, chain } = processAddressFlags(opts);
1076
+ let specifiersBuf = Buffer.alloc(0);
1077
+ if (specifiers !== undefined) {
1078
+ specifiersBuf = Buffer.concat([(0, ledgerWriter_1.writeUint8)(specifiers.isV3R2 ? 1 : 0), (0, ledgerWriter_1.writeUint32)(specifiers.subwalletId)]);
1079
+ }
1080
+ const timestamp = opts?.timestamp ?? Math.floor(Date.now() / 1000);
1081
+ const domainBuf = Buffer.from(req.domain, 'ascii');
1082
+ let typeId;
1083
+ let payload;
1084
+ let signedData;
1085
+ switch (req.type) {
1086
+ case 'plaintext': {
1087
+ typeId = 0;
1088
+ payload = Buffer.from(req.text, 'ascii');
1089
+ signedData = Buffer.concat([
1090
+ Buffer.from([0xff, 0xff]),
1091
+ Buffer.from('ton-connect/sign-data/'),
1092
+ (0, ledgerWriter_1.writeInt32BE)(chain),
1093
+ expectedAddress.hash,
1094
+ (0, ledgerWriter_1.writeUint32)(domainBuf.length),
1095
+ domainBuf,
1096
+ (0, ledgerWriter_1.writeUint64)(BigInt(timestamp)),
1097
+ Buffer.from('txt', 'ascii'),
1098
+ (0, ledgerWriter_1.writeUint32)(payload.length),
1099
+ payload,
1100
+ ]);
1101
+ break;
1102
+ }
1103
+ case 'binary': {
1104
+ typeId = 1;
1105
+ payload = req.data;
1106
+ signedData = Buffer.concat([
1107
+ Buffer.from([0xff, 0xff]),
1108
+ Buffer.from('ton-connect/sign-data/'),
1109
+ (0, ledgerWriter_1.writeInt32BE)(chain),
1110
+ expectedAddress.hash,
1111
+ (0, ledgerWriter_1.writeUint32)(domainBuf.length),
1112
+ domainBuf,
1113
+ (0, ledgerWriter_1.writeUint64)(BigInt(timestamp)),
1114
+ Buffer.from('bin', 'ascii'),
1115
+ (0, ledgerWriter_1.writeUint32)(payload.length),
1116
+ payload,
1117
+ ]);
1118
+ break;
1119
+ }
1120
+ case 'app-data': {
1121
+ typeId = 2;
1122
+ payload = Buffer.concat([
1123
+ (0, ledgerWriter_1.writeUint32)(req.schemaCrc),
1124
+ (0, ledgerWriter_1.writeCellRef)(req.data),
1125
+ ]);
1126
+ let inner = (0, core_1.beginCell)();
1127
+ req.domain.split('.').reverse().forEach(p => {
1128
+ inner.storeBuffer(Buffer.from(p, 'ascii'));
1129
+ inner.storeUint(0, 8);
1130
+ });
1131
+ signedData = (0, core_1.beginCell)()
1132
+ .storeUint(0x75569022, 32) // prefix
1133
+ .storeUint(req.schemaCrc, 32) // schema hash
1134
+ .storeUint(timestamp, 64) // timestamp
1135
+ .storeAddress(expectedAddress) // user wallet address
1136
+ .storeRef(inner) // domain
1137
+ .storeRef(req.data) // payload cell
1138
+ .endCell();
1139
+ break;
1140
+ }
1141
+ default: {
1142
+ throw new Error(`Sign data request type '${req.type}' not supported`);
1143
+ }
1144
+ }
1145
+ const pkg = Buffer.concat([
1146
+ (0, ledgerWriter_1.writeUint8)(typeId),
1147
+ (0, ledgerWriter_1.writeUint8)(flags),
1148
+ specifiersBuf,
1149
+ (0, ledgerWriter_1.writeUint8)(domainBuf.length),
1150
+ domainBuf,
1151
+ (0, ledgerWriter_1.writeUint64)(BigInt(timestamp)),
1152
+ payload,
1153
+ ]);
1154
+ await this.#doRequest(INS_SIGN_DATA, 0x01, 0x03, pathElementsToBuffer(path.map((v) => v + 0x80000000)));
1155
+ const pkgCs = chunks(pkg, 255);
1156
+ for (let i = 0; i < pkgCs.length - 1; i++) {
1157
+ await this.#doRequest(INS_SIGN_DATA, 0x01, 0x02, pkgCs[i]);
1158
+ }
1159
+ const res = await this.#doRequest(INS_SIGN_DATA, 0x01, 0x00, pkgCs[pkgCs.length - 1]);
1160
+ let signature = res.subarray(1, 1 + 64);
1161
+ let hash = res.subarray(2 + 64, 2 + 64 + 32);
1162
+ const signedDataHash = signedData instanceof core_1.Cell ? signedData.hash() : (0, crypto_1.sha256_sync)(signedData);
1163
+ if (!hash.equals(signedDataHash)) {
1164
+ throw Error('Hash mismatch. Expected: ' + signedDataHash.toString('hex') + ', got: ' + hash.toString('hex'));
1165
+ }
1166
+ if (!(0, crypto_1.signVerify)(signedDataHash, signature, publicKey)) {
1167
+ throw Error('Received signature is invalid');
1168
+ }
1169
+ return {
1170
+ signature,
1171
+ address: expectedAddress,
1172
+ signedData,
1173
+ signedDataHash,
1174
+ timestamp,
1175
+ };
1176
+ }
1177
+ signMultiTransaction = async (path, transaction, messages) => {
1178
+ // Check path
1179
+ validatePath(path);
1180
+ //
1181
+ // Fetch key
1182
+ //
1183
+ let publicKey = (await this.getAddress(path)).publicKey;
1184
+ if (transaction.walletSpecifiers?.expectedPublicKey !== undefined && !transaction.walletSpecifiers.expectedPublicKey.equals(publicKey)) {
1185
+ throw Error('Expected public key mismatch');
1186
+ }
1187
+ //
1188
+ // Create package
1189
+ //
1190
+ const includeWalletOp = transaction.walletSpecifiers?.includeWalletOp ?? true;
1191
+ const subwalletId = transaction.walletSpecifiers?.subwalletId ?? DEFAULT_SUBWALLET_ID;
1192
+ const useTag1 = transaction.walletSpecifiers !== undefined;
1193
+ let pkg = Buffer.concat([
1194
+ (0, ledgerWriter_1.writeUint8)(messages.length),
1195
+ (0, ledgerWriter_1.writeUint8)(useTag1 ? 1 : 0), // tag
1196
+ ]);
1197
+ if (useTag1) {
1198
+ let flags = 0;
1199
+ if (includeWalletOp) {
1200
+ flags |= 1;
1201
+ }
1202
+ if (transaction.walletSpecifiers?.expectedPublicKey !== undefined) {
1203
+ flags |= 4;
1204
+ }
1205
+ pkg = Buffer.concat([
1206
+ pkg,
1207
+ (0, ledgerWriter_1.writeUint32)(subwalletId),
1208
+ (0, ledgerWriter_1.writeUint8)(flags),
1209
+ transaction.walletSpecifiers?.expectedPublicKey ?? Buffer.alloc(0),
1210
+ ]);
1211
+ }
1212
+ pkg = Buffer.concat([
1213
+ pkg,
1214
+ (0, ledgerWriter_1.writeUint32)(transaction.seqno),
1215
+ (0, ledgerWriter_1.writeUint32)(transaction.timeout),
1216
+ ]);
1217
+ let messagePkgs = [];
1218
+ let messageCells = [];
1219
+ for (const message of messages) {
1220
+ let pkg = Buffer.concat([
1221
+ (0, ledgerWriter_1.writeVarUInt)(message.amount),
1222
+ (0, ledgerWriter_1.writeAddress)(message.to),
1223
+ (0, ledgerWriter_1.writeUint8)(message.bounce ? 1 : 0),
1224
+ (0, ledgerWriter_1.writeUint8)(message.sendMode),
1225
+ ]);
1226
+ let stateInit = null;
1227
+ if (message.stateInit) {
1228
+ stateInit = (0, core_1.beginCell)()
1229
+ .store((0, core_1.storeStateInit)(message.stateInit))
1230
+ .endCell();
1231
+ pkg = Buffer.concat([
1232
+ pkg,
1233
+ (0, ledgerWriter_1.writeUint8)(1),
1234
+ (0, ledgerWriter_1.writeUint16)(stateInit.depth()),
1235
+ stateInit.hash()
1236
+ ]);
1237
+ }
1238
+ else {
1239
+ pkg = Buffer.concat([
1240
+ pkg,
1241
+ (0, ledgerWriter_1.writeUint8)(0)
1242
+ ]);
1243
+ }
1244
+ const { payload, hints } = convertPayload(message.payload);
1245
+ if (payload) {
1246
+ pkg = Buffer.concat([
1247
+ pkg,
1248
+ (0, ledgerWriter_1.writeUint8)(1),
1249
+ (0, ledgerWriter_1.writeUint16)(payload.depth()),
1250
+ payload.hash(),
1251
+ hints
1252
+ ]);
1253
+ }
1254
+ else {
1255
+ pkg = Buffer.concat([
1256
+ pkg,
1257
+ (0, ledgerWriter_1.writeUint8)(0),
1258
+ (0, ledgerWriter_1.writeUint8)(0)
1259
+ ]);
1260
+ }
1261
+ let orderBuilder = (0, core_1.beginCell)()
1262
+ .storeBit(0)
1263
+ .storeBit(true)
1264
+ .storeBit(message.bounce)
1265
+ .storeBit(false)
1266
+ .storeAddress(null)
1267
+ .storeAddress(message.to)
1268
+ .storeCoins(message.amount)
1269
+ .storeBit(false)
1270
+ .storeCoins(0)
1271
+ .storeCoins(0)
1272
+ .storeUint(0, 64)
1273
+ .storeUint(0, 32);
1274
+ if (stateInit) {
1275
+ orderBuilder = orderBuilder
1276
+ .storeBit(true)
1277
+ .storeBit(true) // Always in reference
1278
+ .storeRef(stateInit);
1279
+ }
1280
+ else {
1281
+ orderBuilder = orderBuilder
1282
+ .storeBit(false);
1283
+ }
1284
+ if (payload) {
1285
+ orderBuilder = orderBuilder
1286
+ .storeBit(true) // Always in reference
1287
+ .storeRef(payload);
1288
+ }
1289
+ else {
1290
+ orderBuilder = orderBuilder
1291
+ .storeBit(false);
1292
+ }
1293
+ messagePkgs.push(pkg);
1294
+ messageCells.push(orderBuilder.endCell());
1295
+ }
1296
+ await this.#doRequest(INS_SIGN_TX, 0x04, 0x03, pathElementsToBuffer(path.map((v) => v + 0x80000000)));
1297
+ const pkgCs = chunks(pkg, 255);
1298
+ for (let i = 0; i < pkgCs.length; i++) {
1299
+ await this.#doRequest(INS_SIGN_TX, 0x04, 0x02, pkgCs[i]);
1300
+ }
1301
+ let res = null;
1302
+ for (let i = 0; i < messagePkgs.length; i++) {
1303
+ const pkgCs = chunks(messagePkgs[i], 255);
1304
+ for (let j = 0; j < pkgCs.length; j++) {
1305
+ let flags = 4;
1306
+ if (j === 0) {
1307
+ flags |= 1;
1308
+ }
1309
+ if (j < pkgCs.length - 1) {
1310
+ flags |= 2;
1311
+ }
1312
+ if (i === messagePkgs.length - 1 && j === pkgCs.length - 1) {
1313
+ res = await this.#doRequest(INS_SIGN_TX, flags, 0, pkgCs[j]);
1314
+ }
1315
+ else {
1316
+ await this.#doRequest(INS_SIGN_TX, flags, 2, pkgCs[j]);
1317
+ }
1318
+ }
1319
+ }
1320
+ if (res === null) {
1321
+ throw Error('No response received');
1322
+ }
1323
+ // Transfer message
1324
+ let transferB = (0, core_1.beginCell)()
1325
+ .storeUint(subwalletId, 32)
1326
+ .storeUint(transaction.timeout, 32)
1327
+ .storeUint(transaction.seqno, 32);
1328
+ if (includeWalletOp) {
1329
+ transferB = transferB.storeUint(0, 8);
1330
+ }
1331
+ for (let i = 0; i < messages.length; i++) {
1332
+ transferB = transferB.storeUint(messages[i].sendMode, 8)
1333
+ .storeRef(messageCells[i]);
1334
+ }
1335
+ let transfer = transferB.endCell();
1336
+ // Parse result
1337
+ let signature = res.slice(1, 1 + 64);
1338
+ let hash = res.slice(2 + 64, 2 + 64 + 32);
1339
+ if (!hash.equals(transfer.hash())) {
1340
+ throw Error('Hash mismatch. Expected: ' + transfer.hash().toString('hex') + ', got: ' + hash.toString('hex'));
1341
+ }
1342
+ if (!(0, crypto_1.signVerify)(hash, signature, publicKey)) {
1343
+ throw Error('Received signature is invalid');
1344
+ }
1345
+ // Build a message
1346
+ return (0, core_1.beginCell)()
1347
+ .storeBuffer(signature)
1348
+ .storeSlice(transfer.beginParse())
1349
+ .endCell();
1350
+ };
1071
1351
  signTransaction = async (path, transaction) => {
1072
1352
  // Check path
1073
1353
  validatePath(path);
@@ -1078,6 +1358,9 @@ class TonTransport {
1078
1358
  // Fetch key
1079
1359
  //
1080
1360
  let publicKey = (await this.getAddress(path)).publicKey;
1361
+ if (transaction.walletSpecifiers?.expectedPublicKey !== undefined && !transaction.walletSpecifiers.expectedPublicKey.equals(publicKey)) {
1362
+ throw Error('Expected public key mismatch');
1363
+ }
1081
1364
  //
1082
1365
  // Create package
1083
1366
  //
@@ -1095,10 +1378,14 @@ class TonTransport {
1095
1378
  if (transaction.extraCurrency !== undefined) {
1096
1379
  flags |= 2;
1097
1380
  }
1381
+ if (transaction.walletSpecifiers?.expectedPublicKey !== undefined) {
1382
+ flags |= 4;
1383
+ }
1098
1384
  pkg = Buffer.concat([
1099
1385
  pkg,
1100
1386
  (0, ledgerWriter_1.writeUint32)(subwalletId),
1101
1387
  (0, ledgerWriter_1.writeUint8)(flags),
1388
+ transaction.walletSpecifiers?.expectedPublicKey ?? Buffer.alloc(0),
1102
1389
  ]);
1103
1390
  }
1104
1391
  let ecBuf = Buffer.alloc(0);
@@ -1250,6 +1537,9 @@ class TonTransport {
1250
1537
  expertMode: (loaded[0] & 0x02) > 0,
1251
1538
  };
1252
1539
  }
1540
+ async launchApp() {
1541
+ await this.#doRequest(0xd8, 0x00, 0x00, Buffer.from('TON', 'ascii'));
1542
+ }
1253
1543
  #doRequest = async (ins, p1, p2, data) => {
1254
1544
  return this.#lock.inLock(async () => {
1255
1545
  let r = await this.transport.send(LEDGER_CLA, ins, p1, p2, data);
@@ -1,6 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  /// <reference types="node" />
3
3
  import { Address, Cell } from '@ton/core';
4
+ export declare function writeInt32BE(value: number): Buffer;
4
5
  export declare function writeUint32(value: number): Buffer;
5
6
  export declare function writeUint16(value: number): Buffer;
6
7
  export declare function writeUint48(value: number): Buffer;
@@ -1,7 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.writeCellInline = exports.writeCellRef = exports.writeAddress = exports.writeUint8 = exports.writeVarUInt = exports.writeUint64 = exports.writeUint48 = exports.writeUint16 = exports.writeUint32 = void 0;
3
+ exports.writeCellInline = exports.writeCellRef = exports.writeAddress = exports.writeUint8 = exports.writeVarUInt = exports.writeUint64 = exports.writeUint48 = exports.writeUint16 = exports.writeUint32 = exports.writeInt32BE = void 0;
4
4
  const core_1 = require("@ton/core");
5
+ function writeInt32BE(value) {
6
+ let b = Buffer.alloc(4);
7
+ b.writeInt32BE(value, 0);
8
+ return b;
9
+ }
10
+ exports.writeInt32BE = writeInt32BE;
5
11
  function writeUint32(value) {
6
12
  let b = Buffer.alloc(4);
7
13
  b.writeUint32BE(value, 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ton-community/ton-ledger",
3
- "version": "7.4.0-pre.0",
3
+ "version": "7.4.0-pre.2",
4
4
  "repository": "https://github.com/ton-community/ton-ledger-ts",
5
5
  "author": "Steve Korshakov <steve@korshakov.com>",
6
6
  "license": "MIT",
package/CHANGELOG.md DELETED
@@ -1,88 +0,0 @@
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
- ## [7.3.0] - 2025-08-21
8
-
9
- - New jettons
10
- - Fixed `includeWalletOp`
11
-
12
- ## [7.2.0] - 2025-02-07
13
-
14
- - Added support for TON Ledger App 2.2.0 features, including:
15
- - Hardcoded jettons
16
- - Wallet specifiers support in more methods
17
-
18
- ## [7.1.0] - Not released
19
-
20
- ### Added
21
-
22
- - Added `parseMessage` helper to parse messages into TON Ledger App format
23
- - Added support for TON Ledger App 2.1.0 features, including:
24
- - New message types
25
- - Wallet specifiers (subwallet ID and wallet op inclusion for v4 support)
26
- - `getSettings` method
27
-
28
- ## [7.0.1] - 2024-01-17
29
-
30
- ### Fixed
31
-
32
- - Fixed incorrect VarUInt encoding of 0 (0 nanoTON, 0 jetton units, etc)
33
-
34
- ## [7.0.0] - 2023-09-15
35
-
36
- ### Changed
37
-
38
- - Switched `ton-core` and `ton-crypto` to `@ton/core` and `@ton/crypto`
39
-
40
- ## [6.0.0] - 2023-07-11
41
-
42
- ### Removed
43
-
44
- - Removed `unsafe` payload format
45
-
46
- ## [5.0.0] - 2023-06-29
47
-
48
- ### Removed
49
-
50
- - Removed `decimals` and `ticker` from `jetton-transfer` request
51
-
52
- ## [4.1.0] - 2023-06-16
53
-
54
- ### Added
55
-
56
- - Added `signData` method along with `SignDataRequest` type
57
-
58
- ## [4.0.1] - 2023-06-16
59
-
60
- ### Fixed
61
-
62
- - Fixed the address flags communication
63
-
64
- ## [4.0.0] - 2023-06-09
65
-
66
- ### Added
67
-
68
- - Added payload types for NFT and Jetton transfers
69
- - Added TON Connect 2.0 address proof request
70
-
71
- ### Removed
72
-
73
- - Removed old payload types except for comment and unsafe
74
-
75
- ### Changed
76
-
77
- - Updated dependencies
78
- - Changed APDU format to be the same as the latest embedded app version (breaking change)
79
-
80
- ## [3.0.0] - 2023-01-08
81
-
82
- ### Changed
83
-
84
- - Migration to `ton-core`
85
-
86
- ## [2.3.2]
87
-
88
- - Update documentation