@bytezhang/ledger-adapter 0.0.1
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/chunk-NFZDZ4OG.mjs +39 -0
- package/dist/chunk-NFZDZ4OG.mjs.map +1 -0
- package/dist/entries/node.d.mts +5 -0
- package/dist/entries/node.d.ts +5 -0
- package/dist/entries/node.js +59 -0
- package/dist/entries/node.js.map +1 -0
- package/dist/entries/node.mjs +26 -0
- package/dist/entries/node.mjs.map +1 -0
- package/dist/entries/react-native.d.mts +5 -0
- package/dist/entries/react-native.d.ts +5 -0
- package/dist/entries/react-native.js +59 -0
- package/dist/entries/react-native.js.map +1 -0
- package/dist/entries/react-native.mjs +26 -0
- package/dist/entries/react-native.mjs.map +1 -0
- package/dist/entries/web.d.mts +5 -0
- package/dist/entries/web.d.ts +5 -0
- package/dist/entries/web.js +59 -0
- package/dist/entries/web.js.map +1 -0
- package/dist/entries/web.mjs +26 -0
- package/dist/entries/web.mjs.map +1 -0
- package/dist/index.d.mts +375 -0
- package/dist/index.d.ts +375 -0
- package/dist/index.js +1010 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +952 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +93 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1010 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
AppManager: () => AppManager,
|
|
34
|
+
LedgerAdapter: () => LedgerAdapter,
|
|
35
|
+
LedgerDeviceManager: () => LedgerDeviceManager,
|
|
36
|
+
SignerBtc: () => SignerBtc,
|
|
37
|
+
SignerEth: () => SignerEth,
|
|
38
|
+
SignerManager: () => SignerManager,
|
|
39
|
+
clearRegistry: () => clearRegistry,
|
|
40
|
+
deviceActionToPromise: () => deviceActionToPromise,
|
|
41
|
+
getTransportProvider: () => getTransportProvider,
|
|
42
|
+
isDeviceLockedError: () => isDeviceLockedError,
|
|
43
|
+
listRegisteredTransports: () => listRegisteredTransports,
|
|
44
|
+
mapLedgerError: () => mapLedgerError,
|
|
45
|
+
registerTransport: () => registerTransport,
|
|
46
|
+
unregisterTransport: () => unregisterTransport
|
|
47
|
+
});
|
|
48
|
+
module.exports = __toCommonJS(index_exports);
|
|
49
|
+
|
|
50
|
+
// src/adapter/LedgerAdapter.ts
|
|
51
|
+
var import_hardware_wallet_core2 = require("@bytezhang/hardware-wallet-core");
|
|
52
|
+
|
|
53
|
+
// src/errors.ts
|
|
54
|
+
var import_hardware_wallet_core = require("@bytezhang/hardware-wallet-core");
|
|
55
|
+
var LOCKED_ERROR_CODES = /* @__PURE__ */ new Set(["5515", "21781", "6982", "27010", "5303", "21251"]);
|
|
56
|
+
var USER_REJECTED_CODES = /* @__PURE__ */ new Set(["6985", "27013"]);
|
|
57
|
+
var WRONG_APP_CODES = /* @__PURE__ */ new Set(["6e00", "28160", "6d00", "27904"]);
|
|
58
|
+
function isDeviceLockedError(err) {
|
|
59
|
+
if (!err || typeof err !== "object") return false;
|
|
60
|
+
const e = err;
|
|
61
|
+
if (e.errorCode != null && LOCKED_ERROR_CODES.has(String(e.errorCode))) return true;
|
|
62
|
+
if (e.statusCode != null && LOCKED_ERROR_CODES.has(String(e.statusCode))) return true;
|
|
63
|
+
if (e._tag === "DeviceLockedError") return true;
|
|
64
|
+
if (typeof e.message === "string" && /locked/i.test(e.message)) return true;
|
|
65
|
+
if (e.originalError != null && isDeviceLockedError(e.originalError)) return true;
|
|
66
|
+
if (e.error != null && e._tag && isDeviceLockedError(e.error)) return true;
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
function hasStatusCode(err, codeSet) {
|
|
70
|
+
if (!err || typeof err !== "object") return false;
|
|
71
|
+
const e = err;
|
|
72
|
+
if (e.errorCode != null && codeSet.has(String(e.errorCode))) return true;
|
|
73
|
+
if (e.statusCode != null && codeSet.has(String(e.statusCode))) return true;
|
|
74
|
+
if (e.originalError != null && hasStatusCode(e.originalError, codeSet)) return true;
|
|
75
|
+
if (e.error != null && e._tag && hasStatusCode(e.error, codeSet)) return true;
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
function isUserRejectedError(err) {
|
|
79
|
+
if (!err || typeof err !== "object") return false;
|
|
80
|
+
const e = err;
|
|
81
|
+
if (e._tag === "UserRefusedOnDevice") return true;
|
|
82
|
+
if (typeof e.message === "string" && /denied|rejected|refused/i.test(e.message)) return true;
|
|
83
|
+
if (hasStatusCode(err, USER_REJECTED_CODES)) return true;
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
function isWrongAppError(err) {
|
|
87
|
+
if (!err || typeof err !== "object") return false;
|
|
88
|
+
const e = err;
|
|
89
|
+
if (e._tag === "WrongAppOpenedError" || e._tag === "InvalidStatusWordError") {
|
|
90
|
+
if (hasStatusCode(err, WRONG_APP_CODES)) return true;
|
|
91
|
+
}
|
|
92
|
+
if (typeof e.message === "string" && /wrong app|open the .* app|CLA not supported/i.test(e.message)) return true;
|
|
93
|
+
if (hasStatusCode(err, WRONG_APP_CODES)) return true;
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
function isDeviceDisconnectedError(err) {
|
|
97
|
+
if (!err || typeof err !== "object") return false;
|
|
98
|
+
const e = err;
|
|
99
|
+
if (e._tag === "DeviceNotRecognizedError" || e._tag === "DeviceSessionNotFound") return true;
|
|
100
|
+
if (typeof e.message === "string" && /disconnected|not found|no device|unplugged|session.*not.*found/i.test(e.message)) return true;
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
function isTimeoutError(err) {
|
|
104
|
+
if (!err || typeof err !== "object") return false;
|
|
105
|
+
const e = err;
|
|
106
|
+
if (typeof e.message === "string" && /timeout|timed?\s*out/i.test(e.message)) return true;
|
|
107
|
+
if (e._tag === "DeviceExchangeTimeoutError") return true;
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
function mapLedgerError(err) {
|
|
111
|
+
if (isDeviceLockedError(err)) {
|
|
112
|
+
return {
|
|
113
|
+
code: import_hardware_wallet_core.HardwareErrorCode.DeviceLocked,
|
|
114
|
+
message: "Device is locked. Please unlock your Ledger device and try again."
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (isUserRejectedError(err)) {
|
|
118
|
+
return {
|
|
119
|
+
code: import_hardware_wallet_core.HardwareErrorCode.UserRejected,
|
|
120
|
+
message: "User rejected the request on the device."
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (isWrongAppError(err)) {
|
|
124
|
+
return {
|
|
125
|
+
code: import_hardware_wallet_core.HardwareErrorCode.WrongApp,
|
|
126
|
+
message: "Wrong app is open on the Ledger device. Please open the correct app (e.g. Ethereum) and try again."
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (isDeviceDisconnectedError(err)) {
|
|
130
|
+
return {
|
|
131
|
+
code: import_hardware_wallet_core.HardwareErrorCode.DeviceDisconnected,
|
|
132
|
+
message: "Ledger device was disconnected. Please reconnect the device and try again."
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (isTimeoutError(err)) {
|
|
136
|
+
return {
|
|
137
|
+
code: import_hardware_wallet_core.HardwareErrorCode.OperationTimeout,
|
|
138
|
+
message: "Operation timed out. Please ensure the Ledger device is connected and responsive."
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
let message = "Unknown Ledger error";
|
|
142
|
+
if (err instanceof Error) {
|
|
143
|
+
message = err.message;
|
|
144
|
+
} else if (err && typeof err === "object") {
|
|
145
|
+
const e = err;
|
|
146
|
+
message = String(e.message ?? e._tag ?? e.type ?? JSON.stringify(err));
|
|
147
|
+
}
|
|
148
|
+
return { code: import_hardware_wallet_core.HardwareErrorCode.UnknownError, message };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/adapter/LedgerAdapter.ts
|
|
152
|
+
function ensure0x(hex) {
|
|
153
|
+
return hex.startsWith("0x") ? hex : `0x${hex}`;
|
|
154
|
+
}
|
|
155
|
+
function stripHex(hex) {
|
|
156
|
+
return hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
157
|
+
}
|
|
158
|
+
function padHex64(hex) {
|
|
159
|
+
return `0x${stripHex(hex).padStart(64, "0")}`;
|
|
160
|
+
}
|
|
161
|
+
var LedgerAdapter = class {
|
|
162
|
+
constructor(connector) {
|
|
163
|
+
this.vendor = "ledger";
|
|
164
|
+
this.emitter = new import_hardware_wallet_core2.TypedEventEmitter();
|
|
165
|
+
this._uiHandler = null;
|
|
166
|
+
// Device cache: tracks discovered devices from connector events
|
|
167
|
+
this._discoveredDevices = /* @__PURE__ */ new Map();
|
|
168
|
+
// Session tracking: maps connectId -> sessionId
|
|
169
|
+
this._sessions = /* @__PURE__ */ new Map();
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// Event translation
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
this.deviceConnectHandler = (data) => {
|
|
174
|
+
const deviceInfo = this.connectorDeviceToDeviceInfo(data.device);
|
|
175
|
+
this._discoveredDevices.set(deviceInfo.connectId, deviceInfo);
|
|
176
|
+
this.emitter.emit(import_hardware_wallet_core2.DEVICE.CONNECT, {
|
|
177
|
+
type: import_hardware_wallet_core2.DEVICE.CONNECT,
|
|
178
|
+
payload: deviceInfo
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
this.deviceDisconnectHandler = (data) => {
|
|
182
|
+
this._discoveredDevices.delete(data.connectId);
|
|
183
|
+
this._sessions.delete(data.connectId);
|
|
184
|
+
this.emitter.emit(import_hardware_wallet_core2.DEVICE.DISCONNECT, {
|
|
185
|
+
type: import_hardware_wallet_core2.DEVICE.DISCONNECT,
|
|
186
|
+
payload: { connectId: data.connectId }
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
this.uiRequestHandler = (data) => {
|
|
190
|
+
this.handleUiEvent(data);
|
|
191
|
+
};
|
|
192
|
+
this.uiEventHandler = (data) => {
|
|
193
|
+
this.handleUiEvent(data);
|
|
194
|
+
};
|
|
195
|
+
this.connector = connector;
|
|
196
|
+
this.registerEventListeners();
|
|
197
|
+
}
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// Transport
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Transport is decided at connector creation time. These methods
|
|
202
|
+
// satisfy the IHardwareWallet interface with sensible defaults.
|
|
203
|
+
get activeTransport() {
|
|
204
|
+
return "hid";
|
|
205
|
+
}
|
|
206
|
+
getAvailableTransports() {
|
|
207
|
+
return ["hid"];
|
|
208
|
+
}
|
|
209
|
+
async switchTransport(_type) {
|
|
210
|
+
}
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
// UI handler
|
|
213
|
+
// ---------------------------------------------------------------------------
|
|
214
|
+
setUiHandler(handler) {
|
|
215
|
+
this._uiHandler = handler;
|
|
216
|
+
}
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// Lifecycle
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
async init(_config) {
|
|
221
|
+
}
|
|
222
|
+
async dispose() {
|
|
223
|
+
this.unregisterEventListeners();
|
|
224
|
+
this.connector.reset();
|
|
225
|
+
this._uiHandler = null;
|
|
226
|
+
this._discoveredDevices.clear();
|
|
227
|
+
this._sessions.clear();
|
|
228
|
+
this.emitter.removeAllListeners();
|
|
229
|
+
}
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
// Device management
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
async searchDevices() {
|
|
234
|
+
await this._ensureDevicePermission();
|
|
235
|
+
const devices = await this.connector.searchDevices();
|
|
236
|
+
for (const d of devices) {
|
|
237
|
+
if (d.connectId && !this._discoveredDevices.has(d.connectId)) {
|
|
238
|
+
this._discoveredDevices.set(d.connectId, this.connectorDeviceToDeviceInfo(d));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (this._discoveredDevices.size === 0) {
|
|
242
|
+
await this._ensureDevicePermission();
|
|
243
|
+
}
|
|
244
|
+
return Array.from(this._discoveredDevices.values());
|
|
245
|
+
}
|
|
246
|
+
async connectDevice(connectId) {
|
|
247
|
+
await this._ensureDevicePermission(connectId);
|
|
248
|
+
try {
|
|
249
|
+
const session = await this.connector.connect(connectId);
|
|
250
|
+
this._sessions.set(connectId, session.sessionId);
|
|
251
|
+
if (session.deviceInfo) {
|
|
252
|
+
this._discoveredDevices.set(connectId, session.deviceInfo);
|
|
253
|
+
}
|
|
254
|
+
return (0, import_hardware_wallet_core2.success)(connectId);
|
|
255
|
+
} catch (err) {
|
|
256
|
+
return this.errorToFailure(err);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async disconnectDevice(connectId) {
|
|
260
|
+
const sessionId = this._sessions.get(connectId);
|
|
261
|
+
if (sessionId) {
|
|
262
|
+
await this.connector.disconnect(sessionId);
|
|
263
|
+
this._sessions.delete(connectId);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async getDeviceInfo(connectId, deviceId) {
|
|
267
|
+
await this._ensureDevicePermission(connectId, deviceId);
|
|
268
|
+
const cached = this._discoveredDevices.get(connectId) ?? Array.from(this._discoveredDevices.values()).find(
|
|
269
|
+
(d) => d.deviceId === deviceId
|
|
270
|
+
);
|
|
271
|
+
if (cached) {
|
|
272
|
+
return (0, import_hardware_wallet_core2.success)(cached);
|
|
273
|
+
}
|
|
274
|
+
return (0, import_hardware_wallet_core2.failure)(
|
|
275
|
+
import_hardware_wallet_core2.HardwareErrorCode.DeviceNotFound,
|
|
276
|
+
"Device not found in cache. Call searchDevices() or wait for a device-connected event first."
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
getSupportedChains() {
|
|
280
|
+
return ["evm", "btc", "sol"];
|
|
281
|
+
}
|
|
282
|
+
on(event, listener) {
|
|
283
|
+
this.emitter.on(event, listener);
|
|
284
|
+
}
|
|
285
|
+
off(event, listener) {
|
|
286
|
+
this.emitter.off(event, listener);
|
|
287
|
+
}
|
|
288
|
+
cancel(connectId) {
|
|
289
|
+
const sessionId = this._sessions.get(connectId) ?? connectId;
|
|
290
|
+
void this.connector.cancel(sessionId);
|
|
291
|
+
}
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
// EVM methods
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
async evmGetAddress(connectId, _deviceId, params) {
|
|
296
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
297
|
+
try {
|
|
298
|
+
const result = await this.connectorCall(connectId, "evmGetAddress", {
|
|
299
|
+
path: params.path,
|
|
300
|
+
showOnDevice: params.showOnDevice,
|
|
301
|
+
chainId: params.chainId
|
|
302
|
+
});
|
|
303
|
+
return (0, import_hardware_wallet_core2.success)({
|
|
304
|
+
address: result.address,
|
|
305
|
+
path: params.path
|
|
306
|
+
});
|
|
307
|
+
} catch (err) {
|
|
308
|
+
return this.errorToFailure(err);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
async evmGetAddresses(connectId, deviceId, params, onProgress) {
|
|
312
|
+
return this.batchCall(
|
|
313
|
+
params,
|
|
314
|
+
(p) => this.evmGetAddress(connectId, deviceId, p),
|
|
315
|
+
onProgress
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
async evmGetPublicKey(connectId, _deviceId, params) {
|
|
319
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
320
|
+
try {
|
|
321
|
+
const result = await this.connectorCall(connectId, "evmGetAddress", {
|
|
322
|
+
path: params.path,
|
|
323
|
+
showOnDevice: params.showOnDevice
|
|
324
|
+
});
|
|
325
|
+
return (0, import_hardware_wallet_core2.success)({
|
|
326
|
+
publicKey: result.publicKey,
|
|
327
|
+
path: params.path
|
|
328
|
+
});
|
|
329
|
+
} catch (err) {
|
|
330
|
+
return this.errorToFailure(err);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
async evmSignTransaction(connectId, _deviceId, params) {
|
|
334
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
335
|
+
try {
|
|
336
|
+
const result = await this.connectorCall(connectId, "evmSignTransaction", {
|
|
337
|
+
path: params.path,
|
|
338
|
+
transaction: {
|
|
339
|
+
to: params.to,
|
|
340
|
+
value: params.value,
|
|
341
|
+
chainId: params.chainId,
|
|
342
|
+
nonce: params.nonce,
|
|
343
|
+
gasLimit: params.gasLimit,
|
|
344
|
+
gasPrice: params.gasPrice,
|
|
345
|
+
maxFeePerGas: params.maxFeePerGas,
|
|
346
|
+
maxPriorityFeePerGas: params.maxPriorityFeePerGas,
|
|
347
|
+
accessList: params.accessList,
|
|
348
|
+
data: params.data
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
return (0, import_hardware_wallet_core2.success)({
|
|
352
|
+
v: ensure0x(result.v),
|
|
353
|
+
r: padHex64(result.r),
|
|
354
|
+
s: padHex64(result.s)
|
|
355
|
+
});
|
|
356
|
+
} catch (err) {
|
|
357
|
+
return this.errorToFailure(err);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
async evmSignMessage(connectId, _deviceId, params) {
|
|
361
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
362
|
+
try {
|
|
363
|
+
const result = await this.connectorCall(connectId, "evmSignMessage", {
|
|
364
|
+
path: params.path,
|
|
365
|
+
message: params.message
|
|
366
|
+
});
|
|
367
|
+
return (0, import_hardware_wallet_core2.success)({
|
|
368
|
+
signature: ensure0x(result.signature)
|
|
369
|
+
});
|
|
370
|
+
} catch (err) {
|
|
371
|
+
return this.errorToFailure(err);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async evmSignTypedData(connectId, _deviceId, params) {
|
|
375
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
376
|
+
if (params.mode === "hash") {
|
|
377
|
+
return (0, import_hardware_wallet_core2.failure)(
|
|
378
|
+
import_hardware_wallet_core2.HardwareErrorCode.MethodNotSupported,
|
|
379
|
+
'Ledger does not support hash-only EIP-712 signing. Use mode "full" with the complete typed data structure.'
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
try {
|
|
383
|
+
const result = await this.connectorCall(connectId, "evmSignTypedData", {
|
|
384
|
+
path: params.path,
|
|
385
|
+
data: params.data
|
|
386
|
+
});
|
|
387
|
+
return (0, import_hardware_wallet_core2.success)({
|
|
388
|
+
signature: ensure0x(result.signature)
|
|
389
|
+
});
|
|
390
|
+
} catch (err) {
|
|
391
|
+
return this.errorToFailure(err);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// ---------------------------------------------------------------------------
|
|
395
|
+
// BTC methods
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
async btcGetAddress(connectId, _deviceId, params) {
|
|
398
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
399
|
+
try {
|
|
400
|
+
const result = await this.connectorCall(connectId, "btcGetAddress", {
|
|
401
|
+
path: params.path,
|
|
402
|
+
coin: params.coin,
|
|
403
|
+
showOnDevice: params.showOnDevice,
|
|
404
|
+
scriptType: params.scriptType
|
|
405
|
+
});
|
|
406
|
+
return (0, import_hardware_wallet_core2.success)({
|
|
407
|
+
address: result.address,
|
|
408
|
+
path: params.path
|
|
409
|
+
});
|
|
410
|
+
} catch (err) {
|
|
411
|
+
return this.errorToFailure(err);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
async btcGetAddresses(connectId, deviceId, params, onProgress) {
|
|
415
|
+
return this.batchCall(
|
|
416
|
+
params,
|
|
417
|
+
(p) => this.btcGetAddress(connectId, deviceId, p),
|
|
418
|
+
onProgress
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
async btcGetPublicKey(connectId, _deviceId, params) {
|
|
422
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
423
|
+
try {
|
|
424
|
+
const result = await this.connectorCall(connectId, "btcGetPublicKey", {
|
|
425
|
+
path: params.path,
|
|
426
|
+
coin: params.coin,
|
|
427
|
+
showOnDevice: params.showOnDevice
|
|
428
|
+
});
|
|
429
|
+
return (0, import_hardware_wallet_core2.success)({
|
|
430
|
+
xpub: result.xpub,
|
|
431
|
+
publicKey: result.publicKey ?? "",
|
|
432
|
+
fingerprint: result.fingerprint ?? 0,
|
|
433
|
+
chainCode: result.chainCode ?? "",
|
|
434
|
+
path: params.path,
|
|
435
|
+
depth: result.depth ?? 0
|
|
436
|
+
});
|
|
437
|
+
} catch (err) {
|
|
438
|
+
return this.errorToFailure(err);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
async btcSignTransaction(connectId, _deviceId, params) {
|
|
442
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
443
|
+
if (!params.psbt) {
|
|
444
|
+
return (0, import_hardware_wallet_core2.failure)(
|
|
445
|
+
import_hardware_wallet_core2.HardwareErrorCode.InvalidParams,
|
|
446
|
+
"Ledger requires PSBT format for BTC transaction signing. Provide params.psbt."
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
return (0, import_hardware_wallet_core2.failure)(
|
|
450
|
+
import_hardware_wallet_core2.HardwareErrorCode.MethodNotSupported,
|
|
451
|
+
"BTC transaction signing via PSBT is not yet implemented for Ledger."
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
async btcSignMessage(_connectId, _deviceId, _params) {
|
|
455
|
+
return (0, import_hardware_wallet_core2.failure)(
|
|
456
|
+
import_hardware_wallet_core2.HardwareErrorCode.MethodNotSupported,
|
|
457
|
+
"BTC message signing is not yet supported on Ledger."
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
// ---------------------------------------------------------------------------
|
|
461
|
+
// Device fingerprint
|
|
462
|
+
// ---------------------------------------------------------------------------
|
|
463
|
+
async btcGetMasterFingerprint(connectId, _deviceId, params) {
|
|
464
|
+
await this._ensureDevicePermission(connectId, _deviceId);
|
|
465
|
+
try {
|
|
466
|
+
const result = await this.connectorCall(connectId, "btcGetMasterFingerprint", {
|
|
467
|
+
skipOpenApp: params?.skipOpenApp
|
|
468
|
+
});
|
|
469
|
+
return (0, import_hardware_wallet_core2.success)({ masterFingerprint: result.masterFingerprint });
|
|
470
|
+
} catch (err) {
|
|
471
|
+
return this.errorToFailure(err);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
// ---------------------------------------------------------------------------
|
|
475
|
+
// Solana methods (stubs -- not yet supported)
|
|
476
|
+
// ---------------------------------------------------------------------------
|
|
477
|
+
async solGetAddress(_connectId, _deviceId, _params) {
|
|
478
|
+
return (0, import_hardware_wallet_core2.failure)(import_hardware_wallet_core2.HardwareErrorCode.MethodNotSupported, "Solana not supported on Ledger yet");
|
|
479
|
+
}
|
|
480
|
+
async solGetAddresses(_connectId, _deviceId, _params, _onProgress) {
|
|
481
|
+
return (0, import_hardware_wallet_core2.failure)(import_hardware_wallet_core2.HardwareErrorCode.MethodNotSupported, "Solana not supported on Ledger yet");
|
|
482
|
+
}
|
|
483
|
+
async solGetPublicKey(_connectId, _deviceId, _params) {
|
|
484
|
+
return (0, import_hardware_wallet_core2.failure)(import_hardware_wallet_core2.HardwareErrorCode.MethodNotSupported, "Solana not supported on Ledger yet");
|
|
485
|
+
}
|
|
486
|
+
async solSignTransaction(_connectId, _deviceId, _params) {
|
|
487
|
+
return (0, import_hardware_wallet_core2.failure)(import_hardware_wallet_core2.HardwareErrorCode.MethodNotSupported, "Solana not supported on Ledger yet");
|
|
488
|
+
}
|
|
489
|
+
async solSignMessage(_connectId, _deviceId, _params) {
|
|
490
|
+
return (0, import_hardware_wallet_core2.failure)(import_hardware_wallet_core2.HardwareErrorCode.MethodNotSupported, "Solana signMessage is not supported on Ledger yet");
|
|
491
|
+
}
|
|
492
|
+
// ---------------------------------------------------------------------------
|
|
493
|
+
// Private helpers
|
|
494
|
+
// ---------------------------------------------------------------------------
|
|
495
|
+
/**
|
|
496
|
+
* Call the connector with session resolution.
|
|
497
|
+
* Looks up sessionId from connectId, falls back to connectId itself.
|
|
498
|
+
*/
|
|
499
|
+
async connectorCall(connectId, method, params) {
|
|
500
|
+
const sessionId = this._sessions.get(connectId) ?? connectId;
|
|
501
|
+
return this.connector.call(sessionId, method, params);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Ensure device permission before proceeding.
|
|
505
|
+
* - No connectId (searchDevices): check environment-level permission
|
|
506
|
+
* - With connectId (business methods): check device-level permission
|
|
507
|
+
* If not granted, calls onDevicePermission so the consumer can request access.
|
|
508
|
+
*/
|
|
509
|
+
async _ensureDevicePermission(connectId, deviceId) {
|
|
510
|
+
const transportType = "hid";
|
|
511
|
+
let granted = false;
|
|
512
|
+
let context;
|
|
513
|
+
if (this._uiHandler?.checkDevicePermission) {
|
|
514
|
+
try {
|
|
515
|
+
const result = await this._uiHandler.checkDevicePermission({ transportType, connectId, deviceId });
|
|
516
|
+
granted = result.granted;
|
|
517
|
+
context = result.context;
|
|
518
|
+
} catch {
|
|
519
|
+
granted = false;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (!granted) {
|
|
523
|
+
try {
|
|
524
|
+
await this._uiHandler?.onDevicePermission?.({ transportType, context });
|
|
525
|
+
} catch {
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Convert a thrown error to a Response failure.
|
|
531
|
+
* Uses mapLedgerError to parse Ledger DMK error codes into HardwareErrorCode values.
|
|
532
|
+
*/
|
|
533
|
+
errorToFailure(err) {
|
|
534
|
+
console.error("[LedgerAdapter] error:", err);
|
|
535
|
+
const mapped = mapLedgerError(err);
|
|
536
|
+
if (mapped.code === import_hardware_wallet_core2.HardwareErrorCode.DeviceLocked) {
|
|
537
|
+
this.emitter.emit(import_hardware_wallet_core2.UI_REQUEST.REQUEST_BUTTON, {
|
|
538
|
+
type: import_hardware_wallet_core2.UI_REQUEST.REQUEST_BUTTON,
|
|
539
|
+
payload: {
|
|
540
|
+
device: this.unknownDevice(),
|
|
541
|
+
code: "ButtonRequest_Other"
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return (0, import_hardware_wallet_core2.failure)(mapped.code, mapped.message);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Generic batch call with progress reporting.
|
|
549
|
+
* If any single call fails, returns the failure immediately.
|
|
550
|
+
*/
|
|
551
|
+
async batchCall(params, callFn, onProgress) {
|
|
552
|
+
const results = [];
|
|
553
|
+
for (let i = 0; i < params.length; i++) {
|
|
554
|
+
const result = await callFn(params[i]);
|
|
555
|
+
if (!result.success) {
|
|
556
|
+
return result;
|
|
557
|
+
}
|
|
558
|
+
results.push(result.payload);
|
|
559
|
+
onProgress?.({ index: i, total: params.length });
|
|
560
|
+
}
|
|
561
|
+
return (0, import_hardware_wallet_core2.success)(results);
|
|
562
|
+
}
|
|
563
|
+
registerEventListeners() {
|
|
564
|
+
this.connector.on("device-connect", this.deviceConnectHandler);
|
|
565
|
+
this.connector.on("device-disconnect", this.deviceDisconnectHandler);
|
|
566
|
+
this.connector.on("ui-request", this.uiRequestHandler);
|
|
567
|
+
this.connector.on("ui-event", this.uiEventHandler);
|
|
568
|
+
}
|
|
569
|
+
unregisterEventListeners() {
|
|
570
|
+
this.connector.off("device-connect", this.deviceConnectHandler);
|
|
571
|
+
this.connector.off("device-disconnect", this.deviceDisconnectHandler);
|
|
572
|
+
this.connector.off("ui-request", this.uiRequestHandler);
|
|
573
|
+
this.connector.off("ui-event", this.uiEventHandler);
|
|
574
|
+
}
|
|
575
|
+
handleUiEvent(event) {
|
|
576
|
+
if (!event.type) return;
|
|
577
|
+
const payload = event.payload;
|
|
578
|
+
const deviceInfo = payload ? this.extractDeviceInfoFromPayload(payload) : this.unknownDevice();
|
|
579
|
+
switch (event.type) {
|
|
580
|
+
case "ui-request_confirmation":
|
|
581
|
+
this.emitter.emit(import_hardware_wallet_core2.UI_REQUEST.REQUEST_BUTTON, {
|
|
582
|
+
type: import_hardware_wallet_core2.UI_REQUEST.REQUEST_BUTTON,
|
|
583
|
+
payload: { device: deviceInfo }
|
|
584
|
+
});
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
// ---------------------------------------------------------------------------
|
|
589
|
+
// Device info mapping
|
|
590
|
+
// ---------------------------------------------------------------------------
|
|
591
|
+
connectorDeviceToDeviceInfo(device) {
|
|
592
|
+
return {
|
|
593
|
+
vendor: "ledger",
|
|
594
|
+
model: device.model ?? "unknown",
|
|
595
|
+
firmwareVersion: "",
|
|
596
|
+
deviceId: device.deviceId,
|
|
597
|
+
connectId: device.connectId,
|
|
598
|
+
label: device.name,
|
|
599
|
+
connectionType: "usb"
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
extractDeviceInfoFromPayload(payload) {
|
|
603
|
+
return {
|
|
604
|
+
vendor: "ledger",
|
|
605
|
+
model: payload["model"] ?? "unknown",
|
|
606
|
+
firmwareVersion: "",
|
|
607
|
+
deviceId: payload["deviceId"] ?? payload["id"] ?? "",
|
|
608
|
+
connectId: payload["connectId"] ?? payload["path"] ?? "",
|
|
609
|
+
label: payload["label"],
|
|
610
|
+
connectionType: "usb"
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
unknownDevice() {
|
|
614
|
+
return {
|
|
615
|
+
vendor: "ledger",
|
|
616
|
+
model: "unknown",
|
|
617
|
+
firmwareVersion: "",
|
|
618
|
+
deviceId: "",
|
|
619
|
+
connectId: "",
|
|
620
|
+
connectionType: "usb"
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
|
|
625
|
+
// src/device/LedgerDeviceManager.ts
|
|
626
|
+
var LedgerDeviceManager = class {
|
|
627
|
+
constructor(dmk) {
|
|
628
|
+
this._discovered = /* @__PURE__ */ new Map();
|
|
629
|
+
this._sessions = /* @__PURE__ */ new Map();
|
|
630
|
+
// deviceId → sessionId
|
|
631
|
+
this._sessionToDevice = /* @__PURE__ */ new Map();
|
|
632
|
+
// sessionId → deviceId
|
|
633
|
+
this._listenSub = null;
|
|
634
|
+
this._dmk = dmk;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* One-shot enumeration: subscribe to listenToAvailableDevices,
|
|
638
|
+
* take the first emission, unsubscribe, return DeviceDescriptors.
|
|
639
|
+
*/
|
|
640
|
+
enumerate() {
|
|
641
|
+
return new Promise((resolve) => {
|
|
642
|
+
const sub = this._dmk.listenToAvailableDevices().subscribe({
|
|
643
|
+
next: (devices) => {
|
|
644
|
+
this._discovered.clear();
|
|
645
|
+
for (const d of devices) {
|
|
646
|
+
this._discovered.set(d.id, d);
|
|
647
|
+
}
|
|
648
|
+
sub.unsubscribe();
|
|
649
|
+
resolve(devices.map((d) => ({ path: d.id, type: d.deviceModel.id })));
|
|
650
|
+
},
|
|
651
|
+
error: () => {
|
|
652
|
+
sub.unsubscribe();
|
|
653
|
+
resolve([]);
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* Continuous listening: tracks device connect/disconnect via diffing.
|
|
660
|
+
*/
|
|
661
|
+
listen(onChange) {
|
|
662
|
+
this.stopListening();
|
|
663
|
+
let previousIds = /* @__PURE__ */ new Set();
|
|
664
|
+
this._listenSub = this._dmk.listenToAvailableDevices().subscribe({
|
|
665
|
+
next: (devices) => {
|
|
666
|
+
const currentIds = new Set(devices.map((d) => d.id));
|
|
667
|
+
for (const d of devices) {
|
|
668
|
+
this._discovered.set(d.id, d);
|
|
669
|
+
if (!previousIds.has(d.id)) {
|
|
670
|
+
onChange({ type: "device-connected", descriptor: { path: d.id, type: d.deviceModel.id } });
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
for (const id of previousIds) {
|
|
674
|
+
if (!currentIds.has(id)) {
|
|
675
|
+
this._discovered.delete(id);
|
|
676
|
+
onChange({ type: "device-disconnected", descriptor: { path: id } });
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
previousIds = currentIds;
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
stopListening() {
|
|
684
|
+
this._listenSub?.unsubscribe();
|
|
685
|
+
this._listenSub = null;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* Trigger browser device selection (WebHID requestDevice).
|
|
689
|
+
* Starts discovery for a short period, then stops.
|
|
690
|
+
*/
|
|
691
|
+
requestDevice(timeoutMs = 3e3) {
|
|
692
|
+
return new Promise((resolve) => {
|
|
693
|
+
const sub = this._dmk.startDiscovering().subscribe({
|
|
694
|
+
next: (d) => {
|
|
695
|
+
this._discovered.set(d.id, d);
|
|
696
|
+
},
|
|
697
|
+
error: () => {
|
|
698
|
+
sub.unsubscribe();
|
|
699
|
+
resolve();
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
setTimeout(() => {
|
|
703
|
+
sub.unsubscribe();
|
|
704
|
+
this._dmk.stopDiscovering();
|
|
705
|
+
resolve();
|
|
706
|
+
}, timeoutMs);
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
/** Connect to a previously discovered device. Returns sessionId. */
|
|
710
|
+
async connect(deviceId) {
|
|
711
|
+
const device = this._discovered.get(deviceId);
|
|
712
|
+
if (!device) {
|
|
713
|
+
throw new Error(`Device "${deviceId}" not found. Call enumerate() or listen() first.`);
|
|
714
|
+
}
|
|
715
|
+
const sessionId = await this._dmk.connect({ device });
|
|
716
|
+
this._sessions.set(deviceId, sessionId);
|
|
717
|
+
this._sessionToDevice.set(sessionId, deviceId);
|
|
718
|
+
return sessionId;
|
|
719
|
+
}
|
|
720
|
+
/** Disconnect a session. */
|
|
721
|
+
async disconnect(sessionId) {
|
|
722
|
+
await this._dmk.disconnect({ sessionId });
|
|
723
|
+
const deviceId = this._sessionToDevice.get(sessionId);
|
|
724
|
+
if (deviceId) this._sessions.delete(deviceId);
|
|
725
|
+
this._sessionToDevice.delete(sessionId);
|
|
726
|
+
}
|
|
727
|
+
getSessionId(deviceId) {
|
|
728
|
+
return this._sessions.get(deviceId);
|
|
729
|
+
}
|
|
730
|
+
getDeviceId(sessionId) {
|
|
731
|
+
return this._sessionToDevice.get(sessionId);
|
|
732
|
+
}
|
|
733
|
+
/** Get the underlying DMK instance (needed by SignerManager). */
|
|
734
|
+
getDmk() {
|
|
735
|
+
return this._dmk;
|
|
736
|
+
}
|
|
737
|
+
dispose() {
|
|
738
|
+
this.stopListening();
|
|
739
|
+
this._discovered.clear();
|
|
740
|
+
this._sessions.clear();
|
|
741
|
+
this._sessionToDevice.clear();
|
|
742
|
+
this._dmk.close?.();
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
// src/signer/deviceActionToPromise.ts
|
|
747
|
+
function deviceActionToPromise(action, onInteraction) {
|
|
748
|
+
return new Promise((resolve, reject) => {
|
|
749
|
+
let settled = false;
|
|
750
|
+
let sub;
|
|
751
|
+
sub = action.observable.subscribe({
|
|
752
|
+
next: (state) => {
|
|
753
|
+
if (settled) return;
|
|
754
|
+
if (state.status === "completed") {
|
|
755
|
+
settled = true;
|
|
756
|
+
sub?.unsubscribe();
|
|
757
|
+
resolve(state.output);
|
|
758
|
+
} else if (state.status === "error") {
|
|
759
|
+
settled = true;
|
|
760
|
+
sub?.unsubscribe();
|
|
761
|
+
reject(state.error);
|
|
762
|
+
} else if (state.status === "pending" && onInteraction) {
|
|
763
|
+
const interaction = state.intermediateValue?.requiredUserInteraction;
|
|
764
|
+
if (interaction && interaction !== "none") {
|
|
765
|
+
onInteraction(interaction);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
},
|
|
769
|
+
error: (err) => {
|
|
770
|
+
if (!settled) {
|
|
771
|
+
settled = true;
|
|
772
|
+
sub?.unsubscribe();
|
|
773
|
+
reject(err);
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
complete: () => {
|
|
777
|
+
if (!settled) {
|
|
778
|
+
settled = true;
|
|
779
|
+
reject(new Error("Device action completed without result"));
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// src/signer/SignerEth.ts
|
|
787
|
+
function hexToBytes(hex) {
|
|
788
|
+
const h = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
789
|
+
const bytes = new Uint8Array(h.length / 2);
|
|
790
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
791
|
+
bytes[i] = parseInt(h.substring(i * 2, i * 2 + 2), 16);
|
|
792
|
+
}
|
|
793
|
+
return bytes;
|
|
794
|
+
}
|
|
795
|
+
var SignerEth = class {
|
|
796
|
+
constructor(_sdk) {
|
|
797
|
+
this._sdk = _sdk;
|
|
798
|
+
}
|
|
799
|
+
async getAddress(derivationPath, options) {
|
|
800
|
+
const action = this._sdk.getAddress(derivationPath, {
|
|
801
|
+
checkOnDevice: options?.checkOnDevice ?? false
|
|
802
|
+
});
|
|
803
|
+
return deviceActionToPromise(action, this.onInteraction);
|
|
804
|
+
}
|
|
805
|
+
async signTransaction(derivationPath, serializedTxHex) {
|
|
806
|
+
const action = this._sdk.signTransaction(derivationPath, hexToBytes(serializedTxHex));
|
|
807
|
+
return deviceActionToPromise(action, this.onInteraction);
|
|
808
|
+
}
|
|
809
|
+
async signMessage(derivationPath, message) {
|
|
810
|
+
const action = this._sdk.signMessage(derivationPath, message);
|
|
811
|
+
return deviceActionToPromise(action, this.onInteraction);
|
|
812
|
+
}
|
|
813
|
+
async signTypedData(derivationPath, data) {
|
|
814
|
+
const action = this._sdk.signTypedData(derivationPath, data);
|
|
815
|
+
return deviceActionToPromise(action, this.onInteraction);
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
// src/signer/SignerManager.ts
|
|
820
|
+
var SignerManager = class _SignerManager {
|
|
821
|
+
constructor(dmk, builderFn) {
|
|
822
|
+
this._cache = /* @__PURE__ */ new Map();
|
|
823
|
+
this._dmk = dmk;
|
|
824
|
+
this._builderFn = builderFn ?? _SignerManager._defaultBuilder();
|
|
825
|
+
}
|
|
826
|
+
async getOrCreate(sessionId) {
|
|
827
|
+
let signer = this._cache.get(sessionId);
|
|
828
|
+
if (signer) return signer;
|
|
829
|
+
const builder = await this._builderFn({ dmk: this._dmk, sessionId });
|
|
830
|
+
const sdkSigner = builder.build();
|
|
831
|
+
signer = new SignerEth(sdkSigner);
|
|
832
|
+
this._cache.set(sessionId, signer);
|
|
833
|
+
return signer;
|
|
834
|
+
}
|
|
835
|
+
invalidate(sessionId) {
|
|
836
|
+
this._cache.delete(sessionId);
|
|
837
|
+
}
|
|
838
|
+
clearAll() {
|
|
839
|
+
this._cache.clear();
|
|
840
|
+
}
|
|
841
|
+
static _defaultBuilder() {
|
|
842
|
+
let BuilderClass = null;
|
|
843
|
+
return async (args) => {
|
|
844
|
+
if (!BuilderClass) {
|
|
845
|
+
const mod = await import("@ledgerhq/device-signer-kit-ethereum");
|
|
846
|
+
BuilderClass = mod.SignerEthBuilder;
|
|
847
|
+
}
|
|
848
|
+
return new BuilderClass(args);
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
// src/signer/SignerBtc.ts
|
|
854
|
+
var SignerBtc = class {
|
|
855
|
+
constructor(_sdk) {
|
|
856
|
+
this._sdk = _sdk;
|
|
857
|
+
}
|
|
858
|
+
async getWalletAddress(wallet, addressIndex, options) {
|
|
859
|
+
const action = this._sdk.getWalletAddress(wallet, addressIndex, {
|
|
860
|
+
checkOnDevice: options?.checkOnDevice ?? false,
|
|
861
|
+
change: options?.change ?? false
|
|
862
|
+
});
|
|
863
|
+
return deviceActionToPromise(action, this.onInteraction);
|
|
864
|
+
}
|
|
865
|
+
async getExtendedPublicKey(derivationPath, options) {
|
|
866
|
+
const action = this._sdk.getExtendedPublicKey(derivationPath, {
|
|
867
|
+
checkOnDevice: options?.checkOnDevice ?? false
|
|
868
|
+
});
|
|
869
|
+
return deviceActionToPromise(action, this.onInteraction);
|
|
870
|
+
}
|
|
871
|
+
async getMasterFingerprint(options) {
|
|
872
|
+
const action = this._sdk.getMasterFingerprint(options);
|
|
873
|
+
const result = await deviceActionToPromise(action, this.onInteraction);
|
|
874
|
+
return result.masterFingerprint;
|
|
875
|
+
}
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
// src/transport/registry.ts
|
|
879
|
+
var registry = /* @__PURE__ */ new Map();
|
|
880
|
+
function normalizeType(type) {
|
|
881
|
+
return type.trim().toLowerCase();
|
|
882
|
+
}
|
|
883
|
+
function registerTransport(type, provider) {
|
|
884
|
+
const key = normalizeType(type);
|
|
885
|
+
if (!key) throw new Error("Transport type must be a non-empty string");
|
|
886
|
+
registry.set(key, provider);
|
|
887
|
+
}
|
|
888
|
+
function unregisterTransport(type) {
|
|
889
|
+
registry.delete(normalizeType(type));
|
|
890
|
+
}
|
|
891
|
+
function getTransportProvider(type) {
|
|
892
|
+
return registry.get(normalizeType(type)) ?? null;
|
|
893
|
+
}
|
|
894
|
+
function listRegisteredTransports() {
|
|
895
|
+
return Array.from(registry.keys());
|
|
896
|
+
}
|
|
897
|
+
function clearRegistry() {
|
|
898
|
+
registry.clear();
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// src/app/AppManager.ts
|
|
902
|
+
var APP_NAME_MAP = {
|
|
903
|
+
ETH: "Ethereum",
|
|
904
|
+
BTC: "Bitcoin",
|
|
905
|
+
SOL: "Solana",
|
|
906
|
+
TRX: "Tron",
|
|
907
|
+
XRP: "XRP",
|
|
908
|
+
ADA: "Cardano",
|
|
909
|
+
DOT: "Polkadot",
|
|
910
|
+
ATOM: "Cosmos"
|
|
911
|
+
};
|
|
912
|
+
var DASHBOARD_APP_NAME = "BOLOS";
|
|
913
|
+
var AppManager = class {
|
|
914
|
+
constructor(dmk, options) {
|
|
915
|
+
this._dmk = dmk;
|
|
916
|
+
this._waitMs = options?.waitMs ?? 1e3;
|
|
917
|
+
this._maxRetries = options?.maxRetries ?? 10;
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Return the Ledger app name for a given chain ticker,
|
|
921
|
+
* or undefined if the chain is not supported.
|
|
922
|
+
*/
|
|
923
|
+
static getAppName(chain) {
|
|
924
|
+
return APP_NAME_MAP[chain];
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Ensure the target app is open on the device identified by `sessionId`.
|
|
928
|
+
*
|
|
929
|
+
* Flow:
|
|
930
|
+
* 1. Check the currently running app.
|
|
931
|
+
* 2. If it is already the target, return immediately.
|
|
932
|
+
* 3. If a different app is running (not dashboard), close it first.
|
|
933
|
+
* 4. Open the target app.
|
|
934
|
+
* 5. Poll until the device confirms the target app is running.
|
|
935
|
+
*/
|
|
936
|
+
async ensureAppOpen(sessionId, targetAppName) {
|
|
937
|
+
const currentApp = await this._getCurrentApp(sessionId);
|
|
938
|
+
if (currentApp === targetAppName) {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (!this._isDashboard(currentApp)) {
|
|
942
|
+
await this._closeCurrentApp(sessionId);
|
|
943
|
+
await this._waitForApp(sessionId, DASHBOARD_APP_NAME);
|
|
944
|
+
}
|
|
945
|
+
await this._openApp(sessionId, targetAppName);
|
|
946
|
+
await this._waitForApp(sessionId, targetAppName);
|
|
947
|
+
}
|
|
948
|
+
// ---------------------------------------------------------------------------
|
|
949
|
+
// Private helpers
|
|
950
|
+
// ---------------------------------------------------------------------------
|
|
951
|
+
async _getCurrentApp(sessionId) {
|
|
952
|
+
const result = await this._dmk.sendCommand({
|
|
953
|
+
sessionId,
|
|
954
|
+
command: { type: "get-app-and-version" }
|
|
955
|
+
});
|
|
956
|
+
return result.name;
|
|
957
|
+
}
|
|
958
|
+
async _openApp(sessionId, appName) {
|
|
959
|
+
await this._dmk.sendCommand({
|
|
960
|
+
sessionId,
|
|
961
|
+
command: { type: "open-app", appName }
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
async _closeCurrentApp(sessionId) {
|
|
965
|
+
await this._dmk.sendCommand({
|
|
966
|
+
sessionId,
|
|
967
|
+
command: { type: "close-app" }
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Poll the device until the expected app is reported as running,
|
|
972
|
+
* or throw after `_maxRetries` attempts.
|
|
973
|
+
*/
|
|
974
|
+
async _waitForApp(sessionId, expectedAppName) {
|
|
975
|
+
for (let i = 0; i < this._maxRetries; i++) {
|
|
976
|
+
await this._wait();
|
|
977
|
+
const current = await this._getCurrentApp(sessionId);
|
|
978
|
+
if (current === expectedAppName) {
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
throw new Error(
|
|
983
|
+
`Ledger: failed to open "${expectedAppName}" after ${this._maxRetries} retries`
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
_isDashboard(appName) {
|
|
987
|
+
return appName === DASHBOARD_APP_NAME;
|
|
988
|
+
}
|
|
989
|
+
_wait() {
|
|
990
|
+
return new Promise((resolve) => setTimeout(resolve, this._waitMs));
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
994
|
+
0 && (module.exports = {
|
|
995
|
+
AppManager,
|
|
996
|
+
LedgerAdapter,
|
|
997
|
+
LedgerDeviceManager,
|
|
998
|
+
SignerBtc,
|
|
999
|
+
SignerEth,
|
|
1000
|
+
SignerManager,
|
|
1001
|
+
clearRegistry,
|
|
1002
|
+
deviceActionToPromise,
|
|
1003
|
+
getTransportProvider,
|
|
1004
|
+
isDeviceLockedError,
|
|
1005
|
+
listRegisteredTransports,
|
|
1006
|
+
mapLedgerError,
|
|
1007
|
+
registerTransport,
|
|
1008
|
+
unregisterTransport
|
|
1009
|
+
});
|
|
1010
|
+
//# sourceMappingURL=index.js.map
|