or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

tessl/npm-ledgerhq--hw-transport-node-hid

Node.js HID transport implementation for Ledger Hardware Wallets with device event listening capabilities

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/@ledgerhq/hw-transport-node-hid@6.29.x

To install, run

npx @tessl/cli install tessl/npm-ledgerhq--hw-transport-node-hid@6.29.0

0

# Ledger Hardware Wallet Node HID Transport

1

2

@ledgerhq/hw-transport-node-hid provides a Node.js HID transport implementation for Ledger Hardware Wallets with device event listening capabilities. It extends the base TransportNodeHidNoEvents class to add real-time device monitoring and event handling, enabling applications to detect when Ledger devices are plugged in or removed.

3

4

## Package Information

5

6

- **Package Name**: @ledgerhq/hw-transport-node-hid

7

- **Package Type**: npm

8

- **Language**: TypeScript

9

- **Installation**: `npm install @ledgerhq/hw-transport-node-hid`

10

11

## Core Imports

12

13

```typescript

14

import TransportNodeHid from "@ledgerhq/hw-transport-node-hid";

15

```

16

17

For CommonJS:

18

19

```javascript

20

const TransportNodeHid = require("@ledgerhq/hw-transport-node-hid").default;

21

```

22

23

## Basic Usage

24

25

```typescript

26

import TransportNodeHid from "@ledgerhq/hw-transport-node-hid";

27

28

// Create a transport instance to the first available device

29

const transport = await TransportNodeHid.create();

30

31

// Send an APDU command

32

const response = await transport.exchange(apduBuffer);

33

34

// Close the transport when done

35

await transport.close();

36

```

37

38

## Architecture

39

40

The package is built around these key components:

41

42

- **TransportNodeHid Class**: Main transport class extending TransportNodeHidNoEvents with event listening

43

- **Device Discovery**: Real-time USB device monitoring with debounced polling

44

- **HID Communication**: Low-level HID protocol handling through node-hid library

45

- **Event System**: Observer pattern for device add/remove notifications

46

- **APDU Protocol**: High-level and low-level APIs for Ledger device communication

47

48

## Capabilities

49

50

### Transport Creation and Management

51

52

Core functionality for creating and managing transport connections to Ledger devices.

53

54

```typescript { .api }

55

class TransportNodeHid {

56

/**

57

* Check if HID transport is supported on current platform

58

* @returns Promise resolving to boolean indicating support

59

*/

60

static isSupported(): Promise<boolean>;

61

62

/**

63

* List all available Ledger device paths

64

* @returns Promise resolving to array of device paths

65

*/

66

static list(): Promise<string[]>;

67

68

/**

69

* Create transport to first available device with timeouts

70

* @param openTimeout - Optional timeout in ms for opening device (default: 3000)

71

* @param listenTimeout - Optional timeout in ms for device discovery

72

* @returns Promise resolving to TransportNodeHid instance

73

*/

74

static create(openTimeout?: number, listenTimeout?: number): Promise<TransportNodeHid>;

75

76

/**

77

* Open connection to specific device or first available device

78

* @param path - Device path string, null, or undefined (auto-selects first device if falsy)

79

* @returns Promise resolving to TransportNodeHid instance

80

*/

81

static open(path: string | null | undefined): Promise<TransportNodeHid>;

82

83

/**

84

* Close connection to device and release resources

85

* @returns Promise resolving when closed

86

*/

87

close(): Promise<void>;

88

}

89

```

90

91

### Device Event Listening

92

93

Real-time monitoring of Ledger device connections with automatic discovery and removal detection.

94

95

```typescript { .api }

96

/**

97

* Listen for device add/remove events with real-time monitoring

98

* @param observer - Observer object with next, error, complete methods

99

* @returns Subscription object with unsubscribe method

100

*/

101

static listen(observer: Observer<DescriptorEvent<string | null | undefined>>): Subscription;

102

103

/**

104

* Configure debounce delay for device polling

105

* @param delay - Debounce delay in milliseconds

106

*/

107

static setListenDevicesDebounce(delay: number): void;

108

109

/**

110

* Set condition function to skip device polling

111

* @param conditionToSkip - Function returning boolean to determine when to skip polling

112

*/

113

static setListenDevicesPollingSkip(conditionToSkip: () => boolean): void;

114

115

/**

116

* Deprecated debug method (logs deprecation warning)

117

* @deprecated Use @ledgerhq/logs instead

118

*/

119

static setListenDevicesDebug(): void;

120

```

121

122

**Usage Example:**

123

124

```typescript

125

import TransportNodeHid from "@ledgerhq/hw-transport-node-hid";

126

127

// Listen for device events

128

const subscription = TransportNodeHid.listen({

129

next: (event) => {

130

if (event.type === "add") {

131

console.log("Device connected:", event.descriptor);

132

console.log("Device model:", event.deviceModel);

133

} else if (event.type === "remove") {

134

console.log("Device disconnected:", event.descriptor);

135

}

136

},

137

error: (err) => console.error("Device listening error:", err),

138

complete: () => console.log("Device listening completed")

139

});

140

141

// Stop listening

142

subscription.unsubscribe();

143

144

// Configure polling behavior

145

TransportNodeHid.setListenDevicesDebounce(1000); // 1 second debounce

146

TransportNodeHid.setListenDevicesPollingSkip(() => someCondition);

147

```

148

149

### APDU Communication

150

151

Low-level and high-level APIs for communicating with Ledger devices using the APDU protocol.

152

153

```typescript { .api }

154

/**

155

* Send APDU command to device and receive response

156

* @param apdu - Buffer containing APDU command

157

* @param options - Optional object with abortTimeoutMs property

158

* @returns Promise resolving to response Buffer

159

*/

160

exchange(apdu: Buffer, options?: { abortTimeoutMs?: number }): Promise<Buffer>;

161

162

/**

163

* High-level API to send structured commands to device

164

* @param cla - Instruction class

165

* @param ins - Instruction code

166

* @param p1 - First parameter

167

* @param p2 - Second parameter

168

* @param data - Optional data buffer (default: empty buffer)

169

* @param statusList - Optional acceptable status codes (default: [StatusCodes.OK])

170

* @param options - Optional object with abortTimeoutMs property

171

* @returns Promise resolving to response Buffer

172

*/

173

send(

174

cla: number,

175

ins: number,

176

p1: number,

177

p2: number,

178

data?: Buffer,

179

statusList?: number[],

180

options?: { abortTimeoutMs?: number }

181

): Promise<Buffer>;

182

183

/**

184

* Send multiple APDUs in sequence

185

* @param apdus - Array of APDU buffers

186

* @param observer - Observer to receive individual responses

187

* @returns Subscription object with unsubscribe method

188

*/

189

exchangeBulk(apdus: Buffer[], observer: Observer<Buffer>): Subscription;

190

```

191

192

**Usage Example:**

193

194

```typescript

195

import TransportNodeHid from "@ledgerhq/hw-transport-node-hid";

196

import { StatusCodes } from "@ledgerhq/errors";

197

198

const transport = await TransportNodeHid.create();

199

200

// Low-level APDU exchange

201

const apduBuffer = Buffer.from([0xe0, 0x01, 0x00, 0x00]);

202

const response = await transport.exchange(apduBuffer);

203

204

// High-level structured command

205

const response2 = await transport.send(

206

0xe0, // CLA

207

0x01, // INS

208

0x00, // P1

209

0x00, // P2

210

Buffer.from("data"), // Data

211

[StatusCodes.OK, 0x6985] // Acceptable status codes

212

);

213

214

// Bulk APDU operations

215

const apdus = [

216

Buffer.from([0xe0, 0x01, 0x00, 0x00]),

217

Buffer.from([0xe0, 0x02, 0x00, 0x00])

218

];

219

220

const subscription = transport.exchangeBulk(apdus, {

221

next: (response) => console.log("Response:", response),

222

error: (err) => console.error("Error:", err),

223

complete: () => console.log("All APDUs completed")

224

});

225

```

226

227

### Transport Configuration and Events

228

229

Configuration options and event handling for transport instances.

230

231

```typescript { .api }

232

/**

233

* Set timeout for exchange operations

234

* @param exchangeTimeout - Timeout in milliseconds

235

*/

236

setExchangeTimeout(exchangeTimeout: number): void;

237

238

/**

239

* Set timeout before emitting unresponsive event

240

* @param unresponsiveTimeout - Timeout in milliseconds

241

*/

242

setExchangeUnresponsiveTimeout(unresponsiveTimeout: number): void;

243

244

/**

245

* Add event listener for transport events

246

* @param eventName - Event name (e.g., "disconnect", "unresponsive", "responsive")

247

* @param cb - Callback function

248

*/

249

on(eventName: string, cb: (...args: any[]) => any): void;

250

251

/**

252

* Remove event listener

253

* @param eventName - Event name

254

* @param cb - Callback function to remove

255

*/

256

off(eventName: string, cb: (...args: any[]) => any): void;

257

258

/**

259

* Set scramble key for data exchanges (deprecated)

260

* @param key - Optional scramble key

261

* @deprecated This method is no longer needed for modern transports

262

*/

263

setScrambleKey(key?: string): void;

264

265

/**

266

* Deprecated debug method (logs deprecation warning)

267

* @deprecated Use @ledgerhq/logs instead

268

*/

269

setDebugMode(): void;

270

```

271

272

**Usage Example:**

273

274

```typescript

275

const transport = await TransportNodeHid.create();

276

277

// Configure timeouts

278

transport.setExchangeTimeout(60000); // 60 seconds

279

transport.setExchangeUnresponsiveTimeout(30000); // 30 seconds

280

281

// Listen for events

282

transport.on("disconnect", () => {

283

console.log("Device disconnected");

284

});

285

286

transport.on("unresponsive", () => {

287

console.log("Device is not responding");

288

});

289

290

transport.on("responsive", () => {

291

console.log("Device is responsive again");

292

});

293

```

294

295

### Tracing and Debugging

296

297

Logging and tracing functionality for debugging transport operations.

298

299

```typescript { .api }

300

/**

301

* Set tracing context for logging

302

* @param context - Optional TraceContext object

303

*/

304

setTraceContext(context?: TraceContext): void;

305

306

/**

307

* Update existing tracing context

308

* @param contextToAdd - TraceContext to merge with current context

309

*/

310

updateTraceContext(contextToAdd: TraceContext): void;

311

312

/**

313

* Get current tracing context

314

* @returns Current TraceContext or undefined

315

*/

316

getTraceContext(): TraceContext | undefined;

317

```

318

319

## Types

320

321

```typescript { .api }

322

/**

323

* Observer pattern interface for handling events

324

*/

325

interface Observer<EventType, EventError = unknown> {

326

next: (event: EventType) => unknown;

327

error: (e: EventError) => unknown;

328

complete: () => unknown;

329

}

330

331

/**

332

* Subscription interface for cancelling event listeners

333

*/

334

interface Subscription {

335

unsubscribe: () => void;

336

}

337

338

/**

339

* Device event descriptor for add/remove notifications

340

*/

341

interface DescriptorEvent<Descriptor> {

342

type: "add" | "remove";

343

descriptor: Descriptor;

344

deviceModel?: DeviceModel | null | undefined;

345

device?: Device;

346

}

347

348

/**

349

* Device model information

350

*/

351

interface DeviceModel {

352

id: string;

353

productName: string;

354

productIdMM: number;

355

legacyUsbProductId: number;

356

usbOnly: boolean;

357

memorySize: number;

358

masks: number[];

359

getBlockSize: (firmwareVersion: string) => number;

360

bluetoothSpec?: {

361

serviceUuid: string;

362

writeUuid: string;

363

writeCmdUuid: string;

364

notifyUuid: string;

365

}[];

366

}

367

368

/**

369

* Generic device object type

370

*/

371

type Device = any;

372

373

/**

374

* Tracing context for logging operations

375

*/

376

type TraceContext = Record<string, any>;

377

378

/**

379

* Log type for tracing operations

380

*/

381

type LogType = string;

382

```

383

384

## Error Handling

385

386

The package uses error types from @ledgerhq/errors:

387

388

```typescript { .api }

389

/**

390

* General transport errors

391

*/

392

class TransportError extends Error {

393

constructor(message: string, id: string);

394

}

395

396

/**

397

* APDU status code errors

398

*/

399

class TransportStatusError extends TransportError {

400

constructor(statusCode: number);

401

statusCode: number;

402

}

403

404

/**

405

* Concurrent operation errors

406

*/

407

class TransportRaceCondition extends TransportError {

408

constructor(message: string);

409

}

410

411

/**

412

* Device disconnection errors

413

*/

414

class DisconnectedDevice extends TransportError {

415

constructor(message?: string);

416

}

417

418

/**

419

* Errors during active operations

420

*/

421

class DisconnectedDeviceDuringOperation extends TransportError {

422

constructor(message: string);

423

}

424

425

/**

426

* Status code constants

427

*/

428

const StatusCodes: {

429

OK: 0x9000;

430

// Additional status codes...

431

};

432

```

433

434

**Common Error Scenarios:**

435

436

```typescript

437

import TransportNodeHid from "@ledgerhq/hw-transport-node-hid";

438

import { TransportError, TransportStatusError } from "@ledgerhq/errors";

439

440

try {

441

const transport = await TransportNodeHid.create();

442

const response = await transport.exchange(apduBuffer);

443

} catch (error) {

444

if (error instanceof TransportError) {

445

if (error.id === "NoDevice") {

446

console.error("No Ledger device found");

447

} else if (error.id === "ListenTimeout") {

448

console.error("Device discovery timed out");

449

}

450

} else if (error instanceof TransportStatusError) {

451

console.error("Device returned error status:", error.statusCode.toString(16));

452

}

453

}

454

```

455

456

## Platform Support

457

458

- **Supported Platforms**: Node.js environments (Windows, macOS, Linux)

459

- **Requirements**: Node.js with node-hid and usb native modules

460

- **Device Compatibility**: Ledger Nano S, Nano X, Nano S Plus, Blue, and other Ledger hardware wallets

461

- **Limitations**: Desktop/server environments only (not browser-compatible)

462

463

## Installation Requirements

464

465

The package requires native dependencies:

466

467

```bash

468

npm install @ledgerhq/hw-transport-node-hid

469

470

# On Linux, you may need additional system dependencies:

471

# sudo apt-get install libudev-dev libusb-1.0-0-dev

472

473

# On Windows, you may need build tools:

474

# npm install --global windows-build-tools

475

```