@bytezhang/ledger-adapter 0.0.6 → 0.0.8
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 +222 -30
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +224 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
- package/dist/entries/node.d.mts +0 -5
- package/dist/entries/node.d.ts +0 -5
- package/dist/entries/react-native.d.mts +0 -5
- package/dist/entries/react-native.d.ts +0 -5
- package/dist/entries/web.d.mts +0 -5
- package/dist/entries/web.d.ts +0 -5
- package/dist/index.d.mts +0 -406
- package/dist/index.d.ts +0 -406
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,34 +515,75 @@ 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);
|
|
441
528
|
}
|
|
442
529
|
}
|
|
443
530
|
// ---------------------------------------------------------------------------
|
|
444
|
-
// Solana methods
|
|
531
|
+
// Solana methods
|
|
445
532
|
// ---------------------------------------------------------------------------
|
|
446
|
-
async solGetAddress(
|
|
447
|
-
|
|
533
|
+
async solGetAddress(connectId, _deviceId, params) {
|
|
534
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
535
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "sol")) {
|
|
536
|
+
return failure(HardwareErrorCode2.DeviceMismatch, "Wrong device connected");
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
const result = await this.connectorCall(connectId, "solGetAddress", {
|
|
540
|
+
path: params.path,
|
|
541
|
+
showOnDevice: params.showOnDevice
|
|
542
|
+
});
|
|
543
|
+
return success({
|
|
544
|
+
address: result.address,
|
|
545
|
+
path: params.path
|
|
546
|
+
});
|
|
547
|
+
} catch (err) {
|
|
548
|
+
return this.errorToFailure(err);
|
|
549
|
+
}
|
|
448
550
|
}
|
|
449
|
-
async solGetAddresses(
|
|
450
|
-
return
|
|
551
|
+
async solGetAddresses(connectId, deviceId, params, onProgress) {
|
|
552
|
+
return this.batchCall(
|
|
553
|
+
params,
|
|
554
|
+
(p) => this.solGetAddress(connectId, deviceId, p),
|
|
555
|
+
onProgress
|
|
556
|
+
);
|
|
451
557
|
}
|
|
452
|
-
async solGetPublicKey(
|
|
453
|
-
|
|
558
|
+
async solGetPublicKey(connectId, _deviceId, params) {
|
|
559
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
560
|
+
if (!await this._verifyDeviceFingerprint(connectId, _deviceId, "sol")) {
|
|
561
|
+
return failure(HardwareErrorCode2.DeviceMismatch, "Wrong device connected");
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
const result = await this.connectorCall(connectId, "solGetAddress", {
|
|
565
|
+
path: params.path,
|
|
566
|
+
showOnDevice: params.showOnDevice
|
|
567
|
+
});
|
|
568
|
+
return success({
|
|
569
|
+
publicKey: result.address,
|
|
570
|
+
path: params.path
|
|
571
|
+
});
|
|
572
|
+
} catch (err) {
|
|
573
|
+
return this.errorToFailure(err);
|
|
574
|
+
}
|
|
454
575
|
}
|
|
455
576
|
async solSignTransaction(_connectId, _deviceId, _params) {
|
|
456
|
-
return failure(
|
|
577
|
+
return failure(
|
|
578
|
+
HardwareErrorCode2.MethodNotSupported,
|
|
579
|
+
"Solana transaction signing via Ledger is not yet implemented."
|
|
580
|
+
);
|
|
457
581
|
}
|
|
458
582
|
async solSignMessage(_connectId, _deviceId, _params) {
|
|
459
|
-
return failure(
|
|
583
|
+
return failure(
|
|
584
|
+
HardwareErrorCode2.MethodNotSupported,
|
|
585
|
+
"Solana message signing via Ledger is not yet implemented."
|
|
586
|
+
);
|
|
460
587
|
}
|
|
461
588
|
/**
|
|
462
589
|
* Wait for user to connect and unlock device.
|
|
@@ -481,7 +608,10 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
481
608
|
clearTimeout(timer);
|
|
482
609
|
this._deviceConnectResolve = null;
|
|
483
610
|
if (cancelled) {
|
|
484
|
-
reject(
|
|
611
|
+
reject(Object.assign(
|
|
612
|
+
new Error("User cancelled Ledger connection"),
|
|
613
|
+
{ _tag: "DeviceNotRecognizedError" }
|
|
614
|
+
));
|
|
485
615
|
} else {
|
|
486
616
|
resolve();
|
|
487
617
|
}
|
|
@@ -512,6 +642,17 @@ var _LedgerAdapter = class _LedgerAdapter {
|
|
|
512
642
|
if (this._sessions.size > 0) {
|
|
513
643
|
return this._sessions.keys().next().value;
|
|
514
644
|
}
|
|
645
|
+
if (this._connectingPromise) {
|
|
646
|
+
return this._connectingPromise;
|
|
647
|
+
}
|
|
648
|
+
this._connectingPromise = this._doConnect();
|
|
649
|
+
try {
|
|
650
|
+
return await this._connectingPromise;
|
|
651
|
+
} finally {
|
|
652
|
+
this._connectingPromise = null;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
async _doConnect() {
|
|
515
656
|
for (let attempt = 0; attempt < _LedgerAdapter.MAX_DEVICE_RETRY; attempt++) {
|
|
516
657
|
const devices = await this.searchDevices();
|
|
517
658
|
if (devices.length > 0) {
|
|
@@ -745,7 +886,8 @@ var LedgerDeviceManager = class {
|
|
|
745
886
|
return new Promise((resolve) => {
|
|
746
887
|
let resolved = false;
|
|
747
888
|
let syncResult = null;
|
|
748
|
-
|
|
889
|
+
let sub = null;
|
|
890
|
+
sub = this._dmk.listenToAvailableDevices().subscribe({
|
|
749
891
|
next: (devices) => {
|
|
750
892
|
if (resolved) return;
|
|
751
893
|
resolved = true;
|
|
@@ -753,7 +895,17 @@ var LedgerDeviceManager = class {
|
|
|
753
895
|
for (const d of devices) {
|
|
754
896
|
this._discovered.set(d.id, d);
|
|
755
897
|
}
|
|
756
|
-
|
|
898
|
+
if (sub) {
|
|
899
|
+
sub.unsubscribe();
|
|
900
|
+
resolve(devices.map((d) => ({
|
|
901
|
+
path: d.id,
|
|
902
|
+
type: d.deviceModel.id,
|
|
903
|
+
name: d.name,
|
|
904
|
+
transport: d.transport
|
|
905
|
+
})));
|
|
906
|
+
} else {
|
|
907
|
+
syncResult = devices;
|
|
908
|
+
}
|
|
757
909
|
},
|
|
758
910
|
error: () => {
|
|
759
911
|
if (!resolved) {
|
|
@@ -765,7 +917,12 @@ var LedgerDeviceManager = class {
|
|
|
765
917
|
if (syncResult !== null) {
|
|
766
918
|
sub.unsubscribe();
|
|
767
919
|
const devices = syncResult;
|
|
768
|
-
resolve(devices.map((d) => ({
|
|
920
|
+
resolve(devices.map((d) => ({
|
|
921
|
+
path: d.id,
|
|
922
|
+
type: d.deviceModel.id,
|
|
923
|
+
name: d.name,
|
|
924
|
+
transport: d.transport
|
|
925
|
+
})));
|
|
769
926
|
}
|
|
770
927
|
});
|
|
771
928
|
}
|
|
@@ -786,7 +943,12 @@ var LedgerDeviceManager = class {
|
|
|
786
943
|
name: d.name
|
|
787
944
|
}));
|
|
788
945
|
if (!previousIds.has(d.id)) {
|
|
789
|
-
onChange({ type: "device-connected", descriptor: {
|
|
946
|
+
onChange({ type: "device-connected", descriptor: {
|
|
947
|
+
path: d.id,
|
|
948
|
+
type: d.deviceModel.id,
|
|
949
|
+
name: d.name,
|
|
950
|
+
transport: d.transport
|
|
951
|
+
} });
|
|
790
952
|
}
|
|
791
953
|
}
|
|
792
954
|
for (const id of previousIds) {
|
|
@@ -996,6 +1158,36 @@ var SignerBtc = class {
|
|
|
996
1158
|
}
|
|
997
1159
|
};
|
|
998
1160
|
|
|
1161
|
+
// src/signer/SignerSol.ts
|
|
1162
|
+
var SignerSol = class {
|
|
1163
|
+
constructor(_sdk) {
|
|
1164
|
+
this._sdk = _sdk;
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Get the Solana address (base58-encoded Ed25519 public key) at the given derivation path.
|
|
1168
|
+
*/
|
|
1169
|
+
async getAddress(derivationPath, options) {
|
|
1170
|
+
const action = this._sdk.getAddress(derivationPath, {
|
|
1171
|
+
checkOnDevice: options?.checkOnDevice ?? false
|
|
1172
|
+
});
|
|
1173
|
+
return deviceActionToPromise(action, this.onInteraction);
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Sign a Solana transaction.
|
|
1177
|
+
*/
|
|
1178
|
+
async signTransaction(derivationPath, transaction, options) {
|
|
1179
|
+
const action = this._sdk.signTransaction(derivationPath, transaction, options);
|
|
1180
|
+
return deviceActionToPromise(action, this.onInteraction);
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Sign a message with the Solana app.
|
|
1184
|
+
*/
|
|
1185
|
+
async signMessage(derivationPath, message, options) {
|
|
1186
|
+
const action = this._sdk.signMessage(derivationPath, message, options);
|
|
1187
|
+
return deviceActionToPromise(action, this.onInteraction);
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
|
|
999
1191
|
// src/app/AppManager.ts
|
|
1000
1192
|
var APP_NAME_MAP = {
|
|
1001
1193
|
ETH: "Ethereum",
|
|
@@ -1095,6 +1287,7 @@ export {
|
|
|
1095
1287
|
SignerBtc,
|
|
1096
1288
|
SignerEth,
|
|
1097
1289
|
SignerManager,
|
|
1290
|
+
SignerSol,
|
|
1098
1291
|
clearRegistry,
|
|
1099
1292
|
deviceActionToPromise,
|
|
1100
1293
|
getTransportProvider,
|