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