@bytezhang/ledger-adapter 0.0.3 → 0.0.4
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/entries/node.d.mts +5 -0
- package/dist/entries/node.d.ts +5 -0
- package/dist/entries/react-native.d.mts +5 -0
- package/dist/entries/react-native.d.ts +5 -0
- package/dist/entries/web.d.mts +5 -0
- package/dist/entries/web.d.ts +5 -0
- package/dist/index.d.mts +389 -0
- package/dist/index.d.ts +389 -0
- package/dist/index.js +95 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +95 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import { IHardwareWallet, IConnector, TransportType, IUiHandler, DeviceInfo, Response, ChainCapability, HardwareEventMap, DeviceEventListener, EvmGetAddressParams, EvmAddress, ProgressCallback, EvmGetPublicKeyParams, EvmPublicKey, EvmSignTxParams, EvmSignedTx, EvmSignMsgParams, EvmSignature, EvmSignTypedDataParams, BtcGetAddressParams, BtcAddress, BtcGetPublicKeyParams, BtcPublicKey, BtcSignTxParams, BtcSignedTx, BtcSignMsgParams, BtcSignature, SolGetAddressParams, SolAddress, SolGetPublicKeyParams, SolPublicKey, SolSignTxParams, SolSignedTx, SolSignMsgParams, SolSignature, DeviceDescriptor, DeviceChangeEvent, HardwareErrorCode } from '@bytezhang/hardware-wallet-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ledger hardware wallet adapter that delegates to an IConnector.
|
|
5
|
+
*
|
|
6
|
+
* This is a thin translation layer that:
|
|
7
|
+
* - Accepts a pre-configured IConnector (transport decisions are made at connector creation time)
|
|
8
|
+
* - Translates IHardwareWallet method calls to connector.call() invocations
|
|
9
|
+
* - Maps connector results/errors to our Response<T> format with enriched error messages
|
|
10
|
+
* - Translates connector events to HardwareEventMap events
|
|
11
|
+
* - Integrates with IUiHandler for permission flows
|
|
12
|
+
*/
|
|
13
|
+
declare class LedgerAdapter implements IHardwareWallet {
|
|
14
|
+
readonly vendor: "ledger";
|
|
15
|
+
private readonly connector;
|
|
16
|
+
private readonly emitter;
|
|
17
|
+
private _uiHandler;
|
|
18
|
+
private _discoveredDevices;
|
|
19
|
+
private _sessions;
|
|
20
|
+
constructor(connector: IConnector);
|
|
21
|
+
get activeTransport(): TransportType | null;
|
|
22
|
+
getAvailableTransports(): TransportType[];
|
|
23
|
+
switchTransport(_type: TransportType): Promise<void>;
|
|
24
|
+
setUiHandler(handler: Partial<IUiHandler>): void;
|
|
25
|
+
init(_config?: unknown): Promise<void>;
|
|
26
|
+
dispose(): Promise<void>;
|
|
27
|
+
searchDevices(): Promise<DeviceInfo[]>;
|
|
28
|
+
connectDevice(connectId: string): Promise<Response<string>>;
|
|
29
|
+
disconnectDevice(connectId: string): Promise<void>;
|
|
30
|
+
getDeviceInfo(connectId: string, deviceId: string): Promise<Response<DeviceInfo>>;
|
|
31
|
+
getSupportedChains(): ChainCapability[];
|
|
32
|
+
on<K extends keyof HardwareEventMap>(event: K, listener: (event: HardwareEventMap[K]) => void): void;
|
|
33
|
+
on(event: string, listener: DeviceEventListener): void;
|
|
34
|
+
off<K extends keyof HardwareEventMap>(event: K, listener: (event: HardwareEventMap[K]) => void): void;
|
|
35
|
+
off(event: string, listener: DeviceEventListener): void;
|
|
36
|
+
cancel(connectId: string): void;
|
|
37
|
+
evmGetAddress(connectId: string, _deviceId: string, params: EvmGetAddressParams): Promise<Response<EvmAddress>>;
|
|
38
|
+
evmGetAddresses(connectId: string, deviceId: string, params: EvmGetAddressParams[], onProgress?: ProgressCallback): Promise<Response<EvmAddress[]>>;
|
|
39
|
+
evmGetPublicKey(connectId: string, _deviceId: string, params: EvmGetPublicKeyParams): Promise<Response<EvmPublicKey>>;
|
|
40
|
+
evmSignTransaction(connectId: string, _deviceId: string, params: EvmSignTxParams): Promise<Response<EvmSignedTx>>;
|
|
41
|
+
evmSignMessage(connectId: string, _deviceId: string, params: EvmSignMsgParams): Promise<Response<EvmSignature>>;
|
|
42
|
+
evmSignTypedData(connectId: string, _deviceId: string, params: EvmSignTypedDataParams): Promise<Response<EvmSignature>>;
|
|
43
|
+
btcGetAddress(connectId: string, _deviceId: string, params: BtcGetAddressParams): Promise<Response<BtcAddress>>;
|
|
44
|
+
btcGetAddresses(connectId: string, deviceId: string, params: BtcGetAddressParams[], onProgress?: ProgressCallback): Promise<Response<BtcAddress[]>>;
|
|
45
|
+
btcGetPublicKey(connectId: string, _deviceId: string, params: BtcGetPublicKeyParams): Promise<Response<BtcPublicKey>>;
|
|
46
|
+
btcSignTransaction(connectId: string, _deviceId: string, params: BtcSignTxParams): Promise<Response<BtcSignedTx>>;
|
|
47
|
+
btcSignMessage(_connectId: string, _deviceId: string, _params: BtcSignMsgParams): Promise<Response<BtcSignature>>;
|
|
48
|
+
btcGetMasterFingerprint(connectId: string, _deviceId: string, params?: {
|
|
49
|
+
skipOpenApp?: boolean;
|
|
50
|
+
}): Promise<Response<{
|
|
51
|
+
masterFingerprint: string;
|
|
52
|
+
}>>;
|
|
53
|
+
solGetAddress(_connectId: string, _deviceId: string, _params: SolGetAddressParams): Promise<Response<SolAddress>>;
|
|
54
|
+
solGetAddresses(_connectId: string, _deviceId: string, _params: SolGetAddressParams[], _onProgress?: ProgressCallback): Promise<Response<SolAddress[]>>;
|
|
55
|
+
solGetPublicKey(_connectId: string, _deviceId: string, _params: SolGetPublicKeyParams): Promise<Response<SolPublicKey>>;
|
|
56
|
+
solSignTransaction(_connectId: string, _deviceId: string, _params: SolSignTxParams): Promise<Response<SolSignedTx>>;
|
|
57
|
+
solSignMessage(_connectId: string, _deviceId: string, _params: SolSignMsgParams): Promise<Response<SolSignature>>;
|
|
58
|
+
/**
|
|
59
|
+
* Ensure at least one device is connected and return a valid connectId.
|
|
60
|
+
*
|
|
61
|
+
* - If a session already exists for the given connectId, reuse it.
|
|
62
|
+
* - If ANY session exists (Ledger IDs are ephemeral), reuse it.
|
|
63
|
+
* - Otherwise: search → 1 device: auto-connect, multiple: ask user, 0: throw.
|
|
64
|
+
*/
|
|
65
|
+
private ensureConnected;
|
|
66
|
+
/**
|
|
67
|
+
* Call the connector with automatic session resolution and disconnect retry.
|
|
68
|
+
*
|
|
69
|
+
* 1. Resolves a valid connectId via ensureConnected()
|
|
70
|
+
* 2. Looks up sessionId from _sessions
|
|
71
|
+
* 3. Calls connector.call()
|
|
72
|
+
* 4. On disconnect error: clears stale session, re-connects, retries once
|
|
73
|
+
*/
|
|
74
|
+
private connectorCall;
|
|
75
|
+
/**
|
|
76
|
+
* Ensure device permission before proceeding.
|
|
77
|
+
* - No connectId (searchDevices): check environment-level permission
|
|
78
|
+
* - With connectId (business methods): check device-level permission
|
|
79
|
+
* If not granted, calls onDevicePermission so the consumer can request access.
|
|
80
|
+
*/
|
|
81
|
+
private _ensureDevicePermission;
|
|
82
|
+
/**
|
|
83
|
+
* Convert a thrown error to a Response failure.
|
|
84
|
+
* Uses mapLedgerError to parse Ledger DMK error codes into HardwareErrorCode values.
|
|
85
|
+
*/
|
|
86
|
+
private errorToFailure;
|
|
87
|
+
/**
|
|
88
|
+
* Generic batch call with progress reporting.
|
|
89
|
+
* If any single call fails, returns the failure immediately.
|
|
90
|
+
*/
|
|
91
|
+
private batchCall;
|
|
92
|
+
private deviceConnectHandler;
|
|
93
|
+
private deviceDisconnectHandler;
|
|
94
|
+
private uiRequestHandler;
|
|
95
|
+
private uiEventHandler;
|
|
96
|
+
private registerEventListeners;
|
|
97
|
+
private unregisterEventListeners;
|
|
98
|
+
private handleUiEvent;
|
|
99
|
+
private connectorDeviceToDeviceInfo;
|
|
100
|
+
private extractDeviceInfoFromPayload;
|
|
101
|
+
private unknownDevice;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface DmkDiscoveredDevice {
|
|
105
|
+
id: string;
|
|
106
|
+
deviceModel: {
|
|
107
|
+
id: string;
|
|
108
|
+
productName: string;
|
|
109
|
+
model: string;
|
|
110
|
+
name: string;
|
|
111
|
+
};
|
|
112
|
+
transport: string;
|
|
113
|
+
[key: string]: unknown;
|
|
114
|
+
}
|
|
115
|
+
interface IDmk {
|
|
116
|
+
startDiscovering(args?: {
|
|
117
|
+
transport?: string;
|
|
118
|
+
}): {
|
|
119
|
+
subscribe(observer: {
|
|
120
|
+
next: (device: DmkDiscoveredDevice) => void;
|
|
121
|
+
error?: (err: unknown) => void;
|
|
122
|
+
}): {
|
|
123
|
+
unsubscribe: () => void;
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
stopDiscovering(): void;
|
|
127
|
+
listenToAvailableDevices(args?: {
|
|
128
|
+
transport?: string;
|
|
129
|
+
}): {
|
|
130
|
+
subscribe(observer: {
|
|
131
|
+
next: (devices: DmkDiscoveredDevice[]) => void;
|
|
132
|
+
error?: (err: unknown) => void;
|
|
133
|
+
}): {
|
|
134
|
+
unsubscribe: () => void;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
connect(params: {
|
|
138
|
+
device: DmkDiscoveredDevice;
|
|
139
|
+
}): Promise<string>;
|
|
140
|
+
disconnect(params: {
|
|
141
|
+
sessionId: string;
|
|
142
|
+
}): Promise<void>;
|
|
143
|
+
sendCommand(params: {
|
|
144
|
+
sessionId: string;
|
|
145
|
+
command: unknown;
|
|
146
|
+
}): Promise<unknown>;
|
|
147
|
+
close?(): void;
|
|
148
|
+
}
|
|
149
|
+
interface DeviceActionState$1<T> {
|
|
150
|
+
status: 'pending' | 'completed' | 'error';
|
|
151
|
+
output?: T;
|
|
152
|
+
error?: unknown;
|
|
153
|
+
intermediateValue?: {
|
|
154
|
+
requiredUserInteraction?: string;
|
|
155
|
+
[key: string]: unknown;
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
interface SignerEvmAddress {
|
|
159
|
+
address: string;
|
|
160
|
+
publicKey: string;
|
|
161
|
+
}
|
|
162
|
+
interface SignerEvmSignature {
|
|
163
|
+
r: string;
|
|
164
|
+
s: string;
|
|
165
|
+
v: number;
|
|
166
|
+
}
|
|
167
|
+
interface SignerBtcAddress {
|
|
168
|
+
address: string;
|
|
169
|
+
}
|
|
170
|
+
interface TransportProviderOptions {
|
|
171
|
+
logger?: unknown;
|
|
172
|
+
}
|
|
173
|
+
interface TransportProviderInstance {
|
|
174
|
+
dmk: IDmk;
|
|
175
|
+
dispose?: () => Promise<void>;
|
|
176
|
+
}
|
|
177
|
+
interface TransportProvider {
|
|
178
|
+
create(options?: TransportProviderOptions): TransportProviderInstance;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Manages device discovery, connection, and session tracking.
|
|
183
|
+
* Wraps DMK's Observable APIs into simpler imperative calls.
|
|
184
|
+
*/
|
|
185
|
+
declare class LedgerDeviceManager {
|
|
186
|
+
private readonly _dmk;
|
|
187
|
+
private readonly _discovered;
|
|
188
|
+
private readonly _sessions;
|
|
189
|
+
private readonly _sessionToDevice;
|
|
190
|
+
private _listenSub;
|
|
191
|
+
constructor(dmk: IDmk);
|
|
192
|
+
/**
|
|
193
|
+
* One-shot enumeration: subscribe to listenToAvailableDevices,
|
|
194
|
+
* take the first emission, unsubscribe, return DeviceDescriptors.
|
|
195
|
+
*/
|
|
196
|
+
enumerate(): Promise<DeviceDescriptor[]>;
|
|
197
|
+
/**
|
|
198
|
+
* Continuous listening: tracks device connect/disconnect via diffing.
|
|
199
|
+
*/
|
|
200
|
+
listen(onChange: (event: DeviceChangeEvent) => void): void;
|
|
201
|
+
stopListening(): void;
|
|
202
|
+
/**
|
|
203
|
+
* Trigger browser device selection (WebHID requestDevice).
|
|
204
|
+
* Starts discovery for a short period, then stops.
|
|
205
|
+
*/
|
|
206
|
+
requestDevice(timeoutMs?: number): Promise<void>;
|
|
207
|
+
/** Connect to a previously discovered device. Returns sessionId. */
|
|
208
|
+
connect(deviceId: string): Promise<string>;
|
|
209
|
+
/** Disconnect a session. */
|
|
210
|
+
disconnect(sessionId: string): Promise<void>;
|
|
211
|
+
getSessionId(deviceId: string): string | undefined;
|
|
212
|
+
getDeviceId(sessionId: string): string | undefined;
|
|
213
|
+
/** Get the underlying DMK instance (needed by SignerManager). */
|
|
214
|
+
getDmk(): IDmk;
|
|
215
|
+
dispose(): void;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* SDK signer interface — duck-typed to avoid hard dependency on
|
|
220
|
+
* @ledgerhq/device-signer-kit-ethereum.
|
|
221
|
+
*/
|
|
222
|
+
interface ISdkSignerEth {
|
|
223
|
+
getAddress(derivationPath: string, options?: {
|
|
224
|
+
checkOnDevice?: boolean;
|
|
225
|
+
}): unknown;
|
|
226
|
+
signTransaction(derivationPath: string, transaction: Uint8Array, options?: unknown): unknown;
|
|
227
|
+
signMessage(derivationPath: string, message: string): unknown;
|
|
228
|
+
signTypedData(derivationPath: string, data: unknown): unknown;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Wraps Ledger's SDK signer (Observable-based DeviceActions) into
|
|
232
|
+
* a simple async interface returning plain serializable data.
|
|
233
|
+
*/
|
|
234
|
+
declare class SignerEth {
|
|
235
|
+
private readonly _sdk;
|
|
236
|
+
onInteraction?: (interaction: string) => void;
|
|
237
|
+
constructor(_sdk: ISdkSignerEth);
|
|
238
|
+
getAddress(derivationPath: string, options?: {
|
|
239
|
+
checkOnDevice?: boolean;
|
|
240
|
+
}): Promise<SignerEvmAddress>;
|
|
241
|
+
signTransaction(derivationPath: string, serializedTxHex: string): Promise<SignerEvmSignature>;
|
|
242
|
+
signMessage(derivationPath: string, message: string): Promise<SignerEvmSignature>;
|
|
243
|
+
signTypedData(derivationPath: string, data: unknown): Promise<SignerEvmSignature>;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
type SignerEthBuilderFn = (args: {
|
|
247
|
+
dmk: IDmk;
|
|
248
|
+
sessionId: string;
|
|
249
|
+
}) => {
|
|
250
|
+
build(): unknown;
|
|
251
|
+
} | Promise<{
|
|
252
|
+
build(): unknown;
|
|
253
|
+
}>;
|
|
254
|
+
/**
|
|
255
|
+
* Manages per-sessionId SignerEth instances.
|
|
256
|
+
* Creates on demand, caches for reuse, invalidates on session change.
|
|
257
|
+
*/
|
|
258
|
+
declare class SignerManager {
|
|
259
|
+
private readonly _cache;
|
|
260
|
+
private readonly _dmk;
|
|
261
|
+
private readonly _builderFn;
|
|
262
|
+
constructor(dmk: IDmk, builderFn?: SignerEthBuilderFn);
|
|
263
|
+
getOrCreate(sessionId: string): Promise<SignerEth>;
|
|
264
|
+
invalidate(sessionId: string): void;
|
|
265
|
+
clearAll(): void;
|
|
266
|
+
private static _defaultBuilder;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* SDK BTC signer interface — duck-typed to avoid hard dependency on
|
|
271
|
+
* @ledgerhq/device-signer-kit-bitcoin.
|
|
272
|
+
*/
|
|
273
|
+
interface ISdkSignerBtc {
|
|
274
|
+
getExtendedPublicKey(derivationPath: string, options?: {
|
|
275
|
+
checkOnDevice?: boolean;
|
|
276
|
+
}): unknown;
|
|
277
|
+
getWalletAddress(wallet: unknown, addressIndex: number, options?: {
|
|
278
|
+
checkOnDevice?: boolean;
|
|
279
|
+
change?: boolean;
|
|
280
|
+
}): unknown;
|
|
281
|
+
getMasterFingerprint(options?: {
|
|
282
|
+
skipOpenApp?: boolean;
|
|
283
|
+
}): unknown;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Wraps Ledger's BTC SDK signer (Observable-based DeviceActions) into
|
|
287
|
+
* a simple async interface returning plain serializable data.
|
|
288
|
+
*/
|
|
289
|
+
declare class SignerBtc {
|
|
290
|
+
private readonly _sdk;
|
|
291
|
+
onInteraction?: (interaction: string) => void;
|
|
292
|
+
constructor(_sdk: ISdkSignerBtc);
|
|
293
|
+
getWalletAddress(wallet: unknown, addressIndex: number, options?: {
|
|
294
|
+
checkOnDevice?: boolean;
|
|
295
|
+
change?: boolean;
|
|
296
|
+
}): Promise<SignerBtcAddress>;
|
|
297
|
+
getExtendedPublicKey(derivationPath: string, options?: {
|
|
298
|
+
checkOnDevice?: boolean;
|
|
299
|
+
}): Promise<string>;
|
|
300
|
+
getMasterFingerprint(options?: {
|
|
301
|
+
skipOpenApp?: boolean;
|
|
302
|
+
}): Promise<Uint8Array>;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** DeviceAction state emitted by DMK signer operations. */
|
|
306
|
+
interface DeviceActionState<T> {
|
|
307
|
+
status: 'pending' | 'completed' | 'error';
|
|
308
|
+
output?: T;
|
|
309
|
+
error?: unknown;
|
|
310
|
+
intermediateValue?: {
|
|
311
|
+
requiredUserInteraction?: string;
|
|
312
|
+
[key: string]: unknown;
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Convert a DMK DeviceAction (Observable-based) into a Promise.
|
|
317
|
+
* Handles pending → completed/error state transitions and interaction callbacks.
|
|
318
|
+
*/
|
|
319
|
+
declare function deviceActionToPromise<T>(action: {
|
|
320
|
+
observable: {
|
|
321
|
+
subscribe(observer: {
|
|
322
|
+
next: (value: DeviceActionState<T>) => void;
|
|
323
|
+
error?: (err: unknown) => void;
|
|
324
|
+
complete?: () => void;
|
|
325
|
+
}): {
|
|
326
|
+
unsubscribe: () => void;
|
|
327
|
+
};
|
|
328
|
+
};
|
|
329
|
+
}, onInteraction?: (interaction: string) => void): Promise<T>;
|
|
330
|
+
|
|
331
|
+
declare function registerTransport(type: string, provider: TransportProvider): void;
|
|
332
|
+
declare function unregisterTransport(type: string): void;
|
|
333
|
+
declare function getTransportProvider(type: string): TransportProvider | null;
|
|
334
|
+
declare function listRegisteredTransports(): string[];
|
|
335
|
+
declare function clearRegistry(): void;
|
|
336
|
+
|
|
337
|
+
interface AppManagerOptions {
|
|
338
|
+
waitMs?: number;
|
|
339
|
+
maxRetries?: number;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Orchestrates opening / closing Ledger on-device apps so that the
|
|
343
|
+
* correct signer application is running before any signing call.
|
|
344
|
+
*/
|
|
345
|
+
declare class AppManager {
|
|
346
|
+
private readonly _dmk;
|
|
347
|
+
private readonly _waitMs;
|
|
348
|
+
private readonly _maxRetries;
|
|
349
|
+
constructor(dmk: IDmk, options?: AppManagerOptions);
|
|
350
|
+
/**
|
|
351
|
+
* Return the Ledger app name for a given chain ticker,
|
|
352
|
+
* or undefined if the chain is not supported.
|
|
353
|
+
*/
|
|
354
|
+
static getAppName(chain: string): string | undefined;
|
|
355
|
+
/**
|
|
356
|
+
* Ensure the target app is open on the device identified by `sessionId`.
|
|
357
|
+
*
|
|
358
|
+
* Flow:
|
|
359
|
+
* 1. Check the currently running app.
|
|
360
|
+
* 2. If it is already the target, return immediately.
|
|
361
|
+
* 3. If a different app is running (not dashboard), close it first.
|
|
362
|
+
* 4. Open the target app.
|
|
363
|
+
* 5. Poll until the device confirms the target app is running.
|
|
364
|
+
*/
|
|
365
|
+
ensureAppOpen(sessionId: string, targetAppName: string): Promise<void>;
|
|
366
|
+
private _getCurrentApp;
|
|
367
|
+
private _openApp;
|
|
368
|
+
private _closeCurrentApp;
|
|
369
|
+
/**
|
|
370
|
+
* Poll the device until the expected app is reported as running,
|
|
371
|
+
* or throw after `_maxRetries` attempts.
|
|
372
|
+
*/
|
|
373
|
+
private _waitForApp;
|
|
374
|
+
private _isDashboard;
|
|
375
|
+
private _wait;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Check if an error (or any error in its chain) represents a locked Ledger device. */
|
|
379
|
+
declare function isDeviceLockedError(err: unknown): boolean;
|
|
380
|
+
/**
|
|
381
|
+
* Map a Ledger DMK error to a HardwareErrorCode and human-readable message
|
|
382
|
+
* with actionable recovery information for the caller.
|
|
383
|
+
*/
|
|
384
|
+
declare function mapLedgerError(err: unknown): {
|
|
385
|
+
code: HardwareErrorCode;
|
|
386
|
+
message: string;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
export { AppManager, type DeviceActionState$1 as DeviceActionState, type DmkDiscoveredDevice, type IDmk, LedgerAdapter, LedgerDeviceManager, SignerBtc, type SignerBtcAddress, SignerEth, type SignerEvmAddress, type SignerEvmSignature, SignerManager, type TransportProvider, type TransportProviderInstance, type TransportProviderOptions, clearRegistry, deviceActionToPromise, getTransportProvider, isDeviceLockedError, listRegisteredTransports, mapLedgerError, registerTransport, unregisterTransport };
|
package/dist/index.js
CHANGED
|
@@ -494,12 +494,88 @@ var LedgerAdapter = class {
|
|
|
494
494
|
// Private helpers
|
|
495
495
|
// ---------------------------------------------------------------------------
|
|
496
496
|
/**
|
|
497
|
-
*
|
|
498
|
-
*
|
|
497
|
+
* Ensure at least one device is connected and return a valid connectId.
|
|
498
|
+
*
|
|
499
|
+
* - If a session already exists for the given connectId, reuse it.
|
|
500
|
+
* - If ANY session exists (Ledger IDs are ephemeral), reuse it.
|
|
501
|
+
* - Otherwise: search → 1 device: auto-connect, multiple: ask user, 0: throw.
|
|
502
|
+
*/
|
|
503
|
+
async ensureConnected(connectId) {
|
|
504
|
+
if (connectId && this._sessions.has(connectId)) {
|
|
505
|
+
return connectId;
|
|
506
|
+
}
|
|
507
|
+
if (this._sessions.size > 0) {
|
|
508
|
+
return this._sessions.keys().next().value;
|
|
509
|
+
}
|
|
510
|
+
const devices = await this.searchDevices();
|
|
511
|
+
if (devices.length === 0) {
|
|
512
|
+
throw Object.assign(
|
|
513
|
+
new Error("No Ledger device found. Make sure the device is connected via USB and ready."),
|
|
514
|
+
{ _tag: "DeviceNotRecognizedError" }
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
if (devices.length === 1) {
|
|
518
|
+
const result2 = await this.connectDevice(devices[0].connectId);
|
|
519
|
+
if (!result2.success) {
|
|
520
|
+
throw Object.assign(
|
|
521
|
+
new Error(result2.payload.error),
|
|
522
|
+
{ _tag: "DeviceNotRecognizedError" }
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
return devices[0].connectId;
|
|
526
|
+
}
|
|
527
|
+
if (this._uiHandler?.onSelectDevice) {
|
|
528
|
+
const selectedConnectId = await this._uiHandler.onSelectDevice(devices);
|
|
529
|
+
const result2 = await this.connectDevice(selectedConnectId);
|
|
530
|
+
if (!result2.success) {
|
|
531
|
+
throw Object.assign(
|
|
532
|
+
new Error(result2.payload.error),
|
|
533
|
+
{ _tag: "DeviceNotRecognizedError" }
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
return selectedConnectId;
|
|
537
|
+
}
|
|
538
|
+
const result = await this.connectDevice(devices[0].connectId);
|
|
539
|
+
if (!result.success) {
|
|
540
|
+
throw Object.assign(
|
|
541
|
+
new Error(result.payload.error),
|
|
542
|
+
{ _tag: "DeviceNotRecognizedError" }
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
return devices[0].connectId;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Call the connector with automatic session resolution and disconnect retry.
|
|
549
|
+
*
|
|
550
|
+
* 1. Resolves a valid connectId via ensureConnected()
|
|
551
|
+
* 2. Looks up sessionId from _sessions
|
|
552
|
+
* 3. Calls connector.call()
|
|
553
|
+
* 4. On disconnect error: clears stale session, re-connects, retries once
|
|
499
554
|
*/
|
|
500
555
|
async connectorCall(connectId, method, params) {
|
|
501
|
-
const
|
|
502
|
-
|
|
556
|
+
const resolvedConnectId = await this.ensureConnected(connectId);
|
|
557
|
+
const sessionId = this._sessions.get(resolvedConnectId);
|
|
558
|
+
if (!sessionId) {
|
|
559
|
+
throw Object.assign(
|
|
560
|
+
new Error("Auto-connect succeeded but no session found"),
|
|
561
|
+
{ _tag: "DeviceSessionNotFound" }
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
return await this.connector.call(sessionId, method, params);
|
|
566
|
+
} catch (err) {
|
|
567
|
+
if (isDeviceDisconnectedError(err)) {
|
|
568
|
+
this._sessions.delete(resolvedConnectId);
|
|
569
|
+
this._discoveredDevices.clear();
|
|
570
|
+
const retryConnectId = await this.ensureConnected();
|
|
571
|
+
const retrySessionId = this._sessions.get(retryConnectId);
|
|
572
|
+
if (!retrySessionId) {
|
|
573
|
+
throw err;
|
|
574
|
+
}
|
|
575
|
+
return this.connector.call(retrySessionId, method, params);
|
|
576
|
+
}
|
|
577
|
+
throw err;
|
|
578
|
+
}
|
|
503
579
|
}
|
|
504
580
|
/**
|
|
505
581
|
* Ensure device permission before proceeding.
|
|
@@ -597,7 +673,8 @@ var LedgerAdapter = class {
|
|
|
597
673
|
deviceId: device.deviceId,
|
|
598
674
|
connectId: device.connectId,
|
|
599
675
|
label: device.name,
|
|
600
|
-
connectionType: "usb"
|
|
676
|
+
connectionType: "usb",
|
|
677
|
+
capabilities: device.capabilities
|
|
601
678
|
};
|
|
602
679
|
}
|
|
603
680
|
extractDeviceInfoFromPayload(payload) {
|
|
@@ -640,13 +717,21 @@ var LedgerDeviceManager = class {
|
|
|
640
717
|
*/
|
|
641
718
|
enumerate() {
|
|
642
719
|
return new Promise((resolve) => {
|
|
643
|
-
|
|
720
|
+
let resolved = false;
|
|
721
|
+
let sub = null;
|
|
722
|
+
sub = this._dmk.listenToAvailableDevices().subscribe({
|
|
644
723
|
next: (devices) => {
|
|
724
|
+
if (resolved) return;
|
|
725
|
+
resolved = true;
|
|
645
726
|
this._discovered.clear();
|
|
646
727
|
for (const d of devices) {
|
|
647
728
|
this._discovered.set(d.id, d);
|
|
648
729
|
}
|
|
649
|
-
sub
|
|
730
|
+
if (sub) {
|
|
731
|
+
sub.unsubscribe();
|
|
732
|
+
} else {
|
|
733
|
+
Promise.resolve().then(() => sub?.unsubscribe());
|
|
734
|
+
}
|
|
650
735
|
console.log("[LedgerDeviceManager] enumerate devices:", JSON.stringify(devices.map((d) => ({
|
|
651
736
|
id: d.id,
|
|
652
737
|
deviceModel: d.deviceModel,
|
|
@@ -655,7 +740,9 @@ var LedgerDeviceManager = class {
|
|
|
655
740
|
resolve(devices.map((d) => ({ path: d.id, type: d.deviceModel.name })));
|
|
656
741
|
},
|
|
657
742
|
error: () => {
|
|
658
|
-
|
|
743
|
+
if (resolved) return;
|
|
744
|
+
resolved = true;
|
|
745
|
+
sub?.unsubscribe();
|
|
659
746
|
resolve([]);
|
|
660
747
|
}
|
|
661
748
|
});
|