Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d7e6756
feat: add new hardware errors
montelaidev Jan 8, 2026
8023f97
fix: casing
montelaidev Jan 8, 2026
b86b627
refactor: remove unneeded
montelaidev Jan 8, 2026
5777cba
fix: refactor to pascal case and remove unused
montelaidev Jan 8, 2026
53206ed
fix: align key
montelaidev Jan 8, 2026
7d906ee
fix: test
montelaidev Jan 8, 2026
cae2667
refactor: remove codes and refactor error values to numbers
montelaidev Jan 9, 2026
c4cd240
fix: add code
montelaidev Jan 9, 2026
8cf427b
fix: test
montelaidev Jan 9, 2026
928e0de
fix: use Error for value
montelaidev Jan 12, 2026
9a617f4
fix: unknown error code
montelaidev Jan 12, 2026
a829975
feat: add erro prefix helper
montelaidev Jan 12, 2026
6e59f1f
fix; update subcategory
montelaidev Jan 12, 2026
131e513
fix: lint and test
montelaidev Jan 12, 2026
b3f1404
fix: fallback
montelaidev Jan 13, 2026
de46ef7
Apply suggestions from code review
montelaidev Jan 13, 2026
a555c4b
fix: remove retryStrategy and userActionable
montelaidev Jan 13, 2026
4c01d43
fix: remove trezor
montelaidev Jan 13, 2026
f5b2230
fix: lint
montelaidev Jan 13, 2026
85fe698
fix: remove unused
montelaidev Jan 13, 2026
ce66cea
fix test
montelaidev Jan 13, 2026
954c66a
fix: refactor mapping and update enum key
montelaidev Jan 13, 2026
6fc8735
Merge remote-tracking branch 'origin/main' into feat/hardware-errors-…
montelaidev Jan 13, 2026
414fef0
refactor: move to new package
montelaidev Jan 13, 2026
b865d0a
fix: lint
montelaidev Jan 14, 2026
8d064af
fix: enum name and change customCode to code
montelaidev Jan 14, 2026
4ff27b1
fix: remove dep
montelaidev Jan 14, 2026
95c731c
fix: changelog
montelaidev Jan 14, 2026
9b4fd1b
Apply suggestions from code review
montelaidev Jan 14, 2026
b91f07e
fix: tests
montelaidev Jan 14, 2026
cd66ace
fix: test
montelaidev Jan 14, 2026
887c781
fix: changelog
montelaidev Jan 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/hw-device-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
"test:types": "../../scripts/tsd-test.sh ./src",
"test:watch": "jest --watch"
},
"dependencies": {
"@metamask/keyring-utils": "workspace:^"
},
"devDependencies": {
"@lavamoat/allow-scripts": "^3.2.1",
"@lavamoat/preinstall-always-fail": "^2.1.0",
Expand Down
117 changes: 117 additions & 0 deletions packages/hw-device-sdk/src/hardware-error-mappings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { LEDGER_ERROR_MAPPINGS } from './hardware-error-mappings';
import { ErrorCode, Severity, Category } from './hardware-errors-enums';

describe('HARDWARE_ERROR_MAPPINGS', () => {
describe('Ledger mappings', () => {
const errorMappings = LEDGER_ERROR_MAPPINGS;

it('have errorMappings object', () => {
expect(errorMappings).toBeDefined();
expect(typeof errorMappings).toBe('object');
});

describe('success codes', () => {
it('map 0x9000 to success', () => {
const mapping = errorMappings['0x9000'];
expect(mapping).toBeDefined();
expect(mapping.customCode).toBe(ErrorCode.Success);
expect(mapping.severity).toBe(Severity.Info);
expect(mapping.category).toBe(Category.Success);
});
});

describe('authentication errors', () => {
it('map 0x6300 to authentication failed', () => {
const mapping = errorMappings['0x6300'];
expect(mapping.customCode).toBe(ErrorCode.AuthenticationFailed);
expect(mapping.severity).toBe(Severity.Err);
expect(mapping.category).toBe(Category.Authentication);
expect(mapping.userMessage).toBeDefined();
});

it('map 0x63c0 to PIN attempts remaining', () => {
const mapping = errorMappings['0x63c0'];
expect(mapping.customCode).toBe(
ErrorCode.AuthenticationPinAttemptsRemaining,
);
expect(mapping.severity).toBe(Severity.Warning);
});

it('map 0x5515 to device locked', () => {
const mapping = errorMappings['0x5515'];
expect(mapping.customCode).toBe(ErrorCode.AuthenticationDeviceLocked);
expect(mapping.severity).toBe(Severity.Err);
expect(mapping.userMessage).toContain('unlock');
});

it('map 0x9840 to device blocked', () => {
const mapping = errorMappings['0x9840'];
expect(mapping.customCode).toBe(ErrorCode.AuthenticationDeviceBlocked);
expect(mapping.severity).toBe(Severity.Critical);
});
});

describe('user action errors', () => {
it('map 0x6985 to user rejected', () => {
const mapping = errorMappings['0x6985'];
expect(mapping.customCode).toBe(ErrorCode.UserRejected);
expect(mapping.severity).toBe(Severity.Warning);
expect(mapping.category).toBe(Category.UserAction);
});

it('map 0x5501 to user refused', () => {
const mapping = errorMappings['0x5501'];
expect(mapping.customCode).toBe(ErrorCode.UserRejected);
expect(mapping.severity).toBe(Severity.Warning);
});
});
describe('connection errors', () => {
it('map 0x650f to connection issue', () => {
const mapping = errorMappings['0x650f'];
expect(mapping.customCode).toBe(ErrorCode.ConnClosed);
expect(mapping.category).toBe(Category.Connection);
});
});

it('have valid structure for all mappings', () => {
Object.entries(errorMappings).forEach(([_, mapping]) => {
expect(mapping).toHaveProperty('customCode');
expect(mapping).toHaveProperty('message');
expect(mapping).toHaveProperty('severity');
expect(mapping).toHaveProperty('category');

const numericErrorCodes = Object.values(ErrorCode).filter(
(value): value is number => typeof value === 'number',
);
expect(numericErrorCodes).toContain(mapping.customCode);
expect(Object.values(Severity)).toContain(mapping.severity);
expect(Object.values(Category)).toContain(mapping.category);
expect(typeof mapping.message).toBe('string');
});
});

it('have valid userMessage when present', () => {
const mappingsWithUserMessage = Object.values(errorMappings).filter(
(mapping): mapping is typeof mapping & { userMessage: string } =>
'userMessage' in mapping &&
typeof mapping.userMessage === 'string' &&
mapping.userMessage.length > 0,
);
expect(mappingsWithUserMessage.length).toBeGreaterThan(0);
mappingsWithUserMessage.forEach((mapping) => {
expect(typeof mapping.userMessage).toBe('string');
expect(mapping.userMessage.length).toBeGreaterThan(0);
});
});
});

describe('consistency checks', () => {
it('have unique error codes', () => {
const ledgerCodes = Object.values(LEDGER_ERROR_MAPPINGS);
const ledgerCustomCodes = ledgerCodes.map(
(mapping) => mapping.customCode,
);
expect(ledgerCustomCodes.length).toBeGreaterThan(0);
});
});
});
117 changes: 117 additions & 0 deletions packages/hw-device-sdk/src/hardware-error-mappings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { ErrorCode, Severity, Category } from './hardware-errors-enums';

export const LEDGER_ERROR_MAPPINGS = {
'0x9000': {
customCode: ErrorCode.Success,
message: 'Operation successful',
severity: Severity.Info,
category: Category.Success,
},
'0x6300': {
customCode: ErrorCode.AuthenticationFailed,
message: 'Authentication failed',
severity: Severity.Err,
category: Category.Authentication,
userMessage: 'Authentication failed. Please verify your credentials.',
},
'0x63c0': {
customCode: ErrorCode.AuthenticationPinAttemptsRemaining,
message: 'PIN attempts remaining',
severity: Severity.Warning,
category: Category.Authentication,
userMessage: 'Incorrect PIN. Please try again.',
},
'0x6982': {
customCode: ErrorCode.AuthenticationSecurityCondition,
message: 'Security conditions not satisfied',
severity: Severity.Err,
category: Category.Authentication,

userMessage:
'Device is locked or access rights are insufficient. Please unlock your device.',
},
'0x6985': {
customCode: ErrorCode.UserRejected,
message: 'User rejected action on device',
severity: Severity.Warning,
category: Category.UserAction,

userMessage:
'Transaction was rejected. Please approve on your device to continue.',
},
'0x9804': {
customCode: ErrorCode.AuthenticationSecurityCondition,
message: 'App update required',
severity: Severity.Err,
category: Category.Authentication,

userMessage: 'Please update your Ledger app to continue.',
},
'0x9808': {
customCode: ErrorCode.AuthenticationFailed,
message: 'Contradiction in secret code status',
severity: Severity.Err,
category: Category.Authentication,
},
'0x9840': {
customCode: ErrorCode.AuthenticationDeviceBlocked,
message: 'Code blocked',
severity: Severity.Critical,
category: Category.Authentication,

userMessage:
'Your device is blocked due to too many failed attempts. Please follow device recovery procedures.',
},
'0x650f': {
customCode: ErrorCode.ConnClosed,
message: 'App closed or connection issue',
severity: Severity.Err,
category: Category.Connection,
userMessage:
'Connection lost or app closed. Please open the corresponding app on your Ledger device.',
},
'0x5515': {
customCode: ErrorCode.AuthenticationDeviceLocked,
message: 'Device is locked',
severity: Severity.Err,
category: Category.Authentication,
userMessage: 'Please unlock your Ledger device to continue.',
},
'0x5501': {
customCode: ErrorCode.UserRejected,
message: 'User refused on device',
severity: Severity.Warning,
category: Category.UserAction,
userMessage:
'Operation was rejected. Please approve on your device to continue.',
},
'0x6a80': {
customCode: ErrorCode.DeviceStateBlindSignNotSupported,
message: 'Blind signing not supported',
severity: Severity.Err,
category: Category.DeviceState,
userMessage: 'Blind signing is not supported on this device.',
},
'0x6d00': {
customCode: ErrorCode.DeviceStateOnlyV4Supported,
message: 'Ledger Only V4 supported',
severity: Severity.Err,
category: Category.DeviceState,
userMessage: 'Only V4 is supported on this device.',
},
'0x6e00': {
customCode: ErrorCode.DeviceStateEthAppClosed,
message: 'Ethereum app closed',
severity: Severity.Err,
category: Category.DeviceState,
userMessage: 'Ethereum app is closed. Please open it to continue.',
},
'0x6501': {
customCode: ErrorCode.DeviceStateEthAppOutOfDate,
message: 'Ethereum app out of date',
severity: Severity.Err,
category: Category.DeviceState,
userMessage: 'Ethereum app is out of date. Please update it to continue.',
},
};
Loading
Loading