or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

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

index.mddocs/

Ledger Hardware Wallet Node HID Transport

@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.

Package Information

  • Package Name: @ledgerhq/hw-transport-node-hid
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install @ledgerhq/hw-transport-node-hid

Core Imports

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

For CommonJS:

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

Basic Usage

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

// Create a transport instance to the first available device
const transport = await TransportNodeHid.create();

// Send an APDU command
const response = await transport.exchange(apduBuffer);

// Close the transport when done
await transport.close();

Architecture

The package is built around these key components:

  • TransportNodeHid Class: Main transport class extending TransportNodeHidNoEvents with event listening
  • Device Discovery: Real-time USB device monitoring with debounced polling
  • HID Communication: Low-level HID protocol handling through node-hid library
  • Event System: Observer pattern for device add/remove notifications
  • APDU Protocol: High-level and low-level APIs for Ledger device communication

Capabilities

Transport Creation and Management

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

class TransportNodeHid {
  /**
   * Check if HID transport is supported on current platform
   * @returns Promise resolving to boolean indicating support
   */
  static isSupported(): Promise<boolean>;

  /**
   * List all available Ledger device paths
   * @returns Promise resolving to array of device paths
   */
  static list(): Promise<string[]>;

  /**
   * Create transport to first available device with timeouts
   * @param openTimeout - Optional timeout in ms for opening device (default: 3000)
   * @param listenTimeout - Optional timeout in ms for device discovery
   * @returns Promise resolving to TransportNodeHid instance
   */
  static create(openTimeout?: number, listenTimeout?: number): Promise<TransportNodeHid>;

  /**
   * Open connection to specific device or first available device
   * @param path - Device path string, null, or undefined (auto-selects first device if falsy)
   * @returns Promise resolving to TransportNodeHid instance
   */
  static open(path: string | null | undefined): Promise<TransportNodeHid>;

  /**
   * Close connection to device and release resources
   * @returns Promise resolving when closed
   */
  close(): Promise<void>;
}

Device Event Listening

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

/**
 * Listen for device add/remove events with real-time monitoring
 * @param observer - Observer object with next, error, complete methods
 * @returns Subscription object with unsubscribe method
 */
static listen(observer: Observer<DescriptorEvent<string | null | undefined>>): Subscription;

/**
 * Configure debounce delay for device polling
 * @param delay - Debounce delay in milliseconds
 */
static setListenDevicesDebounce(delay: number): void;

/**
 * Set condition function to skip device polling
 * @param conditionToSkip - Function returning boolean to determine when to skip polling
 */
static setListenDevicesPollingSkip(conditionToSkip: () => boolean): void;

/**
 * Deprecated debug method (logs deprecation warning)
 * @deprecated Use @ledgerhq/logs instead
 */
static setListenDevicesDebug(): void;

Usage Example:

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

// Listen for device events
const subscription = TransportNodeHid.listen({
  next: (event) => {
    if (event.type === "add") {
      console.log("Device connected:", event.descriptor);
      console.log("Device model:", event.deviceModel);
    } else if (event.type === "remove") {
      console.log("Device disconnected:", event.descriptor);
    }
  },
  error: (err) => console.error("Device listening error:", err),
  complete: () => console.log("Device listening completed")
});

// Stop listening
subscription.unsubscribe();

// Configure polling behavior
TransportNodeHid.setListenDevicesDebounce(1000); // 1 second debounce
TransportNodeHid.setListenDevicesPollingSkip(() => someCondition);

APDU Communication

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

/**
 * Send APDU command to device and receive response
 * @param apdu - Buffer containing APDU command
 * @param options - Optional object with abortTimeoutMs property
 * @returns Promise resolving to response Buffer
 */
exchange(apdu: Buffer, options?: { abortTimeoutMs?: number }): Promise<Buffer>;

/**
 * High-level API to send structured commands to device
 * @param cla - Instruction class
 * @param ins - Instruction code
 * @param p1 - First parameter
 * @param p2 - Second parameter
 * @param data - Optional data buffer (default: empty buffer)
 * @param statusList - Optional acceptable status codes (default: [StatusCodes.OK])
 * @param options - Optional object with abortTimeoutMs property
 * @returns Promise resolving to response Buffer
 */
send(
  cla: number,
  ins: number,
  p1: number,
  p2: number,
  data?: Buffer,
  statusList?: number[],
  options?: { abortTimeoutMs?: number }
): Promise<Buffer>;

/**
 * Send multiple APDUs in sequence
 * @param apdus - Array of APDU buffers
 * @param observer - Observer to receive individual responses
 * @returns Subscription object with unsubscribe method
 */
exchangeBulk(apdus: Buffer[], observer: Observer<Buffer>): Subscription;

Usage Example:

import TransportNodeHid from "@ledgerhq/hw-transport-node-hid";
import { StatusCodes } from "@ledgerhq/errors";

const transport = await TransportNodeHid.create();

// Low-level APDU exchange
const apduBuffer = Buffer.from([0xe0, 0x01, 0x00, 0x00]);
const response = await transport.exchange(apduBuffer);

// High-level structured command
const response2 = await transport.send(
  0xe0, // CLA
  0x01, // INS
  0x00, // P1
  0x00, // P2
  Buffer.from("data"), // Data
  [StatusCodes.OK, 0x6985] // Acceptable status codes
);

// Bulk APDU operations
const apdus = [
  Buffer.from([0xe0, 0x01, 0x00, 0x00]),
  Buffer.from([0xe0, 0x02, 0x00, 0x00])
];

const subscription = transport.exchangeBulk(apdus, {
  next: (response) => console.log("Response:", response),
  error: (err) => console.error("Error:", err),
  complete: () => console.log("All APDUs completed")
});

Transport Configuration and Events

Configuration options and event handling for transport instances.

/**
 * Set timeout for exchange operations
 * @param exchangeTimeout - Timeout in milliseconds
 */
setExchangeTimeout(exchangeTimeout: number): void;

/**
 * Set timeout before emitting unresponsive event
 * @param unresponsiveTimeout - Timeout in milliseconds
 */
setExchangeUnresponsiveTimeout(unresponsiveTimeout: number): void;

/**
 * Add event listener for transport events
 * @param eventName - Event name (e.g., "disconnect", "unresponsive", "responsive")
 * @param cb - Callback function
 */
on(eventName: string, cb: (...args: any[]) => any): void;

/**
 * Remove event listener
 * @param eventName - Event name
 * @param cb - Callback function to remove
 */
off(eventName: string, cb: (...args: any[]) => any): void;

/**
 * Set scramble key for data exchanges (deprecated)
 * @param key - Optional scramble key
 * @deprecated This method is no longer needed for modern transports
 */
setScrambleKey(key?: string): void;

/**
 * Deprecated debug method (logs deprecation warning)
 * @deprecated Use @ledgerhq/logs instead
 */
setDebugMode(): void;

Usage Example:

const transport = await TransportNodeHid.create();

// Configure timeouts
transport.setExchangeTimeout(60000); // 60 seconds
transport.setExchangeUnresponsiveTimeout(30000); // 30 seconds

// Listen for events
transport.on("disconnect", () => {
  console.log("Device disconnected");
});

transport.on("unresponsive", () => {
  console.log("Device is not responding");
});

transport.on("responsive", () => {
  console.log("Device is responsive again");
});

Tracing and Debugging

Logging and tracing functionality for debugging transport operations.

/**
 * Set tracing context for logging
 * @param context - Optional TraceContext object
 */
setTraceContext(context?: TraceContext): void;

/**
 * Update existing tracing context
 * @param contextToAdd - TraceContext to merge with current context
 */
updateTraceContext(contextToAdd: TraceContext): void;

/**
 * Get current tracing context
 * @returns Current TraceContext or undefined
 */
getTraceContext(): TraceContext | undefined;

Types

/**
 * Observer pattern interface for handling events
 */
interface Observer<EventType, EventError = unknown> {
  next: (event: EventType) => unknown;
  error: (e: EventError) => unknown;
  complete: () => unknown;
}

/**
 * Subscription interface for cancelling event listeners
 */
interface Subscription {
  unsubscribe: () => void;
}

/**
 * Device event descriptor for add/remove notifications
 */
interface DescriptorEvent<Descriptor> {
  type: "add" | "remove";
  descriptor: Descriptor;
  deviceModel?: DeviceModel | null | undefined;
  device?: Device;
}

/**
 * Device model information
 */
interface DeviceModel {
  id: string;
  productName: string;
  productIdMM: number;
  legacyUsbProductId: number;
  usbOnly: boolean;
  memorySize: number;
  masks: number[];
  getBlockSize: (firmwareVersion: string) => number;
  bluetoothSpec?: {
    serviceUuid: string;
    writeUuid: string;
    writeCmdUuid: string;
    notifyUuid: string;
  }[];
}

/**
 * Generic device object type
 */
type Device = any;

/**
 * Tracing context for logging operations
 */
type TraceContext = Record<string, any>;

/**
 * Log type for tracing operations
 */
type LogType = string;

Error Handling

The package uses error types from @ledgerhq/errors:

/**
 * General transport errors
 */
class TransportError extends Error {
  constructor(message: string, id: string);
}

/**
 * APDU status code errors
 */
class TransportStatusError extends TransportError {
  constructor(statusCode: number);
  statusCode: number;
}

/**
 * Concurrent operation errors
 */
class TransportRaceCondition extends TransportError {
  constructor(message: string);
}

/**
 * Device disconnection errors
 */
class DisconnectedDevice extends TransportError {
  constructor(message?: string);
}

/**
 * Errors during active operations
 */
class DisconnectedDeviceDuringOperation extends TransportError {
  constructor(message: string);
}

/**
 * Status code constants
 */
const StatusCodes: {
  OK: 0x9000;
  // Additional status codes...
};

Common Error Scenarios:

import TransportNodeHid from "@ledgerhq/hw-transport-node-hid";
import { TransportError, TransportStatusError } from "@ledgerhq/errors";

try {
  const transport = await TransportNodeHid.create();
  const response = await transport.exchange(apduBuffer);
} catch (error) {
  if (error instanceof TransportError) {
    if (error.id === "NoDevice") {
      console.error("No Ledger device found");
    } else if (error.id === "ListenTimeout") {
      console.error("Device discovery timed out");
    }
  } else if (error instanceof TransportStatusError) {
    console.error("Device returned error status:", error.statusCode.toString(16));
  }
}

Platform Support

  • Supported Platforms: Node.js environments (Windows, macOS, Linux)
  • Requirements: Node.js with node-hid and usb native modules
  • Device Compatibility: Ledger Nano S, Nano X, Nano S Plus, Blue, and other Ledger hardware wallets
  • Limitations: Desktop/server environments only (not browser-compatible)

Installation Requirements

The package requires native dependencies:

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

# On Linux, you may need additional system dependencies:
# sudo apt-get install libudev-dev libusb-1.0-0-dev

# On Windows, you may need build tools:
# npm install --global windows-build-tools