or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

apdu-communication.mdconfiguration.mddevice-management.mderror-handling.mdevents-lifecycle.mdindex.md
tile.json

error-handling.mddocs/

0

# Error Handling

1

2

Comprehensive error classes and patterns for handling transport-specific and device status errors. The @ledgerhq/hw-transport package provides detailed error information with specific error codes, status messages, and recovery strategies.

3

4

## Capabilities

5

6

### Transport Errors

7

8

Generic transport errors that occur during communication setup, data exchange, or connection management.

9

10

```typescript { .api }

11

/**

12

* TransportError is used for any generic transport errors

13

* e.g. Error thrown when data received by exchanges are incorrect or if exchange failed to communicate with the device for various reasons

14

*/

15

class TransportError extends Error {

16

constructor(message: string, id: string);

17

name: "TransportError";

18

message: string;

19

id: string;

20

stack: string;

21

}

22

```

23

24

**Usage Example:**

25

26

```javascript

27

import { TransportError } from "@ledgerhq/hw-transport";

28

29

try {

30

const transport = await MyTransport.create();

31

const largeData = Buffer.alloc(300); // Too large for APDU

32

await transport.send(0xB0, 0x01, 0x00, 0x00, largeData);

33

34

} catch (error) {

35

if (error instanceof TransportError) {

36

console.log("Transport error ID:", error.id); // "DataLengthTooBig"

37

console.log("Error message:", error.message); // "data.length exceed 256 bytes limit. Got: 300"

38

39

// Handle specific transport errors

40

switch (error.id) {

41

case "DataLengthTooBig":

42

console.log("Split data into smaller chunks");

43

break;

44

case "NoDeviceFound":

45

console.log("Please connect your Ledger device");

46

break;

47

case "ListenTimeout":

48

console.log("Timeout waiting for device - please retry");

49

break;

50

default:

51

console.log("Unknown transport error:", error.id);

52

}

53

}

54

}

55

```

56

57

### Device Status Errors

58

59

Errors returned by the Ledger device as status codes, indicating specific device states or command failures.

60

61

```typescript { .api }

62

/**

63

* Error thrown when a device returned a non-success status

64

* The error.statusCode is one of the StatusCodes exported by this library

65

*/

66

class TransportStatusError extends Error {

67

constructor(statusCode: number);

68

name: "TransportStatusError";

69

message: string;

70

statusCode: number;

71

statusText: string;

72

stack: string;

73

}

74

```

75

76

### Race Condition Errors

77

78

Errors that occur when multiple operations attempt to use the transport simultaneously, violating the atomic operation requirement.

79

80

```javascript { .api }

81

/**

82

* Error thrown when an operation is attempted while another operation is in progress

83

* Prevents race conditions by enforcing atomic operations

84

*/

85

class TransportRaceCondition extends Error {

86

constructor(message: string);

87

name: "TransportRaceCondition";

88

message: string;

89

stack: string;

90

}

91

```

92

93

**Usage Example:**

94

95

```javascript

96

import { TransportRaceCondition } from "@ledgerhq/hw-transport";

97

98

// Example of race condition prevention

99

async function performOperation(transport) {

100

try {

101

// Start first operation

102

const operation1 = transport.send(0xB0, 0x01, 0x00, 0x00);

103

104

// Attempt second operation while first is running - this will throw

105

const operation2 = transport.send(0xB0, 0x02, 0x00, 0x00);

106

107

} catch (error) {

108

if (error instanceof TransportRaceCondition) {

109

console.log("Race condition detected:", error.message);

110

// "An action was already pending on the Ledger device. Please deny or reconnect."

111

112

// Wait for first operation to complete, then retry

113

await new Promise(resolve => setTimeout(resolve, 1000));

114

return performOperation(transport);

115

}

116

}

117

}

118

```

119

120

**Transport Status Error Usage:**

121

122

```javascript

123

import { TransportStatusError, StatusCodes } from "@ledgerhq/hw-transport";

124

125

try {

126

// Attempt to sign a transaction

127

const signature = await transport.send(0xE0, 0x04, 0x01, 0x00, transactionData);

128

129

} catch (error) {

130

if (error instanceof TransportStatusError) {

131

console.log("Status code:", "0x" + error.statusCode.toString(16));

132

console.log("Status text:", error.statusText);

133

console.log("Message:", error.message);

134

135

// Handle specific device status codes

136

switch (error.statusCode) {

137

case StatusCodes.CONDITIONS_OF_USE_NOT_SATISFIED:

138

console.log("User rejected the transaction");

139

showUserMessage("Transaction cancelled by user");

140

break;

141

142

case StatusCodes.SECURITY_STATUS_NOT_SATISFIED:

143

console.log("Device is locked or access denied");

144

showUserMessage("Please unlock your device and try again");

145

break;

146

147

case StatusCodes.INCORRECT_DATA:

148

console.log("Invalid transaction data");

149

showUserMessage("Transaction data is invalid");

150

break;

151

152

case StatusCodes.INS_NOT_SUPPORTED:

153

console.log("Command not supported by this app");

154

showUserMessage("Please open the correct app on your device");

155

break;

156

157

default:

158

console.log("Unknown device error:", error.statusText);

159

showUserMessage("Device error: " + error.message);

160

}

161

}

162

}

163

```

164

165

### Status Codes

166

167

Comprehensive set of status codes returned by Ledger devices, following ISO 7816-4 standards.

168

169

```typescript { .api }

170

interface StatusCodes {

171

// Success

172

OK: 0x9000;

173

174

// Parameter errors

175

INCORRECT_LENGTH: 0x6700;

176

MISSING_CRITICAL_PARAMETER: 0x6800;

177

INCORRECT_DATA: 0x6A80;

178

INCORRECT_P1_P2: 0x6B00;

179

180

// Security errors

181

SECURITY_STATUS_NOT_SATISFIED: 0x6982;

182

CONDITIONS_OF_USE_NOT_SATISFIED: 0x6985;

183

184

// Command errors

185

INS_NOT_SUPPORTED: 0x6D00;

186

CLA_NOT_SUPPORTED: 0x6E00;

187

188

// System errors

189

TECHNICAL_PROBLEM: 0x6F00;

190

NOT_ENOUGH_MEMORY_SPACE: 0x6A84;

191

192

// Authentication errors

193

PIN_REMAINING_ATTEMPTS: 0x63C0;

194

CODE_BLOCKED: 0x9840;

195

196

// And many more...

197

}

198

```

199

200

### Status Message Helper

201

202

Utility function to get human-readable messages for status codes.

203

204

```typescript { .api }

205

/**

206

* Get alternative status message for common error codes

207

* @param code Status code number

208

* @returns Human-readable error message or undefined

209

*/

210

function getAltStatusMessage(code: number): string | undefined | null;

211

```

212

213

**Usage Example:**

214

215

```javascript

216

import { getAltStatusMessage, StatusCodes } from "@ledgerhq/hw-transport";

217

218

// Get user-friendly messages

219

console.log(getAltStatusMessage(0x6700)); // "Incorrect length"

220

console.log(getAltStatusMessage(0x6982)); // "Security not satisfied (dongle locked or have invalid access rights)"

221

console.log(getAltStatusMessage(0x6985)); // "Condition of use not satisfied (denied by the user?)"

222

223

// Use in error handling

224

function handleStatusError(statusCode) {

225

const friendlyMessage = getAltStatusMessage(statusCode);

226

if (friendlyMessage) {

227

console.log("User-friendly error:", friendlyMessage);

228

} else {

229

console.log("Status code:", "0x" + statusCode.toString(16));

230

}

231

}

232

```

233

234

## Common Error Patterns

235

236

### Connection and Discovery Errors

237

238

```javascript

239

// Device not found

240

try {

241

const transport = await MyTransport.create();

242

} catch (error) {

243

if (error instanceof TransportError && error.id === "NoDeviceFound") {

244

console.log("No Ledger device found. Please:");

245

console.log("1. Connect your device");

246

console.log("2. Make sure it's unlocked");

247

console.log("3. Open the correct app");

248

}

249

}

250

251

// Platform not supported

252

try {

253

const isSupported = await MyTransport.isSupported();

254

if (!isSupported) {

255

throw new Error("Transport not supported");

256

}

257

} catch (error) {

258

console.log("This browser/platform doesn't support Ledger connectivity");

259

console.log("Please try Chrome, Firefox, or use the desktop app");

260

}

261

```

262

263

### User Interaction Errors

264

265

```javascript

266

async function handleUserRejection(operation) {

267

try {

268

return await operation();

269

} catch (error) {

270

if (error instanceof TransportStatusError) {

271

switch (error.statusCode) {

272

case StatusCodes.CONDITIONS_OF_USE_NOT_SATISFIED:

273

return { rejected: true, reason: "user_cancelled" };

274

275

case StatusCodes.SECURITY_STATUS_NOT_SATISFIED:

276

return { rejected: true, reason: "device_locked" };

277

278

default:

279

throw error; // Re-throw other status errors

280

}

281

}

282

throw error; // Re-throw non-status errors

283

}

284

}

285

286

// Usage

287

const result = await handleUserRejection(async () => {

288

return await transport.send(0xE0, 0x04, 0x01, 0x00, txData);

289

});

290

291

if (result.rejected) {

292

if (result.reason === "user_cancelled") {

293

showMessage("Transaction cancelled by user");

294

} else if (result.reason === "device_locked") {

295

showMessage("Please unlock your device and try again");

296

}

297

} else {

298

showMessage("Transaction signed successfully");

299

}

300

```

301

302

### Data Validation Errors

303

304

```javascript

305

function validateAndSend(transport, data) {

306

// Pre-validate data size

307

if (data.length >= 256) {

308

throw new TransportError(

309

`Data too large: ${data.length} bytes (max 255)`,

310

"DataLengthTooBig"

311

);

312

}

313

314

// Pre-validate data format

315

if (!Buffer.isBuffer(data)) {

316

throw new TransportError(

317

"Data must be a Buffer",

318

"InvalidDataType"

319

);

320

}

321

322

return transport.send(0xB0, 0x01, 0x00, 0x00, data);

323

}

324

```

325

326

### Race Condition Handling

327

328

```javascript

329

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

330

331

async function safeExchange(transport, operation) {

332

try {

333

return await operation();

334

} catch (error) {

335

if (error instanceof TransportRaceCondition) {

336

console.log("Another operation is in progress");

337

338

// Wait and retry

339

await new Promise(resolve => setTimeout(resolve, 1000));

340

return await operation();

341

}

342

throw error;

343

}

344

}

345

```

346

347

## Error Recovery Strategies

348

349

### Automatic Retry with Backoff

350

351

```javascript

352

async function withRetry(operation, maxRetries = 3) {

353

let lastError;

354

355

for (let attempt = 1; attempt <= maxRetries; attempt++) {

356

try {

357

return await operation();

358

} catch (error) {

359

lastError = error;

360

361

// Determine if error is retryable

362

const retryableErrors = [

363

"TransportRaceCondition",

364

"DisconnectedDevice",

365

"TransportInterfaceNotAvailable"

366

];

367

368

if (error instanceof TransportError &&

369

retryableErrors.includes(error.id)) {

370

371

console.log(`Attempt ${attempt} failed: ${error.id}, retrying...`);

372

373

// Exponential backoff

374

const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);

375

await new Promise(resolve => setTimeout(resolve, delay));

376

377

} else {

378

// Non-retryable error

379

throw error;

380

}

381

}

382

}

383

384

throw lastError;

385

}

386

387

// Usage

388

const result = await withRetry(async () => {

389

const transport = await MyTransport.create();

390

try {

391

return await transport.send(0xB0, 0x01, 0x00, 0x00);

392

} finally {

393

await transport.close();

394

}

395

});

396

```

397

398

### Error Context Enhancement

399

400

```javascript

401

class EnhancedTransportError extends Error {

402

constructor(originalError, context) {

403

super(originalError.message);

404

this.name = "EnhancedTransportError";

405

this.originalError = originalError;

406

this.context = context;

407

this.timestamp = new Date().toISOString();

408

}

409

}

410

411

async function enhancedOperation(transport, operationName, operation) {

412

try {

413

return await operation();

414

} catch (error) {

415

const context = {

416

operation: operationName,

417

deviceModel: transport.deviceModel?.id,

418

exchangeTimeout: transport.exchangeTimeout,

419

timestamp: new Date().toISOString()

420

};

421

422

throw new EnhancedTransportError(error, context);

423

}

424

}

425

```

426

427

### User-Friendly Error Messages

428

429

```javascript

430

function getUserFriendlyErrorMessage(error) {

431

if (error instanceof TransportStatusError) {

432

const messages = {

433

[StatusCodes.CONDITIONS_OF_USE_NOT_SATISFIED]:

434

"Please approve the action on your Ledger device",

435

[StatusCodes.SECURITY_STATUS_NOT_SATISFIED]:

436

"Please unlock your Ledger device",

437

[StatusCodes.INS_NOT_SUPPORTED]:

438

"Please open the correct app on your Ledger device",

439

[StatusCodes.INCORRECT_DATA]:

440

"Invalid data format - please check your input",

441

[StatusCodes.NOT_ENOUGH_MEMORY_SPACE]:

442

"Not enough space on your Ledger device"

443

};

444

445

return messages[error.statusCode] ||

446

`Device error: ${error.statusText}`;

447

}

448

449

if (error instanceof TransportError) {

450

const messages = {

451

"NoDeviceFound": "Please connect and unlock your Ledger device",

452

"ListenTimeout": "Device not found - please check connection",

453

"DataLengthTooBig": "Data too large - please contact support",

454

"TransportLocked": "Please wait for current operation to complete"

455

};

456

457

return messages[error.id] || `Connection error: ${error.message}`;

458

}

459

460

return "An unexpected error occurred. Please try again.";

461

}

462

```