@bytezhang/ledger-adapter 0.0.18 → 0.0.20

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/dist/index.js CHANGED
@@ -38,6 +38,7 @@ __export(index_exports, {
38
38
  SignerEth: () => SignerEth,
39
39
  SignerManager: () => SignerManager,
40
40
  SignerSol: () => SignerSol,
41
+ SignerTron: () => SignerTron,
41
42
  clearRegistry: () => clearRegistry,
42
43
  deviceActionToPromise: () => deviceActionToPromise,
43
44
  getTransportProvider: () => getTransportProvider,
@@ -63,7 +64,7 @@ function isDeviceLockedError(err) {
63
64
  if (e.errorCode != null && LOCKED_ERROR_CODES.has(String(e.errorCode))) return true;
64
65
  if (e.statusCode != null && LOCKED_ERROR_CODES.has(String(e.statusCode))) return true;
65
66
  if (e._tag === "DeviceLockedError") return true;
66
- if (typeof e.message === "string" && /locked/i.test(e.message)) return true;
67
+ if (typeof e.message === "string" && /locked|device exchange error/i.test(e.message)) return true;
67
68
  if (e.originalError != null && isDeviceLockedError(e.originalError)) return true;
68
69
  if (e.error != null && e._tag && isDeviceLockedError(e.error)) return true;
69
70
  return false;
@@ -99,7 +100,7 @@ function isDeviceDisconnectedError(err) {
99
100
  if (!err || typeof err !== "object") return false;
100
101
  const e = err;
101
102
  if (e._tag === "DeviceNotRecognizedError" || e._tag === "DeviceSessionNotFound") return true;
102
- if (typeof e.message === "string" && /disconnected|not found|no device|unplugged|session.*not.*found/i.test(e.message)) return true;
103
+ if (typeof e.message === "string" && /disconnected|not found|no device|unplugged|session.*not.*found|timed out.*locked/i.test(e.message)) return true;
103
104
  return false;
104
105
  }
105
106
  function isTimeoutError(err) {
@@ -286,7 +287,7 @@ var _LedgerAdapter = class _LedgerAdapter {
286
287
  );
287
288
  }
288
289
  getSupportedChains() {
289
- return ["evm", "btc", "sol"];
290
+ return ["evm", "btc", "sol", "tron"];
290
291
  }
291
292
  on(event, listener) {
292
293
  this.emitter.on(event, listener);
@@ -536,16 +537,40 @@ var _LedgerAdapter = class _LedgerAdapter {
536
537
  "Ledger requires PSBT format for BTC transaction signing. Provide params.psbt."
537
538
  );
538
539
  }
539
- return (0, import_hardware_wallet_core2.failure)(
540
- import_hardware_wallet_core2.HardwareErrorCode.MethodNotSupported,
541
- "BTC transaction signing via PSBT is not yet implemented for Ledger."
542
- );
540
+ try {
541
+ const accountPath = params.inputs?.[0]?.path ? params.inputs[0].path.split("/").slice(0, 3).join("/") : void 0;
542
+ const result = await this.connectorCall(connectId, "btcSignTransaction", {
543
+ psbt: params.psbt,
544
+ coin: params.coin,
545
+ path: accountPath
546
+ });
547
+ return (0, import_hardware_wallet_core2.success)({
548
+ signatures: [],
549
+ serializedTx: result.signedPsbt,
550
+ signedPsbt: result.signedPsbt
551
+ });
552
+ } catch (err) {
553
+ return this.errorToFailure(err);
554
+ }
543
555
  }
544
- async btcSignMessage(_connectId, _deviceId, _params) {
545
- return (0, import_hardware_wallet_core2.failure)(
546
- import_hardware_wallet_core2.HardwareErrorCode.MethodNotSupported,
547
- "BTC message signing is not yet supported on Ledger."
548
- );
556
+ async btcSignMessage(connectId, _deviceId, params) {
557
+ await this._ensureDevicePermission(connectId, _deviceId);
558
+ if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "btc")) {
559
+ return (0, import_hardware_wallet_core2.failure)(import_hardware_wallet_core2.HardwareErrorCode.DeviceMismatch, "Wrong device connected");
560
+ }
561
+ try {
562
+ const result = await this.connectorCall(connectId, "btcSignMessage", {
563
+ path: params.path,
564
+ message: params.message,
565
+ coin: params.coin
566
+ });
567
+ return (0, import_hardware_wallet_core2.success)({
568
+ signature: result.signature,
569
+ address: result.address || ""
570
+ });
571
+ } catch (err) {
572
+ return this.errorToFailure(err);
573
+ }
549
574
  }
550
575
  // ---------------------------------------------------------------------------
551
576
  // Device fingerprint
@@ -608,18 +633,91 @@ var _LedgerAdapter = class _LedgerAdapter {
608
633
  return this.errorToFailure(err);
609
634
  }
610
635
  }
611
- async solSignTransaction(_connectId, _deviceId, _params) {
612
- return (0, import_hardware_wallet_core2.failure)(
613
- import_hardware_wallet_core2.HardwareErrorCode.MethodNotSupported,
614
- "Solana transaction signing via Ledger is not yet implemented."
615
- );
636
+ async solSignTransaction(connectId, _deviceId, params) {
637
+ await this._ensureDevicePermission(connectId, _deviceId);
638
+ if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "sol")) {
639
+ return (0, import_hardware_wallet_core2.failure)(import_hardware_wallet_core2.HardwareErrorCode.DeviceMismatch, "Wrong device connected");
640
+ }
641
+ try {
642
+ const result = await this.connectorCall(connectId, "solSignTransaction", {
643
+ path: params.path,
644
+ serializedTx: params.serializedTx
645
+ });
646
+ return (0, import_hardware_wallet_core2.success)({ signature: result.signature });
647
+ } catch (err) {
648
+ return this.errorToFailure(err);
649
+ }
616
650
  }
617
- async solSignMessage(_connectId, _deviceId, _params) {
618
- return (0, import_hardware_wallet_core2.failure)(
619
- import_hardware_wallet_core2.HardwareErrorCode.MethodNotSupported,
620
- "Solana message signing via Ledger is not yet implemented."
651
+ async solSignMessage(connectId, _deviceId, params) {
652
+ await this._ensureDevicePermission(connectId, _deviceId);
653
+ if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "sol")) {
654
+ return (0, import_hardware_wallet_core2.failure)(import_hardware_wallet_core2.HardwareErrorCode.DeviceMismatch, "Wrong device connected");
655
+ }
656
+ try {
657
+ const result = await this.connectorCall(connectId, "solSignMessage", {
658
+ path: params.path,
659
+ message: params.message
660
+ });
661
+ return (0, import_hardware_wallet_core2.success)({ signature: result.signature });
662
+ } catch (err) {
663
+ return this.errorToFailure(err);
664
+ }
665
+ }
666
+ // ---------------------------------------------------------------------------
667
+ // TRON methods
668
+ // ---------------------------------------------------------------------------
669
+ async tronGetAddress(connectId, _deviceId, params) {
670
+ await this._ensureDevicePermission(connectId, _deviceId);
671
+ try {
672
+ const result = await this.connectorCall(connectId, "tronGetAddress", {
673
+ path: params.path,
674
+ showOnDevice: params.showOnDevice
675
+ });
676
+ return (0, import_hardware_wallet_core2.success)({
677
+ address: result.address,
678
+ path: params.path
679
+ });
680
+ } catch (err) {
681
+ return this.errorToFailure(err);
682
+ }
683
+ }
684
+ async tronGetAddresses(connectId, deviceId, params, onProgress) {
685
+ return this.batchCall(
686
+ params,
687
+ (p) => this.tronGetAddress(connectId, deviceId, p),
688
+ onProgress
621
689
  );
622
690
  }
691
+ async tronSignTransaction(connectId, _deviceId, params) {
692
+ await this._ensureDevicePermission(connectId, _deviceId);
693
+ try {
694
+ if (!params.rawTxHex) {
695
+ return (0, import_hardware_wallet_core2.failure)(
696
+ import_hardware_wallet_core2.HardwareErrorCode.InvalidParams,
697
+ "TRON signing requires a protobuf-encoded raw transaction hex (rawTxHex)."
698
+ );
699
+ }
700
+ const result = await this.connectorCall(connectId, "tronSignTransaction", {
701
+ path: params.path,
702
+ rawTxHex: params.rawTxHex
703
+ });
704
+ return (0, import_hardware_wallet_core2.success)({ signature: result.signature });
705
+ } catch (err) {
706
+ return this.errorToFailure(err);
707
+ }
708
+ }
709
+ async tronSignMessage(connectId, _deviceId, params) {
710
+ await this._ensureDevicePermission(connectId, _deviceId);
711
+ try {
712
+ const result = await this.connectorCall(connectId, "tronSignMessage", {
713
+ path: params.path,
714
+ messageHex: params.message
715
+ });
716
+ return (0, import_hardware_wallet_core2.success)({ signature: result.signature });
717
+ } catch (err) {
718
+ return this.errorToFailure(err);
719
+ }
720
+ }
623
721
  /**
624
722
  * Wait for user to connect and unlock device.
625
723
  * Emits 'ui-request' event via the adapter's own emitter.
@@ -1069,12 +1167,30 @@ var LedgerDeviceManager = class {
1069
1167
  };
1070
1168
 
1071
1169
  // src/signer/deviceActionToPromise.ts
1072
- function deviceActionToPromise(action, onInteraction) {
1170
+ var DEFAULT_TIMEOUT_MS = 3e4;
1171
+ function deviceActionToPromise(action, onInteraction, timeoutMs = DEFAULT_TIMEOUT_MS) {
1073
1172
  return new Promise((resolve, reject) => {
1074
1173
  let settled = false;
1075
1174
  let sub;
1175
+ let timer = null;
1176
+ const resetTimer = () => {
1177
+ if (timer) clearTimeout(timer);
1178
+ if (timeoutMs > 0) {
1179
+ timer = setTimeout(() => {
1180
+ if (!settled) {
1181
+ settled = true;
1182
+ sub?.unsubscribe();
1183
+ reject(new Error("Device action timed out \u2014 device may be locked or disconnected"));
1184
+ }
1185
+ }, timeoutMs);
1186
+ }
1187
+ };
1188
+ resetTimer();
1189
+ console.log("[DMK-Observable] subscribing to action.observable...");
1076
1190
  sub = action.observable.subscribe({
1077
1191
  next: (state) => {
1192
+ console.log("[DMK-Observable] next received");
1193
+ resetTimer();
1078
1194
  console.log("[DMK-Observable] state:", JSON.stringify({
1079
1195
  status: state.status,
1080
1196
  interaction: state.intermediateValue?.requiredUserInteraction,
@@ -1085,11 +1201,13 @@ function deviceActionToPromise(action, onInteraction) {
1085
1201
  if (settled) return;
1086
1202
  if (state.status === "completed") {
1087
1203
  settled = true;
1204
+ if (timer) clearTimeout(timer);
1088
1205
  onInteraction?.("interaction-complete");
1089
1206
  sub?.unsubscribe();
1090
1207
  resolve(state.output);
1091
1208
  } else if (state.status === "error") {
1092
1209
  settled = true;
1210
+ if (timer) clearTimeout(timer);
1093
1211
  onInteraction?.("interaction-complete");
1094
1212
  sub?.unsubscribe();
1095
1213
  reject(state.error);
@@ -1105,6 +1223,7 @@ function deviceActionToPromise(action, onInteraction) {
1105
1223
  error: (err) => {
1106
1224
  if (!settled) {
1107
1225
  settled = true;
1226
+ if (timer) clearTimeout(timer);
1108
1227
  sub?.unsubscribe();
1109
1228
  reject(err);
1110
1229
  }
@@ -1112,6 +1231,7 @@ function deviceActionToPromise(action, onInteraction) {
1112
1231
  complete: () => {
1113
1232
  if (!settled) {
1114
1233
  settled = true;
1234
+ if (timer) clearTimeout(timer);
1115
1235
  reject(new Error("Device action completed without result"));
1116
1236
  }
1117
1237
  }
@@ -1128,6 +1248,7 @@ function hexToBytes(hex) {
1128
1248
  }
1129
1249
  return bytes;
1130
1250
  }
1251
+ var INTERACTIVE_TIMEOUT_MS = 5 * 6e4;
1131
1252
  var SignerEth = class {
1132
1253
  constructor(_sdk) {
1133
1254
  this._sdk = _sdk;
@@ -1135,22 +1256,25 @@ var SignerEth = class {
1135
1256
  async getAddress(derivationPath, options) {
1136
1257
  const checkOnDevice = options?.checkOnDevice ?? false;
1137
1258
  console.log("[DMK] getAddress \u2192 DMK:", { derivationPath, checkOnDevice });
1259
+ console.log("[DMK] signer instance id:", this._instanceId ?? "none");
1138
1260
  const action = this._sdk.getAddress(derivationPath, {
1139
1261
  checkOnDevice
1140
1262
  });
1141
- return deviceActionToPromise(action, this.onInteraction);
1263
+ console.log("[DMK] getAddress action created:", !!action, "hasObservable:", !!action?.observable);
1264
+ const timeout = checkOnDevice ? INTERACTIVE_TIMEOUT_MS : void 0;
1265
+ return deviceActionToPromise(action, this.onInteraction, timeout);
1142
1266
  }
1143
1267
  async signTransaction(derivationPath, serializedTxHex) {
1144
1268
  const action = this._sdk.signTransaction(derivationPath, hexToBytes(serializedTxHex));
1145
- return deviceActionToPromise(action, this.onInteraction);
1269
+ return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS);
1146
1270
  }
1147
1271
  async signMessage(derivationPath, message) {
1148
1272
  const action = this._sdk.signMessage(derivationPath, message);
1149
- return deviceActionToPromise(action, this.onInteraction);
1273
+ return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS);
1150
1274
  }
1151
1275
  async signTypedData(derivationPath, data) {
1152
1276
  const action = this._sdk.signTypedData(derivationPath, data);
1153
- return deviceActionToPromise(action, this.onInteraction);
1277
+ return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS);
1154
1278
  }
1155
1279
  };
1156
1280
 
@@ -1162,11 +1286,13 @@ var SignerManager = class _SignerManager {
1162
1286
  this._builderFn = builderFn ?? _SignerManager._defaultBuilder();
1163
1287
  }
1164
1288
  async getOrCreate(sessionId) {
1165
- let signer = this._cache.get(sessionId);
1166
- if (signer) return signer;
1289
+ const hadCached = this._cache.has(sessionId);
1290
+ this._cache.delete(sessionId);
1291
+ console.log("[DMK] SignerManager.getOrCreate:", { sessionId, hadCached, creating: true });
1167
1292
  const builder = await this._builderFn({ dmk: this._dmk, sessionId });
1168
1293
  const sdkSigner = builder.build();
1169
- signer = new SignerEth(sdkSigner);
1294
+ console.log("[DMK] SignerManager: new signer built");
1295
+ const signer = new SignerEth(sdkSigner);
1170
1296
  this._cache.set(sessionId, signer);
1171
1297
  return signer;
1172
1298
  }
@@ -1189,6 +1315,7 @@ var SignerManager = class _SignerManager {
1189
1315
  };
1190
1316
 
1191
1317
  // src/signer/SignerBtc.ts
1318
+ var INTERACTIVE_TIMEOUT_MS2 = 5 * 6e4;
1192
1319
  var SignerBtc = class {
1193
1320
  constructor(_sdk) {
1194
1321
  this._sdk = _sdk;
@@ -1211,6 +1338,31 @@ var SignerBtc = class {
1211
1338
  const result = await deviceActionToPromise(action, this.onInteraction);
1212
1339
  return result.masterFingerprint;
1213
1340
  }
1341
+ /**
1342
+ * Sign a PSBT and return the array of partial signatures.
1343
+ * The `wallet` param is a DefaultWallet or WalletPolicy instance.
1344
+ * The `psbt` param can be a hex string, base64 string, or Uint8Array.
1345
+ */
1346
+ async signPsbt(wallet, psbt, options) {
1347
+ const action = this._sdk.signPsbt(wallet, psbt, options);
1348
+ return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS2);
1349
+ }
1350
+ /**
1351
+ * Sign a PSBT and return the fully extracted raw transaction as a hex string.
1352
+ * Like signPsbt, but also finalises the PSBT and extracts the transaction.
1353
+ */
1354
+ async signTransaction(wallet, psbt, options) {
1355
+ const action = this._sdk.signTransaction(wallet, psbt, options);
1356
+ return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS2);
1357
+ }
1358
+ /**
1359
+ * Sign a message with the BTC app (BIP-137 / "Bitcoin Signed Message").
1360
+ * Returns `{ r, s, v }` signature object.
1361
+ */
1362
+ async signMessage(derivationPath, message, options) {
1363
+ const action = this._sdk.signMessage(derivationPath, message, options);
1364
+ return deviceActionToPromise(action, this.onInteraction, INTERACTIVE_TIMEOUT_MS2);
1365
+ }
1214
1366
  };
1215
1367
 
1216
1368
  // src/signer/SignerSol.ts
@@ -1243,6 +1395,200 @@ var SignerSol = class {
1243
1395
  }
1244
1396
  };
1245
1397
 
1398
+ // src/signer/SignerTron.ts
1399
+ var CLA = 224;
1400
+ var INS_ADDRESS = 2;
1401
+ var INS_SIGN = 4;
1402
+ var INS_SIGN_MESSAGE = 8;
1403
+ var CHUNK_SIZE = 250;
1404
+ function hexToBytes2(hex) {
1405
+ const h = hex.startsWith("0x") ? hex.slice(2) : hex;
1406
+ const bytes = new Uint8Array(h.length / 2);
1407
+ for (let i = 0; i < bytes.length; i++) {
1408
+ bytes[i] = parseInt(h.substring(i * 2, i * 2 + 2), 16);
1409
+ }
1410
+ return bytes;
1411
+ }
1412
+ function bytesToHex(bytes) {
1413
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1414
+ }
1415
+ function splitPath(path) {
1416
+ const p = path.startsWith("m/") ? path.slice(2) : path;
1417
+ return p.split("/").map((component) => {
1418
+ const hardened = component.endsWith("'");
1419
+ const index = parseInt(hardened ? component.slice(0, -1) : component, 10);
1420
+ return hardened ? index + 2147483648 : index;
1421
+ });
1422
+ }
1423
+ function serializePath(path) {
1424
+ const components = splitPath(path);
1425
+ const buf = new Uint8Array(1 + components.length * 4);
1426
+ buf[0] = components.length;
1427
+ for (let i = 0; i < components.length; i++) {
1428
+ const val = components[i];
1429
+ const offset = 1 + i * 4;
1430
+ buf[offset] = val >>> 24 & 255;
1431
+ buf[offset + 1] = val >>> 16 & 255;
1432
+ buf[offset + 2] = val >>> 8 & 255;
1433
+ buf[offset + 3] = val & 255;
1434
+ }
1435
+ return buf;
1436
+ }
1437
+ function buildApdu(cla, ins, p1, p2, data) {
1438
+ const dataLen = data?.length ?? 0;
1439
+ const apdu = new Uint8Array(5 + dataLen);
1440
+ apdu[0] = cla;
1441
+ apdu[1] = ins;
1442
+ apdu[2] = p1;
1443
+ apdu[3] = p2;
1444
+ apdu[4] = dataLen;
1445
+ if (data && dataLen > 0) {
1446
+ apdu.set(data, 5);
1447
+ }
1448
+ return apdu;
1449
+ }
1450
+ function checkStatusCode(statusCode, context) {
1451
+ if (statusCode.length < 2) {
1452
+ throw new Error(`${context}: invalid status code length`);
1453
+ }
1454
+ const sw = statusCode[0] << 8 | statusCode[1];
1455
+ if (sw !== 36864) {
1456
+ throw Object.assign(
1457
+ new Error(`${context}: device returned error status 0x${sw.toString(16).padStart(4, "0")}`),
1458
+ { statusCode: sw.toString(16), errorCode: sw.toString() }
1459
+ );
1460
+ }
1461
+ }
1462
+ var SignerTron = class {
1463
+ constructor(_sendApdu) {
1464
+ this._sendApdu = _sendApdu;
1465
+ }
1466
+ /**
1467
+ * Get the TRON address at the given derivation path.
1468
+ *
1469
+ * APDU: CLA=0xE0, INS=0x02, P1=(showOnDevice?0x01:0x00), P2=0x00
1470
+ * Data: pathCount(1) + paths(4 bytes each, big-endian, hardened bit)
1471
+ * Response: pubKeyLen(1) + pubKey(pubKeyLen) + addrLen(1) + addr(addrLen, ASCII base58)
1472
+ */
1473
+ async getAddress(path, options) {
1474
+ const showOnDevice = options?.checkOnDevice ?? false;
1475
+ const pathData = serializePath(path);
1476
+ const apdu = buildApdu(
1477
+ CLA,
1478
+ INS_ADDRESS,
1479
+ showOnDevice ? 1 : 0,
1480
+ 0,
1481
+ pathData
1482
+ );
1483
+ const response = await this._sendApdu(apdu);
1484
+ checkStatusCode(response.statusCode, "tronGetAddress");
1485
+ const data = response.data;
1486
+ let offset = 0;
1487
+ const pubKeyLen = data[offset];
1488
+ offset += 1;
1489
+ const publicKey = bytesToHex(data.slice(offset, offset + pubKeyLen));
1490
+ offset += pubKeyLen;
1491
+ const addrLen = data[offset];
1492
+ offset += 1;
1493
+ const addressBytes = data.slice(offset, offset + addrLen);
1494
+ const address = new TextDecoder().decode(addressBytes);
1495
+ return { publicKey, address };
1496
+ }
1497
+ /**
1498
+ * Sign a TRON transaction (protobuf-encoded raw transaction).
1499
+ *
1500
+ * The transaction bytes are split into 250-byte chunks and sent sequentially.
1501
+ * First chunk includes the serialized derivation path prefix.
1502
+ *
1503
+ * P1 flags:
1504
+ * 0x10 = single chunk (entire tx fits in one APDU)
1505
+ * 0x00 = first chunk of multi-chunk
1506
+ * 0x80 = middle chunk (continuation)
1507
+ * 0x90 = last chunk (final continuation)
1508
+ *
1509
+ * Returns: 65-byte signature as hex string (no 0x prefix).
1510
+ */
1511
+ async signTransaction(path, rawTxHex) {
1512
+ const pathData = serializePath(path);
1513
+ const txBytes = hexToBytes2(rawTxHex);
1514
+ const firstChunkMaxTx = CHUNK_SIZE - pathData.length;
1515
+ const txForFirst = txBytes.slice(0, firstChunkMaxTx);
1516
+ const firstPayload = new Uint8Array(pathData.length + txForFirst.length);
1517
+ firstPayload.set(pathData, 0);
1518
+ firstPayload.set(txForFirst, pathData.length);
1519
+ const remaining = txBytes.slice(firstChunkMaxTx);
1520
+ const chunks = [];
1521
+ let pos = 0;
1522
+ while (pos < remaining.length) {
1523
+ chunks.push(remaining.slice(pos, pos + CHUNK_SIZE));
1524
+ pos += CHUNK_SIZE;
1525
+ }
1526
+ const totalChunks = 1 + chunks.length;
1527
+ let response;
1528
+ if (totalChunks === 1) {
1529
+ const apdu = buildApdu(CLA, INS_SIGN, 16, 0, firstPayload);
1530
+ response = await this._sendApdu(apdu);
1531
+ checkStatusCode(response.statusCode, "tronSignTransaction");
1532
+ } else {
1533
+ const firstApdu = buildApdu(CLA, INS_SIGN, 0, 0, firstPayload);
1534
+ response = await this._sendApdu(firstApdu);
1535
+ checkStatusCode(response.statusCode, "tronSignTransaction (first)");
1536
+ for (let i = 0; i < chunks.length; i++) {
1537
+ const isLast = i === chunks.length - 1;
1538
+ const p1 = isLast ? 144 : 128;
1539
+ const apdu = buildApdu(CLA, INS_SIGN, p1, 0, chunks[i]);
1540
+ response = await this._sendApdu(apdu);
1541
+ checkStatusCode(response.statusCode, `tronSignTransaction (chunk ${i + 1})`);
1542
+ }
1543
+ }
1544
+ return bytesToHex(response.data.slice(0, 65));
1545
+ }
1546
+ /**
1547
+ * Sign a personal message with the TRON app.
1548
+ *
1549
+ * First chunk: pathCount(1) + paths(4 bytes BE) + messageLength(4 bytes BE) + message bytes
1550
+ * Subsequent: continuation bytes
1551
+ * P1: 0x00 = first, 0x80 = continuation
1552
+ *
1553
+ * Returns: 65-byte signature as hex string (no 0x prefix).
1554
+ */
1555
+ async signMessage(path, messageHex) {
1556
+ const pathData = serializePath(path);
1557
+ const messageBytes = hexToBytes2(messageHex);
1558
+ const msgLenBuf = new Uint8Array(4);
1559
+ const msgLen = messageBytes.length;
1560
+ msgLenBuf[0] = msgLen >>> 24 & 255;
1561
+ msgLenBuf[1] = msgLen >>> 16 & 255;
1562
+ msgLenBuf[2] = msgLen >>> 8 & 255;
1563
+ msgLenBuf[3] = msgLen & 255;
1564
+ const header = new Uint8Array(pathData.length + 4);
1565
+ header.set(pathData, 0);
1566
+ header.set(msgLenBuf, pathData.length);
1567
+ const firstChunkMaxMsg = CHUNK_SIZE - header.length;
1568
+ const msgForFirst = messageBytes.slice(0, firstChunkMaxMsg);
1569
+ const firstPayload = new Uint8Array(header.length + msgForFirst.length);
1570
+ firstPayload.set(header, 0);
1571
+ firstPayload.set(msgForFirst, header.length);
1572
+ const remaining = messageBytes.slice(firstChunkMaxMsg);
1573
+ const chunks = [];
1574
+ let pos = 0;
1575
+ while (pos < remaining.length) {
1576
+ chunks.push(remaining.slice(pos, pos + CHUNK_SIZE));
1577
+ pos += CHUNK_SIZE;
1578
+ }
1579
+ let response;
1580
+ const firstApdu = buildApdu(CLA, INS_SIGN_MESSAGE, 0, 0, firstPayload);
1581
+ response = await this._sendApdu(firstApdu);
1582
+ checkStatusCode(response.statusCode, "tronSignMessage (first)");
1583
+ for (let i = 0; i < chunks.length; i++) {
1584
+ const apdu = buildApdu(CLA, INS_SIGN_MESSAGE, 128, 0, chunks[i]);
1585
+ response = await this._sendApdu(apdu);
1586
+ checkStatusCode(response.statusCode, `tronSignMessage (chunk ${i + 1})`);
1587
+ }
1588
+ return bytesToHex(response.data.slice(0, 65));
1589
+ }
1590
+ };
1591
+
1246
1592
  // src/connector/LedgerConnectorBase.ts
1247
1593
  function normalizePath(path) {
1248
1594
  return path.startsWith("m/") ? path.slice(2) : path;
@@ -1253,6 +1599,17 @@ function stripHex2(hex) {
1253
1599
  function padHex642(hex) {
1254
1600
  return `0x${stripHex2(hex).padStart(64, "0")}`;
1255
1601
  }
1602
+ function hexToBytes3(hex) {
1603
+ const clean = stripHex2(hex);
1604
+ const bytes = new Uint8Array(clean.length / 2);
1605
+ for (let i = 0; i < bytes.length; i++) {
1606
+ bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
1607
+ }
1608
+ return bytes;
1609
+ }
1610
+ function bytesToHex2(bytes) {
1611
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
1612
+ }
1256
1613
  var LedgerConnectorBase = class {
1257
1614
  constructor(createTransport, options) {
1258
1615
  this._deviceManager = null;
@@ -1413,6 +1770,12 @@ var LedgerConnectorBase = class {
1413
1770
  return this._solSignTransaction(sessionId, params);
1414
1771
  case "solSignMessage":
1415
1772
  return this._solSignMessage(sessionId, params);
1773
+ case "tronGetAddress":
1774
+ return this._tronGetAddress(sessionId, params);
1775
+ case "tronSignTransaction":
1776
+ return this._tronSignTransaction(sessionId, params);
1777
+ case "tronSignMessage":
1778
+ return this._tronSignMessage(sessionId, params);
1416
1779
  default:
1417
1780
  throw new Error(`LedgerConnector: unknown method "${method}"`);
1418
1781
  }
@@ -1547,11 +1910,38 @@ var LedgerConnectorBase = class {
1547
1910
  throw this._wrapError(err);
1548
1911
  }
1549
1912
  }
1550
- async _btcSignTransaction(_sessionId, _params) {
1551
- throw new Error("LedgerConnector: btcSignTransaction is not yet implemented");
1913
+ async _btcSignTransaction(sessionId, params) {
1914
+ const btcSigner = await this._createBtcSigner(sessionId);
1915
+ try {
1916
+ const { DefaultWallet, DefaultDescriptorTemplate } = await import("@ledgerhq/device-signer-kit-bitcoin");
1917
+ const path = normalizePath(params.path || "84'/0'/0'");
1918
+ const purpose = path.split("/")[0]?.replace("'", "");
1919
+ let template = DefaultDescriptorTemplate.NATIVE_SEGWIT;
1920
+ if (purpose === "44") template = DefaultDescriptorTemplate.LEGACY;
1921
+ else if (purpose === "49")
1922
+ template = DefaultDescriptorTemplate.NESTED_SEGWIT;
1923
+ else if (purpose === "86") template = DefaultDescriptorTemplate.TAPROOT;
1924
+ const wallet = new DefaultWallet(path, template);
1925
+ const signedTxHex = await btcSigner.signTransaction(wallet, params.psbt);
1926
+ return { signedPsbt: stripHex2(signedTxHex) };
1927
+ } catch (err) {
1928
+ this._invalidateSession(sessionId);
1929
+ throw this._wrapError(err);
1930
+ }
1552
1931
  }
1553
- async _btcSignMessage(_sessionId, _params) {
1554
- throw new Error("LedgerConnector: btcSignMessage is not yet implemented");
1932
+ async _btcSignMessage(sessionId, params) {
1933
+ const btcSigner = await this._createBtcSigner(sessionId);
1934
+ const path = normalizePath(params.path);
1935
+ try {
1936
+ const result = await btcSigner.signMessage(path, params.message);
1937
+ const rHex = stripHex2(result.r).padStart(64, "0");
1938
+ const sHex = stripHex2(result.s).padStart(64, "0");
1939
+ const vHex = result.v.toString(16).padStart(2, "0");
1940
+ return { signature: `${rHex}${sHex}${vHex}`, address: "" };
1941
+ } catch (err) {
1942
+ this._invalidateSession(sessionId);
1943
+ throw this._wrapError(err);
1944
+ }
1555
1945
  }
1556
1946
  async _btcGetMasterFingerprint(sessionId, params) {
1557
1947
  const btcSigner = await this._createBtcSigner(sessionId);
@@ -1582,11 +1972,67 @@ var LedgerConnectorBase = class {
1582
1972
  throw this._wrapError(err);
1583
1973
  }
1584
1974
  }
1585
- async _solSignTransaction(_sessionId, _params) {
1586
- throw new Error("LedgerConnector: solSignTransaction is not yet implemented");
1975
+ async _solSignTransaction(sessionId, params) {
1976
+ const solSigner = await this._createSolSigner(sessionId);
1977
+ const path = normalizePath(params.path);
1978
+ const txBytes = hexToBytes3(params.serializedTx);
1979
+ try {
1980
+ const result = await solSigner.signTransaction(path, txBytes);
1981
+ return { signature: bytesToHex2(result) };
1982
+ } catch (err) {
1983
+ this._invalidateSession(sessionId);
1984
+ throw this._wrapError(err);
1985
+ }
1587
1986
  }
1588
- async _solSignMessage(_sessionId, _params) {
1589
- throw new Error("LedgerConnector: solSignMessage is not yet implemented");
1987
+ async _solSignMessage(sessionId, params) {
1988
+ const solSigner = await this._createSolSigner(sessionId);
1989
+ const path = normalizePath(params.path);
1990
+ const messageBytes = hexToBytes3(params.message);
1991
+ try {
1992
+ const result = await solSigner.signMessage(path, messageBytes);
1993
+ return { signature: bytesToHex2(result) };
1994
+ } catch (err) {
1995
+ this._invalidateSession(sessionId);
1996
+ throw this._wrapError(err);
1997
+ }
1998
+ }
1999
+ // ---------------------------------------------------------------------------
2000
+ // Private -- TRON methods
2001
+ // ---------------------------------------------------------------------------
2002
+ async _tronGetAddress(sessionId, params) {
2003
+ const tronSigner = await this._createTronSigner(sessionId);
2004
+ const path = normalizePath(params.path);
2005
+ try {
2006
+ const result = await tronSigner.getAddress(path, {
2007
+ checkOnDevice: params.showOnDevice ?? false
2008
+ });
2009
+ return { address: result.address, publicKey: result.publicKey, path: params.path };
2010
+ } catch (err) {
2011
+ this._invalidateSession(sessionId);
2012
+ throw this._wrapError(err);
2013
+ }
2014
+ }
2015
+ async _tronSignTransaction(sessionId, params) {
2016
+ const tronSigner = await this._createTronSigner(sessionId);
2017
+ const path = normalizePath(params.path);
2018
+ try {
2019
+ const signature = await tronSigner.signTransaction(path, params.rawTxHex);
2020
+ return { signature };
2021
+ } catch (err) {
2022
+ this._invalidateSession(sessionId);
2023
+ throw this._wrapError(err);
2024
+ }
2025
+ }
2026
+ async _tronSignMessage(sessionId, params) {
2027
+ const tronSigner = await this._createTronSigner(sessionId);
2028
+ const path = normalizePath(params.path);
2029
+ try {
2030
+ const signature = await tronSigner.signMessage(path, params.messageHex);
2031
+ return { signature };
2032
+ } catch (err) {
2033
+ this._invalidateSession(sessionId);
2034
+ throw this._wrapError(err);
2035
+ }
1590
2036
  }
1591
2037
  // ---------------------------------------------------------------------------
1592
2038
  // Private -- DMK / Manager lifecycle
@@ -1639,7 +2085,14 @@ var LedgerConnectorBase = class {
1639
2085
  dmk,
1640
2086
  sessionId
1641
2087
  }).build();
1642
- return new SignerBtc(sdkSigner);
2088
+ const signer = new SignerBtc(sdkSigner);
2089
+ signer.onInteraction = (interaction) => {
2090
+ this._emit("ui-event", {
2091
+ type: interaction,
2092
+ payload: { sessionId }
2093
+ });
2094
+ };
2095
+ return signer;
1643
2096
  }
1644
2097
  async _createSolSigner(sessionId) {
1645
2098
  const dmk = await this._getOrCreateDmk();
@@ -1650,6 +2103,14 @@ var LedgerConnectorBase = class {
1650
2103
  }).build();
1651
2104
  return new SignerSol(sdkSigner);
1652
2105
  }
2106
+ async _createTronSigner(sessionId) {
2107
+ const dmk = await this._getOrCreateDmk();
2108
+ const sendApdu = async (rawApdu) => {
2109
+ const response = await dmk.sendApdu({ sessionId, apdu: rawApdu });
2110
+ return response;
2111
+ };
2112
+ return new SignerTron(sendApdu);
2113
+ }
1653
2114
  _invalidateSession(sessionId) {
1654
2115
  this._signerManager?.invalidate(sessionId);
1655
2116
  }
@@ -1810,6 +2271,7 @@ var AppManager = class {
1810
2271
  SignerEth,
1811
2272
  SignerManager,
1812
2273
  SignerSol,
2274
+ SignerTron,
1813
2275
  clearRegistry,
1814
2276
  deviceActionToPromise,
1815
2277
  getTransportProvider,