@bytezhang/ledger-adapter 0.0.9 → 0.0.11

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
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  AppManager: () => AppManager,
34
34
  LedgerAdapter: () => LedgerAdapter,
35
+ LedgerConnectorBase: () => LedgerConnectorBase,
35
36
  LedgerDeviceManager: () => LedgerDeviceManager,
36
37
  SignerBtc: () => SignerBtc,
37
38
  SignerEth: () => SignerEth,
@@ -1221,6 +1222,445 @@ var SignerSol = class {
1221
1222
  }
1222
1223
  };
1223
1224
 
1225
+ // src/connector/LedgerConnectorBase.ts
1226
+ function normalizePath(path) {
1227
+ return path.startsWith("m/") ? path.slice(2) : path;
1228
+ }
1229
+ function stripHex2(hex) {
1230
+ return hex.startsWith("0x") ? hex.slice(2) : hex;
1231
+ }
1232
+ function padHex642(hex) {
1233
+ return `0x${stripHex2(hex).padStart(64, "0")}`;
1234
+ }
1235
+ var LedgerConnectorBase = class {
1236
+ constructor(createTransport, options) {
1237
+ this._deviceManager = null;
1238
+ this._signerManager = null;
1239
+ this._dmk = null;
1240
+ this._eventHandlers = /* @__PURE__ */ new Map();
1241
+ this._createTransport = createTransport;
1242
+ this._connectionType = options?.connectionType ?? "usb";
1243
+ this._providedDmk = options?.dmk;
1244
+ if (this._providedDmk) {
1245
+ this._initManagers(this._providedDmk);
1246
+ }
1247
+ }
1248
+ // ---------------------------------------------------------------------------
1249
+ // Protected — hooks for subclasses
1250
+ // ---------------------------------------------------------------------------
1251
+ /**
1252
+ * Resolve the connectId for a discovered device descriptor.
1253
+ * Default: use the DMK path (ephemeral UUID).
1254
+ * Override in subclasses to extract stable identifiers (e.g. BLE hex ID).
1255
+ */
1256
+ _resolveConnectId(descriptor) {
1257
+ return descriptor.path;
1258
+ }
1259
+ // ---------------------------------------------------------------------------
1260
+ // IConnector -- Device discovery
1261
+ // ---------------------------------------------------------------------------
1262
+ async searchDevices() {
1263
+ const dm = await this._getDeviceManager();
1264
+ let descriptors = await dm.enumerate();
1265
+ if (descriptors.length === 0) {
1266
+ try {
1267
+ await dm.requestDevice();
1268
+ } catch {
1269
+ }
1270
+ descriptors = await dm.enumerate();
1271
+ }
1272
+ const result = descriptors.map((d) => {
1273
+ const connectId = this._resolveConnectId(d);
1274
+ return {
1275
+ connectId,
1276
+ deviceId: d.path,
1277
+ name: d.name || d.type || "Ledger",
1278
+ model: d.type
1279
+ };
1280
+ });
1281
+ return result;
1282
+ }
1283
+ // ---------------------------------------------------------------------------
1284
+ // IConnector -- Connection
1285
+ // ---------------------------------------------------------------------------
1286
+ async connect(deviceId) {
1287
+ const dm = await this._getDeviceManager();
1288
+ await this.searchDevices();
1289
+ let resolvedDeviceId = deviceId;
1290
+ if (!resolvedDeviceId) {
1291
+ const descriptors = await dm.enumerate();
1292
+ if (descriptors.length === 0) {
1293
+ throw new Error(
1294
+ `No Ledger device found. Make sure the device is connected${this._connectionType === "ble" ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
1295
+ );
1296
+ }
1297
+ resolvedDeviceId = descriptors[0].path;
1298
+ }
1299
+ try {
1300
+ const sessionId = await dm.connect(resolvedDeviceId);
1301
+ const session = {
1302
+ sessionId,
1303
+ deviceInfo: {
1304
+ vendor: "ledger",
1305
+ model: "unknown",
1306
+ firmwareVersion: "unknown",
1307
+ deviceId: resolvedDeviceId,
1308
+ connectId: resolvedDeviceId,
1309
+ connectionType: this._connectionType,
1310
+ capabilities: {
1311
+ persistentDeviceIdentity: false
1312
+ }
1313
+ }
1314
+ };
1315
+ this._emit("device-connect", {
1316
+ device: {
1317
+ connectId: resolvedDeviceId,
1318
+ deviceId: resolvedDeviceId,
1319
+ name: "Ledger"
1320
+ }
1321
+ });
1322
+ return session;
1323
+ } catch (err) {
1324
+ this._resetManagers();
1325
+ const dm2 = await this._getDeviceManager();
1326
+ await this.searchDevices();
1327
+ const descriptors = await dm2.enumerate();
1328
+ const retryId = deviceId ? descriptors.find((d) => d.path === deviceId)?.path : descriptors[0]?.path;
1329
+ if (!retryId) {
1330
+ throw new Error(
1331
+ `No Ledger device found after retry. Make sure the device is connected${this._connectionType === "ble" ? " nearby with Bluetooth enabled" : " via USB"} and unlocked.`
1332
+ );
1333
+ }
1334
+ const sessionId = await dm2.connect(retryId);
1335
+ const session = {
1336
+ sessionId,
1337
+ deviceInfo: {
1338
+ vendor: "ledger",
1339
+ model: "unknown",
1340
+ firmwareVersion: "unknown",
1341
+ deviceId: retryId,
1342
+ connectId: retryId,
1343
+ connectionType: this._connectionType
1344
+ }
1345
+ };
1346
+ this._emit("device-connect", {
1347
+ device: {
1348
+ connectId: retryId,
1349
+ deviceId: retryId,
1350
+ name: "Ledger"
1351
+ }
1352
+ });
1353
+ return session;
1354
+ }
1355
+ }
1356
+ async disconnect(sessionId) {
1357
+ if (!this._deviceManager) return;
1358
+ const deviceId = this._deviceManager.getDeviceId(sessionId);
1359
+ this._signerManager?.invalidate(sessionId);
1360
+ await this._deviceManager.disconnect(sessionId);
1361
+ if (deviceId) {
1362
+ this._emit("device-disconnect", { connectId: deviceId });
1363
+ }
1364
+ }
1365
+ // ---------------------------------------------------------------------------
1366
+ // IConnector -- Method dispatch
1367
+ // ---------------------------------------------------------------------------
1368
+ async call(sessionId, method, params) {
1369
+ switch (method) {
1370
+ case "evmGetAddress":
1371
+ return this._evmGetAddress(sessionId, params);
1372
+ case "evmSignTransaction":
1373
+ return this._evmSignTransaction(sessionId, params);
1374
+ case "evmSignMessage":
1375
+ return this._evmSignMessage(sessionId, params);
1376
+ case "evmSignTypedData":
1377
+ return this._evmSignTypedData(sessionId, params);
1378
+ case "btcGetAddress":
1379
+ return this._btcGetAddress(sessionId, params);
1380
+ case "btcGetPublicKey":
1381
+ return this._btcGetPublicKey(sessionId, params);
1382
+ case "btcSignTransaction":
1383
+ return this._btcSignTransaction(sessionId, params);
1384
+ case "btcSignMessage":
1385
+ return this._btcSignMessage(sessionId, params);
1386
+ case "btcGetMasterFingerprint":
1387
+ return this._btcGetMasterFingerprint(sessionId, params);
1388
+ case "solGetAddress":
1389
+ return this._solGetAddress(sessionId, params);
1390
+ case "solSignTransaction":
1391
+ return this._solSignTransaction(sessionId, params);
1392
+ case "solSignMessage":
1393
+ return this._solSignMessage(sessionId, params);
1394
+ default:
1395
+ throw new Error(`LedgerConnector: unknown method "${method}"`);
1396
+ }
1397
+ }
1398
+ async cancel(_sessionId) {
1399
+ }
1400
+ uiResponse(_response) {
1401
+ }
1402
+ // ---------------------------------------------------------------------------
1403
+ // IConnector -- Events
1404
+ // ---------------------------------------------------------------------------
1405
+ on(event, handler) {
1406
+ if (!this._eventHandlers.has(event)) {
1407
+ this._eventHandlers.set(event, /* @__PURE__ */ new Set());
1408
+ }
1409
+ this._eventHandlers.get(event).add(handler);
1410
+ }
1411
+ off(event, handler) {
1412
+ this._eventHandlers.get(event)?.delete(handler);
1413
+ }
1414
+ // ---------------------------------------------------------------------------
1415
+ // IConnector -- Reset
1416
+ // ---------------------------------------------------------------------------
1417
+ reset() {
1418
+ this._resetManagers();
1419
+ }
1420
+ // ---------------------------------------------------------------------------
1421
+ // Private -- EVM methods
1422
+ // ---------------------------------------------------------------------------
1423
+ async _evmGetAddress(sessionId, params) {
1424
+ const signer = await this._getEthSigner(sessionId);
1425
+ const path = normalizePath(params.path);
1426
+ try {
1427
+ const result = await signer.getAddress(path, {
1428
+ checkOnDevice: params.showOnDevice ?? false
1429
+ });
1430
+ return { address: result.address, publicKey: result.publicKey };
1431
+ } catch (err) {
1432
+ this._invalidateSession(sessionId);
1433
+ throw this._wrapError(err);
1434
+ }
1435
+ }
1436
+ async _evmSignTransaction(sessionId, params) {
1437
+ const signer = await this._getEthSigner(sessionId);
1438
+ const path = normalizePath(params.path);
1439
+ try {
1440
+ const result = await signer.signTransaction(
1441
+ path,
1442
+ params.serializedTx
1443
+ );
1444
+ return {
1445
+ v: `0x${result.v.toString(16)}`,
1446
+ r: padHex642(result.r),
1447
+ s: padHex642(result.s)
1448
+ };
1449
+ } catch (err) {
1450
+ this._invalidateSession(sessionId);
1451
+ throw this._wrapError(err);
1452
+ }
1453
+ }
1454
+ async _evmSignMessage(sessionId, params) {
1455
+ const signer = await this._getEthSigner(sessionId);
1456
+ const path = normalizePath(params.path);
1457
+ try {
1458
+ const result = await signer.signMessage(
1459
+ path,
1460
+ params.message
1461
+ );
1462
+ const rHex = stripHex2(result.r).padStart(64, "0");
1463
+ const sHex = stripHex2(result.s).padStart(64, "0");
1464
+ const vHex = result.v.toString(16).padStart(2, "0");
1465
+ return { signature: `0x${rHex}${sHex}${vHex}` };
1466
+ } catch (err) {
1467
+ this._invalidateSession(sessionId);
1468
+ throw this._wrapError(err);
1469
+ }
1470
+ }
1471
+ async _evmSignTypedData(sessionId, params) {
1472
+ const signer = await this._getEthSigner(sessionId);
1473
+ const path = normalizePath(params.path);
1474
+ try {
1475
+ const result = await signer.signTypedData(
1476
+ path,
1477
+ params.data
1478
+ );
1479
+ const rHex = stripHex2(result.r).padStart(64, "0");
1480
+ const sHex = stripHex2(result.s).padStart(64, "0");
1481
+ const vHex = result.v.toString(16).padStart(2, "0");
1482
+ return { signature: `0x${rHex}${sHex}${vHex}` };
1483
+ } catch (err) {
1484
+ this._invalidateSession(sessionId);
1485
+ throw this._wrapError(err);
1486
+ }
1487
+ }
1488
+ // ---------------------------------------------------------------------------
1489
+ // Private -- BTC methods
1490
+ // ---------------------------------------------------------------------------
1491
+ async _btcGetAddress(sessionId, params) {
1492
+ const btcSigner = await this._createBtcSigner(sessionId);
1493
+ const path = normalizePath(params.path);
1494
+ try {
1495
+ const { DefaultWallet, DefaultDescriptorTemplate } = await import("@ledgerhq/device-signer-kit-bitcoin");
1496
+ const purpose = path.split("/")[0]?.replace("'", "");
1497
+ let template = DefaultDescriptorTemplate.NATIVE_SEGWIT;
1498
+ if (purpose === "44") template = DefaultDescriptorTemplate.LEGACY;
1499
+ else if (purpose === "49")
1500
+ template = DefaultDescriptorTemplate.NESTED_SEGWIT;
1501
+ else if (purpose === "86") template = DefaultDescriptorTemplate.TAPROOT;
1502
+ const wallet = new DefaultWallet(path, template);
1503
+ const result = await btcSigner.getWalletAddress(wallet, 0, {
1504
+ checkOnDevice: params.showOnDevice ?? false,
1505
+ change: false
1506
+ });
1507
+ return { address: result.address, path: params.path };
1508
+ } catch (err) {
1509
+ this._invalidateSession(sessionId);
1510
+ throw this._wrapError(err);
1511
+ }
1512
+ }
1513
+ async _btcGetPublicKey(sessionId, params) {
1514
+ const btcSigner = await this._createBtcSigner(sessionId);
1515
+ const path = normalizePath(params.path);
1516
+ try {
1517
+ const xpub = await btcSigner.getExtendedPublicKey(path, {
1518
+ checkOnDevice: params.showOnDevice ?? false
1519
+ });
1520
+ return { xpub, path: params.path };
1521
+ } catch (err) {
1522
+ this._invalidateSession(sessionId);
1523
+ throw this._wrapError(err);
1524
+ }
1525
+ }
1526
+ async _btcSignTransaction(_sessionId, _params) {
1527
+ throw new Error("LedgerConnector: btcSignTransaction is not yet implemented");
1528
+ }
1529
+ async _btcSignMessage(_sessionId, _params) {
1530
+ throw new Error("LedgerConnector: btcSignMessage is not yet implemented");
1531
+ }
1532
+ async _btcGetMasterFingerprint(sessionId, params) {
1533
+ const btcSigner = await this._createBtcSigner(sessionId);
1534
+ try {
1535
+ const fingerprint = await btcSigner.getMasterFingerprint({
1536
+ skipOpenApp: params?.skipOpenApp
1537
+ });
1538
+ const hex = Array.from(fingerprint).map((b) => b.toString(16).padStart(2, "0")).join("");
1539
+ return { masterFingerprint: hex };
1540
+ } catch (err) {
1541
+ this._invalidateSession(sessionId);
1542
+ throw this._wrapError(err);
1543
+ }
1544
+ }
1545
+ // ---------------------------------------------------------------------------
1546
+ // Private -- SOL methods
1547
+ // ---------------------------------------------------------------------------
1548
+ async _solGetAddress(sessionId, params) {
1549
+ const solSigner = await this._createSolSigner(sessionId);
1550
+ const path = normalizePath(params.path);
1551
+ try {
1552
+ const publicKey = await solSigner.getAddress(path, {
1553
+ checkOnDevice: params.showOnDevice ?? false
1554
+ });
1555
+ return { address: publicKey, path: params.path };
1556
+ } catch (err) {
1557
+ this._invalidateSession(sessionId);
1558
+ throw this._wrapError(err);
1559
+ }
1560
+ }
1561
+ async _solSignTransaction(_sessionId, _params) {
1562
+ throw new Error("LedgerConnector: solSignTransaction is not yet implemented");
1563
+ }
1564
+ async _solSignMessage(_sessionId, _params) {
1565
+ throw new Error("LedgerConnector: solSignMessage is not yet implemented");
1566
+ }
1567
+ // ---------------------------------------------------------------------------
1568
+ // Private -- DMK / Manager lifecycle
1569
+ // ---------------------------------------------------------------------------
1570
+ /**
1571
+ * Lazily create or return the DMK instance.
1572
+ * If a DMK was provided via constructor, it is used directly.
1573
+ * Otherwise, one is created via the transport factory.
1574
+ */
1575
+ async _getOrCreateDmk() {
1576
+ if (this._dmk) return this._dmk;
1577
+ if (this._providedDmk) {
1578
+ this._dmk = this._providedDmk;
1579
+ return this._dmk;
1580
+ }
1581
+ const { DeviceManagementKitBuilder } = await import("@ledgerhq/device-management-kit");
1582
+ const transportFactory = await this._createTransport();
1583
+ this._dmk = new DeviceManagementKitBuilder().addTransport(transportFactory).build();
1584
+ return this._dmk;
1585
+ }
1586
+ _initManagers(dmk) {
1587
+ this._dmk = dmk;
1588
+ this._deviceManager = new LedgerDeviceManager(dmk);
1589
+ this._signerManager = new SignerManager(dmk);
1590
+ }
1591
+ async _getDeviceManager() {
1592
+ if (this._deviceManager) return this._deviceManager;
1593
+ const dmk = await this._getOrCreateDmk();
1594
+ this._initManagers(dmk);
1595
+ return this._deviceManager;
1596
+ }
1597
+ async _getEthSigner(sessionId) {
1598
+ if (!this._signerManager) {
1599
+ const dmk = await this._getOrCreateDmk();
1600
+ this._initManagers(dmk);
1601
+ }
1602
+ const signer = await this._signerManager.getOrCreate(sessionId);
1603
+ signer.onInteraction = (interaction) => {
1604
+ this._emit("ui-event", {
1605
+ type: interaction,
1606
+ payload: { sessionId }
1607
+ });
1608
+ };
1609
+ return signer;
1610
+ }
1611
+ async _createBtcSigner(sessionId) {
1612
+ const dmk = await this._getOrCreateDmk();
1613
+ const { SignerBtcBuilder } = await import("@ledgerhq/device-signer-kit-bitcoin");
1614
+ const sdkSigner = new SignerBtcBuilder({
1615
+ dmk,
1616
+ sessionId
1617
+ }).build();
1618
+ return new SignerBtc(sdkSigner);
1619
+ }
1620
+ async _createSolSigner(sessionId) {
1621
+ const dmk = await this._getOrCreateDmk();
1622
+ const { SignerSolanaBuilder } = await import("@ledgerhq/device-signer-kit-solana");
1623
+ const sdkSigner = new SignerSolanaBuilder({
1624
+ dmk,
1625
+ sessionId
1626
+ }).build();
1627
+ return new SignerSol(sdkSigner);
1628
+ }
1629
+ _invalidateSession(sessionId) {
1630
+ this._signerManager?.invalidate(sessionId);
1631
+ }
1632
+ _resetManagers() {
1633
+ this._signerManager?.clearAll();
1634
+ this._deviceManager?.dispose();
1635
+ this._deviceManager = null;
1636
+ this._signerManager = null;
1637
+ this._dmk = null;
1638
+ }
1639
+ // ---------------------------------------------------------------------------
1640
+ // Private -- Events
1641
+ // ---------------------------------------------------------------------------
1642
+ _emit(event, data) {
1643
+ const handlers = this._eventHandlers.get(event);
1644
+ if (handlers) {
1645
+ for (const handler of handlers) {
1646
+ try {
1647
+ handler(data);
1648
+ } catch {
1649
+ }
1650
+ }
1651
+ }
1652
+ }
1653
+ // ---------------------------------------------------------------------------
1654
+ // Private -- Error handling
1655
+ // ---------------------------------------------------------------------------
1656
+ _wrapError(err) {
1657
+ const mapped = mapLedgerError(err);
1658
+ const error = new Error(mapped.message);
1659
+ error.code = mapped.code;
1660
+ return error;
1661
+ }
1662
+ };
1663
+
1224
1664
  // src/transport/registry.ts
1225
1665
  var registry = /* @__PURE__ */ new Map();
1226
1666
  function normalizeType(type) {
@@ -1340,6 +1780,7 @@ var AppManager = class {
1340
1780
  0 && (module.exports = {
1341
1781
  AppManager,
1342
1782
  LedgerAdapter,
1783
+ LedgerConnectorBase,
1343
1784
  LedgerDeviceManager,
1344
1785
  SignerBtc,
1345
1786
  SignerEth,