@bytezhang/ledger-adapter 0.0.6 → 0.0.7
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.d.mts +16 -4
- package/dist/index.d.ts +16 -4
- package/dist/index.js +126 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +129 -22
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
HardwareErrorCode as HardwareErrorCode2,
|
|
14
14
|
TypedEventEmitter,
|
|
15
15
|
DEVICE,
|
|
16
|
-
UI_REQUEST
|
|
16
|
+
UI_REQUEST,
|
|
17
|
+
CHAIN_FINGERPRINT_PATHS,
|
|
18
|
+
deriveDeviceFingerprint
|
|
17
19
|
} from "@bytezhang/hardware-wallet-core";
|
|
18
20
|
|
|
19
21
|
// src/errors.ts
|
|
@@ -135,6 +137,8 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
135
137
|
this._sessions = /* @__PURE__ */ new Map();
|
|
136
138
|
// Pending device-connect resolve — set by _waitForDeviceConnect, resolved by uiResponse
|
|
137
139
|
this._deviceConnectResolve = null;
|
|
140
|
+
// Mutex for ensureConnected — prevents concurrent calls from establishing duplicate connections
|
|
141
|
+
this._connectingPromise = null;
|
|
138
142
|
// ---------------------------------------------------------------------------
|
|
139
143
|
// Event translation
|
|
140
144
|
// ---------------------------------------------------------------------------
|
|
@@ -188,6 +192,8 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
188
192
|
async init(_config) {
|
|
189
193
|
}
|
|
190
194
|
async dispose() {
|
|
195
|
+
this._deviceConnectResolve?.(true);
|
|
196
|
+
this._deviceConnectResolve = null;
|
|
191
197
|
this.unregisterEventListeners();
|
|
192
198
|
this.connector.reset();
|
|
193
199
|
this._uiHandler = null;
|
|
@@ -259,10 +265,74 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
259
265
|
void this.connector.cancel(sessionId);
|
|
260
266
|
}
|
|
261
267
|
// ---------------------------------------------------------------------------
|
|
268
|
+
// Chain fingerprint
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
async getChainFingerprint(connectId, deviceId, chain) {
|
|
271
|
+
await this._ensureDevicePermission(connectId, deviceId);
|
|
272
|
+
try {
|
|
273
|
+
const address = await this._deriveAddressForFingerprint(connectId, chain);
|
|
274
|
+
return success(deriveDeviceFingerprint(address));
|
|
275
|
+
} catch (err) {
|
|
276
|
+
return this.errorToFailure(err);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Verify that the connected device matches the expected fingerprint.
|
|
281
|
+
*
|
|
282
|
+
* - If deviceId is empty, verification is skipped (returns true).
|
|
283
|
+
* - deviceId is used here as the stored fingerprint to compare against.
|
|
284
|
+
*/
|
|
285
|
+
async _verifyDeviceFingerprint(connectId, deviceId, chain) {
|
|
286
|
+
if (!deviceId) return true;
|
|
287
|
+
try {
|
|
288
|
+
const address = await this._deriveAddressForFingerprint(connectId, chain);
|
|
289
|
+
const fingerprint = deriveDeviceFingerprint(address);
|
|
290
|
+
return fingerprint === deviceId;
|
|
291
|
+
} catch (err) {
|
|
292
|
+
const mapped = mapLedgerError(err);
|
|
293
|
+
if (mapped.code === HardwareErrorCode2.WrongApp || mapped.code === HardwareErrorCode2.DeviceLocked) {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
throw err;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Derive an address at the fixed testnet path for fingerprint generation.
|
|
301
|
+
*/
|
|
302
|
+
async _deriveAddressForFingerprint(connectId, chain) {
|
|
303
|
+
const path = CHAIN_FINGERPRINT_PATHS[chain];
|
|
304
|
+
if (chain === "evm") {
|
|
305
|
+
const result = await this.connectorCall(connectId, "evmGetAddress", {
|
|
306
|
+
path,
|
|
307
|
+
showOnDevice: false
|
|
308
|
+
});
|
|
309
|
+
return result.address;
|
|
310
|
+
}
|
|
311
|
+
if (chain === "btc") {
|
|
312
|
+
const result = await this.connectorCall(connectId, "btcGetAddress", {
|
|
313
|
+
path,
|
|
314
|
+
showOnDevice: false,
|
|
315
|
+
coin: "Testnet"
|
|
316
|
+
});
|
|
317
|
+
return result.address;
|
|
318
|
+
}
|
|
319
|
+
if (chain === "sol") {
|
|
320
|
+
const result = await this.connectorCall(connectId, "solGetAddress", {
|
|
321
|
+
path,
|
|
322
|
+
showOnDevice: false
|
|
323
|
+
});
|
|
324
|
+
return result.address;
|
|
325
|
+
}
|
|
326
|
+
throw new Error(`Unsupported chain for fingerprint: ${chain}`);
|
|
327
|
+
}
|
|
328
|
+
// ---------------------------------------------------------------------------
|
|
262
329
|
// EVM methods
|
|
263
330
|
// ---------------------------------------------------------------------------
|
|
264
331
|
async evmGetAddress(connectId, _deviceId, params) {
|
|
265
332
|
await this._ensureDevicePermission(connectId, _deviceId);
|
|
333
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "evm")) {
|
|
334
|
+
return failure(HardwareErrorCode2.DeviceMismatch, "Wrong device connected");
|
|
335
|
+
}
|
|
266
336
|
try {
|
|
267
337
|
const result = await this.connectorCall(connectId, "evmGetAddress", {
|
|
268
338
|
path: params.path,
|
|
@@ -286,6 +356,9 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
286
356
|
}
|
|
287
357
|
async evmGetPublicKey(connectId, _deviceId, params) {
|
|
288
358
|
await this._ensureDevicePermission(connectId, _deviceId);
|
|
359
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "evm")) {
|
|
360
|
+
return failure(HardwareErrorCode2.DeviceMismatch, "Wrong device connected");
|
|
361
|
+
}
|
|
289
362
|
try {
|
|
290
363
|
const result = await this.connectorCall(connectId, "evmGetAddress", {
|
|
291
364
|
path: params.path,
|
|
@@ -301,21 +374,19 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
301
374
|
}
|
|
302
375
|
async evmSignTransaction(connectId, _deviceId, params) {
|
|
303
376
|
await this._ensureDevicePermission(connectId, _deviceId);
|
|
377
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "evm")) {
|
|
378
|
+
return failure(HardwareErrorCode2.DeviceMismatch, "Wrong device connected");
|
|
379
|
+
}
|
|
304
380
|
try {
|
|
381
|
+
if (!params.serializedTx) {
|
|
382
|
+
return failure(
|
|
383
|
+
HardwareErrorCode2.InvalidParams,
|
|
384
|
+
"Ledger requires a pre-serialized transaction (serializedTx). Provide an RLP-encoded hex string."
|
|
385
|
+
);
|
|
386
|
+
}
|
|
305
387
|
const result = await this.connectorCall(connectId, "evmSignTransaction", {
|
|
306
388
|
path: params.path,
|
|
307
|
-
|
|
308
|
-
to: params.to,
|
|
309
|
-
value: params.value,
|
|
310
|
-
chainId: params.chainId,
|
|
311
|
-
nonce: params.nonce,
|
|
312
|
-
gasLimit: params.gasLimit,
|
|
313
|
-
gasPrice: params.gasPrice,
|
|
314
|
-
maxFeePerGas: params.maxFeePerGas,
|
|
315
|
-
maxPriorityFeePerGas: params.maxPriorityFeePerGas,
|
|
316
|
-
accessList: params.accessList,
|
|
317
|
-
data: params.data
|
|
318
|
-
}
|
|
389
|
+
serializedTx: params.serializedTx
|
|
319
390
|
});
|
|
320
391
|
return success({
|
|
321
392
|
v: ensure0x(result.v),
|
|
@@ -328,6 +399,9 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
328
399
|
}
|
|
329
400
|
async evmSignMessage(connectId, _deviceId, params) {
|
|
330
401
|
await this._ensureDevicePermission(connectId, _deviceId);
|
|
402
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "evm")) {
|
|
403
|
+
return failure(HardwareErrorCode2.DeviceMismatch, "Wrong device connected");
|
|
404
|
+
}
|
|
331
405
|
try {
|
|
332
406
|
const result = await this.connectorCall(connectId, "evmSignMessage", {
|
|
333
407
|
path: params.path,
|
|
@@ -342,6 +416,9 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
342
416
|
}
|
|
343
417
|
async evmSignTypedData(connectId, _deviceId, params) {
|
|
344
418
|
await this._ensureDevicePermission(connectId, _deviceId);
|
|
419
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "evm")) {
|
|
420
|
+
return failure(HardwareErrorCode2.DeviceMismatch, "Wrong device connected");
|
|
421
|
+
}
|
|
345
422
|
if (params.mode === "hash") {
|
|
346
423
|
return failure(
|
|
347
424
|
HardwareErrorCode2.MethodNotSupported,
|
|
@@ -365,6 +442,9 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
365
442
|
// ---------------------------------------------------------------------------
|
|
366
443
|
async btcGetAddress(connectId, _deviceId, params) {
|
|
367
444
|
await this._ensureDevicePermission(connectId, _deviceId);
|
|
445
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "btc")) {
|
|
446
|
+
return failure(HardwareErrorCode2.DeviceMismatch, "Wrong device connected");
|
|
447
|
+
}
|
|
368
448
|
try {
|
|
369
449
|
const result = await this.connectorCall(connectId, "btcGetAddress", {
|
|
370
450
|
path: params.path,
|
|
@@ -389,6 +469,9 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
389
469
|
}
|
|
390
470
|
async btcGetPublicKey(connectId, _deviceId, params) {
|
|
391
471
|
await this._ensureDevicePermission(connectId, _deviceId);
|
|
472
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "btc")) {
|
|
473
|
+
return failure(HardwareErrorCode2.DeviceMismatch, "Wrong device connected");
|
|
474
|
+
}
|
|
392
475
|
try {
|
|
393
476
|
const result = await this.connectorCall(connectId, "btcGetPublicKey", {
|
|
394
477
|
path: params.path,
|
|
@@ -409,6 +492,9 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
409
492
|
}
|
|
410
493
|
async btcSignTransaction(connectId, _deviceId, params) {
|
|
411
494
|
await this._ensureDevicePermission(connectId, _deviceId);
|
|
495
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "btc")) {
|
|
496
|
+
return failure(HardwareErrorCode2.DeviceMismatch, "Wrong device connected");
|
|
497
|
+
}
|
|
412
498
|
if (!params.psbt) {
|
|
413
499
|
return failure(
|
|
414
500
|
HardwareErrorCode2.InvalidParams,
|
|
@@ -429,12 +515,13 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
429
515
|
// ---------------------------------------------------------------------------
|
|
430
516
|
// Device fingerprint
|
|
431
517
|
// ---------------------------------------------------------------------------
|
|
432
|
-
async btcGetMasterFingerprint(connectId, _deviceId
|
|
518
|
+
async btcGetMasterFingerprint(connectId, _deviceId) {
|
|
433
519
|
await this._ensureDevicePermission(connectId, _deviceId);
|
|
520
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "btc")) {
|
|
521
|
+
return failure(HardwareErrorCode2.DeviceMismatch, "Wrong device connected");
|
|
522
|
+
}
|
|
434
523
|
try {
|
|
435
|
-
const result = await this.connectorCall(connectId, "btcGetMasterFingerprint", {
|
|
436
|
-
skipOpenApp: params?.skipOpenApp
|
|
437
|
-
});
|
|
524
|
+
const result = await this.connectorCall(connectId, "btcGetMasterFingerprint", {});
|
|
438
525
|
return success({ masterFingerprint: result.masterFingerprint });
|
|
439
526
|
} catch (err) {
|
|
440
527
|
return this.errorToFailure(err);
|
|
@@ -481,7 +568,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
481
568
|
clearTimeout(timer);
|
|
482
569
|
this._deviceConnectResolve = null;
|
|
483
570
|
if (cancelled) {
|
|
484
|
-
reject(
|
|
571
|
+
reject(Object.assign(
|
|
572
|
+
new Error("User cancelled Ledger connection"),
|
|
573
|
+
{ _tag: "DeviceNotRecognizedError" }
|
|
574
|
+
));
|
|
485
575
|
} else {
|
|
486
576
|
resolve();
|
|
487
577
|
}
|
|
@@ -512,6 +602,17 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
512
602
|
if (this._sessions.size > 0) {
|
|
513
603
|
return this._sessions.keys().next().value;
|
|
514
604
|
}
|
|
605
|
+
if (this._connectingPromise) {
|
|
606
|
+
return this._connectingPromise;
|
|
607
|
+
}
|
|
608
|
+
this._connectingPromise = this._doConnect();
|
|
609
|
+
try {
|
|
610
|
+
return await this._connectingPromise;
|
|
611
|
+
} finally {
|
|
612
|
+
this._connectingPromise = null;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
async _doConnect() {
|
|
515
616
|
for (let attempt = 0; attempt < _LedgerAdapter.MAX_DEVICE_RETRY; attempt++) {
|
|
516
617
|
const devices = await this.searchDevices();
|
|
517
618
|
if (devices.length > 0) {
|
|
@@ -745,7 +846,8 @@ var LedgerDeviceManager = class {
|
|
|
745
846
|
return new Promise((resolve) => {
|
|
746
847
|
let resolved = false;
|
|
747
848
|
let syncResult = null;
|
|
748
|
-
|
|
849
|
+
let sub = null;
|
|
850
|
+
sub = this._dmk.listenToAvailableDevices().subscribe({
|
|
749
851
|
next: (devices) => {
|
|
750
852
|
if (resolved) return;
|
|
751
853
|
resolved = true;
|
|
@@ -753,7 +855,12 @@ var LedgerDeviceManager = class {
|
|
|
753
855
|
for (const d of devices) {
|
|
754
856
|
this._discovered.set(d.id, d);
|
|
755
857
|
}
|
|
756
|
-
|
|
858
|
+
if (sub) {
|
|
859
|
+
sub.unsubscribe();
|
|
860
|
+
resolve(devices.map((d) => ({ path: d.id, type: d.deviceModel.id })));
|
|
861
|
+
} else {
|
|
862
|
+
syncResult = devices;
|
|
863
|
+
}
|
|
757
864
|
},
|
|
758
865
|
error: () => {
|
|
759
866
|
if (!resolved) {
|
|
@@ -765,7 +872,7 @@ var LedgerDeviceManager = class {
|
|
|
765
872
|
if (syncResult !== null) {
|
|
766
873
|
sub.unsubscribe();
|
|
767
874
|
const devices = syncResult;
|
|
768
|
-
resolve(devices.map((d) => ({ path: d.id, type: d.deviceModel.
|
|
875
|
+
resolve(devices.map((d) => ({ path: d.id, type: d.deviceModel.id })));
|
|
769
876
|
}
|
|
770
877
|
});
|
|
771
878
|
}
|
|
@@ -786,7 +893,7 @@ var LedgerDeviceManager = class {
|
|
|
786
893
|
name: d.name
|
|
787
894
|
}));
|
|
788
895
|
if (!previousIds.has(d.id)) {
|
|
789
|
-
onChange({ type: "device-connected", descriptor: { path: d.id, type: d.deviceModel.
|
|
896
|
+
onChange({ type: "device-connected", descriptor: { path: d.id, type: d.deviceModel.id } });
|
|
790
897
|
}
|
|
791
898
|
}
|
|
792
899
|
for (const id of previousIds) {
|