Ledger Hardware Wallet common interface of the communication layer
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Low-level and high-level methods for sending Application Protocol Data Units (APDUs) to Ledger devices. APDUs are the standard command format for communicating with smart cards and hardware security modules.
Direct APDU exchange method intended for transport implementations. Application developers should prefer the send() method instead.
/**
* Low level API to communicate with the device
* This method is for implementations to implement but should not be directly called
* Instead, the recommended way is to use send() method
* @param apdu The APDU data to send as a Buffer
* @returns Promise of response data as Buffer
*/
exchange(apdu: Buffer): Promise<Buffer>;Note: This method is abstract in the base Transport class and must be implemented by concrete transport implementations. Applications should use send() instead.
Internal method that ensures atomic execution of exchange operations with race condition protection and unresponsive device detection.
/**
* Internal atomic exchange implementation with race condition protection
* This method wraps exchange operations to ensure atomicity and provide unresponsive detection
* @param f Function to execute atomically
* @returns Promise resolving to the function result
* @internal
*/
exchangeAtomicImpl(f: () => Promise<any>): Promise<any>;Internal Properties:
class Transport<Descriptor> {
/** Promise tracking current exchange operation for race condition prevention */
exchangeBusyPromise: Promise<void> | null = null;
}How it works:
exchangeBusyPromise)TransportRaceCondition if another operation is pendingThis method is used internally by the send() method to ensure atomic operations and prevent race conditions.
Wrapper on top of exchange() that provides a more convenient interface for sending APDU commands with automatic status code checking and error handling.
/**
* Wrapper on top of exchange to simplify work of the implementation
* @param cla Class byte (instruction class)
* @param ins Instruction byte (command code)
* @param p1 Parameter 1 byte
* @param p2 Parameter 2 byte
* @param data Data payload (optional, default: empty buffer)
* @param statusList List of accepted status codes (default: [0x9000])
* @returns Promise of response buffer (without status bytes)
*/
send(
cla: number,
ins: number,
p1: number,
p2: number,
data?: Buffer = Buffer.alloc(0),
statusList?: Array<number> = [StatusCodes.OK]
): Promise<Buffer>;Usage Examples:
import Transport, { StatusCodes } from "@ledgerhq/hw-transport";
// Assuming you have a transport instance
const transport = await MyTransport.create();
// Get application version (example APDU)
const versionResponse = await transport.send(
0xB0, // CLA: application class
0x01, // INS: get version instruction
0x00, // P1: parameter 1
0x00 // P2: parameter 2
// data: omitted (empty)
// statusList: omitted (defaults to [0x9000])
);
// Send data with custom status codes
const data = Buffer.from("hello", "utf8");
const response = await transport.send(
0xB0, // CLA
0x02, // INS
0x00, // P1
0x00, // P2
data, // Data payload
[StatusCodes.OK, 0x6A80] // Accept OK or INCORRECT_DATA
);
// Handle large data (automatically validates < 256 bytes)
try {
const largeData = Buffer.alloc(300); // This will throw an error
await transport.send(0xB0, 0x03, 0x00, 0x00, largeData);
} catch (error) {
if (error.id === "DataLengthTooBig") {
console.error("Data too large:", error.message);
}
}APDUs follow the ISO 7816-4 standard structure:
| CLA | INS | P1 | P2 | Lc | Data | Le |Where:
send())APDU responses contain:
| Data | SW1 | SW2 |Where:
The send() method automatically:
exchange()statusListTransportStatusError for unaccepted status codes// Get app version
const version = await transport.send(0xB0, 0x01, 0x00, 0x00);
// Get device public key
const publicKey = await transport.send(
0xE0, // CLA for crypto operations
0x02, // INS for get public key
0x00, // P1: display on screen
0x00, // P2: return key format
derivationPath // BIP32 path as Buffer
);// Sign transaction in chunks
const chunks = splitTransactionIntoChunks(transactionData);
for (let i = 0; i < chunks.length; i++) {
const isFirst = i === 0;
const isLast = i === chunks.length - 1;
const response = await transport.send(
0xE0, // CLA
0x04, // INS for transaction signing
isFirst ? 0x00 : 0x80, // P1: first chunk vs continuation
isLast ? 0x80 : 0x00, // P2: last chunk marker
chunks[i] // Chunk data
);
if (isLast) {
// Final response contains signature
return response;
}
}import { StatusCodes } from "@ledgerhq/hw-transport";
try {
const signature = await transport.send(
0xE0, 0x04, 0x01, 0x00,
transactionData,
[StatusCodes.OK, StatusCodes.CONDITIONS_OF_USE_NOT_SATISFIED]
);
console.log("Transaction signed:", signature);
} catch (error) {
if (error instanceof TransportStatusError) {
if (error.statusCode === StatusCodes.CONDITIONS_OF_USE_NOT_SATISFIED) {
console.log("User rejected the transaction");
} else {
console.error("Device error:", error.statusText);
}
}
}APDU communication can fail with various errors:
// Automatic validation prevents oversized commands
try {
const largeData = Buffer.alloc(256); // Exactly 256 bytes
await transport.send(0xB0, 0x01, 0x00, 0x00, largeData);
} catch (error) {
// TransportError with id "DataLengthTooBig"
console.error(error.message); // "data.length exceed 256 bytes limit. Got: 256"
}import { StatusCodes, TransportStatusError } from "@ledgerhq/hw-transport";
try {
await transport.send(0xB0, 0xFF, 0x00, 0x00); // Invalid instruction
} catch (error) {
if (error instanceof TransportStatusError) {
console.log("Status code:", error.statusCode.toString(16)); // "6d00"
console.log("Status text:", error.statusText); // "INS_NOT_SUPPORTED"
console.log("Message:", error.message); // "Ledger device: Instruction not supported (0x6d00)"
}
}