From d7e6756df0707513da05ca61bdaf08eea97cfe7f Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Thu, 8 Jan 2026 13:02:29 +0800 Subject: [PATCH 01/24] feat: add new hardware errors --- .../src/hardware-error-codes.test.ts | 283 ++++++ .../keyring-utils/src/hardware-error-codes.ts | 86 ++ .../src/hardware-error-mappings.test.ts | 557 ++++++++++++ .../src/hardware-error-mappings.ts | 843 ++++++++++++++++++ .../keyring-utils/src/hardware-error.test.ts | 481 ++++++++++ packages/keyring-utils/src/hardware-error.ts | 234 +++++ .../src/hardware-errors-enums.ts | 121 +++ packages/keyring-utils/src/index.ts | 4 + 8 files changed, 2609 insertions(+) create mode 100644 packages/keyring-utils/src/hardware-error-codes.test.ts create mode 100644 packages/keyring-utils/src/hardware-error-codes.ts create mode 100644 packages/keyring-utils/src/hardware-error-mappings.test.ts create mode 100644 packages/keyring-utils/src/hardware-error-mappings.ts create mode 100644 packages/keyring-utils/src/hardware-error.test.ts create mode 100644 packages/keyring-utils/src/hardware-error.ts create mode 100644 packages/keyring-utils/src/hardware-errors-enums.ts diff --git a/packages/keyring-utils/src/hardware-error-codes.test.ts b/packages/keyring-utils/src/hardware-error-codes.test.ts new file mode 100644 index 000000000..2429b83e1 --- /dev/null +++ b/packages/keyring-utils/src/hardware-error-codes.test.ts @@ -0,0 +1,283 @@ +import * as errorCodes from './hardware-error-codes'; + +describe('hardware-error-codes', () => { + describe('exports', () => { + it('should export all error code constants', () => { + expect(Object.keys(errorCodes).length).toBeGreaterThan(0); + }); + + it('should export all constants as strings', () => { + Object.values(errorCodes).forEach((value) => { + expect(typeof value).toBe('string'); + }); + }); + }); + + describe('Authentication & Security codes', () => { + it('should have PIN error codes', () => { + expect(errorCodes.AUTH_PIN_001).toBe('PIN invalid'); + expect(errorCodes.AUTH_PIN_002).toBe('PIN cancelled by user'); + expect(errorCodes.AUTH_PIN_003).toBe('PIN attempts remaining'); + expect(errorCodes.AUTH_PIN_004).toBe('PIN mismatch'); + }); + + it('should have lock error codes', () => { + expect(errorCodes.AUTH_LOCK_001).toBe('Device is locked'); + expect(errorCodes.AUTH_LOCK_002).toBe( + 'Device blocked due to failed attempts', + ); + }); + + it('should have security error codes', () => { + expect(errorCodes.AUTH_SEC_001).toBe('Security conditions not satisfied'); + expect(errorCodes.AUTH_SEC_002).toBe('Access rights insufficient'); + }); + + it('should have wipe code error', () => { + expect(errorCodes.AUTH_WIPE_001).toBe('Wipe code mismatch'); + }); + }); + + describe('User Action codes', () => { + it('should have cancel error codes', () => { + expect(errorCodes.USER_CANCEL_001).toBe('User rejected action on device'); + expect(errorCodes.USER_CANCEL_002).toBe('User cancelled operation'); + }); + + it('should have user input codes', () => { + expect(errorCodes.USER_INPUT_001).toBe('User input expected'); + expect(errorCodes.USER_CONFIRM_001).toBe('User confirmation required'); + }); + }); + + describe('Device State codes', () => { + it('should have device state error codes', () => { + expect(errorCodes.DEVICE_STATE_001).toBe('Device not initialized'); + expect(errorCodes.DEVICE_STATE_002).toBe('Device busy'); + expect(errorCodes.DEVICE_STATE_003).toBe('Device disconnected'); + expect(errorCodes.DEVICE_STATE_004).toBe('Device used elsewhere'); + expect(errorCodes.DEVICE_STATE_005).toBe('Device call in progress'); + }); + + it('should have device detection code', () => { + expect(errorCodes.DEVICE_DETECT_001).toBe('Device not found'); + }); + + it('should have device capability codes', () => { + expect(errorCodes.DEVICE_CAP_001).toBe( + 'Device missing required capability', + ); + expect(errorCodes.DEVICE_CAP_002).toBe( + 'Device is BTC-only, operation not supported', + ); + }); + + it('should have device mode code', () => { + expect(errorCodes.DEVICE_MODE_001).toBe('Invalid device mode'); + }); + }); + + describe('Connection & Transport codes', () => { + it('should have transport error code', () => { + expect(errorCodes.CONN_TRANSPORT_001).toBe('Transport layer missing'); + }); + + it('should have connection error codes', () => { + expect(errorCodes.CONN_CLOSED_001).toBe('Connection closed unexpectedly'); + expect(errorCodes.CONN_IFRAME_001).toBe( + 'Unable to establish iframe connection', + ); + expect(errorCodes.CONN_SUITE_001).toBe('Unable to connect to Suite'); + expect(errorCodes.CONN_TIMEOUT_001).toBe('Connection timeout'); + expect(errorCodes.CONN_BLOCKED_001).toBe('Connection blocked'); + }); + }); + + describe('Data & Validation codes', () => { + it('should have data format error codes', () => { + expect(errorCodes.DATA_FORMAT_001).toBe('Incorrect data length'); + expect(errorCodes.DATA_FORMAT_002).toBe('Invalid data received'); + expect(errorCodes.DATA_FORMAT_003).toBe('Invalid parameter'); + }); + + it('should have data missing code', () => { + expect(errorCodes.DATA_MISSING_001).toBe('Missing critical parameter'); + }); + + it('should have data validation codes', () => { + expect(errorCodes.DATA_VALIDATION_001).toBe('Address mismatch'); + expect(errorCodes.DATA_VALIDATION_002).toBe('Invalid signature'); + }); + + it('should have data not found codes', () => { + expect(errorCodes.DATA_NOTFOUND_001).toBe('Referenced data not found'); + expect(errorCodes.DATA_NOTFOUND_002).toBe('File not found'); + expect(errorCodes.DATA_NOTFOUND_003).toBe('Coin not found'); + }); + }); + + describe('Cryptographic Operations codes', () => { + it('should have crypto error codes', () => { + expect(errorCodes.CRYPTO_SIGN_001).toBe('Signature operation failed'); + expect(errorCodes.CRYPTO_ALGO_001).toBe('Algorithm not supported'); + expect(errorCodes.CRYPTO_KEY_001).toBe('Invalid key check value'); + expect(errorCodes.CRYPTO_ENTROPY_001).toBe('Entropy check failed'); + }); + }); + + describe('System & Internal codes', () => { + it('should have internal error code', () => { + expect(errorCodes.SYS_INTERNAL_001).toBe('Internal device error'); + }); + + it('should have memory error codes', () => { + expect(errorCodes.SYS_MEMORY_001).toBe('Not enough memory'); + expect(errorCodes.SYS_MEMORY_002).toBe('Memory problem'); + }); + + it('should have file system error codes', () => { + expect(errorCodes.SYS_FILE_001).toBe('File system error'); + expect(errorCodes.SYS_FILE_002).toBe('Inconsistent file'); + }); + + it('should have license error code', () => { + expect(errorCodes.SYS_LICENSE_001).toBe('Licensing error'); + }); + + it('should have firmware error codes', () => { + expect(errorCodes.SYS_FIRMWARE_001).toBe('Firmware error'); + expect(errorCodes.SYS_FIRMWARE_002).toBe('Firmware installation failed'); + }); + }); + + describe('Command & Protocol codes', () => { + it('should have command error codes', () => { + expect(errorCodes.PROTO_CMD_001).toBe('Command not supported'); + expect(errorCodes.PROTO_CMD_002).toBe('Command incompatible'); + expect(errorCodes.PROTO_CMD_003).toBe('Unexpected message'); + }); + + it('should have protocol message codes', () => { + expect(errorCodes.PROTO_MSG_001).toBe('Invalid APDU command'); + expect(errorCodes.PROTO_PARAM_001).toBe('Invalid command parameters'); + }); + }); + + describe('Configuration & Initialization codes', () => { + it('should have initialization error codes', () => { + expect(errorCodes.CONFIG_INIT_001).toBe('Not initialized'); + expect(errorCodes.CONFIG_INIT_002).toBe('Already initialized'); + expect(errorCodes.CONFIG_INIT_003).toBe('Manifest missing'); + }); + + it('should have permission error code', () => { + expect(errorCodes.CONFIG_PERM_001).toBe('Permissions not granted'); + }); + + it('should have method error code', () => { + expect(errorCodes.CONFIG_METHOD_001).toBe('Method not allowed'); + }); + }); + + describe('Transaction codes', () => { + it('should have transaction error codes', () => { + expect(errorCodes.TX_FUNDS_001).toBe('Insufficient funds'); + expect(errorCodes.TX_FAIL_001).toBe('Transaction failed'); + }); + }); + + describe('Special codes', () => { + it('should have success code', () => { + expect(errorCodes.SUCCESS_000).toBe('Operation successful'); + }); + + it('should have unknown error code', () => { + expect(errorCodes.UNKNOWN_001).toBe('Unknown error'); + }); + }); + + describe('code uniqueness', () => { + it('should have unique error code identifiers', () => { + const codeNames = Object.keys(errorCodes); + const uniqueNames = new Set(codeNames); + expect(uniqueNames.size).toBe(codeNames.length); + }); + + it('should have unique error code messages', () => { + const codeValues = Object.values(errorCodes); + const uniqueValues = new Set(codeValues); + expect(uniqueValues.size).toBe(codeValues.length); + }); + }); + + describe('naming conventions', () => { + it('should follow naming pattern with underscores and numbers', () => { + const codeNames = Object.keys(errorCodes); + // Pattern: WORD_NNN or WORD_WORD_NNN (allows one or more words followed by numbers) + const pattern = /^[A-Z]+(_[A-Z0-9]+)*_\d+$/u; + + codeNames.forEach((name) => { + expect(name).toMatch(pattern); + }); + }); + + it('should have sequential numbering within categories', () => { + const categories: Record = {}; + + Object.keys(errorCodes).forEach((code) => { + const prefix = code.substring(0, code.lastIndexOf('_')); + if (!categories[prefix]) { + categories[prefix] = []; + } + categories[prefix].push(code); + }); + + // Check that each category has at least one code + Object.values(categories).forEach((codes) => { + expect(codes.length).toBeGreaterThan(0); + }); + }); + }); + + describe('error message quality', () => { + it('should have descriptive error messages', () => { + Object.values(errorCodes).forEach((message) => { + expect(message.length).toBeGreaterThan(5); + // Messages should start with a capital letter or be all caps + expect(message[0]).toMatch(/[A-Z]/u); + }); + }); + + it('should not end with punctuation', () => { + Object.values(errorCodes).forEach((message) => { + expect(message).not.toMatch(/[.!?]$/u); + }); + }); + }); + + describe('category coverage', () => { + it('should have error codes for all major categories', () => { + const codeNames = Object.keys(errorCodes); + + const categories = [ + 'AUTH', + 'USER', + 'DEVICE', + 'CONN', + 'DATA', + 'CRYPTO', + 'SYS', + 'PROTO', + 'CONFIG', + 'TX', + ]; + + categories.forEach((category) => { + const hasCategoryCode = codeNames.some((code) => + code.startsWith(category), + ); + expect(hasCategoryCode).toBe(true); + }); + }); + }); +}); diff --git a/packages/keyring-utils/src/hardware-error-codes.ts b/packages/keyring-utils/src/hardware-error-codes.ts new file mode 100644 index 000000000..2a1c4d568 --- /dev/null +++ b/packages/keyring-utils/src/hardware-error-codes.ts @@ -0,0 +1,86 @@ +// Authentication & Security +export const AUTH_PIN_001 = 'PIN invalid'; +export const AUTH_PIN_002 = 'PIN cancelled by user'; +export const AUTH_PIN_003 = 'PIN attempts remaining'; +export const AUTH_PIN_004 = 'PIN mismatch'; +export const AUTH_LOCK_001 = 'Device is locked'; +export const AUTH_LOCK_002 = 'Device blocked due to failed attempts'; +export const AUTH_SEC_001 = 'Security conditions not satisfied'; +export const AUTH_SEC_002 = 'Access rights insufficient'; +export const AUTH_WIPE_001 = 'Wipe code mismatch'; + +// User Action +export const USER_CANCEL_001 = 'User rejected action on device'; +export const USER_CANCEL_002 = 'User cancelled operation'; +export const USER_INPUT_001 = 'User input expected'; +export const USER_CONFIRM_001 = 'User confirmation required'; + +// Device State +export const DEVICE_STATE_001 = 'Device not initialized'; +export const DEVICE_STATE_002 = 'Device busy'; +export const DEVICE_STATE_003 = 'Device disconnected'; +export const DEVICE_STATE_004 = 'Device used elsewhere'; +export const DEVICE_STATE_005 = 'Device call in progress'; +export const DEVICE_DETECT_001 = 'Device not found'; +export const DEVICE_CAP_001 = 'Device missing required capability'; +export const DEVICE_CAP_002 = 'Device is BTC-only, operation not supported'; +export const DEVICE_MODE_001 = 'Invalid device mode'; + +// Connection & Transport +export const CONN_TRANSPORT_001 = 'Transport layer missing'; +export const CONN_CLOSED_001 = 'Connection closed unexpectedly'; +export const CONN_IFRAME_001 = 'Unable to establish iframe connection'; +export const CONN_SUITE_001 = 'Unable to connect to Suite'; +export const CONN_TIMEOUT_001 = 'Connection timeout'; +export const CONN_BLOCKED_001 = 'Connection blocked'; + +// Data & Validation +export const DATA_FORMAT_001 = 'Incorrect data length'; +export const DATA_FORMAT_002 = 'Invalid data received'; +export const DATA_FORMAT_003 = 'Invalid parameter'; +export const DATA_MISSING_001 = 'Missing critical parameter'; +export const DATA_VALIDATION_001 = 'Address mismatch'; +export const DATA_VALIDATION_002 = 'Invalid signature'; +export const DATA_NOTFOUND_001 = 'Referenced data not found'; +export const DATA_NOTFOUND_002 = 'File not found'; +export const DATA_NOTFOUND_003 = 'Coin not found'; + +// Cryptographic Operations +export const CRYPTO_SIGN_001 = 'Signature operation failed'; +export const CRYPTO_ALGO_001 = 'Algorithm not supported'; +export const CRYPTO_KEY_001 = 'Invalid key check value'; +export const CRYPTO_ENTROPY_001 = 'Entropy check failed'; + +// System & Internal +export const SYS_INTERNAL_001 = 'Internal device error'; +export const SYS_MEMORY_001 = 'Not enough memory'; +export const SYS_MEMORY_002 = 'Memory problem'; +export const SYS_FILE_001 = 'File system error'; +export const SYS_FILE_002 = 'Inconsistent file'; +export const SYS_LICENSE_001 = 'Licensing error'; +export const SYS_FIRMWARE_001 = 'Firmware error'; +export const SYS_FIRMWARE_002 = 'Firmware installation failed'; + +// Command & Protocol +export const PROTO_CMD_001 = 'Command not supported'; +export const PROTO_CMD_002 = 'Command incompatible'; +export const PROTO_CMD_003 = 'Unexpected message'; +export const PROTO_MSG_001 = 'Invalid APDU command'; +export const PROTO_PARAM_001 = 'Invalid command parameters'; + +// Configuration & Initialization +export const CONFIG_INIT_001 = 'Not initialized'; +export const CONFIG_INIT_002 = 'Already initialized'; +export const CONFIG_INIT_003 = 'Manifest missing'; +export const CONFIG_PERM_001 = 'Permissions not granted'; +export const CONFIG_METHOD_001 = 'Method not allowed'; + +// Transaction +export const TX_FUNDS_001 = 'Insufficient funds'; +export const TX_FAIL_001 = 'Transaction failed'; + +// Success +export const SUCCESS_000 = 'Operation successful'; + +// Unknown/Fallback +export const UNKNOWN_001 = 'Unknown error'; diff --git a/packages/keyring-utils/src/hardware-error-mappings.test.ts b/packages/keyring-utils/src/hardware-error-mappings.test.ts new file mode 100644 index 000000000..c155e724d --- /dev/null +++ b/packages/keyring-utils/src/hardware-error-mappings.test.ts @@ -0,0 +1,557 @@ +import { HARDWARE_MAPPINGS } from './hardware-error-mappings'; +import { + ErrorCode, + Severity, + Category, + RetryStrategy, +} from './hardware-errors-enums'; + +describe('HARDWARE_MAPPINGS', () => { + describe('structure', () => { + it('should have ledger and trezor vendors', () => { + expect(HARDWARE_MAPPINGS).toHaveProperty('ledger'); + expect(HARDWARE_MAPPINGS).toHaveProperty('trezor'); + }); + + it('should have vendor names', () => { + expect(HARDWARE_MAPPINGS.ledger.vendorName).toBe('Ledger'); + expect(HARDWARE_MAPPINGS.trezor.vendorName).toBe('Trezor'); + }); + }); + + describe('Ledger mappings', () => { + const { errorMappings } = HARDWARE_MAPPINGS.ledger; + + it('should have errorMappings object', () => { + expect(errorMappings).toBeDefined(); + expect(typeof errorMappings).toBe('object'); + }); + + describe('success codes', () => { + it('should map 0x9000 to success', () => { + const mapping = errorMappings['0x9000']; + expect(mapping).toBeDefined(); + expect(mapping.customCode).toBe(ErrorCode.SUCCESS_000); + expect(mapping.severity).toBe(Severity.INFO); + expect(mapping.category).toBe(Category.SUCCESS); + expect(mapping.retryStrategy).toBe(RetryStrategy.NO_RETRY); + expect(mapping.userActionable).toBe(false); + }); + }); + + describe('authentication errors', () => { + it('should map 0x6300 to authentication failed', () => { + const mapping = errorMappings['0x6300']; + expect(mapping.customCode).toBe(ErrorCode.AUTH_SEC_001); + expect(mapping.severity).toBe(Severity.ERROR); + expect(mapping.category).toBe(Category.AUTHENTICATION); + expect(mapping.userActionable).toBe(true); + expect(mapping.userMessage).toBeDefined(); + }); + + it('should map 0x63c0 to PIN attempts remaining', () => { + const mapping = errorMappings['0x63c0']; + expect(mapping.customCode).toBe(ErrorCode.AUTH_PIN_003); + expect(mapping.severity).toBe(Severity.WARNING); + expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + }); + + it('should map 0x5515 to device locked', () => { + const mapping = errorMappings['0x5515']; + expect(mapping.customCode).toBe(ErrorCode.AUTH_LOCK_001); + expect(mapping.severity).toBe(Severity.ERROR); + expect(mapping.userActionable).toBe(true); + expect(mapping.userMessage).toContain('unlock'); + }); + + it('should map 0x9840 to device blocked', () => { + const mapping = errorMappings['0x9840']; + expect(mapping.customCode).toBe(ErrorCode.AUTH_LOCK_002); + expect(mapping.severity).toBe(Severity.CRITICAL); + expect(mapping.retryStrategy).toBe(RetryStrategy.NO_RETRY); + }); + }); + + describe('user action errors', () => { + it('should map 0x6985 to user rejected', () => { + const mapping = errorMappings['0x6985']; + expect(mapping.customCode).toBe(ErrorCode.USER_CANCEL_001); + expect(mapping.severity).toBe(Severity.WARNING); + expect(mapping.category).toBe(Category.USER_ACTION); + expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + expect(mapping.userActionable).toBe(true); + }); + + it('should map 0x5501 to user refused', () => { + const mapping = errorMappings['0x5501']; + expect(mapping.customCode).toBe(ErrorCode.USER_CANCEL_001); + expect(mapping.severity).toBe(Severity.WARNING); + expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + }); + }); + + describe('data validation errors', () => { + it('should map 0x6700 to incorrect data length', () => { + const mapping = errorMappings['0x6700']; + expect(mapping.customCode).toBe(ErrorCode.DATA_FORMAT_001); + expect(mapping.category).toBe(Category.DATA_VALIDATION); + expect(mapping.userActionable).toBe(false); + }); + + it('should map 0x6a80 to invalid data', () => { + const mapping = errorMappings['0x6a80']; + expect(mapping.customCode).toBe(ErrorCode.DATA_FORMAT_002); + expect(mapping.category).toBe(Category.DATA_VALIDATION); + }); + + it('should map 0x6b00 to invalid parameter', () => { + const mapping = errorMappings['0x6b00']; + expect(mapping.customCode).toBe(ErrorCode.DATA_FORMAT_003); + expect(mapping.severity).toBe(Severity.ERROR); + }); + }); + + describe('protocol errors', () => { + it('should map 0x6981 to command incompatible', () => { + const mapping = errorMappings['0x6981']; + expect(mapping.customCode).toBe(ErrorCode.PROTO_CMD_002); + expect(mapping.category).toBe(Category.PROTOCOL); + }); + + it('should map 0x6d00 to instruction not supported', () => { + const mapping = errorMappings['0x6d00']; + expect(mapping.customCode).toBe(ErrorCode.PROTO_CMD_001); + expect(mapping.category).toBe(Category.PROTOCOL); + }); + + it('should map 0x6d02 to unknown APDU command', () => { + const mapping = errorMappings['0x6d02']; + expect(mapping.customCode).toBe(ErrorCode.PROTO_MSG_001); + expect(mapping.category).toBe(Category.PROTOCOL); + }); + }); + + describe('system errors', () => { + it('should map 0x6f00 to internal device error', () => { + const mapping = errorMappings['0x6f00']; + expect(mapping.customCode).toBe(ErrorCode.SYS_INTERNAL_001); + expect(mapping.severity).toBe(Severity.CRITICAL); + expect(mapping.category).toBe(Category.SYSTEM); + }); + + it('should map 0x6a84 to not enough memory', () => { + const mapping = errorMappings['0x6a84']; + expect(mapping.customCode).toBe(ErrorCode.SYS_MEMORY_001); + expect(mapping.category).toBe(Category.SYSTEM); + }); + + it('should map 0x6faa to device halted', () => { + const mapping = errorMappings['0x6faa']; + expect(mapping.customCode).toBe(ErrorCode.SYS_INTERNAL_001); + expect(mapping.severity).toBe(Severity.CRITICAL); + expect(mapping.userMessage).toContain('disconnect and reconnect'); + }); + }); + + describe('connection errors', () => { + it('should map 0x650f to connection issue', () => { + const mapping = errorMappings['0x650f']; + expect(mapping.customCode).toBe(ErrorCode.CONN_CLOSED_001); + expect(mapping.category).toBe(Category.CONNECTION); + expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + }); + }); + + describe('cryptographic errors', () => { + it('should map 0x9484 to algorithm not supported', () => { + const mapping = errorMappings['0x9484']; + expect(mapping.customCode).toBe(ErrorCode.CRYPTO_ALGO_001); + expect(mapping.category).toBe(Category.CRYPTOGRAPHY); + }); + + it('should map 0x9485 to invalid key check value', () => { + const mapping = errorMappings['0x9485']; + expect(mapping.customCode).toBe(ErrorCode.CRYPTO_KEY_001); + expect(mapping.category).toBe(Category.CRYPTOGRAPHY); + }); + }); + + it('should 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'); + expect(mapping).toHaveProperty('retryStrategy'); + expect(mapping).toHaveProperty('userActionable'); + + expect(Object.values(ErrorCode)).toContain(mapping.customCode); + expect(Object.values(Severity)).toContain(mapping.severity); + expect(Object.values(Category)).toContain(mapping.category); + expect(Object.values(RetryStrategy)).toContain(mapping.retryStrategy); + expect(typeof mapping.userActionable).toBe('boolean'); + expect(typeof mapping.message).toBe('string'); + }); + }); + + it('should 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('Trezor mappings', () => { + const { errorMapping } = HARDWARE_MAPPINGS.trezor; + + it('should have errorMapping object', () => { + expect(errorMapping).toBeDefined(); + expect(typeof errorMapping).toBe('object'); + }); + + describe('failure codes', () => { + it('should map code 1 to unexpected message', () => { + const mapping = errorMapping['1']; + expect(mapping.customCode).toBe(ErrorCode.PROTO_CMD_003); + expect(mapping.severity).toBe(Severity.ERROR); + expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + expect(mapping.originalName).toBe('Failure_UnexpectedMessage'); + }); + + it('should map code 4 to action cancelled', () => { + const mapping = errorMapping['4']; + expect(mapping.customCode).toBe(ErrorCode.USER_CANCEL_002); + expect(mapping.category).toBe(Category.USER_ACTION); + expect(mapping.userActionable).toBe(true); + expect(mapping.originalName).toBe('Failure_ActionCancelled'); + }); + + it('should map code 10 to insufficient funds', () => { + const mapping = errorMapping['10']; + expect(mapping.customCode).toBe(ErrorCode.TX_FUNDS_001); + expect(mapping.category).toBe(Category.TRANSACTION); + expect(mapping.originalName).toBe('Failure_NotEnoughFunds'); + }); + + it('should map code 99 to firmware error', () => { + const mapping = errorMapping['99']; + expect(mapping.customCode).toBe(ErrorCode.SYS_FIRMWARE_002); + expect(mapping.severity).toBe(Severity.CRITICAL); + expect(mapping.originalName).toBe('Failure_FirmwareError'); + }); + }); + + describe('PIN errors', () => { + it('should map code 5 to PIN expected', () => { + const mapping = errorMapping['5']; + expect(mapping.customCode).toBe(ErrorCode.USER_INPUT_001); + expect(mapping.category).toBe(Category.USER_ACTION); + expect(mapping.originalName).toBe('Failure_PinExpected'); + }); + + it('should map code 7 to PIN invalid', () => { + const mapping = errorMapping['7']; + expect(mapping.customCode).toBe(ErrorCode.AUTH_PIN_001); + expect(mapping.category).toBe(Category.AUTHENTICATION); + expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + expect(mapping.originalName).toBe('Failure_PinInvalid'); + }); + + it('should map code 12 to PIN mismatch', () => { + const mapping = errorMapping['12']; + expect(mapping.customCode).toBe(ErrorCode.AUTH_PIN_004); + expect(mapping.originalName).toBe('Failure_PinMismatch'); + }); + }); + + describe('device state errors', () => { + it('should map code 11 to device not initialized', () => { + const mapping = errorMapping['11']; + expect(mapping.customCode).toBe(ErrorCode.DEVICE_STATE_001); + expect(mapping.category).toBe(Category.DEVICE_STATE); + expect(mapping.originalName).toBe('Failure_NotInitialized'); + }); + + it('should map code 15 to device busy', () => { + const mapping = errorMapping['15']; + expect(mapping.customCode).toBe(ErrorCode.DEVICE_STATE_002); + expect(mapping.severity).toBe(Severity.WARNING); + expect(mapping.retryStrategy).toBe(RetryStrategy.EXPONENTIAL_BACKOFF); + expect(mapping.originalName).toBe('Failure_Busy'); + }); + + it('should map Device_Disconnected to device disconnected', () => { + const mapping = errorMapping.Device_Disconnected; + expect(mapping.customCode).toBe(ErrorCode.DEVICE_STATE_003); + expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + expect(mapping.sdkMessage).toBe('Device disconnected'); + }); + + it('should map Device_UsedElsewhere correctly', () => { + const mapping = errorMapping.Device_UsedElsewhere; + expect(mapping.customCode).toBe(ErrorCode.DEVICE_STATE_004); + expect(mapping.retryStrategy).toBe(RetryStrategy.NO_RETRY); + expect(mapping.userMessage).toContain('another window'); + }); + }); + + describe('initialization errors', () => { + it('should map Init_NotInitialized', () => { + const mapping = errorMapping.Init_NotInitialized; + expect(mapping.customCode).toBe(ErrorCode.CONFIG_INIT_001); + expect(mapping.category).toBe(Category.CONFIGURATION); + expect(mapping.sdkMessage).toBe('TrezorConnect not initialized'); + }); + + it('should map Init_AlreadyInitialized', () => { + const mapping = errorMapping.Init_AlreadyInitialized; + expect(mapping.customCode).toBe(ErrorCode.CONFIG_INIT_002); + expect(mapping.severity).toBe(Severity.WARNING); + }); + + it('should map Init_ManifestMissing', () => { + const mapping = errorMapping.Init_ManifestMissing; + expect(mapping.customCode).toBe(ErrorCode.CONFIG_INIT_003); + expect(mapping.category).toBe(Category.CONFIGURATION); + }); + }); + + describe('connection errors', () => { + it('should map Init_IframeBlocked', () => { + const mapping = errorMapping.Init_IframeBlocked; + expect(mapping.customCode).toBe(ErrorCode.CONN_BLOCKED_001); + expect(mapping.category).toBe(Category.CONNECTION); + expect(mapping.userMessage).toContain('browser settings'); + }); + + it('should map Init_IframeTimeout', () => { + const mapping = errorMapping.Init_IframeTimeout; + expect(mapping.customCode).toBe(ErrorCode.CONN_TIMEOUT_001); + expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + }); + + it('should map Transport_Missing', () => { + const mapping = errorMapping.Transport_Missing; + expect(mapping.customCode).toBe(ErrorCode.CONN_TRANSPORT_001); + expect(mapping.category).toBe(Category.CONNECTION); + }); + }); + + describe('method errors', () => { + it('should map Method_InvalidParameter', () => { + const mapping = errorMapping.Method_InvalidParameter; + expect(mapping.customCode).toBe(ErrorCode.DATA_FORMAT_003); + expect(mapping.category).toBe(Category.DATA_VALIDATION); + }); + + it('should map Method_Cancel', () => { + const mapping = errorMapping.Method_Cancel; + expect(mapping.customCode).toBe(ErrorCode.USER_CANCEL_002); + expect(mapping.category).toBe(Category.USER_ACTION); + expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + }); + + it('should map Method_UnknownCoin', () => { + const mapping = errorMapping.Method_UnknownCoin; + expect(mapping.customCode).toBe(ErrorCode.DATA_NOTFOUND_003); + expect(mapping.userMessage).toContain('not supported'); + }); + }); + + describe('device capability errors', () => { + it('should map Device_MissingCapability', () => { + const mapping = errorMapping.Device_MissingCapability; + expect(mapping.customCode).toBe(ErrorCode.DEVICE_CAP_001); + expect(mapping.userMessage).toContain('firmware update'); + }); + + it('should map Device_MissingCapabilityBtcOnly', () => { + const mapping = errorMapping.Device_MissingCapabilityBtcOnly; + expect(mapping.customCode).toBe(ErrorCode.DEVICE_CAP_002); + expect(mapping.userMessage).toContain('Bitcoin-only'); + }); + }); + + describe('special codes', () => { + it('should have UNKNOWN fallback', () => { + const mapping = errorMapping.UNKNOWN; + expect(mapping.customCode).toBe(ErrorCode.UNKNOWN_001); + expect(mapping.category).toBe(Category.UNKNOWN); + expect(mapping.originalName).toBe('Failure_UnknownCode'); + }); + + it('should map ENTROPY_CHECK', () => { + const mapping = errorMapping.ENTROPY_CHECK; + expect(mapping.customCode).toBe(ErrorCode.CRYPTO_ENTROPY_001); + expect(mapping.category).toBe(Category.CRYPTOGRAPHY); + expect(mapping.originalName).toBe('Failure_EntropyCheck'); + }); + }); + + it('should have valid structure for all mappings', () => { + Object.entries(errorMapping).forEach(([_code, mapping]) => { + expect(mapping).toHaveProperty('customCode'); + expect(mapping).toHaveProperty('message'); + expect(mapping).toHaveProperty('severity'); + expect(mapping).toHaveProperty('category'); + expect(mapping).toHaveProperty('retryStrategy'); + expect(mapping).toHaveProperty('userActionable'); + + expect(Object.values(ErrorCode)).toContain(mapping.customCode); + expect(Object.values(Severity)).toContain(mapping.severity); + expect(Object.values(Category)).toContain(mapping.category); + expect(Object.values(RetryStrategy)).toContain(mapping.retryStrategy); + expect(typeof mapping.userActionable).toBe('boolean'); + expect(typeof mapping.message).toBe('string'); + }); + }); + + it('should have valid optional fields when present', () => { + const mappingsWithUserMessage = Object.values(errorMapping).filter( + (mapping): mapping is typeof mapping & { userMessage: string } => + 'userMessage' in mapping && + typeof mapping.userMessage === 'string' && + mapping.userMessage.length > 0, + ); + mappingsWithUserMessage.forEach((mapping) => { + expect(typeof mapping.userMessage).toBe('string'); + expect(mapping.userMessage.length).toBeGreaterThan(0); + }); + + const mappingsWithOriginalName = Object.values(errorMapping).filter( + (mapping): mapping is typeof mapping & { originalName: string } => + 'originalName' in mapping && + typeof mapping.originalName === 'string' && + mapping.originalName.length > 0, + ); + mappingsWithOriginalName.forEach((mapping) => { + expect(typeof mapping.originalName).toBe('string'); + expect(mapping.originalName.length).toBeGreaterThan(0); + }); + + const mappingsWithSdkMessage = Object.values(errorMapping).filter( + (mapping): mapping is typeof mapping & { sdkMessage: string } => + 'sdkMessage' in mapping && + typeof mapping.sdkMessage === 'string' && + mapping.sdkMessage.length > 0, + ); + mappingsWithSdkMessage.forEach((mapping) => { + expect(typeof mapping.sdkMessage).toBe('string'); + expect(mapping.sdkMessage.length).toBeGreaterThan(0); + }); + }); + }); + + describe('Trezor default and patterns', () => { + it('should have default error mapping', () => { + const { default: defaultMapping } = HARDWARE_MAPPINGS.trezor; + expect(defaultMapping).toBeDefined(); + expect(defaultMapping.custom_code).toBe(ErrorCode.UNKNOWN_001); + expect(defaultMapping.category).toBe(Category.UNKNOWN); + }); + + it('should have error_patterns array', () => { + const { error_patterns: errorPatterns } = HARDWARE_MAPPINGS.trezor; + expect(Array.isArray(errorPatterns)).toBe(true); + expect(errorPatterns.length).toBeGreaterThan(0); + }); + + it('should have valid pattern structure', () => { + const { error_patterns: errorPatterns } = HARDWARE_MAPPINGS.trezor; + errorPatterns.forEach((pattern) => { + expect(pattern).toHaveProperty('pattern'); + expect(pattern).toHaveProperty('type'); + expect(pattern).toHaveProperty('description'); + expect(pattern).toHaveProperty('defaultSeverity'); + expect(typeof pattern.pattern).toBe('string'); + expect(typeof pattern.type).toBe('string'); + expect(typeof pattern.description).toBe('string'); + expect(Object.values(Severity)).toContain(pattern.defaultSeverity); + }); + }); + + it('should have patterns for common error prefixes', () => { + const { error_patterns: errorPatterns } = HARDWARE_MAPPINGS.trezor; + const patterns = errorPatterns.map((patternObj) => patternObj.pattern); + expect(patterns).toContain('^Failure_.*'); + expect(patterns).toContain('^Init_.*'); + expect(patterns).toContain('^Method_.*'); + expect(patterns).toContain('^Device_.*'); + }); + }); + + describe('consistency checks', () => { + it('should have unique error codes within each vendor', () => { + const ledgerCodes = Object.values(HARDWARE_MAPPINGS.ledger.errorMappings); + const ledgerCustomCodes = ledgerCodes.map( + (mapping) => mapping.customCode, + ); + expect(ledgerCustomCodes.length).toBeGreaterThan(0); + + const trezorCodes = Object.values(HARDWARE_MAPPINGS.trezor.errorMapping); + const trezorCustomCodes = trezorCodes.map( + (mapping) => mapping.customCode, + ); + expect(trezorCustomCodes.length).toBeGreaterThan(0); + }); + + it('should have user messages for user-actionable errors', () => { + const ledgerMappings = Object.values( + HARDWARE_MAPPINGS.ledger.errorMappings, + ).filter( + (mapping): mapping is typeof mapping & { userMessage: string } => + mapping.userActionable && + mapping.severity !== Severity.INFO && + 'userMessage' in mapping && + typeof mapping.userMessage === 'string' && + mapping.userMessage.length > 0, + ); + + ledgerMappings.forEach((mapping) => { + expect(mapping.userMessage).toBeDefined(); + expect(mapping.userMessage.length).toBeGreaterThan(0); + }); + + const trezorMappings = Object.values( + HARDWARE_MAPPINGS.trezor.errorMapping, + ).filter( + (mapping): mapping is typeof mapping & { userMessage: string } => + mapping.userActionable && + mapping.severity !== Severity.INFO && + 'userMessage' in mapping && + typeof mapping.userMessage === 'string' && + mapping.userMessage.length > 0, + ); + + trezorMappings.forEach((mapping) => { + expect(mapping.userMessage).toBeDefined(); + expect(mapping.userMessage.length).toBeGreaterThan(0); + }); + }); + + it('should use NO_RETRY for critical errors', () => { + const allMappings = [ + ...Object.values(HARDWARE_MAPPINGS.ledger.errorMappings), + ...Object.values(HARDWARE_MAPPINGS.trezor.errorMapping), + ]; + + const criticalMappings = allMappings.filter( + (mapping) => mapping.severity === Severity.CRITICAL, + ); + + criticalMappings.forEach((mapping) => { + expect([RetryStrategy.NO_RETRY, RetryStrategy.RETRY]).toContain( + mapping.retryStrategy, + ); + }); + }); + }); +}); diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/keyring-utils/src/hardware-error-mappings.ts new file mode 100644 index 000000000..b5e4d1323 --- /dev/null +++ b/packages/keyring-utils/src/hardware-error-mappings.ts @@ -0,0 +1,843 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { + ErrorCode, + Severity, + Category, + RetryStrategy, +} from './hardware-errors-enums'; + +export const HARDWARE_MAPPINGS = { + ledger: { + vendorName: 'Ledger', + errorMappings: { + '0x9000': { + customCode: ErrorCode.SUCCESS_000, + message: 'Operation successful', + severity: Severity.INFO, + category: Category.SUCCESS, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x6300': { + customCode: ErrorCode.AUTH_SEC_001, + message: 'Authentication failed', + severity: Severity.ERROR, + category: Category.AUTHENTICATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: 'Authentication failed. Please verify your credentials.', + }, + '0x63c0': { + customCode: ErrorCode.AUTH_PIN_003, + message: 'PIN attempts remaining', + severity: Severity.WARNING, + category: Category.AUTHENTICATION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: 'Incorrect PIN. Please try again.', + }, + '0x6700': { + customCode: ErrorCode.DATA_FORMAT_001, + message: 'Incorrect data length', + severity: Severity.ERROR, + category: Category.DATA_VALIDATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x6800': { + customCode: ErrorCode.DATA_MISSING_001, + message: 'Missing critical parameter', + severity: Severity.ERROR, + category: Category.DATA_VALIDATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x6981': { + customCode: ErrorCode.PROTO_CMD_002, + message: 'Command incompatible with file structure', + severity: Severity.ERROR, + category: Category.PROTOCOL, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x6982': { + customCode: ErrorCode.AUTH_SEC_002, + message: 'Security conditions not satisfied', + severity: Severity.ERROR, + category: Category.AUTHENTICATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: + 'Device is locked or access rights are insufficient. Please unlock your device.', + }, + '0x6985': { + customCode: ErrorCode.USER_CANCEL_001, + message: 'User rejected action on device', + severity: Severity.WARNING, + category: Category.USER_ACTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: + 'Transaction was rejected. Please approve on your device to continue.', + }, + '0x6a80': { + customCode: ErrorCode.DATA_FORMAT_002, + message: 'Invalid data received', + severity: Severity.ERROR, + category: Category.DATA_VALIDATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x6a84': { + customCode: ErrorCode.SYS_MEMORY_001, + message: 'Not enough memory space', + severity: Severity.ERROR, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x6a88': { + customCode: ErrorCode.DATA_NOTFOUND_001, + message: 'Referenced data not found', + severity: Severity.ERROR, + category: Category.DATA_VALIDATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x6a89': { + customCode: ErrorCode.SYS_FILE_001, + message: 'File already exists', + severity: Severity.ERROR, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x6b00': { + customCode: ErrorCode.DATA_FORMAT_003, + message: 'Invalid parameter received', + severity: Severity.ERROR, + category: Category.DATA_VALIDATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x6d00': { + customCode: ErrorCode.PROTO_CMD_001, + message: 'Instruction not supported', + severity: Severity.ERROR, + category: Category.PROTOCOL, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x6d02': { + customCode: ErrorCode.PROTO_MSG_001, + message: 'Unknown APDU command', + severity: Severity.ERROR, + category: Category.PROTOCOL, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x6e00': { + customCode: ErrorCode.PROTO_CMD_001, + message: 'Class not supported', + severity: Severity.ERROR, + category: Category.PROTOCOL, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x6501': { + customCode: ErrorCode.PROTO_CMD_001, + message: 'Ethereum app specific error', + severity: Severity.ERROR, + category: Category.PROTOCOL, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: + 'Please ensure the Ethereum app is open on your Ledger device.', + }, + '0x6f00': { + customCode: ErrorCode.SYS_INTERNAL_001, + message: 'Internal device error', + severity: Severity.CRITICAL, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + userMessage: 'An internal error occurred. Please report this issue.', + }, + '0x6f42': { + customCode: ErrorCode.SYS_LICENSE_001, + message: 'Licensing error', + severity: Severity.CRITICAL, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + userMessage: 'A licensing error occurred. Please contact support.', + }, + '0x6faa': { + customCode: ErrorCode.SYS_INTERNAL_001, + message: 'Device halted', + severity: Severity.CRITICAL, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + userMessage: + 'Device has halted. Please disconnect and reconnect your device.', + }, + '0x9240': { + customCode: ErrorCode.SYS_MEMORY_002, + message: 'Memory problem', + severity: Severity.ERROR, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.RETRY, + userActionable: false, + }, + '0x9400': { + customCode: ErrorCode.SYS_FILE_001, + message: 'No elementary file selected', + severity: Severity.ERROR, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x9402': { + customCode: ErrorCode.DATA_FORMAT_003, + message: 'Invalid offset', + severity: Severity.ERROR, + category: Category.DATA_VALIDATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x9404': { + customCode: ErrorCode.DATA_NOTFOUND_002, + message: 'File not found', + severity: Severity.ERROR, + category: Category.DATA_VALIDATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x9408': { + customCode: ErrorCode.SYS_FILE_002, + message: 'Inconsistent file', + severity: Severity.ERROR, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x9484': { + customCode: ErrorCode.CRYPTO_ALGO_001, + message: 'Algorithm not supported', + severity: Severity.ERROR, + category: Category.CRYPTOGRAPHY, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x9485': { + customCode: ErrorCode.CRYPTO_KEY_001, + message: 'Invalid key check value', + severity: Severity.ERROR, + category: Category.CRYPTOGRAPHY, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x9802': { + customCode: ErrorCode.CONFIG_INIT_001, + message: 'Code not initialized', + severity: Severity.ERROR, + category: Category.CONFIGURATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x9804': { + customCode: ErrorCode.AUTH_SEC_002, + message: 'Access condition not fulfilled', + severity: Severity.ERROR, + category: Category.AUTHENTICATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + }, + '0x9808': { + customCode: ErrorCode.AUTH_PIN_001, + message: 'Contradiction in secret code status', + severity: Severity.ERROR, + category: Category.AUTHENTICATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x9810': { + customCode: ErrorCode.SYS_INTERNAL_001, + message: 'Contradiction invalidation', + severity: Severity.ERROR, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x9840': { + customCode: ErrorCode.AUTH_LOCK_002, + message: 'Code blocked', + severity: Severity.CRITICAL, + category: Category.AUTHENTICATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: + 'Your device is blocked due to too many failed attempts. Please follow device recovery procedures.', + }, + '0x9850': { + customCode: ErrorCode.SYS_INTERNAL_001, + message: 'Maximum value reached', + severity: Severity.ERROR, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + '0x650f': { + customCode: ErrorCode.CONN_CLOSED_001, + message: 'App closed or connection issue', + severity: Severity.ERROR, + category: Category.CONNECTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: + 'Connection lost or app closed. Please open the corresponding app on your Ledger device.', + }, + '0x5515': { + customCode: ErrorCode.AUTH_LOCK_001, + message: 'Device is locked', + severity: Severity.ERROR, + category: Category.AUTHENTICATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: 'Please unlock your Ledger device to continue.', + }, + '0x5501': { + customCode: ErrorCode.USER_CANCEL_001, + message: 'User refused on device', + severity: Severity.WARNING, + category: Category.USER_ACTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: + 'Transaction was rejected. Please approve on your device to continue.', + }, + }, + }, + trezor: { + vendorName: 'Trezor', + errorMapping: { + '1': { + customCode: ErrorCode.PROTO_CMD_003, + message: 'Unexpected message received', + severity: Severity.ERROR, + category: Category.PROTOCOL, + retryStrategy: RetryStrategy.RETRY, + userActionable: false, + originalName: 'Failure_UnexpectedMessage', + }, + '2': { + customCode: ErrorCode.USER_CONFIRM_001, + message: 'Button confirmation required', + severity: Severity.WARNING, + category: Category.USER_ACTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: 'Please confirm the action on your Trezor device.', + originalName: 'Failure_ButtonExpected', + }, + '3': { + customCode: ErrorCode.DATA_FORMAT_002, + message: 'Data error', + severity: Severity.ERROR, + category: Category.DATA_VALIDATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + originalName: 'Failure_DataError', + }, + '4': { + customCode: ErrorCode.USER_CANCEL_002, + message: 'Action cancelled by user', + severity: Severity.WARNING, + category: Category.USER_ACTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: + 'You cancelled the operation. Please try again if this was unintentional.', + originalName: 'Failure_ActionCancelled', + }, + '5': { + customCode: ErrorCode.USER_INPUT_001, + message: 'PIN entry expected', + severity: Severity.WARNING, + category: Category.USER_ACTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: 'Please enter your PIN on the Trezor device.', + originalName: 'Failure_PinExpected', + }, + '6': { + customCode: ErrorCode.AUTH_PIN_002, + message: 'PIN cancelled by user', + severity: Severity.WARNING, + category: Category.AUTHENTICATION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: 'PIN entry was cancelled. Please try again.', + originalName: 'Failure_PinCancelled', + }, + '7': { + customCode: ErrorCode.AUTH_PIN_001, + message: 'PIN invalid', + severity: Severity.ERROR, + category: Category.AUTHENTICATION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: 'Incorrect PIN entered. Please try again.', + originalName: 'Failure_PinInvalid', + }, + '8': { + customCode: ErrorCode.CRYPTO_SIGN_001, + message: 'Invalid signature', + severity: Severity.ERROR, + category: Category.CRYPTOGRAPHY, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + originalName: 'Failure_InvalidSignature', + }, + '9': { + customCode: ErrorCode.SYS_INTERNAL_001, + message: 'Process error', + severity: Severity.ERROR, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.RETRY, + userActionable: false, + userMessage: 'A processing error occurred. Please try again.', + originalName: 'Failure_ProcessError', + }, + '10': { + customCode: ErrorCode.TX_FUNDS_001, + message: 'Insufficient funds', + severity: Severity.ERROR, + category: Category.TRANSACTION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: 'Insufficient funds to complete this transaction.', + originalName: 'Failure_NotEnoughFunds', + }, + '11': { + customCode: ErrorCode.DEVICE_STATE_001, + message: 'Device not initialized', + severity: Severity.ERROR, + category: Category.DEVICE_STATE, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: + 'Your Trezor device needs to be initialized. Please set it up first.', + originalName: 'Failure_NotInitialized', + }, + '12': { + customCode: ErrorCode.AUTH_PIN_004, + message: 'PIN mismatch', + severity: Severity.ERROR, + category: Category.AUTHENTICATION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: 'PINs do not match. Please try again.', + originalName: 'Failure_PinMismatch', + }, + '13': { + customCode: ErrorCode.AUTH_WIPE_001, + message: 'Wipe code mismatch', + severity: Severity.ERROR, + category: Category.AUTHENTICATION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: 'Wipe codes do not match. Please try again.', + originalName: 'Failure_WipeCodeMismatch', + }, + '14': { + customCode: ErrorCode.DEVICE_STATE_002, + message: 'Invalid session', + severity: Severity.ERROR, + category: Category.DEVICE_STATE, + retryStrategy: RetryStrategy.RETRY, + userActionable: false, + userMessage: 'Session expired. Please reconnect your device.', + originalName: 'Failure_InvalidSession', + }, + '15': { + customCode: ErrorCode.DEVICE_STATE_002, + message: 'Device busy', + severity: Severity.WARNING, + category: Category.DEVICE_STATE, + retryStrategy: RetryStrategy.EXPONENTIAL_BACKOFF, + userActionable: false, + userMessage: 'Device is busy. Please wait and try again.', + originalName: 'Failure_Busy', + }, + '99': { + customCode: ErrorCode.SYS_FIRMWARE_002, + message: 'Firmware installation failed', + severity: Severity.CRITICAL, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: + 'Firmware installation failed. Please contact Trezor support.', + originalName: 'Failure_FirmwareError', + }, + UNKNOWN: { + customCode: ErrorCode.UNKNOWN_001, + message: 'Unknown error', + severity: Severity.ERROR, + category: Category.UNKNOWN, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + userMessage: + 'An unknown error occurred. Please try again or contact support.', + originalName: 'Failure_UnknownCode', + }, + ENTROPY_CHECK: { + customCode: ErrorCode.CRYPTO_ENTROPY_001, + message: 'Entropy check failed', + severity: Severity.ERROR, + category: Category.CRYPTOGRAPHY, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + originalName: 'Failure_EntropyCheck', + }, + Init_NotInitialized: { + customCode: ErrorCode.CONFIG_INIT_001, + message: 'TrezorConnect not initialized', + severity: Severity.ERROR, + category: Category.CONFIGURATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + sdkMessage: 'TrezorConnect not initialized', + }, + Init_AlreadyInitialized: { + customCode: ErrorCode.CONFIG_INIT_002, + message: 'TrezorConnect already initialized', + severity: Severity.WARNING, + category: Category.CONFIGURATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + sdkMessage: 'TrezorConnect has been already initialized', + }, + Init_IframeBlocked: { + customCode: ErrorCode.CONN_BLOCKED_001, + message: 'Iframe blocked', + severity: Severity.ERROR, + category: Category.CONNECTION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: + 'Connection blocked. Please check your browser settings and allow iframes.', + sdkMessage: 'Iframe blocked', + }, + Init_IframeTimeout: { + customCode: ErrorCode.CONN_TIMEOUT_001, + message: 'Iframe connection timeout', + severity: Severity.ERROR, + category: Category.CONNECTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: false, + userMessage: + 'Connection timed out. Please check your internet connection and try again.', + sdkMessage: 'Iframe timeout', + }, + Init_ManifestMissing: { + customCode: ErrorCode.CONFIG_INIT_003, + message: 'Manifest not set', + severity: Severity.ERROR, + category: Category.CONFIGURATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + sdkMessage: 'Manifest not set...', + }, + Popup_ConnectionMissing: { + customCode: ErrorCode.CONN_IFRAME_001, + message: 'Unable to establish connection with iframe', + severity: Severity.ERROR, + category: Category.CONNECTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: false, + userMessage: 'Connection failed. Please try again.', + sdkMessage: 'Unable to establish connection with iframe', + }, + Desktop_ConnectionMissing: { + customCode: ErrorCode.CONN_SUITE_001, + message: 'Unable to establish connection with Suite', + severity: Severity.ERROR, + category: Category.CONNECTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: + 'Cannot connect to Trezor Suite. Please ensure Trezor Suite is running.', + sdkMessage: 'Unable to establish connection with Suite', + }, + Transport_Missing: { + customCode: ErrorCode.CONN_TRANSPORT_001, + message: 'Transport is missing', + severity: Severity.ERROR, + category: Category.CONNECTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: + 'Transport layer not available. Please reconnect your device.', + sdkMessage: 'Transport is missing', + }, + Method_InvalidPackage: { + customCode: ErrorCode.CONFIG_METHOD_001, + message: 'Invalid package for browser environment', + severity: Severity.ERROR, + category: Category.CONFIGURATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + sdkMessage: 'This package is not suitable to work with browser...', + }, + Method_InvalidParameter: { + customCode: ErrorCode.DATA_FORMAT_003, + message: 'Invalid method parameter', + severity: Severity.ERROR, + category: Category.DATA_VALIDATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + }, + Method_NotAllowed: { + customCode: ErrorCode.CONFIG_METHOD_001, + message: 'Method not allowed for this configuration', + severity: Severity.ERROR, + category: Category.CONFIGURATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + sdkMessage: 'Method not allowed for this configuration', + }, + Method_PermissionsNotGranted: { + customCode: ErrorCode.CONFIG_PERM_001, + message: 'Permissions not granted', + severity: Severity.ERROR, + category: Category.CONFIGURATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: + 'Required permissions were not granted. Please allow access to continue.', + sdkMessage: 'Permissions not granted', + }, + Method_Cancel: { + customCode: ErrorCode.USER_CANCEL_002, + message: 'Method cancelled by user', + severity: Severity.WARNING, + category: Category.USER_ACTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: 'Operation was cancelled.', + sdkMessage: 'Cancelled', + }, + Method_Interrupted: { + customCode: ErrorCode.USER_CANCEL_002, + message: 'Popup closed by user', + severity: Severity.WARNING, + category: Category.USER_ACTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: 'Operation interrupted. The popup was closed.', + sdkMessage: 'Popup closed', + }, + Method_UnknownCoin: { + customCode: ErrorCode.DATA_NOTFOUND_003, + message: 'Coin not found', + severity: Severity.ERROR, + category: Category.DATA_VALIDATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: 'The requested cryptocurrency is not supported.', + sdkMessage: 'Coin not found', + }, + Method_AddressNotMatch: { + customCode: ErrorCode.DATA_VALIDATION_001, + message: 'Addresses do not match', + severity: Severity.ERROR, + category: Category.DATA_VALIDATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: 'Address verification failed. The addresses do not match.', + sdkMessage: 'Addresses do not match', + }, + Method_Discovery_BundleException: { + customCode: ErrorCode.SYS_INTERNAL_001, + message: 'Discovery bundle exception', + severity: Severity.ERROR, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.RETRY, + userActionable: false, + }, + Method_Override: { + customCode: ErrorCode.CONFIG_METHOD_001, + message: 'Method override', + severity: Severity.WARNING, + category: Category.CONFIGURATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: false, + sdkMessage: 'override', + }, + Method_NoResponse: { + customCode: ErrorCode.PROTO_CMD_003, + message: 'Call resolved without response', + severity: Severity.ERROR, + category: Category.PROTOCOL, + retryStrategy: RetryStrategy.RETRY, + userActionable: false, + sdkMessage: 'Call resolved without response', + }, + Device_NotFound: { + customCode: ErrorCode.DEVICE_DETECT_001, + message: 'Device not found', + severity: Severity.ERROR, + category: Category.DEVICE_STATE, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: + 'Trezor device not detected. Please connect your device and try again.', + sdkMessage: 'Device not found', + }, + Device_InitializeFailed: { + customCode: ErrorCode.DEVICE_STATE_001, + message: 'Device initialization failed', + severity: Severity.ERROR, + category: Category.DEVICE_STATE, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: + 'Failed to initialize device. Please reconnect and try again.', + }, + Device_FwException: { + customCode: ErrorCode.SYS_FIRMWARE_001, + message: 'Firmware exception', + severity: Severity.ERROR, + category: Category.SYSTEM, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: + 'Firmware error detected. Please update your device firmware.', + }, + Device_ModeException: { + customCode: ErrorCode.DEVICE_MODE_001, + message: 'Device mode exception', + severity: Severity.ERROR, + category: Category.DEVICE_STATE, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: + 'Device is in an incompatible mode. Please check your device settings.', + }, + Device_Disconnected: { + customCode: ErrorCode.DEVICE_STATE_003, + message: 'Device disconnected', + severity: Severity.ERROR, + category: Category.DEVICE_STATE, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: + 'Device was disconnected. Please reconnect your Trezor device.', + sdkMessage: 'Device disconnected', + }, + Device_UsedElsewhere: { + customCode: ErrorCode.DEVICE_STATE_004, + message: 'Device is used in another window', + severity: Severity.ERROR, + category: Category.DEVICE_STATE, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: + 'Your Trezor is being used in another window or application. Please close other connections.', + sdkMessage: 'Device is used in another window', + }, + Device_InvalidState: { + customCode: ErrorCode.AUTH_SEC_001, + message: 'Passphrase is incorrect', + severity: Severity.ERROR, + category: Category.AUTHENTICATION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: 'Incorrect passphrase. Please try again.', + sdkMessage: 'Passphrase is incorrect', + }, + Device_CallInProgress: { + customCode: ErrorCode.DEVICE_STATE_005, + message: 'Device call in progress', + severity: Severity.WARNING, + category: Category.DEVICE_STATE, + retryStrategy: RetryStrategy.EXPONENTIAL_BACKOFF, + userActionable: false, + userMessage: 'Another operation is in progress. Please wait.', + sdkMessage: 'Device call in progress', + }, + Device_MultipleNotSupported: { + customCode: ErrorCode.DEVICE_CAP_001, + message: 'Multiple devices are not supported', + severity: Severity.ERROR, + category: Category.DEVICE_STATE, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: + 'Multiple devices detected. Please connect only one Trezor device.', + sdkMessage: 'Multiple devices are not supported', + }, + Device_MissingCapability: { + customCode: ErrorCode.DEVICE_CAP_001, + message: 'Device is missing required capability', + severity: Severity.ERROR, + category: Category.DEVICE_STATE, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: + 'Your device does not support this feature. A firmware update may be required.', + sdkMessage: 'Device is missing capability', + }, + Device_MissingCapabilityBtcOnly: { + customCode: ErrorCode.DEVICE_CAP_002, + message: 'Device is BTC-only, operation not supported', + severity: Severity.ERROR, + category: Category.DEVICE_STATE, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: + 'This operation is not supported on Bitcoin-only firmware.', + sdkMessage: 'Device is missing capability (BTC only)', + }, + }, + default: { + custom_code: ErrorCode.UNKNOWN_001, + message: 'Unknown Trezor error', + severity: Severity.ERROR, + category: Category.UNKNOWN, + retry_strategy: RetryStrategy.NO_RETRY, + userActionable: false, + userMessage: + 'An unexpected error occurred. Please try again or contact support.', + }, + error_patterns: [ + { + pattern: '^Failure_.*', + type: 'prefix', + description: 'Device failure codes', + defaultSeverity: Severity.ERROR, + }, + { + pattern: '^Init_.*', + type: 'prefix', + description: 'Initialization errors', + defaultSeverity: Severity.ERROR, + }, + { + pattern: '^Method_.*', + type: 'prefix', + description: 'Method invocation errors', + defaultSeverity: Severity.ERROR, + }, + { + pattern: '^Device_.*', + type: 'prefix', + description: 'Device state errors', + defaultSeverity: Severity.ERROR, + }, + ], + }, +}; diff --git a/packages/keyring-utils/src/hardware-error.test.ts b/packages/keyring-utils/src/hardware-error.test.ts new file mode 100644 index 000000000..c8f43fcda --- /dev/null +++ b/packages/keyring-utils/src/hardware-error.test.ts @@ -0,0 +1,481 @@ +import { HardwareWalletError } from './hardware-error'; +import { + ErrorCode, + Severity, + Category, + RetryStrategy, +} from './hardware-errors-enums'; + +describe('HardwareWalletError', () => { + const mockOptions = { + code: ErrorCode.USER_CANCEL_001, + severity: Severity.WARNING, + category: Category.USER_ACTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: 'Transaction was rejected', + }; + + describe('constructor', () => { + it('should create an error with required properties', () => { + const error = new HardwareWalletError('Test error', mockOptions); + + expect(error.message).toBe('Test error'); + expect(error.name).toBe('HardwareWalletError'); + expect(error.code).toBe(ErrorCode.USER_CANCEL_001); + expect(error.severity).toBe(Severity.WARNING); + expect(error.category).toBe(Category.USER_ACTION); + expect(error.retryStrategy).toBe(RetryStrategy.RETRY); + expect(error.userActionable).toBe(true); + expect(error.userMessage).toBe('Transaction was rejected'); + expect(error.retryCount).toBe(0); + }); + + it('should generate a unique error ID', () => { + const error1 = new HardwareWalletError('Test error 1', mockOptions); + const error2 = new HardwareWalletError('Test error 2', mockOptions); + + expect(error1.id).toBeDefined(); + expect(error2.id).toBeDefined(); + expect(error1.id).not.toBe(error2.id); + expect(error1.id).toMatch(/^err_[a-z0-9]+_[a-z0-9]+$/u); + }); + + it('should set timestamp to current date', () => { + const before = new Date(); + const error = new HardwareWalletError('Test error', mockOptions); + const after = new Date(); + + expect(error.timestamp.getTime()).toBeGreaterThanOrEqual( + before.getTime(), + ); + expect(error.timestamp.getTime()).toBeLessThanOrEqual(after.getTime()); + }); + + it('should set optional properties when provided', () => { + const cause = new Error('Original error'); + const metadata = { deviceId: '12345', attempt: 1 }; + + const error = new HardwareWalletError('Test error', { + ...mockOptions, + cause, + metadata, + retryCount: 3, + }); + + expect(error.cause).toBe(cause); + expect(error.metadata).toStrictEqual(metadata); + expect(error.retryCount).toBe(3); + }); + + it('should default retryCount to 0 when not provided', () => { + const error = new HardwareWalletError('Test error', mockOptions); + expect(error.retryCount).toBe(0); + }); + + it('should work with instanceof checks', () => { + const error = new HardwareWalletError('Test error', mockOptions); + expect(error instanceof HardwareWalletError).toBe(true); + expect(error instanceof Error).toBe(true); + }); + + it('should capture stack trace', () => { + const error = new HardwareWalletError('Test error', mockOptions); + expect(error.stack).toBeDefined(); + expect(error.stack).toContain('HardwareWalletError'); + }); + }); + + describe('isRetryable', () => { + it('should return true for RETRY strategy', () => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + retryStrategy: RetryStrategy.RETRY, + }); + expect(error.isRetryable()).toBe(true); + }); + + it('should return true for EXPONENTIAL_BACKOFF strategy', () => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + retryStrategy: RetryStrategy.EXPONENTIAL_BACKOFF, + }); + expect(error.isRetryable()).toBe(true); + }); + + it('should return false for NO_RETRY strategy', () => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + retryStrategy: RetryStrategy.NO_RETRY, + }); + expect(error.isRetryable()).toBe(false); + }); + }); + + describe('isCritical', () => { + it('should return true for CRITICAL severity', () => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + severity: Severity.CRITICAL, + }); + expect(error.isCritical()).toBe(true); + }); + + it('should return false for non-CRITICAL severity', () => { + const severities = [Severity.ERROR, Severity.WARNING, Severity.INFO]; + severities.forEach((severity) => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + severity, + }); + expect(error.isCritical()).toBe(false); + }); + }); + }); + + describe('isWarning', () => { + it('should return true for WARNING severity', () => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + severity: Severity.WARNING, + }); + expect(error.isWarning()).toBe(true); + }); + + it('should return false for non-WARNING severity', () => { + const severities = [Severity.ERROR, Severity.CRITICAL, Severity.INFO]; + severities.forEach((severity) => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + severity, + }); + expect(error.isWarning()).toBe(false); + }); + }); + }); + + describe('requiresUserAction', () => { + it('should return true when userActionable is true', () => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + userActionable: true, + }); + expect(error.requiresUserAction()).toBe(true); + }); + + it('should return false when userActionable is false', () => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + userActionable: false, + }); + expect(error.requiresUserAction()).toBe(false); + }); + }); + + describe('withIncrementedRetryCount', () => { + it('should create a new error with incremented retry count', () => { + const originalError = new HardwareWalletError('Test error', { + ...mockOptions, + retryCount: 2, + }); + + const newError = originalError.withIncrementedRetryCount(); + + expect(newError.retryCount).toBe(3); + expect(originalError.retryCount).toBe(2); // Original unchanged + expect(newError).not.toBe(originalError); // New instance + }); + + it('should preserve all other properties', () => { + const cause = new Error('Original error'); + const metadata = { deviceId: '12345' }; + + const originalError = new HardwareWalletError('Test error', { + ...mockOptions, + cause, + metadata, + }); + + const newError = originalError.withIncrementedRetryCount(); + + expect(newError.message).toBe(originalError.message); + expect(newError.code).toBe(originalError.code); + expect(newError.severity).toBe(originalError.severity); + expect(newError.category).toBe(originalError.category); + expect(newError.retryStrategy).toBe(originalError.retryStrategy); + expect(newError.userActionable).toBe(originalError.userActionable); + expect(newError.userMessage).toBe(originalError.userMessage); + expect(newError.cause).toBe(originalError.cause); + expect(newError.metadata).toStrictEqual(originalError.metadata); + }); + + it('should work when optional properties are undefined', () => { + const originalError = new HardwareWalletError('Test error', mockOptions); + const newError = originalError.withIncrementedRetryCount(); + + expect(newError.retryCount).toBe(1); + expect(newError.cause).toBeUndefined(); + expect(newError.metadata).toBeUndefined(); + }); + }); + + describe('withMetadata', () => { + it('should create a new error with additional metadata', () => { + const originalMetadata = { deviceId: '12345' }; + const originalError = new HardwareWalletError('Test error', { + ...mockOptions, + metadata: originalMetadata, + }); + + const additionalMetadata = { attempt: 1, timestamp: Date.now() }; + const newError = originalError.withMetadata(additionalMetadata); + + expect(newError.metadata).toStrictEqual({ + ...originalMetadata, + ...additionalMetadata, + }); + expect(originalError.metadata).toStrictEqual(originalMetadata); // Original unchanged + expect(newError).not.toBe(originalError); // New instance + }); + + it('should create metadata when original has none', () => { + const originalError = new HardwareWalletError('Test error', mockOptions); + const metadata = { deviceId: '12345' }; + const newError = originalError.withMetadata(metadata); + + expect(newError.metadata).toStrictEqual(metadata); + }); + + it('should override existing metadata keys', () => { + const originalError = new HardwareWalletError('Test error', { + ...mockOptions, + metadata: { key: 'old', other: 'value' }, + }); + + const newError = originalError.withMetadata({ key: 'new' }); + + expect(newError.metadata).toStrictEqual({ key: 'new', other: 'value' }); + }); + + it('should preserve all other properties', () => { + const cause = new Error('Original error'); + + const originalError = new HardwareWalletError('Test error', { + ...mockOptions, + cause, + retryCount: 5, + }); + + const newError = originalError.withMetadata({ extra: 'data' }); + + expect(newError.message).toBe(originalError.message); + expect(newError.code).toBe(originalError.code); + expect(newError.severity).toBe(originalError.severity); + expect(newError.category).toBe(originalError.category); + expect(newError.retryStrategy).toBe(originalError.retryStrategy); + expect(newError.userActionable).toBe(originalError.userActionable); + expect(newError.userMessage).toBe(originalError.userMessage); + expect(newError.cause).toBe(originalError.cause); + expect(newError.retryCount).toBe(originalError.retryCount); + }); + }); + + describe('toJSON', () => { + it('should serialize all properties to JSON', () => { + const cause = new Error('Original error'); + cause.stack = 'Error stack trace'; + const metadata = { deviceId: '12345' }; + + const error = new HardwareWalletError('Test error', { + ...mockOptions, + cause, + metadata, + retryCount: 3, + }); + + const json = error.toJSON(); + + expect(json.id).toBe(error.id); + expect(json.name).toBe('HardwareWalletError'); + expect(json.message).toBe('Test error'); + expect(json.code).toBe(ErrorCode.USER_CANCEL_001); + expect(json.severity).toBe(Severity.WARNING); + expect(json.category).toBe(Category.USER_ACTION); + expect(json.retryStrategy).toBe(RetryStrategy.RETRY); + expect(json.userActionable).toBe(true); + expect(json.userMessage).toBe('Transaction was rejected'); + expect(json.timestamp).toBe(error.timestamp.toISOString()); + expect(json.metadata).toStrictEqual(metadata); + expect(json.retryCount).toBe(3); + expect(json.stack).toBeDefined(); + }); + + it('should serialize cause when present', () => { + const cause = new Error('Original error'); + cause.stack = 'Error stack trace'; + + const error = new HardwareWalletError('Test error', { + ...mockOptions, + cause, + }); + + const json = error.toJSON(); + + expect(json.cause).toStrictEqual({ + name: 'Error', + message: 'Original error', + stack: 'Error stack trace', + }); + }); + + it('should not include cause when not present', () => { + const error = new HardwareWalletError('Test error', mockOptions); + const json = error.toJSON(); + + expect(json.cause).toBeUndefined(); + }); + + it('should handle undefined optional properties', () => { + const error = new HardwareWalletError('Test error', mockOptions); + const json = error.toJSON(); + + expect(json.metadata).toBeUndefined(); + expect(json.cause).toBeUndefined(); + }); + }); + + describe('toString', () => { + it('should return a user-friendly string representation', () => { + const error = new HardwareWalletError('Test error', mockOptions); + const result = error.toString(); + + expect(result).toBe( + 'HardwareWalletError [USER_CANCEL_001]: Transaction was rejected', + ); + }); + + it('should work with different error codes and messages', () => { + const error = new HardwareWalletError('Internal error', { + ...mockOptions, + code: ErrorCode.SYS_INTERNAL_001, + userMessage: 'An internal error occurred', + }); + const result = error.toString(); + + expect(result).toBe( + 'HardwareWalletError [SYS_INTERNAL_001]: An internal error occurred', + ); + }); + }); + + describe('toDetailedString', () => { + it('should return a detailed string with all information', () => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + retryCount: 2, + }); + + const result = error.toDetailedString(); + + expect(result).toContain('HardwareWalletError [USER_CANCEL_001]'); + expect(result).toContain('Message: Test error'); + expect(result).toContain('User Message: Transaction was rejected'); + expect(result).toContain('Severity: WARNING'); + expect(result).toContain('Category: USER_ACTION'); + expect(result).toContain('Retry Strategy: RETRY'); + expect(result).toContain('User Actionable: true'); + expect(result).toContain('Timestamp:'); + expect(result).toContain('Retry Count: 2'); + }); + + it('should include metadata when present', () => { + const metadata = { deviceId: '12345', attempt: 1 }; + const error = new HardwareWalletError('Test error', { + ...mockOptions, + metadata, + }); + + const result = error.toDetailedString(); + expect(result).toContain('Metadata:'); + expect(result).toContain('"deviceId": "12345"'); + expect(result).toContain('"attempt": 1'); + }); + + it('should include cause when present', () => { + const cause = new Error('Original error'); + const error = new HardwareWalletError('Test error', { + ...mockOptions, + cause, + }); + + const result = error.toDetailedString(); + expect(result).toContain('Caused by: Original error'); + }); + + it('should not include optional fields when not present', () => { + const error = new HardwareWalletError('Test error', mockOptions); + const result = error.toDetailedString(); + + expect(result).not.toContain('Metadata:'); + expect(result).not.toContain('Caused by:'); + }); + + it('should not include metadata section when metadata is empty', () => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + metadata: {}, + }); + + const result = error.toDetailedString(); + expect(result).not.toContain('Metadata:'); + }); + }); + + describe('error scenarios', () => { + it('should handle critical authentication errors', () => { + const error = new HardwareWalletError('Device blocked', { + code: ErrorCode.AUTH_LOCK_002, + severity: Severity.CRITICAL, + category: Category.AUTHENTICATION, + retryStrategy: RetryStrategy.NO_RETRY, + userActionable: true, + userMessage: 'Device is blocked due to too many failed attempts', + }); + + expect(error.isCritical()).toBe(true); + expect(error.isRetryable()).toBe(false); + expect(error.requiresUserAction()).toBe(true); + }); + + it('should handle retryable connection errors', () => { + const error = new HardwareWalletError('Connection timeout', { + code: ErrorCode.CONN_TIMEOUT_001, + severity: Severity.ERROR, + category: Category.CONNECTION, + retryStrategy: RetryStrategy.EXPONENTIAL_BACKOFF, + userActionable: false, + userMessage: 'Connection timed out', + }); + + expect(error.isCritical()).toBe(false); + expect(error.isRetryable()).toBe(true); + expect(error.requiresUserAction()).toBe(false); + }); + + it('should handle user action warnings', () => { + const error = new HardwareWalletError('User confirmation required', { + code: ErrorCode.USER_CONFIRM_001, + severity: Severity.WARNING, + category: Category.USER_ACTION, + retryStrategy: RetryStrategy.RETRY, + userActionable: true, + userMessage: 'Please confirm the action on your device', + }); + + expect(error.isWarning()).toBe(true); + expect(error.isCritical()).toBe(false); + expect(error.isRetryable()).toBe(true); + expect(error.requiresUserAction()).toBe(true); + }); + }); +}); diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts new file mode 100644 index 000000000..78332612a --- /dev/null +++ b/packages/keyring-utils/src/hardware-error.ts @@ -0,0 +1,234 @@ +import type { ErrorCode, Category } from './hardware-errors-enums'; +import { Severity, RetryStrategy } from './hardware-errors-enums'; + +/** + * Generates a unique error ID using timestamp and random values. + * + * @returns A unique error ID string. + */ +function generateErrorId(): string { + const timestamp = Date.now().toString(36); + const randomPart = Math.random().toString(36).substring(2, 9); + return `err_${timestamp}_${randomPart}`; +} + +export type HardwareWalletErrorOptions = { + code: ErrorCode; + severity: Severity; + category: Category; + retryStrategy: RetryStrategy; + userActionable: boolean; + userMessage: string; + cause?: Error; + metadata?: Record; + retryCount?: number; +}; + +export class HardwareWalletError extends Error { + public readonly id: string; + + public readonly code: ErrorCode; + + public readonly severity: Severity; + + public readonly category: Category; + + public readonly retryStrategy: RetryStrategy; + + public readonly userActionable: boolean; + + public readonly userMessage: string; + + public readonly timestamp: Date; + + public readonly metadata: Record | undefined; + + public readonly retryCount: number; + + public readonly cause: Error | undefined; + + constructor(message: string, options: HardwareWalletErrorOptions) { + super(message); + this.name = 'HardwareWalletError'; + this.id = generateErrorId(); + this.code = options.code; + this.severity = options.severity; + this.category = options.category; + this.retryStrategy = options.retryStrategy; + this.userActionable = options.userActionable; + this.userMessage = options.userMessage; + this.timestamp = new Date(); + this.metadata = options.metadata; + this.retryCount = options.retryCount ?? 0; + this.cause = options.cause; + + // Ensure proper prototype chain for instanceof checks + Object.setPrototypeOf(this, HardwareWalletError.prototype); + + // Capture stack trace if available + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + } + + /** + * Checks if this error can be retried based on its retry strategy. + * + * @returns True if the error can be retried, false otherwise. + */ + isRetryable(): boolean { + return this.retryStrategy !== RetryStrategy.NO_RETRY; + } + + /** + * Checks if this error is critical. + * + * @returns True if the error is critical, false otherwise. + */ + isCritical(): boolean { + return this.severity === Severity.CRITICAL; + } + + /** + * Checks if this error is a warning. + * + * @returns True if the error is a warning, false otherwise. + */ + isWarning(): boolean { + return this.severity === Severity.WARNING; + } + + /** + * Checks if this error requires user action. + * + * @returns True if the error requires user action, false otherwise. + */ + requiresUserAction(): boolean { + return this.userActionable; + } + + /** + * Creates a new error instance with an incremented retry count. + * + * @returns A new HardwareWalletError instance with incremented retry count. + */ + withIncrementedRetryCount(): HardwareWalletError { + const options: HardwareWalletErrorOptions = { + code: this.code, + severity: this.severity, + category: this.category, + retryStrategy: this.retryStrategy, + userActionable: this.userActionable, + userMessage: this.userMessage, + retryCount: this.retryCount + 1, + }; + + if (this.cause !== undefined) { + options.cause = this.cause; + } + if (this.metadata !== undefined) { + options.metadata = this.metadata; + } + + return new HardwareWalletError(this.message, options); + } + + /** + * Creates a new error instance with additional metadata. + * + * @param additionalMetadata - Additional metadata to merge with existing metadata. + * @returns A new HardwareWalletError instance with merged metadata. + */ + withMetadata( + additionalMetadata: Record, + ): HardwareWalletError { + const options: HardwareWalletErrorOptions = { + code: this.code, + severity: this.severity, + category: this.category, + retryStrategy: this.retryStrategy, + userActionable: this.userActionable, + userMessage: this.userMessage, + metadata: { ...(this.metadata ?? {}), ...additionalMetadata }, + retryCount: this.retryCount, + }; + + if (this.cause !== undefined) { + options.cause = this.cause; + } + + return new HardwareWalletError(this.message, options); + } + + /** + * Serializes the error to a JSON-compatible object. + * Note: The cause property is serialized if it exists. + * + * @returns A JSON-compatible object representation of the error. + */ + toJSON(): Record { + const json: Record = { + id: this.id, + name: this.name, + message: this.message, + code: this.code, + severity: this.severity, + category: this.category, + retryStrategy: this.retryStrategy, + userActionable: this.userActionable, + userMessage: this.userMessage, + timestamp: this.timestamp.toISOString(), + metadata: this.metadata, + retryCount: this.retryCount, + stack: this.stack, + }; + + if (this.cause !== undefined) { + json.cause = { + name: this.cause.name, + message: this.cause.message, + stack: this.cause.stack, + }; + } + + return json; + } + + /** + * Returns a user-friendly string representation of the error. + * + * @returns A user-friendly string representation of the error. + */ + toString(): string { + return `${this.name} [${this.code}]: ${this.userMessage}`; + } + + /** + * Returns a detailed string representation for debugging. + * + * @returns A detailed string representation of the error for debugging. + */ + toDetailedString(): string { + const details = [ + `${this.name} [${this.code}]`, + `Message: ${this.message}`, + `User Message: ${this.userMessage}`, + `Severity: ${this.severity}`, + `Category: ${this.category}`, + `Retry Strategy: ${this.retryStrategy}`, + `User Actionable: ${this.userActionable}`, + `Timestamp: ${this.timestamp.toISOString()}`, + `Retry Count: ${this.retryCount}`, + ]; + + if (this.metadata && Object.keys(this.metadata).length > 0) { + details.push(`Metadata: ${JSON.stringify(this.metadata, null, 2)}`); + } + + if (this.cause !== undefined) { + details.push(`Caused by: ${this.cause.message}`); + } + + return details.join('\n'); + } +} diff --git a/packages/keyring-utils/src/hardware-errors-enums.ts b/packages/keyring-utils/src/hardware-errors-enums.ts new file mode 100644 index 000000000..249279ed2 --- /dev/null +++ b/packages/keyring-utils/src/hardware-errors-enums.ts @@ -0,0 +1,121 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +// Error Code Enum +export enum ErrorCode { + // Authentication & Security + AUTH_PIN_001 = 'AUTH_PIN_001', + AUTH_PIN_002 = 'AUTH_PIN_002', + AUTH_PIN_003 = 'AUTH_PIN_003', + AUTH_PIN_004 = 'AUTH_PIN_004', + AUTH_LOCK_001 = 'AUTH_LOCK_001', + AUTH_LOCK_002 = 'AUTH_LOCK_002', + AUTH_SEC_001 = 'AUTH_SEC_001', + AUTH_SEC_002 = 'AUTH_SEC_002', + AUTH_WIPE_001 = 'AUTH_WIPE_001', + + // User Action + USER_CANCEL_001 = 'USER_CANCEL_001', + USER_CANCEL_002 = 'USER_CANCEL_002', + USER_INPUT_001 = 'USER_INPUT_001', + USER_CONFIRM_001 = 'USER_CONFIRM_001', + + // Device State + DEVICE_STATE_001 = 'DEVICE_STATE_001', + DEVICE_STATE_002 = 'DEVICE_STATE_002', + DEVICE_STATE_003 = 'DEVICE_STATE_003', + DEVICE_STATE_004 = 'DEVICE_STATE_004', + DEVICE_STATE_005 = 'DEVICE_STATE_005', + DEVICE_DETECT_001 = 'DEVICE_DETECT_001', + DEVICE_CAP_001 = 'DEVICE_CAP_001', + DEVICE_CAP_002 = 'DEVICE_CAP_002', + DEVICE_MODE_001 = 'DEVICE_MODE_001', + + // Connection & Transport + CONN_TRANSPORT_001 = 'CONN_TRANSPORT_001', + CONN_CLOSED_001 = 'CONN_CLOSED_001', + CONN_IFRAME_001 = 'CONN_IFRAME_001', + CONN_SUITE_001 = 'CONN_SUITE_001', + CONN_TIMEOUT_001 = 'CONN_TIMEOUT_001', + CONN_BLOCKED_001 = 'CONN_BLOCKED_001', + + // Data & Validation + DATA_FORMAT_001 = 'DATA_FORMAT_001', + DATA_FORMAT_002 = 'DATA_FORMAT_002', + DATA_FORMAT_003 = 'DATA_FORMAT_003', + DATA_MISSING_001 = 'DATA_MISSING_001', + DATA_VALIDATION_001 = 'DATA_VALIDATION_001', + DATA_VALIDATION_002 = 'DATA_VALIDATION_002', + DATA_NOTFOUND_001 = 'DATA_NOTFOUND_001', + DATA_NOTFOUND_002 = 'DATA_NOTFOUND_002', + DATA_NOTFOUND_003 = 'DATA_NOTFOUND_003', + + // Cryptographic Operations + CRYPTO_SIGN_001 = 'CRYPTO_SIGN_001', + CRYPTO_ALGO_001 = 'CRYPTO_ALGO_001', + CRYPTO_KEY_001 = 'CRYPTO_KEY_001', + CRYPTO_ENTROPY_001 = 'CRYPTO_ENTROPY_001', + + // System & Internal + SYS_INTERNAL_001 = 'SYS_INTERNAL_001', + SYS_MEMORY_001 = 'SYS_MEMORY_001', + SYS_MEMORY_002 = 'SYS_MEMORY_002', + SYS_FILE_001 = 'SYS_FILE_001', + SYS_FILE_002 = 'SYS_FILE_002', + SYS_LICENSE_001 = 'SYS_LICENSE_001', + SYS_FIRMWARE_001 = 'SYS_FIRMWARE_001', + SYS_FIRMWARE_002 = 'SYS_FIRMWARE_002', + + // Command & Protocol + PROTO_CMD_001 = 'PROTO_CMD_001', + PROTO_CMD_002 = 'PROTO_CMD_002', + PROTO_CMD_003 = 'PROTO_CMD_003', + PROTO_MSG_001 = 'PROTO_MSG_001', + PROTO_PARAM_001 = 'PROTO_PARAM_001', + + // Configuration & Initialization + CONFIG_INIT_001 = 'CONFIG_INIT_001', + CONFIG_INIT_002 = 'CONFIG_INIT_002', + CONFIG_INIT_003 = 'CONFIG_INIT_003', + CONFIG_PERM_001 = 'CONFIG_PERM_001', + CONFIG_METHOD_001 = 'CONFIG_METHOD_001', + + // Transaction + TX_FUNDS_001 = 'TX_FUNDS_001', + TX_FAIL_001 = 'TX_FAIL_001', + + // Success + SUCCESS_000 = 'SUCCESS_000', + + // Unknown/Fallback + UNKNOWN_001 = 'UNKNOWN_001', +} + +// Severity Enum +export enum Severity { + INFO = 'INFO', + ERROR = 'ERROR', + WARNING = 'WARNING', + CRITICAL = 'CRITICAL', +} + +// Category Enum +export enum Category { + SUCCESS = 'SUCCESS', + AUTHENTICATION = 'AUTHENTICATION', + DATA_VALIDATION = 'DATA_VALIDATION', + PROTOCOL = 'PROTOCOL', + SYSTEM = 'SYSTEM', + CRYPTOGRAPHY = 'CRYPTOGRAPHY', + CONFIGURATION = 'CONFIGURATION', + CONNECTION = 'CONNECTION', + USER_ACTION = 'USER_ACTION', + DEVICE_STATE = 'DEVICE_STATE', + TRANSACTION = 'TRANSACTION', + UNKNOWN = 'UNKNOWN', +} + +// Retry Strategy Enum +export enum RetryStrategy { + NO_RETRY = 'NO_RETRY', + RETRY = 'RETRY', + EXPONENTIAL_BACKOFF = 'EXPONENTIAL_BACKOFF', +} diff --git a/packages/keyring-utils/src/index.ts b/packages/keyring-utils/src/index.ts index e84e6cc96..428d5609d 100644 --- a/packages/keyring-utils/src/index.ts +++ b/packages/keyring-utils/src/index.ts @@ -5,3 +5,7 @@ export * from './scopes'; export * from './superstruct'; export * from './JsonRpcRequest'; export type * from './keyring'; +export * from './hardware-errors-enums'; +export * from './hardware-error-mappings'; +export * from './hardware-error-codes'; +export * from './hardware-error'; From 8023f973d6a895324e62d0c048debd3a6685986d Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Thu, 8 Jan 2026 18:16:55 +0800 Subject: [PATCH 02/24] fix: casing --- packages/keyring-utils/src/hardware-error-mappings.test.ts | 2 +- packages/keyring-utils/src/hardware-error-mappings.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error-mappings.test.ts b/packages/keyring-utils/src/hardware-error-mappings.test.ts index c155e724d..1870c6c99 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.test.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.test.ts @@ -454,7 +454,7 @@ describe('HARDWARE_MAPPINGS', () => { it('should have default error mapping', () => { const { default: defaultMapping } = HARDWARE_MAPPINGS.trezor; expect(defaultMapping).toBeDefined(); - expect(defaultMapping.custom_code).toBe(ErrorCode.UNKNOWN_001); + expect(defaultMapping.customCode).toBe(ErrorCode.UNKNOWN_001); expect(defaultMapping.category).toBe(Category.UNKNOWN); }); diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/keyring-utils/src/hardware-error-mappings.ts index b5e4d1323..2c4b269e1 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.ts @@ -804,11 +804,11 @@ export const HARDWARE_MAPPINGS = { }, }, default: { - custom_code: ErrorCode.UNKNOWN_001, + customCode: ErrorCode.UNKNOWN_001, message: 'Unknown Trezor error', severity: Severity.ERROR, category: Category.UNKNOWN, - retry_strategy: RetryStrategy.NO_RETRY, + retryStrategy: RetryStrategy.NO_RETRY, userActionable: false, userMessage: 'An unexpected error occurred. Please try again or contact support.', From b86b6274f50b31b4fd0bcd90d57211b0a7ef68cb Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Thu, 8 Jan 2026 22:00:56 +0800 Subject: [PATCH 03/24] refactor: remove unneeded --- .../keyring-utils/src/hardware-error.test.ts | 71 ------------------- packages/keyring-utils/src/hardware-error.ts | 43 ----------- 2 files changed, 114 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error.test.ts b/packages/keyring-utils/src/hardware-error.test.ts index c8f43fcda..6c9fdc4e3 100644 --- a/packages/keyring-utils/src/hardware-error.test.ts +++ b/packages/keyring-utils/src/hardware-error.test.ts @@ -28,7 +28,6 @@ describe('HardwareWalletError', () => { expect(error.retryStrategy).toBe(RetryStrategy.RETRY); expect(error.userActionable).toBe(true); expect(error.userMessage).toBe('Transaction was rejected'); - expect(error.retryCount).toBe(0); }); it('should generate a unique error ID', () => { @@ -60,17 +59,10 @@ describe('HardwareWalletError', () => { ...mockOptions, cause, metadata, - retryCount: 3, }); expect(error.cause).toBe(cause); expect(error.metadata).toStrictEqual(metadata); - expect(error.retryCount).toBe(3); - }); - - it('should default retryCount to 0 when not provided', () => { - const error = new HardwareWalletError('Test error', mockOptions); - expect(error.retryCount).toBe(0); }); it('should work with instanceof checks', () => { @@ -78,12 +70,6 @@ describe('HardwareWalletError', () => { expect(error instanceof HardwareWalletError).toBe(true); expect(error instanceof Error).toBe(true); }); - - it('should capture stack trace', () => { - const error = new HardwareWalletError('Test error', mockOptions); - expect(error.stack).toBeDefined(); - expect(error.stack).toContain('HardwareWalletError'); - }); }); describe('isRetryable', () => { @@ -172,53 +158,6 @@ describe('HardwareWalletError', () => { }); }); - describe('withIncrementedRetryCount', () => { - it('should create a new error with incremented retry count', () => { - const originalError = new HardwareWalletError('Test error', { - ...mockOptions, - retryCount: 2, - }); - - const newError = originalError.withIncrementedRetryCount(); - - expect(newError.retryCount).toBe(3); - expect(originalError.retryCount).toBe(2); // Original unchanged - expect(newError).not.toBe(originalError); // New instance - }); - - it('should preserve all other properties', () => { - const cause = new Error('Original error'); - const metadata = { deviceId: '12345' }; - - const originalError = new HardwareWalletError('Test error', { - ...mockOptions, - cause, - metadata, - }); - - const newError = originalError.withIncrementedRetryCount(); - - expect(newError.message).toBe(originalError.message); - expect(newError.code).toBe(originalError.code); - expect(newError.severity).toBe(originalError.severity); - expect(newError.category).toBe(originalError.category); - expect(newError.retryStrategy).toBe(originalError.retryStrategy); - expect(newError.userActionable).toBe(originalError.userActionable); - expect(newError.userMessage).toBe(originalError.userMessage); - expect(newError.cause).toBe(originalError.cause); - expect(newError.metadata).toStrictEqual(originalError.metadata); - }); - - it('should work when optional properties are undefined', () => { - const originalError = new HardwareWalletError('Test error', mockOptions); - const newError = originalError.withIncrementedRetryCount(); - - expect(newError.retryCount).toBe(1); - expect(newError.cause).toBeUndefined(); - expect(newError.metadata).toBeUndefined(); - }); - }); - describe('withMetadata', () => { it('should create a new error with additional metadata', () => { const originalMetadata = { deviceId: '12345' }; @@ -263,7 +202,6 @@ describe('HardwareWalletError', () => { const originalError = new HardwareWalletError('Test error', { ...mockOptions, cause, - retryCount: 5, }); const newError = originalError.withMetadata({ extra: 'data' }); @@ -276,21 +214,18 @@ describe('HardwareWalletError', () => { expect(newError.userActionable).toBe(originalError.userActionable); expect(newError.userMessage).toBe(originalError.userMessage); expect(newError.cause).toBe(originalError.cause); - expect(newError.retryCount).toBe(originalError.retryCount); }); }); describe('toJSON', () => { it('should serialize all properties to JSON', () => { const cause = new Error('Original error'); - cause.stack = 'Error stack trace'; const metadata = { deviceId: '12345' }; const error = new HardwareWalletError('Test error', { ...mockOptions, cause, metadata, - retryCount: 3, }); const json = error.toJSON(); @@ -306,13 +241,10 @@ describe('HardwareWalletError', () => { expect(json.userMessage).toBe('Transaction was rejected'); expect(json.timestamp).toBe(error.timestamp.toISOString()); expect(json.metadata).toStrictEqual(metadata); - expect(json.retryCount).toBe(3); - expect(json.stack).toBeDefined(); }); it('should serialize cause when present', () => { const cause = new Error('Original error'); - cause.stack = 'Error stack trace'; const error = new HardwareWalletError('Test error', { ...mockOptions, @@ -324,7 +256,6 @@ describe('HardwareWalletError', () => { expect(json.cause).toStrictEqual({ name: 'Error', message: 'Original error', - stack: 'Error stack trace', }); }); @@ -372,7 +303,6 @@ describe('HardwareWalletError', () => { it('should return a detailed string with all information', () => { const error = new HardwareWalletError('Test error', { ...mockOptions, - retryCount: 2, }); const result = error.toDetailedString(); @@ -385,7 +315,6 @@ describe('HardwareWalletError', () => { expect(result).toContain('Retry Strategy: RETRY'); expect(result).toContain('User Actionable: true'); expect(result).toContain('Timestamp:'); - expect(result).toContain('Retry Count: 2'); }); it('should include metadata when present', () => { diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts index 78332612a..4a52c4a3f 100644 --- a/packages/keyring-utils/src/hardware-error.ts +++ b/packages/keyring-utils/src/hardware-error.ts @@ -21,7 +21,6 @@ export type HardwareWalletErrorOptions = { userMessage: string; cause?: Error; metadata?: Record; - retryCount?: number; }; export class HardwareWalletError extends Error { @@ -43,8 +42,6 @@ export class HardwareWalletError extends Error { public readonly metadata: Record | undefined; - public readonly retryCount: number; - public readonly cause: Error | undefined; constructor(message: string, options: HardwareWalletErrorOptions) { @@ -59,16 +56,7 @@ export class HardwareWalletError extends Error { this.userMessage = options.userMessage; this.timestamp = new Date(); this.metadata = options.metadata; - this.retryCount = options.retryCount ?? 0; this.cause = options.cause; - - // Ensure proper prototype chain for instanceof checks - Object.setPrototypeOf(this, HardwareWalletError.prototype); - - // Capture stack trace if available - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor); - } } /** @@ -107,32 +95,6 @@ export class HardwareWalletError extends Error { return this.userActionable; } - /** - * Creates a new error instance with an incremented retry count. - * - * @returns A new HardwareWalletError instance with incremented retry count. - */ - withIncrementedRetryCount(): HardwareWalletError { - const options: HardwareWalletErrorOptions = { - code: this.code, - severity: this.severity, - category: this.category, - retryStrategy: this.retryStrategy, - userActionable: this.userActionable, - userMessage: this.userMessage, - retryCount: this.retryCount + 1, - }; - - if (this.cause !== undefined) { - options.cause = this.cause; - } - if (this.metadata !== undefined) { - options.metadata = this.metadata; - } - - return new HardwareWalletError(this.message, options); - } - /** * Creates a new error instance with additional metadata. * @@ -150,7 +112,6 @@ export class HardwareWalletError extends Error { userActionable: this.userActionable, userMessage: this.userMessage, metadata: { ...(this.metadata ?? {}), ...additionalMetadata }, - retryCount: this.retryCount, }; if (this.cause !== undefined) { @@ -179,15 +140,12 @@ export class HardwareWalletError extends Error { userMessage: this.userMessage, timestamp: this.timestamp.toISOString(), metadata: this.metadata, - retryCount: this.retryCount, - stack: this.stack, }; if (this.cause !== undefined) { json.cause = { name: this.cause.name, message: this.cause.message, - stack: this.cause.stack, }; } @@ -218,7 +176,6 @@ export class HardwareWalletError extends Error { `Retry Strategy: ${this.retryStrategy}`, `User Actionable: ${this.userActionable}`, `Timestamp: ${this.timestamp.toISOString()}`, - `Retry Count: ${this.retryCount}`, ]; if (this.metadata && Object.keys(this.metadata).length > 0) { From 5777cba38163dc9b4f4c66ef29c0205f5a0696be Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Thu, 8 Jan 2026 22:18:43 +0800 Subject: [PATCH 04/24] fix: refactor to pascal case and remove unused --- .../src/hardware-error-codes.test.ts | 283 ------- .../keyring-utils/src/hardware-error-codes.ts | 86 --- .../src/hardware-error-mappings.test.ts | 204 +++--- .../src/hardware-error-mappings.ts | 688 +++++++++--------- .../keyring-utils/src/hardware-error.test.ts | 76 +- packages/keyring-utils/src/hardware-error.ts | 6 +- .../src/hardware-errors-enums.ts | 165 +++-- packages/keyring-utils/src/index.ts | 1 - 8 files changed, 569 insertions(+), 940 deletions(-) delete mode 100644 packages/keyring-utils/src/hardware-error-codes.test.ts delete mode 100644 packages/keyring-utils/src/hardware-error-codes.ts diff --git a/packages/keyring-utils/src/hardware-error-codes.test.ts b/packages/keyring-utils/src/hardware-error-codes.test.ts deleted file mode 100644 index 2429b83e1..000000000 --- a/packages/keyring-utils/src/hardware-error-codes.test.ts +++ /dev/null @@ -1,283 +0,0 @@ -import * as errorCodes from './hardware-error-codes'; - -describe('hardware-error-codes', () => { - describe('exports', () => { - it('should export all error code constants', () => { - expect(Object.keys(errorCodes).length).toBeGreaterThan(0); - }); - - it('should export all constants as strings', () => { - Object.values(errorCodes).forEach((value) => { - expect(typeof value).toBe('string'); - }); - }); - }); - - describe('Authentication & Security codes', () => { - it('should have PIN error codes', () => { - expect(errorCodes.AUTH_PIN_001).toBe('PIN invalid'); - expect(errorCodes.AUTH_PIN_002).toBe('PIN cancelled by user'); - expect(errorCodes.AUTH_PIN_003).toBe('PIN attempts remaining'); - expect(errorCodes.AUTH_PIN_004).toBe('PIN mismatch'); - }); - - it('should have lock error codes', () => { - expect(errorCodes.AUTH_LOCK_001).toBe('Device is locked'); - expect(errorCodes.AUTH_LOCK_002).toBe( - 'Device blocked due to failed attempts', - ); - }); - - it('should have security error codes', () => { - expect(errorCodes.AUTH_SEC_001).toBe('Security conditions not satisfied'); - expect(errorCodes.AUTH_SEC_002).toBe('Access rights insufficient'); - }); - - it('should have wipe code error', () => { - expect(errorCodes.AUTH_WIPE_001).toBe('Wipe code mismatch'); - }); - }); - - describe('User Action codes', () => { - it('should have cancel error codes', () => { - expect(errorCodes.USER_CANCEL_001).toBe('User rejected action on device'); - expect(errorCodes.USER_CANCEL_002).toBe('User cancelled operation'); - }); - - it('should have user input codes', () => { - expect(errorCodes.USER_INPUT_001).toBe('User input expected'); - expect(errorCodes.USER_CONFIRM_001).toBe('User confirmation required'); - }); - }); - - describe('Device State codes', () => { - it('should have device state error codes', () => { - expect(errorCodes.DEVICE_STATE_001).toBe('Device not initialized'); - expect(errorCodes.DEVICE_STATE_002).toBe('Device busy'); - expect(errorCodes.DEVICE_STATE_003).toBe('Device disconnected'); - expect(errorCodes.DEVICE_STATE_004).toBe('Device used elsewhere'); - expect(errorCodes.DEVICE_STATE_005).toBe('Device call in progress'); - }); - - it('should have device detection code', () => { - expect(errorCodes.DEVICE_DETECT_001).toBe('Device not found'); - }); - - it('should have device capability codes', () => { - expect(errorCodes.DEVICE_CAP_001).toBe( - 'Device missing required capability', - ); - expect(errorCodes.DEVICE_CAP_002).toBe( - 'Device is BTC-only, operation not supported', - ); - }); - - it('should have device mode code', () => { - expect(errorCodes.DEVICE_MODE_001).toBe('Invalid device mode'); - }); - }); - - describe('Connection & Transport codes', () => { - it('should have transport error code', () => { - expect(errorCodes.CONN_TRANSPORT_001).toBe('Transport layer missing'); - }); - - it('should have connection error codes', () => { - expect(errorCodes.CONN_CLOSED_001).toBe('Connection closed unexpectedly'); - expect(errorCodes.CONN_IFRAME_001).toBe( - 'Unable to establish iframe connection', - ); - expect(errorCodes.CONN_SUITE_001).toBe('Unable to connect to Suite'); - expect(errorCodes.CONN_TIMEOUT_001).toBe('Connection timeout'); - expect(errorCodes.CONN_BLOCKED_001).toBe('Connection blocked'); - }); - }); - - describe('Data & Validation codes', () => { - it('should have data format error codes', () => { - expect(errorCodes.DATA_FORMAT_001).toBe('Incorrect data length'); - expect(errorCodes.DATA_FORMAT_002).toBe('Invalid data received'); - expect(errorCodes.DATA_FORMAT_003).toBe('Invalid parameter'); - }); - - it('should have data missing code', () => { - expect(errorCodes.DATA_MISSING_001).toBe('Missing critical parameter'); - }); - - it('should have data validation codes', () => { - expect(errorCodes.DATA_VALIDATION_001).toBe('Address mismatch'); - expect(errorCodes.DATA_VALIDATION_002).toBe('Invalid signature'); - }); - - it('should have data not found codes', () => { - expect(errorCodes.DATA_NOTFOUND_001).toBe('Referenced data not found'); - expect(errorCodes.DATA_NOTFOUND_002).toBe('File not found'); - expect(errorCodes.DATA_NOTFOUND_003).toBe('Coin not found'); - }); - }); - - describe('Cryptographic Operations codes', () => { - it('should have crypto error codes', () => { - expect(errorCodes.CRYPTO_SIGN_001).toBe('Signature operation failed'); - expect(errorCodes.CRYPTO_ALGO_001).toBe('Algorithm not supported'); - expect(errorCodes.CRYPTO_KEY_001).toBe('Invalid key check value'); - expect(errorCodes.CRYPTO_ENTROPY_001).toBe('Entropy check failed'); - }); - }); - - describe('System & Internal codes', () => { - it('should have internal error code', () => { - expect(errorCodes.SYS_INTERNAL_001).toBe('Internal device error'); - }); - - it('should have memory error codes', () => { - expect(errorCodes.SYS_MEMORY_001).toBe('Not enough memory'); - expect(errorCodes.SYS_MEMORY_002).toBe('Memory problem'); - }); - - it('should have file system error codes', () => { - expect(errorCodes.SYS_FILE_001).toBe('File system error'); - expect(errorCodes.SYS_FILE_002).toBe('Inconsistent file'); - }); - - it('should have license error code', () => { - expect(errorCodes.SYS_LICENSE_001).toBe('Licensing error'); - }); - - it('should have firmware error codes', () => { - expect(errorCodes.SYS_FIRMWARE_001).toBe('Firmware error'); - expect(errorCodes.SYS_FIRMWARE_002).toBe('Firmware installation failed'); - }); - }); - - describe('Command & Protocol codes', () => { - it('should have command error codes', () => { - expect(errorCodes.PROTO_CMD_001).toBe('Command not supported'); - expect(errorCodes.PROTO_CMD_002).toBe('Command incompatible'); - expect(errorCodes.PROTO_CMD_003).toBe('Unexpected message'); - }); - - it('should have protocol message codes', () => { - expect(errorCodes.PROTO_MSG_001).toBe('Invalid APDU command'); - expect(errorCodes.PROTO_PARAM_001).toBe('Invalid command parameters'); - }); - }); - - describe('Configuration & Initialization codes', () => { - it('should have initialization error codes', () => { - expect(errorCodes.CONFIG_INIT_001).toBe('Not initialized'); - expect(errorCodes.CONFIG_INIT_002).toBe('Already initialized'); - expect(errorCodes.CONFIG_INIT_003).toBe('Manifest missing'); - }); - - it('should have permission error code', () => { - expect(errorCodes.CONFIG_PERM_001).toBe('Permissions not granted'); - }); - - it('should have method error code', () => { - expect(errorCodes.CONFIG_METHOD_001).toBe('Method not allowed'); - }); - }); - - describe('Transaction codes', () => { - it('should have transaction error codes', () => { - expect(errorCodes.TX_FUNDS_001).toBe('Insufficient funds'); - expect(errorCodes.TX_FAIL_001).toBe('Transaction failed'); - }); - }); - - describe('Special codes', () => { - it('should have success code', () => { - expect(errorCodes.SUCCESS_000).toBe('Operation successful'); - }); - - it('should have unknown error code', () => { - expect(errorCodes.UNKNOWN_001).toBe('Unknown error'); - }); - }); - - describe('code uniqueness', () => { - it('should have unique error code identifiers', () => { - const codeNames = Object.keys(errorCodes); - const uniqueNames = new Set(codeNames); - expect(uniqueNames.size).toBe(codeNames.length); - }); - - it('should have unique error code messages', () => { - const codeValues = Object.values(errorCodes); - const uniqueValues = new Set(codeValues); - expect(uniqueValues.size).toBe(codeValues.length); - }); - }); - - describe('naming conventions', () => { - it('should follow naming pattern with underscores and numbers', () => { - const codeNames = Object.keys(errorCodes); - // Pattern: WORD_NNN or WORD_WORD_NNN (allows one or more words followed by numbers) - const pattern = /^[A-Z]+(_[A-Z0-9]+)*_\d+$/u; - - codeNames.forEach((name) => { - expect(name).toMatch(pattern); - }); - }); - - it('should have sequential numbering within categories', () => { - const categories: Record = {}; - - Object.keys(errorCodes).forEach((code) => { - const prefix = code.substring(0, code.lastIndexOf('_')); - if (!categories[prefix]) { - categories[prefix] = []; - } - categories[prefix].push(code); - }); - - // Check that each category has at least one code - Object.values(categories).forEach((codes) => { - expect(codes.length).toBeGreaterThan(0); - }); - }); - }); - - describe('error message quality', () => { - it('should have descriptive error messages', () => { - Object.values(errorCodes).forEach((message) => { - expect(message.length).toBeGreaterThan(5); - // Messages should start with a capital letter or be all caps - expect(message[0]).toMatch(/[A-Z]/u); - }); - }); - - it('should not end with punctuation', () => { - Object.values(errorCodes).forEach((message) => { - expect(message).not.toMatch(/[.!?]$/u); - }); - }); - }); - - describe('category coverage', () => { - it('should have error codes for all major categories', () => { - const codeNames = Object.keys(errorCodes); - - const categories = [ - 'AUTH', - 'USER', - 'DEVICE', - 'CONN', - 'DATA', - 'CRYPTO', - 'SYS', - 'PROTO', - 'CONFIG', - 'TX', - ]; - - categories.forEach((category) => { - const hasCategoryCode = codeNames.some((code) => - code.startsWith(category), - ); - expect(hasCategoryCode).toBe(true); - }); - }); - }); -}); diff --git a/packages/keyring-utils/src/hardware-error-codes.ts b/packages/keyring-utils/src/hardware-error-codes.ts deleted file mode 100644 index 2a1c4d568..000000000 --- a/packages/keyring-utils/src/hardware-error-codes.ts +++ /dev/null @@ -1,86 +0,0 @@ -// Authentication & Security -export const AUTH_PIN_001 = 'PIN invalid'; -export const AUTH_PIN_002 = 'PIN cancelled by user'; -export const AUTH_PIN_003 = 'PIN attempts remaining'; -export const AUTH_PIN_004 = 'PIN mismatch'; -export const AUTH_LOCK_001 = 'Device is locked'; -export const AUTH_LOCK_002 = 'Device blocked due to failed attempts'; -export const AUTH_SEC_001 = 'Security conditions not satisfied'; -export const AUTH_SEC_002 = 'Access rights insufficient'; -export const AUTH_WIPE_001 = 'Wipe code mismatch'; - -// User Action -export const USER_CANCEL_001 = 'User rejected action on device'; -export const USER_CANCEL_002 = 'User cancelled operation'; -export const USER_INPUT_001 = 'User input expected'; -export const USER_CONFIRM_001 = 'User confirmation required'; - -// Device State -export const DEVICE_STATE_001 = 'Device not initialized'; -export const DEVICE_STATE_002 = 'Device busy'; -export const DEVICE_STATE_003 = 'Device disconnected'; -export const DEVICE_STATE_004 = 'Device used elsewhere'; -export const DEVICE_STATE_005 = 'Device call in progress'; -export const DEVICE_DETECT_001 = 'Device not found'; -export const DEVICE_CAP_001 = 'Device missing required capability'; -export const DEVICE_CAP_002 = 'Device is BTC-only, operation not supported'; -export const DEVICE_MODE_001 = 'Invalid device mode'; - -// Connection & Transport -export const CONN_TRANSPORT_001 = 'Transport layer missing'; -export const CONN_CLOSED_001 = 'Connection closed unexpectedly'; -export const CONN_IFRAME_001 = 'Unable to establish iframe connection'; -export const CONN_SUITE_001 = 'Unable to connect to Suite'; -export const CONN_TIMEOUT_001 = 'Connection timeout'; -export const CONN_BLOCKED_001 = 'Connection blocked'; - -// Data & Validation -export const DATA_FORMAT_001 = 'Incorrect data length'; -export const DATA_FORMAT_002 = 'Invalid data received'; -export const DATA_FORMAT_003 = 'Invalid parameter'; -export const DATA_MISSING_001 = 'Missing critical parameter'; -export const DATA_VALIDATION_001 = 'Address mismatch'; -export const DATA_VALIDATION_002 = 'Invalid signature'; -export const DATA_NOTFOUND_001 = 'Referenced data not found'; -export const DATA_NOTFOUND_002 = 'File not found'; -export const DATA_NOTFOUND_003 = 'Coin not found'; - -// Cryptographic Operations -export const CRYPTO_SIGN_001 = 'Signature operation failed'; -export const CRYPTO_ALGO_001 = 'Algorithm not supported'; -export const CRYPTO_KEY_001 = 'Invalid key check value'; -export const CRYPTO_ENTROPY_001 = 'Entropy check failed'; - -// System & Internal -export const SYS_INTERNAL_001 = 'Internal device error'; -export const SYS_MEMORY_001 = 'Not enough memory'; -export const SYS_MEMORY_002 = 'Memory problem'; -export const SYS_FILE_001 = 'File system error'; -export const SYS_FILE_002 = 'Inconsistent file'; -export const SYS_LICENSE_001 = 'Licensing error'; -export const SYS_FIRMWARE_001 = 'Firmware error'; -export const SYS_FIRMWARE_002 = 'Firmware installation failed'; - -// Command & Protocol -export const PROTO_CMD_001 = 'Command not supported'; -export const PROTO_CMD_002 = 'Command incompatible'; -export const PROTO_CMD_003 = 'Unexpected message'; -export const PROTO_MSG_001 = 'Invalid APDU command'; -export const PROTO_PARAM_001 = 'Invalid command parameters'; - -// Configuration & Initialization -export const CONFIG_INIT_001 = 'Not initialized'; -export const CONFIG_INIT_002 = 'Already initialized'; -export const CONFIG_INIT_003 = 'Manifest missing'; -export const CONFIG_PERM_001 = 'Permissions not granted'; -export const CONFIG_METHOD_001 = 'Method not allowed'; - -// Transaction -export const TX_FUNDS_001 = 'Insufficient funds'; -export const TX_FAIL_001 = 'Transaction failed'; - -// Success -export const SUCCESS_000 = 'Operation successful'; - -// Unknown/Fallback -export const UNKNOWN_001 = 'Unknown error'; diff --git a/packages/keyring-utils/src/hardware-error-mappings.test.ts b/packages/keyring-utils/src/hardware-error-mappings.test.ts index 1870c6c99..7c662654c 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.test.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.test.ts @@ -31,10 +31,10 @@ describe('HARDWARE_MAPPINGS', () => { it('should map 0x9000 to success', () => { const mapping = errorMappings['0x9000']; expect(mapping).toBeDefined(); - expect(mapping.customCode).toBe(ErrorCode.SUCCESS_000); - expect(mapping.severity).toBe(Severity.INFO); - expect(mapping.category).toBe(Category.SUCCESS); - expect(mapping.retryStrategy).toBe(RetryStrategy.NO_RETRY); + expect(mapping.customCode).toBe(ErrorCode.Success000); + expect(mapping.severity).toBe(Severity.Info); + expect(mapping.category).toBe(Category.Success); + expect(mapping.retryStrategy).toBe(RetryStrategy.NoRetry); expect(mapping.userActionable).toBe(false); }); }); @@ -42,113 +42,113 @@ describe('HARDWARE_MAPPINGS', () => { describe('authentication errors', () => { it('should map 0x6300 to authentication failed', () => { const mapping = errorMappings['0x6300']; - expect(mapping.customCode).toBe(ErrorCode.AUTH_SEC_001); - expect(mapping.severity).toBe(Severity.ERROR); - expect(mapping.category).toBe(Category.AUTHENTICATION); + expect(mapping.customCode).toBe(ErrorCode.AuthSec001); + expect(mapping.severity).toBe(Severity.Err); + expect(mapping.category).toBe(Category.Authentication); expect(mapping.userActionable).toBe(true); expect(mapping.userMessage).toBeDefined(); }); it('should map 0x63c0 to PIN attempts remaining', () => { const mapping = errorMappings['0x63c0']; - expect(mapping.customCode).toBe(ErrorCode.AUTH_PIN_003); - expect(mapping.severity).toBe(Severity.WARNING); - expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + expect(mapping.customCode).toBe(ErrorCode.AuthPin003); + expect(mapping.severity).toBe(Severity.Warning); + expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); it('should map 0x5515 to device locked', () => { const mapping = errorMappings['0x5515']; - expect(mapping.customCode).toBe(ErrorCode.AUTH_LOCK_001); - expect(mapping.severity).toBe(Severity.ERROR); + expect(mapping.customCode).toBe(ErrorCode.AuthLock001); + expect(mapping.severity).toBe(Severity.Err); expect(mapping.userActionable).toBe(true); expect(mapping.userMessage).toContain('unlock'); }); it('should map 0x9840 to device blocked', () => { const mapping = errorMappings['0x9840']; - expect(mapping.customCode).toBe(ErrorCode.AUTH_LOCK_002); - expect(mapping.severity).toBe(Severity.CRITICAL); - expect(mapping.retryStrategy).toBe(RetryStrategy.NO_RETRY); + expect(mapping.customCode).toBe(ErrorCode.AuthLock002); + expect(mapping.severity).toBe(Severity.Critical); + expect(mapping.retryStrategy).toBe(RetryStrategy.NoRetry); }); }); describe('user action errors', () => { it('should map 0x6985 to user rejected', () => { const mapping = errorMappings['0x6985']; - expect(mapping.customCode).toBe(ErrorCode.USER_CANCEL_001); - expect(mapping.severity).toBe(Severity.WARNING); - expect(mapping.category).toBe(Category.USER_ACTION); - expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + expect(mapping.customCode).toBe(ErrorCode.UserCancel001); + expect(mapping.severity).toBe(Severity.Warning); + expect(mapping.category).toBe(Category.UserAction); + expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.userActionable).toBe(true); }); it('should map 0x5501 to user refused', () => { const mapping = errorMappings['0x5501']; - expect(mapping.customCode).toBe(ErrorCode.USER_CANCEL_001); - expect(mapping.severity).toBe(Severity.WARNING); - expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + expect(mapping.customCode).toBe(ErrorCode.UserCancel001); + expect(mapping.severity).toBe(Severity.Warning); + expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); }); describe('data validation errors', () => { it('should map 0x6700 to incorrect data length', () => { const mapping = errorMappings['0x6700']; - expect(mapping.customCode).toBe(ErrorCode.DATA_FORMAT_001); - expect(mapping.category).toBe(Category.DATA_VALIDATION); + expect(mapping.customCode).toBe(ErrorCode.DataFormat001); + expect(mapping.category).toBe(Category.DataValidation); expect(mapping.userActionable).toBe(false); }); it('should map 0x6a80 to invalid data', () => { const mapping = errorMappings['0x6a80']; - expect(mapping.customCode).toBe(ErrorCode.DATA_FORMAT_002); - expect(mapping.category).toBe(Category.DATA_VALIDATION); + expect(mapping.customCode).toBe(ErrorCode.DataFormat002); + expect(mapping.category).toBe(Category.DataValidation); }); it('should map 0x6b00 to invalid parameter', () => { const mapping = errorMappings['0x6b00']; - expect(mapping.customCode).toBe(ErrorCode.DATA_FORMAT_003); - expect(mapping.severity).toBe(Severity.ERROR); + expect(mapping.customCode).toBe(ErrorCode.DataFormat003); + expect(mapping.severity).toBe(Severity.Err); }); }); describe('protocol errors', () => { it('should map 0x6981 to command incompatible', () => { const mapping = errorMappings['0x6981']; - expect(mapping.customCode).toBe(ErrorCode.PROTO_CMD_002); - expect(mapping.category).toBe(Category.PROTOCOL); + expect(mapping.customCode).toBe(ErrorCode.ProtoCmd002); + expect(mapping.category).toBe(Category.Protocol); }); it('should map 0x6d00 to instruction not supported', () => { const mapping = errorMappings['0x6d00']; - expect(mapping.customCode).toBe(ErrorCode.PROTO_CMD_001); - expect(mapping.category).toBe(Category.PROTOCOL); + expect(mapping.customCode).toBe(ErrorCode.ProtoCmd001); + expect(mapping.category).toBe(Category.Protocol); }); it('should map 0x6d02 to unknown APDU command', () => { const mapping = errorMappings['0x6d02']; - expect(mapping.customCode).toBe(ErrorCode.PROTO_MSG_001); - expect(mapping.category).toBe(Category.PROTOCOL); + expect(mapping.customCode).toBe(ErrorCode.ProtoMsg001); + expect(mapping.category).toBe(Category.Protocol); }); }); describe('system errors', () => { it('should map 0x6f00 to internal device error', () => { const mapping = errorMappings['0x6f00']; - expect(mapping.customCode).toBe(ErrorCode.SYS_INTERNAL_001); - expect(mapping.severity).toBe(Severity.CRITICAL); - expect(mapping.category).toBe(Category.SYSTEM); + expect(mapping.customCode).toBe(ErrorCode.SysInternal001); + expect(mapping.severity).toBe(Severity.Critical); + expect(mapping.category).toBe(Category.System); }); it('should map 0x6a84 to not enough memory', () => { const mapping = errorMappings['0x6a84']; - expect(mapping.customCode).toBe(ErrorCode.SYS_MEMORY_001); - expect(mapping.category).toBe(Category.SYSTEM); + expect(mapping.customCode).toBe(ErrorCode.SysMemory001); + expect(mapping.category).toBe(Category.System); }); it('should map 0x6faa to device halted', () => { const mapping = errorMappings['0x6faa']; - expect(mapping.customCode).toBe(ErrorCode.SYS_INTERNAL_001); - expect(mapping.severity).toBe(Severity.CRITICAL); + expect(mapping.customCode).toBe(ErrorCode.SysInternal001); + expect(mapping.severity).toBe(Severity.Critical); expect(mapping.userMessage).toContain('disconnect and reconnect'); }); }); @@ -156,23 +156,23 @@ describe('HARDWARE_MAPPINGS', () => { describe('connection errors', () => { it('should map 0x650f to connection issue', () => { const mapping = errorMappings['0x650f']; - expect(mapping.customCode).toBe(ErrorCode.CONN_CLOSED_001); - expect(mapping.category).toBe(Category.CONNECTION); - expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + expect(mapping.customCode).toBe(ErrorCode.ConnClosed001); + expect(mapping.category).toBe(Category.Connection); + expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); }); describe('cryptographic errors', () => { it('should map 0x9484 to algorithm not supported', () => { const mapping = errorMappings['0x9484']; - expect(mapping.customCode).toBe(ErrorCode.CRYPTO_ALGO_001); - expect(mapping.category).toBe(Category.CRYPTOGRAPHY); + expect(mapping.customCode).toBe(ErrorCode.CryptoAlgo001); + expect(mapping.category).toBe(Category.Cryptography); }); it('should map 0x9485 to invalid key check value', () => { const mapping = errorMappings['0x9485']; - expect(mapping.customCode).toBe(ErrorCode.CRYPTO_KEY_001); - expect(mapping.category).toBe(Category.CRYPTOGRAPHY); + expect(mapping.customCode).toBe(ErrorCode.CryptoKey001); + expect(mapping.category).toBe(Category.Cryptography); }); }); @@ -220,31 +220,31 @@ describe('HARDWARE_MAPPINGS', () => { describe('failure codes', () => { it('should map code 1 to unexpected message', () => { const mapping = errorMapping['1']; - expect(mapping.customCode).toBe(ErrorCode.PROTO_CMD_003); - expect(mapping.severity).toBe(Severity.ERROR); - expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + expect(mapping.customCode).toBe(ErrorCode.ProtoCmd003); + expect(mapping.severity).toBe(Severity.Err); + expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.originalName).toBe('Failure_UnexpectedMessage'); }); it('should map code 4 to action cancelled', () => { const mapping = errorMapping['4']; - expect(mapping.customCode).toBe(ErrorCode.USER_CANCEL_002); - expect(mapping.category).toBe(Category.USER_ACTION); + expect(mapping.customCode).toBe(ErrorCode.UserCancel002); + expect(mapping.category).toBe(Category.UserAction); expect(mapping.userActionable).toBe(true); expect(mapping.originalName).toBe('Failure_ActionCancelled'); }); it('should map code 10 to insufficient funds', () => { const mapping = errorMapping['10']; - expect(mapping.customCode).toBe(ErrorCode.TX_FUNDS_001); - expect(mapping.category).toBe(Category.TRANSACTION); + expect(mapping.customCode).toBe(ErrorCode.TxFunds001); + expect(mapping.category).toBe(Category.Transaction); expect(mapping.originalName).toBe('Failure_NotEnoughFunds'); }); it('should map code 99 to firmware error', () => { const mapping = errorMapping['99']; - expect(mapping.customCode).toBe(ErrorCode.SYS_FIRMWARE_002); - expect(mapping.severity).toBe(Severity.CRITICAL); + expect(mapping.customCode).toBe(ErrorCode.SysFirmware002); + expect(mapping.severity).toBe(Severity.Critical); expect(mapping.originalName).toBe('Failure_FirmwareError'); }); }); @@ -252,22 +252,22 @@ describe('HARDWARE_MAPPINGS', () => { describe('PIN errors', () => { it('should map code 5 to PIN expected', () => { const mapping = errorMapping['5']; - expect(mapping.customCode).toBe(ErrorCode.USER_INPUT_001); - expect(mapping.category).toBe(Category.USER_ACTION); + expect(mapping.customCode).toBe(ErrorCode.UserInput001); + expect(mapping.category).toBe(Category.UserAction); expect(mapping.originalName).toBe('Failure_PinExpected'); }); it('should map code 7 to PIN invalid', () => { const mapping = errorMapping['7']; - expect(mapping.customCode).toBe(ErrorCode.AUTH_PIN_001); - expect(mapping.category).toBe(Category.AUTHENTICATION); - expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + expect(mapping.customCode).toBe(ErrorCode.AuthPin001); + expect(mapping.category).toBe(Category.Authentication); + expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.originalName).toBe('Failure_PinInvalid'); }); it('should map code 12 to PIN mismatch', () => { const mapping = errorMapping['12']; - expect(mapping.customCode).toBe(ErrorCode.AUTH_PIN_004); + expect(mapping.customCode).toBe(ErrorCode.AuthPin004); expect(mapping.originalName).toBe('Failure_PinMismatch'); }); }); @@ -275,30 +275,30 @@ describe('HARDWARE_MAPPINGS', () => { describe('device state errors', () => { it('should map code 11 to device not initialized', () => { const mapping = errorMapping['11']; - expect(mapping.customCode).toBe(ErrorCode.DEVICE_STATE_001); - expect(mapping.category).toBe(Category.DEVICE_STATE); + expect(mapping.customCode).toBe(ErrorCode.DeviceState001); + expect(mapping.category).toBe(Category.DeviceState); expect(mapping.originalName).toBe('Failure_NotInitialized'); }); it('should map code 15 to device busy', () => { const mapping = errorMapping['15']; - expect(mapping.customCode).toBe(ErrorCode.DEVICE_STATE_002); - expect(mapping.severity).toBe(Severity.WARNING); - expect(mapping.retryStrategy).toBe(RetryStrategy.EXPONENTIAL_BACKOFF); + expect(mapping.customCode).toBe(ErrorCode.DeviceState002); + expect(mapping.severity).toBe(Severity.Warning); + expect(mapping.retryStrategy).toBe(RetryStrategy.ExponentialBackoff); expect(mapping.originalName).toBe('Failure_Busy'); }); it('should map Device_Disconnected to device disconnected', () => { const mapping = errorMapping.Device_Disconnected; - expect(mapping.customCode).toBe(ErrorCode.DEVICE_STATE_003); - expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + expect(mapping.customCode).toBe(ErrorCode.DeviceState003); + expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.sdkMessage).toBe('Device disconnected'); }); it('should map Device_UsedElsewhere correctly', () => { const mapping = errorMapping.Device_UsedElsewhere; - expect(mapping.customCode).toBe(ErrorCode.DEVICE_STATE_004); - expect(mapping.retryStrategy).toBe(RetryStrategy.NO_RETRY); + expect(mapping.customCode).toBe(ErrorCode.DeviceState004); + expect(mapping.retryStrategy).toBe(RetryStrategy.NoRetry); expect(mapping.userMessage).toContain('another window'); }); }); @@ -306,62 +306,62 @@ describe('HARDWARE_MAPPINGS', () => { describe('initialization errors', () => { it('should map Init_NotInitialized', () => { const mapping = errorMapping.Init_NotInitialized; - expect(mapping.customCode).toBe(ErrorCode.CONFIG_INIT_001); - expect(mapping.category).toBe(Category.CONFIGURATION); + expect(mapping.customCode).toBe(ErrorCode.ConfigInit001); + expect(mapping.category).toBe(Category.Configuration); expect(mapping.sdkMessage).toBe('TrezorConnect not initialized'); }); it('should map Init_AlreadyInitialized', () => { const mapping = errorMapping.Init_AlreadyInitialized; - expect(mapping.customCode).toBe(ErrorCode.CONFIG_INIT_002); - expect(mapping.severity).toBe(Severity.WARNING); + expect(mapping.customCode).toBe(ErrorCode.ConfigInit002); + expect(mapping.severity).toBe(Severity.Warning); }); it('should map Init_ManifestMissing', () => { const mapping = errorMapping.Init_ManifestMissing; - expect(mapping.customCode).toBe(ErrorCode.CONFIG_INIT_003); - expect(mapping.category).toBe(Category.CONFIGURATION); + expect(mapping.customCode).toBe(ErrorCode.ConfigInit003); + expect(mapping.category).toBe(Category.Configuration); }); }); describe('connection errors', () => { it('should map Init_IframeBlocked', () => { const mapping = errorMapping.Init_IframeBlocked; - expect(mapping.customCode).toBe(ErrorCode.CONN_BLOCKED_001); - expect(mapping.category).toBe(Category.CONNECTION); + expect(mapping.customCode).toBe(ErrorCode.ConnBlocked001); + expect(mapping.category).toBe(Category.Connection); expect(mapping.userMessage).toContain('browser settings'); }); it('should map Init_IframeTimeout', () => { const mapping = errorMapping.Init_IframeTimeout; - expect(mapping.customCode).toBe(ErrorCode.CONN_TIMEOUT_001); - expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + expect(mapping.customCode).toBe(ErrorCode.ConnTimeout001); + expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); it('should map Transport_Missing', () => { const mapping = errorMapping.Transport_Missing; - expect(mapping.customCode).toBe(ErrorCode.CONN_TRANSPORT_001); - expect(mapping.category).toBe(Category.CONNECTION); + expect(mapping.customCode).toBe(ErrorCode.ConnTransport001); + expect(mapping.category).toBe(Category.Connection); }); }); describe('method errors', () => { it('should map Method_InvalidParameter', () => { const mapping = errorMapping.Method_InvalidParameter; - expect(mapping.customCode).toBe(ErrorCode.DATA_FORMAT_003); - expect(mapping.category).toBe(Category.DATA_VALIDATION); + expect(mapping.customCode).toBe(ErrorCode.DataFormat003); + expect(mapping.category).toBe(Category.DataValidation); }); it('should map Method_Cancel', () => { const mapping = errorMapping.Method_Cancel; - expect(mapping.customCode).toBe(ErrorCode.USER_CANCEL_002); - expect(mapping.category).toBe(Category.USER_ACTION); - expect(mapping.retryStrategy).toBe(RetryStrategy.RETRY); + expect(mapping.customCode).toBe(ErrorCode.UserCancel002); + expect(mapping.category).toBe(Category.UserAction); + expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); it('should map Method_UnknownCoin', () => { const mapping = errorMapping.Method_UnknownCoin; - expect(mapping.customCode).toBe(ErrorCode.DATA_NOTFOUND_003); + expect(mapping.customCode).toBe(ErrorCode.DataNotfound003); expect(mapping.userMessage).toContain('not supported'); }); }); @@ -369,13 +369,13 @@ describe('HARDWARE_MAPPINGS', () => { describe('device capability errors', () => { it('should map Device_MissingCapability', () => { const mapping = errorMapping.Device_MissingCapability; - expect(mapping.customCode).toBe(ErrorCode.DEVICE_CAP_001); + expect(mapping.customCode).toBe(ErrorCode.DeviceCap001); expect(mapping.userMessage).toContain('firmware update'); }); it('should map Device_MissingCapabilityBtcOnly', () => { const mapping = errorMapping.Device_MissingCapabilityBtcOnly; - expect(mapping.customCode).toBe(ErrorCode.DEVICE_CAP_002); + expect(mapping.customCode).toBe(ErrorCode.DeviceCap002); expect(mapping.userMessage).toContain('Bitcoin-only'); }); }); @@ -383,15 +383,15 @@ describe('HARDWARE_MAPPINGS', () => { describe('special codes', () => { it('should have UNKNOWN fallback', () => { const mapping = errorMapping.UNKNOWN; - expect(mapping.customCode).toBe(ErrorCode.UNKNOWN_001); - expect(mapping.category).toBe(Category.UNKNOWN); + expect(mapping.customCode).toBe(ErrorCode.Unknown001); + expect(mapping.category).toBe(Category.Unknown); expect(mapping.originalName).toBe('Failure_UnknownCode'); }); it('should map ENTROPY_CHECK', () => { const mapping = errorMapping.ENTROPY_CHECK; - expect(mapping.customCode).toBe(ErrorCode.CRYPTO_ENTROPY_001); - expect(mapping.category).toBe(Category.CRYPTOGRAPHY); + expect(mapping.customCode).toBe(ErrorCode.CryptoEntropy001); + expect(mapping.category).toBe(Category.Cryptography); expect(mapping.originalName).toBe('Failure_EntropyCheck'); }); }); @@ -454,8 +454,8 @@ describe('HARDWARE_MAPPINGS', () => { it('should have default error mapping', () => { const { default: defaultMapping } = HARDWARE_MAPPINGS.trezor; expect(defaultMapping).toBeDefined(); - expect(defaultMapping.customCode).toBe(ErrorCode.UNKNOWN_001); - expect(defaultMapping.category).toBe(Category.UNKNOWN); + expect(defaultMapping.customCode).toBe(ErrorCode.Unknown001); + expect(defaultMapping.category).toBe(Category.Unknown); }); it('should have error_patterns array', () => { @@ -509,7 +509,7 @@ describe('HARDWARE_MAPPINGS', () => { ).filter( (mapping): mapping is typeof mapping & { userMessage: string } => mapping.userActionable && - mapping.severity !== Severity.INFO && + mapping.severity !== Severity.Info && 'userMessage' in mapping && typeof mapping.userMessage === 'string' && mapping.userMessage.length > 0, @@ -525,7 +525,7 @@ describe('HARDWARE_MAPPINGS', () => { ).filter( (mapping): mapping is typeof mapping & { userMessage: string } => mapping.userActionable && - mapping.severity !== Severity.INFO && + mapping.severity !== Severity.Info && 'userMessage' in mapping && typeof mapping.userMessage === 'string' && mapping.userMessage.length > 0, @@ -544,11 +544,11 @@ describe('HARDWARE_MAPPINGS', () => { ]; const criticalMappings = allMappings.filter( - (mapping) => mapping.severity === Severity.CRITICAL, + (mapping) => mapping.severity === Severity.Critical, ); criticalMappings.forEach((mapping) => { - expect([RetryStrategy.NO_RETRY, RetryStrategy.RETRY]).toContain( + expect([RetryStrategy.NoRetry, RetryStrategy.Retry]).toContain( mapping.retryStrategy, ); }); diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/keyring-utils/src/hardware-error-mappings.ts index 2c4b269e1..2eed4fe0a 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.ts @@ -11,308 +11,308 @@ export const HARDWARE_MAPPINGS = { vendorName: 'Ledger', errorMappings: { '0x9000': { - customCode: ErrorCode.SUCCESS_000, + customCode: ErrorCode.Success000, message: 'Operation successful', - severity: Severity.INFO, - category: Category.SUCCESS, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Info, + category: Category.Success, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x6300': { - customCode: ErrorCode.AUTH_SEC_001, + customCode: ErrorCode.AuthSec001, message: 'Authentication failed', - severity: Severity.ERROR, - category: Category.AUTHENTICATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Authentication, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Authentication failed. Please verify your credentials.', }, '0x63c0': { - customCode: ErrorCode.AUTH_PIN_003, + customCode: ErrorCode.AuthPin003, message: 'PIN attempts remaining', - severity: Severity.WARNING, - category: Category.AUTHENTICATION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Warning, + category: Category.Authentication, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Incorrect PIN. Please try again.', }, '0x6700': { - customCode: ErrorCode.DATA_FORMAT_001, + customCode: ErrorCode.DataFormat001, message: 'Incorrect data length', - severity: Severity.ERROR, - category: Category.DATA_VALIDATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DataValidation, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x6800': { - customCode: ErrorCode.DATA_MISSING_001, + customCode: ErrorCode.DataMissing001, message: 'Missing critical parameter', - severity: Severity.ERROR, - category: Category.DATA_VALIDATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DataValidation, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x6981': { - customCode: ErrorCode.PROTO_CMD_002, + customCode: ErrorCode.ProtoCmd002, message: 'Command incompatible with file structure', - severity: Severity.ERROR, - category: Category.PROTOCOL, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Protocol, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x6982': { - customCode: ErrorCode.AUTH_SEC_002, + customCode: ErrorCode.AuthSec002, message: 'Security conditions not satisfied', - severity: Severity.ERROR, - category: Category.AUTHENTICATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Authentication, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Device is locked or access rights are insufficient. Please unlock your device.', }, '0x6985': { - customCode: ErrorCode.USER_CANCEL_001, + customCode: ErrorCode.UserCancel001, message: 'User rejected action on device', - severity: Severity.WARNING, - category: Category.USER_ACTION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Warning, + category: Category.UserAction, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Transaction was rejected. Please approve on your device to continue.', }, '0x6a80': { - customCode: ErrorCode.DATA_FORMAT_002, + customCode: ErrorCode.DataFormat002, message: 'Invalid data received', - severity: Severity.ERROR, - category: Category.DATA_VALIDATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DataValidation, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x6a84': { - customCode: ErrorCode.SYS_MEMORY_001, + customCode: ErrorCode.SysMemory001, message: 'Not enough memory space', - severity: Severity.ERROR, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.System, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x6a88': { - customCode: ErrorCode.DATA_NOTFOUND_001, + customCode: ErrorCode.DataNotfound001, message: 'Referenced data not found', - severity: Severity.ERROR, - category: Category.DATA_VALIDATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DataValidation, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x6a89': { - customCode: ErrorCode.SYS_FILE_001, + customCode: ErrorCode.SysFile001, message: 'File already exists', - severity: Severity.ERROR, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.System, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x6b00': { - customCode: ErrorCode.DATA_FORMAT_003, + customCode: ErrorCode.DataFormat003, message: 'Invalid parameter received', - severity: Severity.ERROR, - category: Category.DATA_VALIDATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DataValidation, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x6d00': { - customCode: ErrorCode.PROTO_CMD_001, + customCode: ErrorCode.ProtoCmd001, message: 'Instruction not supported', - severity: Severity.ERROR, - category: Category.PROTOCOL, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Protocol, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x6d02': { - customCode: ErrorCode.PROTO_MSG_001, + customCode: ErrorCode.ProtoMsg001, message: 'Unknown APDU command', - severity: Severity.ERROR, - category: Category.PROTOCOL, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Protocol, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x6e00': { - customCode: ErrorCode.PROTO_CMD_001, + customCode: ErrorCode.ProtoCmd001, message: 'Class not supported', - severity: Severity.ERROR, - category: Category.PROTOCOL, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Protocol, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x6501': { - customCode: ErrorCode.PROTO_CMD_001, + customCode: ErrorCode.ProtoCmd001, message: 'Ethereum app specific error', - severity: Severity.ERROR, - category: Category.PROTOCOL, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Protocol, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Please ensure the Ethereum app is open on your Ledger device.', }, '0x6f00': { - customCode: ErrorCode.SYS_INTERNAL_001, + customCode: ErrorCode.SysInternal001, message: 'Internal device error', - severity: Severity.CRITICAL, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Critical, + category: Category.System, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, userMessage: 'An internal error occurred. Please report this issue.', }, '0x6f42': { - customCode: ErrorCode.SYS_LICENSE_001, + customCode: ErrorCode.SysLicense001, message: 'Licensing error', - severity: Severity.CRITICAL, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Critical, + category: Category.System, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, userMessage: 'A licensing error occurred. Please contact support.', }, '0x6faa': { - customCode: ErrorCode.SYS_INTERNAL_001, + customCode: ErrorCode.SysInternal001, message: 'Device halted', - severity: Severity.CRITICAL, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Critical, + category: Category.System, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, userMessage: 'Device has halted. Please disconnect and reconnect your device.', }, '0x9240': { - customCode: ErrorCode.SYS_MEMORY_002, + customCode: ErrorCode.SysMemory002, message: 'Memory problem', - severity: Severity.ERROR, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.System, + retryStrategy: RetryStrategy.Retry, userActionable: false, }, '0x9400': { - customCode: ErrorCode.SYS_FILE_001, + customCode: ErrorCode.SysFile001, message: 'No elementary file selected', - severity: Severity.ERROR, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.System, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x9402': { - customCode: ErrorCode.DATA_FORMAT_003, + customCode: ErrorCode.DataFormat003, message: 'Invalid offset', - severity: Severity.ERROR, - category: Category.DATA_VALIDATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DataValidation, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x9404': { - customCode: ErrorCode.DATA_NOTFOUND_002, + customCode: ErrorCode.DataNotfound002, message: 'File not found', - severity: Severity.ERROR, - category: Category.DATA_VALIDATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DataValidation, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x9408': { - customCode: ErrorCode.SYS_FILE_002, + customCode: ErrorCode.SysFile002, message: 'Inconsistent file', - severity: Severity.ERROR, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.System, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x9484': { - customCode: ErrorCode.CRYPTO_ALGO_001, + customCode: ErrorCode.CryptoAlgo001, message: 'Algorithm not supported', - severity: Severity.ERROR, - category: Category.CRYPTOGRAPHY, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Cryptography, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x9485': { - customCode: ErrorCode.CRYPTO_KEY_001, + customCode: ErrorCode.CryptoKey001, message: 'Invalid key check value', - severity: Severity.ERROR, - category: Category.CRYPTOGRAPHY, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Cryptography, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x9802': { - customCode: ErrorCode.CONFIG_INIT_001, + customCode: ErrorCode.ConfigInit001, message: 'Code not initialized', - severity: Severity.ERROR, - category: Category.CONFIGURATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Configuration, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x9804': { - customCode: ErrorCode.AUTH_SEC_002, + customCode: ErrorCode.AuthSec002, message: 'Access condition not fulfilled', - severity: Severity.ERROR, - category: Category.AUTHENTICATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Authentication, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, }, '0x9808': { - customCode: ErrorCode.AUTH_PIN_001, + customCode: ErrorCode.AuthPin001, message: 'Contradiction in secret code status', - severity: Severity.ERROR, - category: Category.AUTHENTICATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Authentication, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x9810': { - customCode: ErrorCode.SYS_INTERNAL_001, + customCode: ErrorCode.SysInternal001, message: 'Contradiction invalidation', - severity: Severity.ERROR, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.System, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x9840': { - customCode: ErrorCode.AUTH_LOCK_002, + customCode: ErrorCode.AuthLock002, message: 'Code blocked', - severity: Severity.CRITICAL, - category: Category.AUTHENTICATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Critical, + category: Category.Authentication, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Your device is blocked due to too many failed attempts. Please follow device recovery procedures.', }, '0x9850': { - customCode: ErrorCode.SYS_INTERNAL_001, + customCode: ErrorCode.SysInternal001, message: 'Maximum value reached', - severity: Severity.ERROR, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.System, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, '0x650f': { - customCode: ErrorCode.CONN_CLOSED_001, + customCode: ErrorCode.ConnClosed001, message: 'App closed or connection issue', - severity: Severity.ERROR, - category: Category.CONNECTION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.Connection, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Connection lost or app closed. Please open the corresponding app on your Ledger device.', }, '0x5515': { - customCode: ErrorCode.AUTH_LOCK_001, + customCode: ErrorCode.AuthLock001, message: 'Device is locked', - severity: Severity.ERROR, - category: Category.AUTHENTICATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Authentication, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Please unlock your Ledger device to continue.', }, '0x5501': { - customCode: ErrorCode.USER_CANCEL_001, + customCode: ErrorCode.UserCancel001, message: 'User refused on device', - severity: Severity.WARNING, - category: Category.USER_ACTION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Warning, + category: Category.UserAction, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Transaction was rejected. Please approve on your device to continue.', @@ -323,480 +323,480 @@ export const HARDWARE_MAPPINGS = { vendorName: 'Trezor', errorMapping: { '1': { - customCode: ErrorCode.PROTO_CMD_003, + customCode: ErrorCode.ProtoCmd003, message: 'Unexpected message received', - severity: Severity.ERROR, - category: Category.PROTOCOL, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.Protocol, + retryStrategy: RetryStrategy.Retry, userActionable: false, originalName: 'Failure_UnexpectedMessage', }, '2': { - customCode: ErrorCode.USER_CONFIRM_001, + customCode: ErrorCode.UserConfirm001, message: 'Button confirmation required', - severity: Severity.WARNING, - category: Category.USER_ACTION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Warning, + category: Category.UserAction, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Please confirm the action on your Trezor device.', originalName: 'Failure_ButtonExpected', }, '3': { - customCode: ErrorCode.DATA_FORMAT_002, + customCode: ErrorCode.DataFormat002, message: 'Data error', - severity: Severity.ERROR, - category: Category.DATA_VALIDATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DataValidation, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, originalName: 'Failure_DataError', }, '4': { - customCode: ErrorCode.USER_CANCEL_002, + customCode: ErrorCode.UserCancel002, message: 'Action cancelled by user', - severity: Severity.WARNING, - category: Category.USER_ACTION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Warning, + category: Category.UserAction, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'You cancelled the operation. Please try again if this was unintentional.', originalName: 'Failure_ActionCancelled', }, '5': { - customCode: ErrorCode.USER_INPUT_001, + customCode: ErrorCode.UserInput001, message: 'PIN entry expected', - severity: Severity.WARNING, - category: Category.USER_ACTION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Warning, + category: Category.UserAction, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Please enter your PIN on the Trezor device.', originalName: 'Failure_PinExpected', }, '6': { - customCode: ErrorCode.AUTH_PIN_002, + customCode: ErrorCode.AuthPin002, message: 'PIN cancelled by user', - severity: Severity.WARNING, - category: Category.AUTHENTICATION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Warning, + category: Category.Authentication, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'PIN entry was cancelled. Please try again.', originalName: 'Failure_PinCancelled', }, '7': { - customCode: ErrorCode.AUTH_PIN_001, + customCode: ErrorCode.AuthPin001, message: 'PIN invalid', - severity: Severity.ERROR, - category: Category.AUTHENTICATION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.Authentication, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Incorrect PIN entered. Please try again.', originalName: 'Failure_PinInvalid', }, '8': { - customCode: ErrorCode.CRYPTO_SIGN_001, + customCode: ErrorCode.CryptoSign001, message: 'Invalid signature', - severity: Severity.ERROR, - category: Category.CRYPTOGRAPHY, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Cryptography, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, originalName: 'Failure_InvalidSignature', }, '9': { - customCode: ErrorCode.SYS_INTERNAL_001, + customCode: ErrorCode.SysInternal001, message: 'Process error', - severity: Severity.ERROR, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.System, + retryStrategy: RetryStrategy.Retry, userActionable: false, userMessage: 'A processing error occurred. Please try again.', originalName: 'Failure_ProcessError', }, '10': { - customCode: ErrorCode.TX_FUNDS_001, + customCode: ErrorCode.TxFunds001, message: 'Insufficient funds', - severity: Severity.ERROR, - category: Category.TRANSACTION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Transaction, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Insufficient funds to complete this transaction.', originalName: 'Failure_NotEnoughFunds', }, '11': { - customCode: ErrorCode.DEVICE_STATE_001, + customCode: ErrorCode.DeviceState001, message: 'Device not initialized', - severity: Severity.ERROR, - category: Category.DEVICE_STATE, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DeviceState, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Your Trezor device needs to be initialized. Please set it up first.', originalName: 'Failure_NotInitialized', }, '12': { - customCode: ErrorCode.AUTH_PIN_004, + customCode: ErrorCode.AuthPin004, message: 'PIN mismatch', - severity: Severity.ERROR, - category: Category.AUTHENTICATION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.Authentication, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'PINs do not match. Please try again.', originalName: 'Failure_PinMismatch', }, '13': { - customCode: ErrorCode.AUTH_WIPE_001, + customCode: ErrorCode.AuthWipe001, message: 'Wipe code mismatch', - severity: Severity.ERROR, - category: Category.AUTHENTICATION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.Authentication, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Wipe codes do not match. Please try again.', originalName: 'Failure_WipeCodeMismatch', }, '14': { - customCode: ErrorCode.DEVICE_STATE_002, + customCode: ErrorCode.DeviceState002, message: 'Invalid session', - severity: Severity.ERROR, - category: Category.DEVICE_STATE, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.DeviceState, + retryStrategy: RetryStrategy.Retry, userActionable: false, userMessage: 'Session expired. Please reconnect your device.', originalName: 'Failure_InvalidSession', }, '15': { - customCode: ErrorCode.DEVICE_STATE_002, + customCode: ErrorCode.DeviceState002, message: 'Device busy', - severity: Severity.WARNING, - category: Category.DEVICE_STATE, - retryStrategy: RetryStrategy.EXPONENTIAL_BACKOFF, + severity: Severity.Warning, + category: Category.DeviceState, + retryStrategy: RetryStrategy.ExponentialBackoff, userActionable: false, userMessage: 'Device is busy. Please wait and try again.', originalName: 'Failure_Busy', }, '99': { - customCode: ErrorCode.SYS_FIRMWARE_002, + customCode: ErrorCode.SysFirmware002, message: 'Firmware installation failed', - severity: Severity.CRITICAL, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Critical, + category: Category.System, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Firmware installation failed. Please contact Trezor support.', originalName: 'Failure_FirmwareError', }, UNKNOWN: { - customCode: ErrorCode.UNKNOWN_001, + customCode: ErrorCode.Unknown001, message: 'Unknown error', - severity: Severity.ERROR, - category: Category.UNKNOWN, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Unknown, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, userMessage: 'An unknown error occurred. Please try again or contact support.', originalName: 'Failure_UnknownCode', }, ENTROPY_CHECK: { - customCode: ErrorCode.CRYPTO_ENTROPY_001, + customCode: ErrorCode.CryptoEntropy001, message: 'Entropy check failed', - severity: Severity.ERROR, - category: Category.CRYPTOGRAPHY, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Cryptography, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, originalName: 'Failure_EntropyCheck', }, Init_NotInitialized: { - customCode: ErrorCode.CONFIG_INIT_001, + customCode: ErrorCode.ConfigInit001, message: 'TrezorConnect not initialized', - severity: Severity.ERROR, - category: Category.CONFIGURATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Configuration, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, sdkMessage: 'TrezorConnect not initialized', }, Init_AlreadyInitialized: { - customCode: ErrorCode.CONFIG_INIT_002, + customCode: ErrorCode.ConfigInit002, message: 'TrezorConnect already initialized', - severity: Severity.WARNING, - category: Category.CONFIGURATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Warning, + category: Category.Configuration, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, sdkMessage: 'TrezorConnect has been already initialized', }, Init_IframeBlocked: { - customCode: ErrorCode.CONN_BLOCKED_001, + customCode: ErrorCode.ConnBlocked001, message: 'Iframe blocked', - severity: Severity.ERROR, - category: Category.CONNECTION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Connection, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Connection blocked. Please check your browser settings and allow iframes.', sdkMessage: 'Iframe blocked', }, Init_IframeTimeout: { - customCode: ErrorCode.CONN_TIMEOUT_001, + customCode: ErrorCode.ConnTimeout001, message: 'Iframe connection timeout', - severity: Severity.ERROR, - category: Category.CONNECTION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.Connection, + retryStrategy: RetryStrategy.Retry, userActionable: false, userMessage: 'Connection timed out. Please check your internet connection and try again.', sdkMessage: 'Iframe timeout', }, Init_ManifestMissing: { - customCode: ErrorCode.CONFIG_INIT_003, + customCode: ErrorCode.ConfigInit003, message: 'Manifest not set', - severity: Severity.ERROR, - category: Category.CONFIGURATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Configuration, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, sdkMessage: 'Manifest not set...', }, Popup_ConnectionMissing: { - customCode: ErrorCode.CONN_IFRAME_001, + customCode: ErrorCode.ConnIframe001, message: 'Unable to establish connection with iframe', - severity: Severity.ERROR, - category: Category.CONNECTION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.Connection, + retryStrategy: RetryStrategy.Retry, userActionable: false, userMessage: 'Connection failed. Please try again.', sdkMessage: 'Unable to establish connection with iframe', }, Desktop_ConnectionMissing: { - customCode: ErrorCode.CONN_SUITE_001, + customCode: ErrorCode.ConnSuite001, message: 'Unable to establish connection with Suite', - severity: Severity.ERROR, - category: Category.CONNECTION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.Connection, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Cannot connect to Trezor Suite. Please ensure Trezor Suite is running.', sdkMessage: 'Unable to establish connection with Suite', }, Transport_Missing: { - customCode: ErrorCode.CONN_TRANSPORT_001, + customCode: ErrorCode.ConnTransport001, message: 'Transport is missing', - severity: Severity.ERROR, - category: Category.CONNECTION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.Connection, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Transport layer not available. Please reconnect your device.', sdkMessage: 'Transport is missing', }, Method_InvalidPackage: { - customCode: ErrorCode.CONFIG_METHOD_001, + customCode: ErrorCode.ConfigMethod001, message: 'Invalid package for browser environment', - severity: Severity.ERROR, - category: Category.CONFIGURATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Configuration, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, sdkMessage: 'This package is not suitable to work with browser...', }, Method_InvalidParameter: { - customCode: ErrorCode.DATA_FORMAT_003, + customCode: ErrorCode.DataFormat003, message: 'Invalid method parameter', - severity: Severity.ERROR, - category: Category.DATA_VALIDATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DataValidation, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, Method_NotAllowed: { - customCode: ErrorCode.CONFIG_METHOD_001, + customCode: ErrorCode.ConfigMethod001, message: 'Method not allowed for this configuration', - severity: Severity.ERROR, - category: Category.CONFIGURATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Configuration, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, sdkMessage: 'Method not allowed for this configuration', }, Method_PermissionsNotGranted: { - customCode: ErrorCode.CONFIG_PERM_001, + customCode: ErrorCode.ConfigPerm001, message: 'Permissions not granted', - severity: Severity.ERROR, - category: Category.CONFIGURATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Configuration, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Required permissions were not granted. Please allow access to continue.', sdkMessage: 'Permissions not granted', }, Method_Cancel: { - customCode: ErrorCode.USER_CANCEL_002, + customCode: ErrorCode.UserCancel002, message: 'Method cancelled by user', - severity: Severity.WARNING, - category: Category.USER_ACTION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Warning, + category: Category.UserAction, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Operation was cancelled.', sdkMessage: 'Cancelled', }, Method_Interrupted: { - customCode: ErrorCode.USER_CANCEL_002, + customCode: ErrorCode.UserCancel002, message: 'Popup closed by user', - severity: Severity.WARNING, - category: Category.USER_ACTION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Warning, + category: Category.UserAction, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Operation interrupted. The popup was closed.', sdkMessage: 'Popup closed', }, Method_UnknownCoin: { - customCode: ErrorCode.DATA_NOTFOUND_003, + customCode: ErrorCode.DataNotfound003, message: 'Coin not found', - severity: Severity.ERROR, - category: Category.DATA_VALIDATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DataValidation, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'The requested cryptocurrency is not supported.', sdkMessage: 'Coin not found', }, Method_AddressNotMatch: { - customCode: ErrorCode.DATA_VALIDATION_001, + customCode: ErrorCode.DataValidation001, message: 'Addresses do not match', - severity: Severity.ERROR, - category: Category.DATA_VALIDATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DataValidation, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Address verification failed. The addresses do not match.', sdkMessage: 'Addresses do not match', }, Method_Discovery_BundleException: { - customCode: ErrorCode.SYS_INTERNAL_001, + customCode: ErrorCode.SysInternal001, message: 'Discovery bundle exception', - severity: Severity.ERROR, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.System, + retryStrategy: RetryStrategy.Retry, userActionable: false, }, Method_Override: { - customCode: ErrorCode.CONFIG_METHOD_001, + customCode: ErrorCode.ConfigMethod001, message: 'Method override', - severity: Severity.WARNING, - category: Category.CONFIGURATION, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Warning, + category: Category.Configuration, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, sdkMessage: 'override', }, Method_NoResponse: { - customCode: ErrorCode.PROTO_CMD_003, + customCode: ErrorCode.ProtoCmd003, message: 'Call resolved without response', - severity: Severity.ERROR, - category: Category.PROTOCOL, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.Protocol, + retryStrategy: RetryStrategy.Retry, userActionable: false, sdkMessage: 'Call resolved without response', }, Device_NotFound: { - customCode: ErrorCode.DEVICE_DETECT_001, + customCode: ErrorCode.DeviceDetect001, message: 'Device not found', - severity: Severity.ERROR, - category: Category.DEVICE_STATE, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.DeviceState, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Trezor device not detected. Please connect your device and try again.', sdkMessage: 'Device not found', }, Device_InitializeFailed: { - customCode: ErrorCode.DEVICE_STATE_001, + customCode: ErrorCode.DeviceState001, message: 'Device initialization failed', - severity: Severity.ERROR, - category: Category.DEVICE_STATE, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.DeviceState, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Failed to initialize device. Please reconnect and try again.', }, Device_FwException: { - customCode: ErrorCode.SYS_FIRMWARE_001, + customCode: ErrorCode.SysFirmware001, message: 'Firmware exception', - severity: Severity.ERROR, - category: Category.SYSTEM, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.System, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Firmware error detected. Please update your device firmware.', }, Device_ModeException: { - customCode: ErrorCode.DEVICE_MODE_001, + customCode: ErrorCode.DeviceMode001, message: 'Device mode exception', - severity: Severity.ERROR, - category: Category.DEVICE_STATE, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DeviceState, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Device is in an incompatible mode. Please check your device settings.', }, Device_Disconnected: { - customCode: ErrorCode.DEVICE_STATE_003, + customCode: ErrorCode.DeviceState003, message: 'Device disconnected', - severity: Severity.ERROR, - category: Category.DEVICE_STATE, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.DeviceState, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Device was disconnected. Please reconnect your Trezor device.', sdkMessage: 'Device disconnected', }, Device_UsedElsewhere: { - customCode: ErrorCode.DEVICE_STATE_004, + customCode: ErrorCode.DeviceState004, message: 'Device is used in another window', - severity: Severity.ERROR, - category: Category.DEVICE_STATE, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DeviceState, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Your Trezor is being used in another window or application. Please close other connections.', sdkMessage: 'Device is used in another window', }, Device_InvalidState: { - customCode: ErrorCode.AUTH_SEC_001, + customCode: ErrorCode.AuthSec001, message: 'Passphrase is incorrect', - severity: Severity.ERROR, - category: Category.AUTHENTICATION, - retryStrategy: RetryStrategy.RETRY, + severity: Severity.Err, + category: Category.Authentication, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Incorrect passphrase. Please try again.', sdkMessage: 'Passphrase is incorrect', }, Device_CallInProgress: { - customCode: ErrorCode.DEVICE_STATE_005, + customCode: ErrorCode.DeviceState005, message: 'Device call in progress', - severity: Severity.WARNING, - category: Category.DEVICE_STATE, - retryStrategy: RetryStrategy.EXPONENTIAL_BACKOFF, + severity: Severity.Warning, + category: Category.DeviceState, + retryStrategy: RetryStrategy.ExponentialBackoff, userActionable: false, userMessage: 'Another operation is in progress. Please wait.', sdkMessage: 'Device call in progress', }, Device_MultipleNotSupported: { - customCode: ErrorCode.DEVICE_CAP_001, + customCode: ErrorCode.DeviceCap001, message: 'Multiple devices are not supported', - severity: Severity.ERROR, - category: Category.DEVICE_STATE, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DeviceState, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Multiple devices detected. Please connect only one Trezor device.', sdkMessage: 'Multiple devices are not supported', }, Device_MissingCapability: { - customCode: ErrorCode.DEVICE_CAP_001, + customCode: ErrorCode.DeviceCap001, message: 'Device is missing required capability', - severity: Severity.ERROR, - category: Category.DEVICE_STATE, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DeviceState, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Your device does not support this feature. A firmware update may be required.', sdkMessage: 'Device is missing capability', }, Device_MissingCapabilityBtcOnly: { - customCode: ErrorCode.DEVICE_CAP_002, + customCode: ErrorCode.DeviceCap002, message: 'Device is BTC-only, operation not supported', - severity: Severity.ERROR, - category: Category.DEVICE_STATE, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.DeviceState, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'This operation is not supported on Bitcoin-only firmware.', @@ -804,11 +804,11 @@ export const HARDWARE_MAPPINGS = { }, }, default: { - customCode: ErrorCode.UNKNOWN_001, + customCode: ErrorCode.Unknown001, message: 'Unknown Trezor error', - severity: Severity.ERROR, - category: Category.UNKNOWN, - retryStrategy: RetryStrategy.NO_RETRY, + severity: Severity.Err, + category: Category.Unknown, + retryStrategy: RetryStrategy.NoRetry, userActionable: false, userMessage: 'An unexpected error occurred. Please try again or contact support.', @@ -818,25 +818,25 @@ export const HARDWARE_MAPPINGS = { pattern: '^Failure_.*', type: 'prefix', description: 'Device failure codes', - defaultSeverity: Severity.ERROR, + defaultSeverity: Severity.Err, }, { pattern: '^Init_.*', type: 'prefix', description: 'Initialization errors', - defaultSeverity: Severity.ERROR, + defaultSeverity: Severity.Err, }, { pattern: '^Method_.*', type: 'prefix', description: 'Method invocation errors', - defaultSeverity: Severity.ERROR, + defaultSeverity: Severity.Err, }, { pattern: '^Device_.*', type: 'prefix', description: 'Device state errors', - defaultSeverity: Severity.ERROR, + defaultSeverity: Severity.Err, }, ], }, diff --git a/packages/keyring-utils/src/hardware-error.test.ts b/packages/keyring-utils/src/hardware-error.test.ts index 6c9fdc4e3..161da8639 100644 --- a/packages/keyring-utils/src/hardware-error.test.ts +++ b/packages/keyring-utils/src/hardware-error.test.ts @@ -8,10 +8,10 @@ import { describe('HardwareWalletError', () => { const mockOptions = { - code: ErrorCode.USER_CANCEL_001, - severity: Severity.WARNING, - category: Category.USER_ACTION, - retryStrategy: RetryStrategy.RETRY, + code: ErrorCode.UserCancel001, + severity: Severity.Warning, + category: Category.UserAction, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Transaction was rejected', }; @@ -22,10 +22,10 @@ describe('HardwareWalletError', () => { expect(error.message).toBe('Test error'); expect(error.name).toBe('HardwareWalletError'); - expect(error.code).toBe(ErrorCode.USER_CANCEL_001); - expect(error.severity).toBe(Severity.WARNING); - expect(error.category).toBe(Category.USER_ACTION); - expect(error.retryStrategy).toBe(RetryStrategy.RETRY); + expect(error.code).toBe(ErrorCode.UserCancel001); + expect(error.severity).toBe(Severity.Warning); + expect(error.category).toBe(Category.UserAction); + expect(error.retryStrategy).toBe(RetryStrategy.Retry); expect(error.userActionable).toBe(true); expect(error.userMessage).toBe('Transaction was rejected'); }); @@ -76,7 +76,7 @@ describe('HardwareWalletError', () => { it('should return true for RETRY strategy', () => { const error = new HardwareWalletError('Test error', { ...mockOptions, - retryStrategy: RetryStrategy.RETRY, + retryStrategy: RetryStrategy.Retry, }); expect(error.isRetryable()).toBe(true); }); @@ -84,7 +84,7 @@ describe('HardwareWalletError', () => { it('should return true for EXPONENTIAL_BACKOFF strategy', () => { const error = new HardwareWalletError('Test error', { ...mockOptions, - retryStrategy: RetryStrategy.EXPONENTIAL_BACKOFF, + retryStrategy: RetryStrategy.ExponentialBackoff, }); expect(error.isRetryable()).toBe(true); }); @@ -92,7 +92,7 @@ describe('HardwareWalletError', () => { it('should return false for NO_RETRY strategy', () => { const error = new HardwareWalletError('Test error', { ...mockOptions, - retryStrategy: RetryStrategy.NO_RETRY, + retryStrategy: RetryStrategy.NoRetry, }); expect(error.isRetryable()).toBe(false); }); @@ -102,13 +102,13 @@ describe('HardwareWalletError', () => { it('should return true for CRITICAL severity', () => { const error = new HardwareWalletError('Test error', { ...mockOptions, - severity: Severity.CRITICAL, + severity: Severity.Critical, }); expect(error.isCritical()).toBe(true); }); it('should return false for non-CRITICAL severity', () => { - const severities = [Severity.ERROR, Severity.WARNING, Severity.INFO]; + const severities = [Severity.Err, Severity.Warning, Severity.Info]; severities.forEach((severity) => { const error = new HardwareWalletError('Test error', { ...mockOptions, @@ -123,13 +123,13 @@ describe('HardwareWalletError', () => { it('should return true for WARNING severity', () => { const error = new HardwareWalletError('Test error', { ...mockOptions, - severity: Severity.WARNING, + severity: Severity.Warning, }); expect(error.isWarning()).toBe(true); }); it('should return false for non-WARNING severity', () => { - const severities = [Severity.ERROR, Severity.CRITICAL, Severity.INFO]; + const severities = [Severity.Err, Severity.Critical, Severity.Info]; severities.forEach((severity) => { const error = new HardwareWalletError('Test error', { ...mockOptions, @@ -233,10 +233,10 @@ describe('HardwareWalletError', () => { expect(json.id).toBe(error.id); expect(json.name).toBe('HardwareWalletError'); expect(json.message).toBe('Test error'); - expect(json.code).toBe(ErrorCode.USER_CANCEL_001); - expect(json.severity).toBe(Severity.WARNING); - expect(json.category).toBe(Category.USER_ACTION); - expect(json.retryStrategy).toBe(RetryStrategy.RETRY); + expect(json.code).toBe(ErrorCode.UserCancel001); + expect(json.severity).toBe(Severity.Warning); + expect(json.category).toBe(Category.UserAction); + expect(json.retryStrategy).toBe(RetryStrategy.Retry); expect(json.userActionable).toBe(true); expect(json.userMessage).toBe('Transaction was rejected'); expect(json.timestamp).toBe(error.timestamp.toISOString()); @@ -281,20 +281,20 @@ describe('HardwareWalletError', () => { const result = error.toString(); expect(result).toBe( - 'HardwareWalletError [USER_CANCEL_001]: Transaction was rejected', + 'HardwareWalletError [UserCancel001]: Transaction was rejected', ); }); it('should work with different error codes and messages', () => { const error = new HardwareWalletError('Internal error', { ...mockOptions, - code: ErrorCode.SYS_INTERNAL_001, + code: ErrorCode.SysInternal001, userMessage: 'An internal error occurred', }); const result = error.toString(); expect(result).toBe( - 'HardwareWalletError [SYS_INTERNAL_001]: An internal error occurred', + 'HardwareWalletError [SysInternal001]: An internal error occurred', ); }); }); @@ -307,12 +307,12 @@ describe('HardwareWalletError', () => { const result = error.toDetailedString(); - expect(result).toContain('HardwareWalletError [USER_CANCEL_001]'); + expect(result).toContain('HardwareWalletError [UserCancel001]'); expect(result).toContain('Message: Test error'); expect(result).toContain('User Message: Transaction was rejected'); - expect(result).toContain('Severity: WARNING'); - expect(result).toContain('Category: USER_ACTION'); - expect(result).toContain('Retry Strategy: RETRY'); + expect(result).toContain('Severity: Warning'); + expect(result).toContain('Category: UserAction'); + expect(result).toContain('Retry Strategy: Retry'); expect(result).toContain('User Actionable: true'); expect(result).toContain('Timestamp:'); }); @@ -363,10 +363,10 @@ describe('HardwareWalletError', () => { describe('error scenarios', () => { it('should handle critical authentication errors', () => { const error = new HardwareWalletError('Device blocked', { - code: ErrorCode.AUTH_LOCK_002, - severity: Severity.CRITICAL, - category: Category.AUTHENTICATION, - retryStrategy: RetryStrategy.NO_RETRY, + code: ErrorCode.AuthLock002, + severity: Severity.Critical, + category: Category.Authentication, + retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Device is blocked due to too many failed attempts', }); @@ -378,10 +378,10 @@ describe('HardwareWalletError', () => { it('should handle retryable connection errors', () => { const error = new HardwareWalletError('Connection timeout', { - code: ErrorCode.CONN_TIMEOUT_001, - severity: Severity.ERROR, - category: Category.CONNECTION, - retryStrategy: RetryStrategy.EXPONENTIAL_BACKOFF, + code: ErrorCode.ConnTimeout001, + severity: Severity.Err, + category: Category.Connection, + retryStrategy: RetryStrategy.ExponentialBackoff, userActionable: false, userMessage: 'Connection timed out', }); @@ -393,10 +393,10 @@ describe('HardwareWalletError', () => { it('should handle user action warnings', () => { const error = new HardwareWalletError('User confirmation required', { - code: ErrorCode.USER_CONFIRM_001, - severity: Severity.WARNING, - category: Category.USER_ACTION, - retryStrategy: RetryStrategy.RETRY, + code: ErrorCode.UserConfirm001, + severity: Severity.Warning, + category: Category.UserAction, + retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: 'Please confirm the action on your device', }); diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts index 4a52c4a3f..1ffe458b9 100644 --- a/packages/keyring-utils/src/hardware-error.ts +++ b/packages/keyring-utils/src/hardware-error.ts @@ -65,7 +65,7 @@ export class HardwareWalletError extends Error { * @returns True if the error can be retried, false otherwise. */ isRetryable(): boolean { - return this.retryStrategy !== RetryStrategy.NO_RETRY; + return this.retryStrategy !== RetryStrategy.NoRetry; } /** @@ -74,7 +74,7 @@ export class HardwareWalletError extends Error { * @returns True if the error is critical, false otherwise. */ isCritical(): boolean { - return this.severity === Severity.CRITICAL; + return this.severity === Severity.Critical; } /** @@ -83,7 +83,7 @@ export class HardwareWalletError extends Error { * @returns True if the error is a warning, false otherwise. */ isWarning(): boolean { - return this.severity === Severity.WARNING; + return this.severity === Severity.Warning; } /** diff --git a/packages/keyring-utils/src/hardware-errors-enums.ts b/packages/keyring-utils/src/hardware-errors-enums.ts index 249279ed2..856e59332 100644 --- a/packages/keyring-utils/src/hardware-errors-enums.ts +++ b/packages/keyring-utils/src/hardware-errors-enums.ts @@ -1,121 +1,120 @@ -/* eslint-disable @typescript-eslint/naming-convention */ // Error Code Enum export enum ErrorCode { // Authentication & Security - AUTH_PIN_001 = 'AUTH_PIN_001', - AUTH_PIN_002 = 'AUTH_PIN_002', - AUTH_PIN_003 = 'AUTH_PIN_003', - AUTH_PIN_004 = 'AUTH_PIN_004', - AUTH_LOCK_001 = 'AUTH_LOCK_001', - AUTH_LOCK_002 = 'AUTH_LOCK_002', - AUTH_SEC_001 = 'AUTH_SEC_001', - AUTH_SEC_002 = 'AUTH_SEC_002', - AUTH_WIPE_001 = 'AUTH_WIPE_001', + AuthPin001 = 'AuthPin001', + AuthPin002 = 'AuthPin002', + AuthPin003 = 'AuthPin003', + AuthPin004 = 'AuthPin004', + AuthLock001 = 'AuthLock001', + AuthLock002 = 'AuthLock002', + AuthSec001 = 'AuthSec001', + AuthSec002 = 'AuthSec002', + AuthWipe001 = 'AuthWipe001', // User Action - USER_CANCEL_001 = 'USER_CANCEL_001', - USER_CANCEL_002 = 'USER_CANCEL_002', - USER_INPUT_001 = 'USER_INPUT_001', - USER_CONFIRM_001 = 'USER_CONFIRM_001', + UserCancel001 = 'UserCancel001', + UserCancel002 = 'UserCancel002', + UserInput001 = 'UserInput001', + UserConfirm001 = 'UserConfirm001', // Device State - DEVICE_STATE_001 = 'DEVICE_STATE_001', - DEVICE_STATE_002 = 'DEVICE_STATE_002', - DEVICE_STATE_003 = 'DEVICE_STATE_003', - DEVICE_STATE_004 = 'DEVICE_STATE_004', - DEVICE_STATE_005 = 'DEVICE_STATE_005', - DEVICE_DETECT_001 = 'DEVICE_DETECT_001', - DEVICE_CAP_001 = 'DEVICE_CAP_001', - DEVICE_CAP_002 = 'DEVICE_CAP_002', - DEVICE_MODE_001 = 'DEVICE_MODE_001', + DeviceState001 = 'DeviceState001', + DeviceState002 = 'DeviceState002', + DeviceState003 = 'DeviceState003', + DeviceState004 = 'DeviceState004', + DeviceState005 = 'DeviceState005', + DeviceDetect001 = 'DeviceDetect001', + DeviceCap001 = 'DeviceCap001', + DeviceCap002 = 'DeviceCap002', + DeviceMode001 = 'DeviceMode001', // Connection & Transport - CONN_TRANSPORT_001 = 'CONN_TRANSPORT_001', - CONN_CLOSED_001 = 'CONN_CLOSED_001', - CONN_IFRAME_001 = 'CONN_IFRAME_001', - CONN_SUITE_001 = 'CONN_SUITE_001', - CONN_TIMEOUT_001 = 'CONN_TIMEOUT_001', - CONN_BLOCKED_001 = 'CONN_BLOCKED_001', + ConnTransport001 = 'ConnTransport001', + ConnClosed001 = 'ConnClosed001', + ConnIframe001 = 'ConnIframe001', + ConnSuite001 = 'ConnSuite001', + ConnTimeout001 = 'ConnTimeout001', + ConnBlocked001 = 'ConnBlocked001', // Data & Validation - DATA_FORMAT_001 = 'DATA_FORMAT_001', - DATA_FORMAT_002 = 'DATA_FORMAT_002', - DATA_FORMAT_003 = 'DATA_FORMAT_003', - DATA_MISSING_001 = 'DATA_MISSING_001', - DATA_VALIDATION_001 = 'DATA_VALIDATION_001', - DATA_VALIDATION_002 = 'DATA_VALIDATION_002', - DATA_NOTFOUND_001 = 'DATA_NOTFOUND_001', - DATA_NOTFOUND_002 = 'DATA_NOTFOUND_002', - DATA_NOTFOUND_003 = 'DATA_NOTFOUND_003', + DataFormat001 = 'DataFormat001', + DataFormat002 = 'DataFormat002', + DataFormat003 = 'DataFormat003', + DataMissing001 = 'DataMissing001', + DataValidation001 = 'DataValidation001', + DataValidation002 = 'DataValidation002', + DataNotfound001 = 'DataNotfound001', + DataNotfound002 = 'DataNotfound002', + DataNotfound003 = 'DataNotfound003', // Cryptographic Operations - CRYPTO_SIGN_001 = 'CRYPTO_SIGN_001', - CRYPTO_ALGO_001 = 'CRYPTO_ALGO_001', - CRYPTO_KEY_001 = 'CRYPTO_KEY_001', - CRYPTO_ENTROPY_001 = 'CRYPTO_ENTROPY_001', + CryptoSign001 = 'CryptoSign001', + CryptoAlgo001 = 'CryptoAlgo001', + CryptoKey001 = 'CryptoKey001', + CryptoEntropy001 = 'CryptoEntropy001', // System & Internal - SYS_INTERNAL_001 = 'SYS_INTERNAL_001', - SYS_MEMORY_001 = 'SYS_MEMORY_001', - SYS_MEMORY_002 = 'SYS_MEMORY_002', - SYS_FILE_001 = 'SYS_FILE_001', - SYS_FILE_002 = 'SYS_FILE_002', - SYS_LICENSE_001 = 'SYS_LICENSE_001', - SYS_FIRMWARE_001 = 'SYS_FIRMWARE_001', - SYS_FIRMWARE_002 = 'SYS_FIRMWARE_002', + SysInternal001 = 'SysInternal001', + SysMemory001 = 'SysMemory001', + SysMemory002 = 'SysMemory002', + SysFile001 = 'SysFile001', + SysFile002 = 'SysFile002', + SysLicense001 = 'SysLicense001', + SysFirmware001 = 'SysFirmware001', + SysFirmware002 = 'SysFirmware002', // Command & Protocol - PROTO_CMD_001 = 'PROTO_CMD_001', - PROTO_CMD_002 = 'PROTO_CMD_002', - PROTO_CMD_003 = 'PROTO_CMD_003', - PROTO_MSG_001 = 'PROTO_MSG_001', - PROTO_PARAM_001 = 'PROTO_PARAM_001', + ProtoCmd001 = 'ProtoCmd001', + ProtoCmd002 = 'ProtoCmd002', + ProtoCmd003 = 'ProtoCmd003', + ProtoMsg001 = 'ProtoMsg001', + ProtoParam001 = 'ProtoParam001', // Configuration & Initialization - CONFIG_INIT_001 = 'CONFIG_INIT_001', - CONFIG_INIT_002 = 'CONFIG_INIT_002', - CONFIG_INIT_003 = 'CONFIG_INIT_003', - CONFIG_PERM_001 = 'CONFIG_PERM_001', - CONFIG_METHOD_001 = 'CONFIG_METHOD_001', + ConfigInit001 = 'ConfigInit001', + ConfigInit002 = 'ConfigInit002', + ConfigInit003 = 'ConfigInit003', + ConfigPerm001 = 'ConfigPerm001', + ConfigMethod001 = 'ConfigMethod001', // Transaction - TX_FUNDS_001 = 'TX_FUNDS_001', - TX_FAIL_001 = 'TX_FAIL_001', + TxFunds001 = 'TxFunds001', + TxFail001 = 'TxFail001', // Success - SUCCESS_000 = 'SUCCESS_000', + Success000 = 'Success000', // Unknown/Fallback - UNKNOWN_001 = 'UNKNOWN_001', + Unknown001 = 'Unknown001', } // Severity Enum export enum Severity { - INFO = 'INFO', - ERROR = 'ERROR', - WARNING = 'WARNING', - CRITICAL = 'CRITICAL', + Info = 'Info', + Err = 'Err', + Warning = 'Warning', + Critical = 'Critical', } // Category Enum export enum Category { - SUCCESS = 'SUCCESS', - AUTHENTICATION = 'AUTHENTICATION', - DATA_VALIDATION = 'DATA_VALIDATION', - PROTOCOL = 'PROTOCOL', - SYSTEM = 'SYSTEM', - CRYPTOGRAPHY = 'CRYPTOGRAPHY', - CONFIGURATION = 'CONFIGURATION', - CONNECTION = 'CONNECTION', - USER_ACTION = 'USER_ACTION', - DEVICE_STATE = 'DEVICE_STATE', - TRANSACTION = 'TRANSACTION', - UNKNOWN = 'UNKNOWN', + Success = 'Success', + Authentication = 'Authentication', + DataValidation = 'DataValidation', + Protocol = 'Protocol', + System = 'System', + Cryptography = 'Cryptography', + Configuration = 'Configuration', + Connection = 'Connection', + UserAction = 'UserAction', + DeviceState = 'DeviceState', + Transaction = 'Transaction', + Unknown = 'Unknown', } // Retry Strategy Enum export enum RetryStrategy { - NO_RETRY = 'NO_RETRY', - RETRY = 'RETRY', - EXPONENTIAL_BACKOFF = 'EXPONENTIAL_BACKOFF', + NoRetry = 'NoRetry', + Retry = 'Retry', + ExponentialBackoff = 'ExponentialBackoff', } diff --git a/packages/keyring-utils/src/index.ts b/packages/keyring-utils/src/index.ts index 428d5609d..7d657dd6d 100644 --- a/packages/keyring-utils/src/index.ts +++ b/packages/keyring-utils/src/index.ts @@ -7,5 +7,4 @@ export * from './JsonRpcRequest'; export type * from './keyring'; export * from './hardware-errors-enums'; export * from './hardware-error-mappings'; -export * from './hardware-error-codes'; export * from './hardware-error'; From 53206ed6d72b6edcc0a185fbd42690e67b3faded Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Thu, 8 Jan 2026 22:19:27 +0800 Subject: [PATCH 05/24] fix: align key --- packages/keyring-utils/src/hardware-error-mappings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/keyring-utils/src/hardware-error-mappings.ts index 2eed4fe0a..651d9f809 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.ts @@ -321,7 +321,7 @@ export const HARDWARE_MAPPINGS = { }, trezor: { vendorName: 'Trezor', - errorMapping: { + errorMappings: { '1': { customCode: ErrorCode.ProtoCmd003, message: 'Unexpected message received', From 7d906ee77018a344c23f1fc727a2b5d68d420561 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Thu, 8 Jan 2026 22:28:27 +0800 Subject: [PATCH 06/24] fix: test --- .../src/hardware-error-mappings.test.ts | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error-mappings.test.ts b/packages/keyring-utils/src/hardware-error-mappings.test.ts index 7c662654c..ad0af29e4 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.test.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.test.ts @@ -210,16 +210,16 @@ describe('HARDWARE_MAPPINGS', () => { }); describe('Trezor mappings', () => { - const { errorMapping } = HARDWARE_MAPPINGS.trezor; + const { errorMappings } = HARDWARE_MAPPINGS.trezor; it('should have errorMapping object', () => { - expect(errorMapping).toBeDefined(); - expect(typeof errorMapping).toBe('object'); + expect(errorMappings).toBeDefined(); + expect(typeof errorMappings).toBe('object'); }); describe('failure codes', () => { it('should map code 1 to unexpected message', () => { - const mapping = errorMapping['1']; + const mapping = errorMappings['1']; expect(mapping.customCode).toBe(ErrorCode.ProtoCmd003); expect(mapping.severity).toBe(Severity.Err); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); @@ -227,7 +227,7 @@ describe('HARDWARE_MAPPINGS', () => { }); it('should map code 4 to action cancelled', () => { - const mapping = errorMapping['4']; + const mapping = errorMappings['4']; expect(mapping.customCode).toBe(ErrorCode.UserCancel002); expect(mapping.category).toBe(Category.UserAction); expect(mapping.userActionable).toBe(true); @@ -235,14 +235,14 @@ describe('HARDWARE_MAPPINGS', () => { }); it('should map code 10 to insufficient funds', () => { - const mapping = errorMapping['10']; + const mapping = errorMappings['10']; expect(mapping.customCode).toBe(ErrorCode.TxFunds001); expect(mapping.category).toBe(Category.Transaction); expect(mapping.originalName).toBe('Failure_NotEnoughFunds'); }); it('should map code 99 to firmware error', () => { - const mapping = errorMapping['99']; + const mapping = errorMappings['99']; expect(mapping.customCode).toBe(ErrorCode.SysFirmware002); expect(mapping.severity).toBe(Severity.Critical); expect(mapping.originalName).toBe('Failure_FirmwareError'); @@ -251,14 +251,14 @@ describe('HARDWARE_MAPPINGS', () => { describe('PIN errors', () => { it('should map code 5 to PIN expected', () => { - const mapping = errorMapping['5']; + const mapping = errorMappings['5']; expect(mapping.customCode).toBe(ErrorCode.UserInput001); expect(mapping.category).toBe(Category.UserAction); expect(mapping.originalName).toBe('Failure_PinExpected'); }); it('should map code 7 to PIN invalid', () => { - const mapping = errorMapping['7']; + const mapping = errorMappings['7']; expect(mapping.customCode).toBe(ErrorCode.AuthPin001); expect(mapping.category).toBe(Category.Authentication); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); @@ -266,7 +266,7 @@ describe('HARDWARE_MAPPINGS', () => { }); it('should map code 12 to PIN mismatch', () => { - const mapping = errorMapping['12']; + const mapping = errorMappings['12']; expect(mapping.customCode).toBe(ErrorCode.AuthPin004); expect(mapping.originalName).toBe('Failure_PinMismatch'); }); @@ -274,14 +274,14 @@ describe('HARDWARE_MAPPINGS', () => { describe('device state errors', () => { it('should map code 11 to device not initialized', () => { - const mapping = errorMapping['11']; + const mapping = errorMappings['11']; expect(mapping.customCode).toBe(ErrorCode.DeviceState001); expect(mapping.category).toBe(Category.DeviceState); expect(mapping.originalName).toBe('Failure_NotInitialized'); }); it('should map code 15 to device busy', () => { - const mapping = errorMapping['15']; + const mapping = errorMappings['15']; expect(mapping.customCode).toBe(ErrorCode.DeviceState002); expect(mapping.severity).toBe(Severity.Warning); expect(mapping.retryStrategy).toBe(RetryStrategy.ExponentialBackoff); @@ -289,14 +289,14 @@ describe('HARDWARE_MAPPINGS', () => { }); it('should map Device_Disconnected to device disconnected', () => { - const mapping = errorMapping.Device_Disconnected; + const mapping = errorMappings.Device_Disconnected; expect(mapping.customCode).toBe(ErrorCode.DeviceState003); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.sdkMessage).toBe('Device disconnected'); }); it('should map Device_UsedElsewhere correctly', () => { - const mapping = errorMapping.Device_UsedElsewhere; + const mapping = errorMappings.Device_UsedElsewhere; expect(mapping.customCode).toBe(ErrorCode.DeviceState004); expect(mapping.retryStrategy).toBe(RetryStrategy.NoRetry); expect(mapping.userMessage).toContain('another window'); @@ -305,20 +305,20 @@ describe('HARDWARE_MAPPINGS', () => { describe('initialization errors', () => { it('should map Init_NotInitialized', () => { - const mapping = errorMapping.Init_NotInitialized; + const mapping = errorMappings.Init_NotInitialized; expect(mapping.customCode).toBe(ErrorCode.ConfigInit001); expect(mapping.category).toBe(Category.Configuration); expect(mapping.sdkMessage).toBe('TrezorConnect not initialized'); }); it('should map Init_AlreadyInitialized', () => { - const mapping = errorMapping.Init_AlreadyInitialized; + const mapping = errorMappings.Init_AlreadyInitialized; expect(mapping.customCode).toBe(ErrorCode.ConfigInit002); expect(mapping.severity).toBe(Severity.Warning); }); it('should map Init_ManifestMissing', () => { - const mapping = errorMapping.Init_ManifestMissing; + const mapping = errorMappings.Init_ManifestMissing; expect(mapping.customCode).toBe(ErrorCode.ConfigInit003); expect(mapping.category).toBe(Category.Configuration); }); @@ -326,20 +326,20 @@ describe('HARDWARE_MAPPINGS', () => { describe('connection errors', () => { it('should map Init_IframeBlocked', () => { - const mapping = errorMapping.Init_IframeBlocked; + const mapping = errorMappings.Init_IframeBlocked; expect(mapping.customCode).toBe(ErrorCode.ConnBlocked001); expect(mapping.category).toBe(Category.Connection); expect(mapping.userMessage).toContain('browser settings'); }); it('should map Init_IframeTimeout', () => { - const mapping = errorMapping.Init_IframeTimeout; + const mapping = errorMappings.Init_IframeTimeout; expect(mapping.customCode).toBe(ErrorCode.ConnTimeout001); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); it('should map Transport_Missing', () => { - const mapping = errorMapping.Transport_Missing; + const mapping = errorMappings.Transport_Missing; expect(mapping.customCode).toBe(ErrorCode.ConnTransport001); expect(mapping.category).toBe(Category.Connection); }); @@ -347,20 +347,20 @@ describe('HARDWARE_MAPPINGS', () => { describe('method errors', () => { it('should map Method_InvalidParameter', () => { - const mapping = errorMapping.Method_InvalidParameter; + const mapping = errorMappings.Method_InvalidParameter; expect(mapping.customCode).toBe(ErrorCode.DataFormat003); expect(mapping.category).toBe(Category.DataValidation); }); it('should map Method_Cancel', () => { - const mapping = errorMapping.Method_Cancel; + const mapping = errorMappings.Method_Cancel; expect(mapping.customCode).toBe(ErrorCode.UserCancel002); expect(mapping.category).toBe(Category.UserAction); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); it('should map Method_UnknownCoin', () => { - const mapping = errorMapping.Method_UnknownCoin; + const mapping = errorMappings.Method_UnknownCoin; expect(mapping.customCode).toBe(ErrorCode.DataNotfound003); expect(mapping.userMessage).toContain('not supported'); }); @@ -368,13 +368,13 @@ describe('HARDWARE_MAPPINGS', () => { describe('device capability errors', () => { it('should map Device_MissingCapability', () => { - const mapping = errorMapping.Device_MissingCapability; + const mapping = errorMappings.Device_MissingCapability; expect(mapping.customCode).toBe(ErrorCode.DeviceCap001); expect(mapping.userMessage).toContain('firmware update'); }); it('should map Device_MissingCapabilityBtcOnly', () => { - const mapping = errorMapping.Device_MissingCapabilityBtcOnly; + const mapping = errorMappings.Device_MissingCapabilityBtcOnly; expect(mapping.customCode).toBe(ErrorCode.DeviceCap002); expect(mapping.userMessage).toContain('Bitcoin-only'); }); @@ -382,14 +382,14 @@ describe('HARDWARE_MAPPINGS', () => { describe('special codes', () => { it('should have UNKNOWN fallback', () => { - const mapping = errorMapping.UNKNOWN; + const mapping = errorMappings.UNKNOWN; expect(mapping.customCode).toBe(ErrorCode.Unknown001); expect(mapping.category).toBe(Category.Unknown); expect(mapping.originalName).toBe('Failure_UnknownCode'); }); it('should map ENTROPY_CHECK', () => { - const mapping = errorMapping.ENTROPY_CHECK; + const mapping = errorMappings.ENTROPY_CHECK; expect(mapping.customCode).toBe(ErrorCode.CryptoEntropy001); expect(mapping.category).toBe(Category.Cryptography); expect(mapping.originalName).toBe('Failure_EntropyCheck'); @@ -397,7 +397,7 @@ describe('HARDWARE_MAPPINGS', () => { }); it('should have valid structure for all mappings', () => { - Object.entries(errorMapping).forEach(([_code, mapping]) => { + Object.entries(errorMappings).forEach(([_code, mapping]) => { expect(mapping).toHaveProperty('customCode'); expect(mapping).toHaveProperty('message'); expect(mapping).toHaveProperty('severity'); @@ -415,7 +415,7 @@ describe('HARDWARE_MAPPINGS', () => { }); it('should have valid optional fields when present', () => { - const mappingsWithUserMessage = Object.values(errorMapping).filter( + const mappingsWithUserMessage = Object.values(errorMappings).filter( (mapping): mapping is typeof mapping & { userMessage: string } => 'userMessage' in mapping && typeof mapping.userMessage === 'string' && @@ -426,7 +426,7 @@ describe('HARDWARE_MAPPINGS', () => { expect(mapping.userMessage.length).toBeGreaterThan(0); }); - const mappingsWithOriginalName = Object.values(errorMapping).filter( + const mappingsWithOriginalName = Object.values(errorMappings).filter( (mapping): mapping is typeof mapping & { originalName: string } => 'originalName' in mapping && typeof mapping.originalName === 'string' && @@ -437,7 +437,7 @@ describe('HARDWARE_MAPPINGS', () => { expect(mapping.originalName.length).toBeGreaterThan(0); }); - const mappingsWithSdkMessage = Object.values(errorMapping).filter( + const mappingsWithSdkMessage = Object.values(errorMappings).filter( (mapping): mapping is typeof mapping & { sdkMessage: string } => 'sdkMessage' in mapping && typeof mapping.sdkMessage === 'string' && @@ -496,7 +496,7 @@ describe('HARDWARE_MAPPINGS', () => { ); expect(ledgerCustomCodes.length).toBeGreaterThan(0); - const trezorCodes = Object.values(HARDWARE_MAPPINGS.trezor.errorMapping); + const trezorCodes = Object.values(HARDWARE_MAPPINGS.trezor.errorMappings); const trezorCustomCodes = trezorCodes.map( (mapping) => mapping.customCode, ); @@ -521,7 +521,7 @@ describe('HARDWARE_MAPPINGS', () => { }); const trezorMappings = Object.values( - HARDWARE_MAPPINGS.trezor.errorMapping, + HARDWARE_MAPPINGS.trezor.errorMappings, ).filter( (mapping): mapping is typeof mapping & { userMessage: string } => mapping.userActionable && @@ -540,7 +540,7 @@ describe('HARDWARE_MAPPINGS', () => { it('should use NO_RETRY for critical errors', () => { const allMappings = [ ...Object.values(HARDWARE_MAPPINGS.ledger.errorMappings), - ...Object.values(HARDWARE_MAPPINGS.trezor.errorMapping), + ...Object.values(HARDWARE_MAPPINGS.trezor.errorMappings), ]; const criticalMappings = allMappings.filter( From cae26675fe25d2be478069e0629905244726a222 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Fri, 9 Jan 2026 21:33:57 +0800 Subject: [PATCH 07/24] refactor: remove codes and refactor error values to numbers --- .../src/hardware-error-mappings.test.ts | 195 ++------ .../src/hardware-error-mappings.ts | 445 ++---------------- .../keyring-utils/src/hardware-error.test.ts | 32 +- packages/keyring-utils/src/hardware-error.ts | 21 +- .../src/hardware-errors-enums.ts | 125 ++--- 5 files changed, 170 insertions(+), 648 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error-mappings.test.ts b/packages/keyring-utils/src/hardware-error-mappings.test.ts index ad0af29e4..8e413ebfb 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.test.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.test.ts @@ -31,7 +31,7 @@ describe('HARDWARE_MAPPINGS', () => { it('should map 0x9000 to success', () => { const mapping = errorMappings['0x9000']; expect(mapping).toBeDefined(); - expect(mapping.customCode).toBe(ErrorCode.Success000); + expect(mapping.customCode).toBe(ErrorCode.Success); expect(mapping.severity).toBe(Severity.Info); expect(mapping.category).toBe(Category.Success); expect(mapping.retryStrategy).toBe(RetryStrategy.NoRetry); @@ -42,7 +42,7 @@ describe('HARDWARE_MAPPINGS', () => { describe('authentication errors', () => { it('should map 0x6300 to authentication failed', () => { const mapping = errorMappings['0x6300']; - expect(mapping.customCode).toBe(ErrorCode.AuthSec001); + expect(mapping.customCode).toBe(ErrorCode.AuthFailed); expect(mapping.severity).toBe(Severity.Err); expect(mapping.category).toBe(Category.Authentication); expect(mapping.userActionable).toBe(true); @@ -51,14 +51,14 @@ describe('HARDWARE_MAPPINGS', () => { it('should map 0x63c0 to PIN attempts remaining', () => { const mapping = errorMappings['0x63c0']; - expect(mapping.customCode).toBe(ErrorCode.AuthPin003); + expect(mapping.customCode).toBe(ErrorCode.AuthPinAttemptsRemaining); expect(mapping.severity).toBe(Severity.Warning); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); it('should map 0x5515 to device locked', () => { const mapping = errorMappings['0x5515']; - expect(mapping.customCode).toBe(ErrorCode.AuthLock001); + expect(mapping.customCode).toBe(ErrorCode.AuthDeviceLocked); expect(mapping.severity).toBe(Severity.Err); expect(mapping.userActionable).toBe(true); expect(mapping.userMessage).toContain('unlock'); @@ -66,7 +66,7 @@ describe('HARDWARE_MAPPINGS', () => { it('should map 0x9840 to device blocked', () => { const mapping = errorMappings['0x9840']; - expect(mapping.customCode).toBe(ErrorCode.AuthLock002); + expect(mapping.customCode).toBe(ErrorCode.AuthDeviceBlocked); expect(mapping.severity).toBe(Severity.Critical); expect(mapping.retryStrategy).toBe(RetryStrategy.NoRetry); }); @@ -75,7 +75,7 @@ describe('HARDWARE_MAPPINGS', () => { describe('user action errors', () => { it('should map 0x6985 to user rejected', () => { const mapping = errorMappings['0x6985']; - expect(mapping.customCode).toBe(ErrorCode.UserCancel001); + expect(mapping.customCode).toBe(ErrorCode.UserRejected); expect(mapping.severity).toBe(Severity.Warning); expect(mapping.category).toBe(Category.UserAction); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); @@ -84,98 +84,20 @@ describe('HARDWARE_MAPPINGS', () => { it('should map 0x5501 to user refused', () => { const mapping = errorMappings['0x5501']; - expect(mapping.customCode).toBe(ErrorCode.UserCancel001); + expect(mapping.customCode).toBe(ErrorCode.UserRejected); expect(mapping.severity).toBe(Severity.Warning); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); }); - - describe('data validation errors', () => { - it('should map 0x6700 to incorrect data length', () => { - const mapping = errorMappings['0x6700']; - expect(mapping.customCode).toBe(ErrorCode.DataFormat001); - expect(mapping.category).toBe(Category.DataValidation); - expect(mapping.userActionable).toBe(false); - }); - - it('should map 0x6a80 to invalid data', () => { - const mapping = errorMappings['0x6a80']; - expect(mapping.customCode).toBe(ErrorCode.DataFormat002); - expect(mapping.category).toBe(Category.DataValidation); - }); - - it('should map 0x6b00 to invalid parameter', () => { - const mapping = errorMappings['0x6b00']; - expect(mapping.customCode).toBe(ErrorCode.DataFormat003); - expect(mapping.severity).toBe(Severity.Err); - }); - }); - - describe('protocol errors', () => { - it('should map 0x6981 to command incompatible', () => { - const mapping = errorMappings['0x6981']; - expect(mapping.customCode).toBe(ErrorCode.ProtoCmd002); - expect(mapping.category).toBe(Category.Protocol); - }); - - it('should map 0x6d00 to instruction not supported', () => { - const mapping = errorMappings['0x6d00']; - expect(mapping.customCode).toBe(ErrorCode.ProtoCmd001); - expect(mapping.category).toBe(Category.Protocol); - }); - - it('should map 0x6d02 to unknown APDU command', () => { - const mapping = errorMappings['0x6d02']; - expect(mapping.customCode).toBe(ErrorCode.ProtoMsg001); - expect(mapping.category).toBe(Category.Protocol); - }); - }); - - describe('system errors', () => { - it('should map 0x6f00 to internal device error', () => { - const mapping = errorMappings['0x6f00']; - expect(mapping.customCode).toBe(ErrorCode.SysInternal001); - expect(mapping.severity).toBe(Severity.Critical); - expect(mapping.category).toBe(Category.System); - }); - - it('should map 0x6a84 to not enough memory', () => { - const mapping = errorMappings['0x6a84']; - expect(mapping.customCode).toBe(ErrorCode.SysMemory001); - expect(mapping.category).toBe(Category.System); - }); - - it('should map 0x6faa to device halted', () => { - const mapping = errorMappings['0x6faa']; - expect(mapping.customCode).toBe(ErrorCode.SysInternal001); - expect(mapping.severity).toBe(Severity.Critical); - expect(mapping.userMessage).toContain('disconnect and reconnect'); - }); - }); - describe('connection errors', () => { it('should map 0x650f to connection issue', () => { const mapping = errorMappings['0x650f']; - expect(mapping.customCode).toBe(ErrorCode.ConnClosed001); + expect(mapping.customCode).toBe(ErrorCode.ConnClosed); expect(mapping.category).toBe(Category.Connection); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); }); - describe('cryptographic errors', () => { - it('should map 0x9484 to algorithm not supported', () => { - const mapping = errorMappings['0x9484']; - expect(mapping.customCode).toBe(ErrorCode.CryptoAlgo001); - expect(mapping.category).toBe(Category.Cryptography); - }); - - it('should map 0x9485 to invalid key check value', () => { - const mapping = errorMappings['0x9485']; - expect(mapping.customCode).toBe(ErrorCode.CryptoKey001); - expect(mapping.category).toBe(Category.Cryptography); - }); - }); - it('should have valid structure for all mappings', () => { Object.entries(errorMappings).forEach(([_, mapping]) => { expect(mapping).toHaveProperty('customCode'); @@ -185,7 +107,10 @@ describe('HARDWARE_MAPPINGS', () => { expect(mapping).toHaveProperty('retryStrategy'); expect(mapping).toHaveProperty('userActionable'); - expect(Object.values(ErrorCode)).toContain(mapping.customCode); + 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(Object.values(RetryStrategy)).toContain(mapping.retryStrategy); @@ -220,7 +145,7 @@ describe('HARDWARE_MAPPINGS', () => { describe('failure codes', () => { it('should map code 1 to unexpected message', () => { const mapping = errorMappings['1']; - expect(mapping.customCode).toBe(ErrorCode.ProtoCmd003); + expect(mapping.customCode).toBe(ErrorCode.ProtoUnexpectedMessage); expect(mapping.severity).toBe(Severity.Err); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.originalName).toBe('Failure_UnexpectedMessage'); @@ -228,38 +153,24 @@ describe('HARDWARE_MAPPINGS', () => { it('should map code 4 to action cancelled', () => { const mapping = errorMappings['4']; - expect(mapping.customCode).toBe(ErrorCode.UserCancel002); + expect(mapping.customCode).toBe(ErrorCode.UserCancelled); expect(mapping.category).toBe(Category.UserAction); expect(mapping.userActionable).toBe(true); expect(mapping.originalName).toBe('Failure_ActionCancelled'); }); - - it('should map code 10 to insufficient funds', () => { - const mapping = errorMappings['10']; - expect(mapping.customCode).toBe(ErrorCode.TxFunds001); - expect(mapping.category).toBe(Category.Transaction); - expect(mapping.originalName).toBe('Failure_NotEnoughFunds'); - }); - - it('should map code 99 to firmware error', () => { - const mapping = errorMappings['99']; - expect(mapping.customCode).toBe(ErrorCode.SysFirmware002); - expect(mapping.severity).toBe(Severity.Critical); - expect(mapping.originalName).toBe('Failure_FirmwareError'); - }); }); describe('PIN errors', () => { it('should map code 5 to PIN expected', () => { const mapping = errorMappings['5']; - expect(mapping.customCode).toBe(ErrorCode.UserInput001); + expect(mapping.customCode).toBe(ErrorCode.UserInputRequired); expect(mapping.category).toBe(Category.UserAction); expect(mapping.originalName).toBe('Failure_PinExpected'); }); it('should map code 7 to PIN invalid', () => { const mapping = errorMappings['7']; - expect(mapping.customCode).toBe(ErrorCode.AuthPin001); + expect(mapping.customCode).toBe(ErrorCode.AuthIncorrectPin); expect(mapping.category).toBe(Category.Authentication); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.originalName).toBe('Failure_PinInvalid'); @@ -267,7 +178,7 @@ describe('HARDWARE_MAPPINGS', () => { it('should map code 12 to PIN mismatch', () => { const mapping = errorMappings['12']; - expect(mapping.customCode).toBe(ErrorCode.AuthPin004); + expect(mapping.customCode).toBe(ErrorCode.AuthPinMismatch); expect(mapping.originalName).toBe('Failure_PinMismatch'); }); }); @@ -275,14 +186,14 @@ describe('HARDWARE_MAPPINGS', () => { describe('device state errors', () => { it('should map code 11 to device not initialized', () => { const mapping = errorMappings['11']; - expect(mapping.customCode).toBe(ErrorCode.DeviceState001); + expect(mapping.customCode).toBe(ErrorCode.DeviceNotReady); expect(mapping.category).toBe(Category.DeviceState); expect(mapping.originalName).toBe('Failure_NotInitialized'); }); it('should map code 15 to device busy', () => { const mapping = errorMappings['15']; - expect(mapping.customCode).toBe(ErrorCode.DeviceState002); + expect(mapping.customCode).toBe(ErrorCode.DeviceCallInProgress); expect(mapping.severity).toBe(Severity.Warning); expect(mapping.retryStrategy).toBe(RetryStrategy.ExponentialBackoff); expect(mapping.originalName).toBe('Failure_Busy'); @@ -290,110 +201,81 @@ describe('HARDWARE_MAPPINGS', () => { it('should map Device_Disconnected to device disconnected', () => { const mapping = errorMappings.Device_Disconnected; - expect(mapping.customCode).toBe(ErrorCode.DeviceState003); + expect(mapping.customCode).toBe(ErrorCode.DeviceDisconnected); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.sdkMessage).toBe('Device disconnected'); }); it('should map Device_UsedElsewhere correctly', () => { const mapping = errorMappings.Device_UsedElsewhere; - expect(mapping.customCode).toBe(ErrorCode.DeviceState004); + expect(mapping.customCode).toBe(ErrorCode.DeviceUsedElsewhere); expect(mapping.retryStrategy).toBe(RetryStrategy.NoRetry); expect(mapping.userMessage).toContain('another window'); }); }); - describe('initialization errors', () => { - it('should map Init_NotInitialized', () => { - const mapping = errorMappings.Init_NotInitialized; - expect(mapping.customCode).toBe(ErrorCode.ConfigInit001); - expect(mapping.category).toBe(Category.Configuration); - expect(mapping.sdkMessage).toBe('TrezorConnect not initialized'); - }); - - it('should map Init_AlreadyInitialized', () => { - const mapping = errorMappings.Init_AlreadyInitialized; - expect(mapping.customCode).toBe(ErrorCode.ConfigInit002); - expect(mapping.severity).toBe(Severity.Warning); - }); - - it('should map Init_ManifestMissing', () => { - const mapping = errorMappings.Init_ManifestMissing; - expect(mapping.customCode).toBe(ErrorCode.ConfigInit003); - expect(mapping.category).toBe(Category.Configuration); - }); - }); - describe('connection errors', () => { it('should map Init_IframeBlocked', () => { const mapping = errorMappings.Init_IframeBlocked; - expect(mapping.customCode).toBe(ErrorCode.ConnBlocked001); + expect(mapping.customCode).toBe(ErrorCode.ConnBlocked); expect(mapping.category).toBe(Category.Connection); expect(mapping.userMessage).toContain('browser settings'); }); it('should map Init_IframeTimeout', () => { const mapping = errorMappings.Init_IframeTimeout; - expect(mapping.customCode).toBe(ErrorCode.ConnTimeout001); + expect(mapping.customCode).toBe(ErrorCode.ConnTimeout); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); it('should map Transport_Missing', () => { const mapping = errorMappings.Transport_Missing; - expect(mapping.customCode).toBe(ErrorCode.ConnTransport001); + expect(mapping.customCode).toBe(ErrorCode.ConnTransportMissing); expect(mapping.category).toBe(Category.Connection); }); }); describe('method errors', () => { - it('should map Method_InvalidParameter', () => { - const mapping = errorMappings.Method_InvalidParameter; - expect(mapping.customCode).toBe(ErrorCode.DataFormat003); - expect(mapping.category).toBe(Category.DataValidation); - }); - it('should map Method_Cancel', () => { const mapping = errorMappings.Method_Cancel; - expect(mapping.customCode).toBe(ErrorCode.UserCancel002); + expect(mapping.customCode).toBe(ErrorCode.UserCancelled); expect(mapping.category).toBe(Category.UserAction); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); it('should map Method_UnknownCoin', () => { const mapping = errorMappings.Method_UnknownCoin; - expect(mapping.customCode).toBe(ErrorCode.DataNotfound003); + expect(mapping.customCode).toBe(ErrorCode.Unknown); expect(mapping.userMessage).toContain('not supported'); }); }); describe('device capability errors', () => { + it('should map Device_MultipleNotSupported', () => { + const mapping = errorMappings.Device_MultipleNotSupported; + expect(mapping.customCode).toBe(ErrorCode.DeviceMultipleConnected); + expect(mapping.userMessage).toContain('only one'); + }); + it('should map Device_MissingCapability', () => { const mapping = errorMappings.Device_MissingCapability; - expect(mapping.customCode).toBe(ErrorCode.DeviceCap001); + expect(mapping.customCode).toBe(ErrorCode.DeviceMissingCapability); expect(mapping.userMessage).toContain('firmware update'); }); it('should map Device_MissingCapabilityBtcOnly', () => { const mapping = errorMappings.Device_MissingCapabilityBtcOnly; - expect(mapping.customCode).toBe(ErrorCode.DeviceCap002); + expect(mapping.customCode).toBe(ErrorCode.DeviceBtcOnlyFirmware); expect(mapping.userMessage).toContain('Bitcoin-only'); }); }); - describe('special codes', () => { it('should have UNKNOWN fallback', () => { const mapping = errorMappings.UNKNOWN; - expect(mapping.customCode).toBe(ErrorCode.Unknown001); + expect(mapping.customCode).toBe(ErrorCode.Unknown); expect(mapping.category).toBe(Category.Unknown); expect(mapping.originalName).toBe('Failure_UnknownCode'); }); - - it('should map ENTROPY_CHECK', () => { - const mapping = errorMappings.ENTROPY_CHECK; - expect(mapping.customCode).toBe(ErrorCode.CryptoEntropy001); - expect(mapping.category).toBe(Category.Cryptography); - expect(mapping.originalName).toBe('Failure_EntropyCheck'); - }); }); it('should have valid structure for all mappings', () => { @@ -405,7 +287,10 @@ describe('HARDWARE_MAPPINGS', () => { expect(mapping).toHaveProperty('retryStrategy'); expect(mapping).toHaveProperty('userActionable'); - expect(Object.values(ErrorCode)).toContain(mapping.customCode); + 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(Object.values(RetryStrategy)).toContain(mapping.retryStrategy); @@ -454,7 +339,7 @@ describe('HARDWARE_MAPPINGS', () => { it('should have default error mapping', () => { const { default: defaultMapping } = HARDWARE_MAPPINGS.trezor; expect(defaultMapping).toBeDefined(); - expect(defaultMapping.customCode).toBe(ErrorCode.Unknown001); + expect(defaultMapping.customCode).toBe(ErrorCode.Unknown); expect(defaultMapping.category).toBe(Category.Unknown); }); diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/keyring-utils/src/hardware-error-mappings.ts index 651d9f809..c69553404 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.ts @@ -11,7 +11,7 @@ export const HARDWARE_MAPPINGS = { vendorName: 'Ledger', errorMappings: { '0x9000': { - customCode: ErrorCode.Success000, + customCode: ErrorCode.Success, message: 'Operation successful', severity: Severity.Info, category: Category.Success, @@ -19,7 +19,7 @@ export const HARDWARE_MAPPINGS = { userActionable: false, }, '0x6300': { - customCode: ErrorCode.AuthSec001, + customCode: ErrorCode.AuthFailed, message: 'Authentication failed', severity: Severity.Err, category: Category.Authentication, @@ -28,7 +28,7 @@ export const HARDWARE_MAPPINGS = { userMessage: 'Authentication failed. Please verify your credentials.', }, '0x63c0': { - customCode: ErrorCode.AuthPin003, + customCode: ErrorCode.AuthPinAttemptsRemaining, message: 'PIN attempts remaining', severity: Severity.Warning, category: Category.Authentication, @@ -36,32 +36,8 @@ export const HARDWARE_MAPPINGS = { userActionable: true, userMessage: 'Incorrect PIN. Please try again.', }, - '0x6700': { - customCode: ErrorCode.DataFormat001, - message: 'Incorrect data length', - severity: Severity.Err, - category: Category.DataValidation, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x6800': { - customCode: ErrorCode.DataMissing001, - message: 'Missing critical parameter', - severity: Severity.Err, - category: Category.DataValidation, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x6981': { - customCode: ErrorCode.ProtoCmd002, - message: 'Command incompatible with file structure', - severity: Severity.Err, - category: Category.Protocol, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, '0x6982': { - customCode: ErrorCode.AuthSec002, + customCode: ErrorCode.AuthSecurityCondition, message: 'Security conditions not satisfied', severity: Severity.Err, category: Category.Authentication, @@ -71,7 +47,7 @@ export const HARDWARE_MAPPINGS = { 'Device is locked or access rights are insufficient. Please unlock your device.', }, '0x6985': { - customCode: ErrorCode.UserCancel001, + customCode: ErrorCode.UserRejected, message: 'User rejected action on device', severity: Severity.Warning, category: Category.UserAction, @@ -80,174 +56,8 @@ export const HARDWARE_MAPPINGS = { userMessage: 'Transaction was rejected. Please approve on your device to continue.', }, - '0x6a80': { - customCode: ErrorCode.DataFormat002, - message: 'Invalid data received', - severity: Severity.Err, - category: Category.DataValidation, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x6a84': { - customCode: ErrorCode.SysMemory001, - message: 'Not enough memory space', - severity: Severity.Err, - category: Category.System, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x6a88': { - customCode: ErrorCode.DataNotfound001, - message: 'Referenced data not found', - severity: Severity.Err, - category: Category.DataValidation, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x6a89': { - customCode: ErrorCode.SysFile001, - message: 'File already exists', - severity: Severity.Err, - category: Category.System, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x6b00': { - customCode: ErrorCode.DataFormat003, - message: 'Invalid parameter received', - severity: Severity.Err, - category: Category.DataValidation, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x6d00': { - customCode: ErrorCode.ProtoCmd001, - message: 'Instruction not supported', - severity: Severity.Err, - category: Category.Protocol, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x6d02': { - customCode: ErrorCode.ProtoMsg001, - message: 'Unknown APDU command', - severity: Severity.Err, - category: Category.Protocol, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x6e00': { - customCode: ErrorCode.ProtoCmd001, - message: 'Class not supported', - severity: Severity.Err, - category: Category.Protocol, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x6501': { - customCode: ErrorCode.ProtoCmd001, - message: 'Ethereum app specific error', - severity: Severity.Err, - category: Category.Protocol, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, - userMessage: - 'Please ensure the Ethereum app is open on your Ledger device.', - }, - '0x6f00': { - customCode: ErrorCode.SysInternal001, - message: 'Internal device error', - severity: Severity.Critical, - category: Category.System, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - userMessage: 'An internal error occurred. Please report this issue.', - }, - '0x6f42': { - customCode: ErrorCode.SysLicense001, - message: 'Licensing error', - severity: Severity.Critical, - category: Category.System, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - userMessage: 'A licensing error occurred. Please contact support.', - }, - '0x6faa': { - customCode: ErrorCode.SysInternal001, - message: 'Device halted', - severity: Severity.Critical, - category: Category.System, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - userMessage: - 'Device has halted. Please disconnect and reconnect your device.', - }, - '0x9240': { - customCode: ErrorCode.SysMemory002, - message: 'Memory problem', - severity: Severity.Err, - category: Category.System, - retryStrategy: RetryStrategy.Retry, - userActionable: false, - }, - '0x9400': { - customCode: ErrorCode.SysFile001, - message: 'No elementary file selected', - severity: Severity.Err, - category: Category.System, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x9402': { - customCode: ErrorCode.DataFormat003, - message: 'Invalid offset', - severity: Severity.Err, - category: Category.DataValidation, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x9404': { - customCode: ErrorCode.DataNotfound002, - message: 'File not found', - severity: Severity.Err, - category: Category.DataValidation, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x9408': { - customCode: ErrorCode.SysFile002, - message: 'Inconsistent file', - severity: Severity.Err, - category: Category.System, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x9484': { - customCode: ErrorCode.CryptoAlgo001, - message: 'Algorithm not supported', - severity: Severity.Err, - category: Category.Cryptography, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x9485': { - customCode: ErrorCode.CryptoKey001, - message: 'Invalid key check value', - severity: Severity.Err, - category: Category.Cryptography, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - '0x9802': { - customCode: ErrorCode.ConfigInit001, - message: 'Code not initialized', - severity: Severity.Err, - category: Category.Configuration, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, '0x9804': { - customCode: ErrorCode.AuthSec002, + customCode: ErrorCode.AuthSecurityCondition, message: 'Access condition not fulfilled', severity: Severity.Err, category: Category.Authentication, @@ -255,23 +65,15 @@ export const HARDWARE_MAPPINGS = { userActionable: true, }, '0x9808': { - customCode: ErrorCode.AuthPin001, + customCode: ErrorCode.AuthFailed, message: 'Contradiction in secret code status', severity: Severity.Err, category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, userActionable: false, }, - '0x9810': { - customCode: ErrorCode.SysInternal001, - message: 'Contradiction invalidation', - severity: Severity.Err, - category: Category.System, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, '0x9840': { - customCode: ErrorCode.AuthLock002, + customCode: ErrorCode.AuthDeviceBlocked, message: 'Code blocked', severity: Severity.Critical, category: Category.Authentication, @@ -280,16 +82,8 @@ export const HARDWARE_MAPPINGS = { userMessage: 'Your device is blocked due to too many failed attempts. Please follow device recovery procedures.', }, - '0x9850': { - customCode: ErrorCode.SysInternal001, - message: 'Maximum value reached', - severity: Severity.Err, - category: Category.System, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, '0x650f': { - customCode: ErrorCode.ConnClosed001, + customCode: ErrorCode.ConnClosed, message: 'App closed or connection issue', severity: Severity.Err, category: Category.Connection, @@ -299,7 +93,7 @@ export const HARDWARE_MAPPINGS = { 'Connection lost or app closed. Please open the corresponding app on your Ledger device.', }, '0x5515': { - customCode: ErrorCode.AuthLock001, + customCode: ErrorCode.AuthDeviceLocked, message: 'Device is locked', severity: Severity.Err, category: Category.Authentication, @@ -308,7 +102,7 @@ export const HARDWARE_MAPPINGS = { userMessage: 'Please unlock your Ledger device to continue.', }, '0x5501': { - customCode: ErrorCode.UserCancel001, + customCode: ErrorCode.UserRejected, message: 'User refused on device', severity: Severity.Warning, category: Category.UserAction, @@ -323,7 +117,7 @@ export const HARDWARE_MAPPINGS = { vendorName: 'Trezor', errorMappings: { '1': { - customCode: ErrorCode.ProtoCmd003, + customCode: ErrorCode.ProtoUnexpectedMessage, message: 'Unexpected message received', severity: Severity.Err, category: Category.Protocol, @@ -332,7 +126,7 @@ export const HARDWARE_MAPPINGS = { originalName: 'Failure_UnexpectedMessage', }, '2': { - customCode: ErrorCode.UserConfirm001, + customCode: ErrorCode.UserConfirmationRequired, message: 'Button confirmation required', severity: Severity.Warning, category: Category.UserAction, @@ -341,17 +135,8 @@ export const HARDWARE_MAPPINGS = { userMessage: 'Please confirm the action on your Trezor device.', originalName: 'Failure_ButtonExpected', }, - '3': { - customCode: ErrorCode.DataFormat002, - message: 'Data error', - severity: Severity.Err, - category: Category.DataValidation, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - originalName: 'Failure_DataError', - }, '4': { - customCode: ErrorCode.UserCancel002, + customCode: ErrorCode.UserCancelled, message: 'Action cancelled by user', severity: Severity.Warning, category: Category.UserAction, @@ -362,7 +147,7 @@ export const HARDWARE_MAPPINGS = { originalName: 'Failure_ActionCancelled', }, '5': { - customCode: ErrorCode.UserInput001, + customCode: ErrorCode.UserInputRequired, message: 'PIN entry expected', severity: Severity.Warning, category: Category.UserAction, @@ -372,7 +157,7 @@ export const HARDWARE_MAPPINGS = { originalName: 'Failure_PinExpected', }, '6': { - customCode: ErrorCode.AuthPin002, + customCode: ErrorCode.AuthPinCancelled, message: 'PIN cancelled by user', severity: Severity.Warning, category: Category.Authentication, @@ -382,7 +167,7 @@ export const HARDWARE_MAPPINGS = { originalName: 'Failure_PinCancelled', }, '7': { - customCode: ErrorCode.AuthPin001, + customCode: ErrorCode.AuthIncorrectPin, message: 'PIN invalid', severity: Severity.Err, category: Category.Authentication, @@ -391,37 +176,8 @@ export const HARDWARE_MAPPINGS = { userMessage: 'Incorrect PIN entered. Please try again.', originalName: 'Failure_PinInvalid', }, - '8': { - customCode: ErrorCode.CryptoSign001, - message: 'Invalid signature', - severity: Severity.Err, - category: Category.Cryptography, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - originalName: 'Failure_InvalidSignature', - }, - '9': { - customCode: ErrorCode.SysInternal001, - message: 'Process error', - severity: Severity.Err, - category: Category.System, - retryStrategy: RetryStrategy.Retry, - userActionable: false, - userMessage: 'A processing error occurred. Please try again.', - originalName: 'Failure_ProcessError', - }, - '10': { - customCode: ErrorCode.TxFunds001, - message: 'Insufficient funds', - severity: Severity.Err, - category: Category.Transaction, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, - userMessage: 'Insufficient funds to complete this transaction.', - originalName: 'Failure_NotEnoughFunds', - }, '11': { - customCode: ErrorCode.DeviceState001, + customCode: ErrorCode.DeviceNotReady, message: 'Device not initialized', severity: Severity.Err, category: Category.DeviceState, @@ -432,7 +188,7 @@ export const HARDWARE_MAPPINGS = { originalName: 'Failure_NotInitialized', }, '12': { - customCode: ErrorCode.AuthPin004, + customCode: ErrorCode.AuthPinMismatch, message: 'PIN mismatch', severity: Severity.Err, category: Category.Authentication, @@ -442,7 +198,7 @@ export const HARDWARE_MAPPINGS = { originalName: 'Failure_PinMismatch', }, '13': { - customCode: ErrorCode.AuthWipe001, + customCode: ErrorCode.AuthWipeCodeMismatch, message: 'Wipe code mismatch', severity: Severity.Err, category: Category.Authentication, @@ -452,7 +208,7 @@ export const HARDWARE_MAPPINGS = { originalName: 'Failure_WipeCodeMismatch', }, '14': { - customCode: ErrorCode.DeviceState002, + customCode: ErrorCode.DeviceInvalidSession, message: 'Invalid session', severity: Severity.Err, category: Category.DeviceState, @@ -462,7 +218,7 @@ export const HARDWARE_MAPPINGS = { originalName: 'Failure_InvalidSession', }, '15': { - customCode: ErrorCode.DeviceState002, + customCode: ErrorCode.DeviceCallInProgress, message: 'Device busy', severity: Severity.Warning, category: Category.DeviceState, @@ -471,19 +227,8 @@ export const HARDWARE_MAPPINGS = { userMessage: 'Device is busy. Please wait and try again.', originalName: 'Failure_Busy', }, - '99': { - customCode: ErrorCode.SysFirmware002, - message: 'Firmware installation failed', - severity: Severity.Critical, - category: Category.System, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, - userMessage: - 'Firmware installation failed. Please contact Trezor support.', - originalName: 'Failure_FirmwareError', - }, UNKNOWN: { - customCode: ErrorCode.Unknown001, + customCode: ErrorCode.Unknown, message: 'Unknown error', severity: Severity.Err, category: Category.Unknown, @@ -493,35 +238,8 @@ export const HARDWARE_MAPPINGS = { 'An unknown error occurred. Please try again or contact support.', originalName: 'Failure_UnknownCode', }, - ENTROPY_CHECK: { - customCode: ErrorCode.CryptoEntropy001, - message: 'Entropy check failed', - severity: Severity.Err, - category: Category.Cryptography, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - originalName: 'Failure_EntropyCheck', - }, - Init_NotInitialized: { - customCode: ErrorCode.ConfigInit001, - message: 'TrezorConnect not initialized', - severity: Severity.Err, - category: Category.Configuration, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - sdkMessage: 'TrezorConnect not initialized', - }, - Init_AlreadyInitialized: { - customCode: ErrorCode.ConfigInit002, - message: 'TrezorConnect already initialized', - severity: Severity.Warning, - category: Category.Configuration, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - sdkMessage: 'TrezorConnect has been already initialized', - }, Init_IframeBlocked: { - customCode: ErrorCode.ConnBlocked001, + customCode: ErrorCode.ConnBlocked, message: 'Iframe blocked', severity: Severity.Err, category: Category.Connection, @@ -532,7 +250,7 @@ export const HARDWARE_MAPPINGS = { sdkMessage: 'Iframe blocked', }, Init_IframeTimeout: { - customCode: ErrorCode.ConnTimeout001, + customCode: ErrorCode.ConnTimeout, message: 'Iframe connection timeout', severity: Severity.Err, category: Category.Connection, @@ -542,17 +260,8 @@ export const HARDWARE_MAPPINGS = { 'Connection timed out. Please check your internet connection and try again.', sdkMessage: 'Iframe timeout', }, - Init_ManifestMissing: { - customCode: ErrorCode.ConfigInit003, - message: 'Manifest not set', - severity: Severity.Err, - category: Category.Configuration, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - sdkMessage: 'Manifest not set...', - }, Popup_ConnectionMissing: { - customCode: ErrorCode.ConnIframe001, + customCode: ErrorCode.ConnIframeMissing, message: 'Unable to establish connection with iframe', severity: Severity.Err, category: Category.Connection, @@ -562,7 +271,7 @@ export const HARDWARE_MAPPINGS = { sdkMessage: 'Unable to establish connection with iframe', }, Desktop_ConnectionMissing: { - customCode: ErrorCode.ConnSuite001, + customCode: ErrorCode.ConnSuiteMissing, message: 'Unable to establish connection with Suite', severity: Severity.Err, category: Category.Connection, @@ -573,7 +282,7 @@ export const HARDWARE_MAPPINGS = { sdkMessage: 'Unable to establish connection with Suite', }, Transport_Missing: { - customCode: ErrorCode.ConnTransport001, + customCode: ErrorCode.ConnTransportMissing, message: 'Transport is missing', severity: Severity.Err, category: Category.Connection, @@ -583,45 +292,8 @@ export const HARDWARE_MAPPINGS = { 'Transport layer not available. Please reconnect your device.', sdkMessage: 'Transport is missing', }, - Method_InvalidPackage: { - customCode: ErrorCode.ConfigMethod001, - message: 'Invalid package for browser environment', - severity: Severity.Err, - category: Category.Configuration, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - sdkMessage: 'This package is not suitable to work with browser...', - }, - Method_InvalidParameter: { - customCode: ErrorCode.DataFormat003, - message: 'Invalid method parameter', - severity: Severity.Err, - category: Category.DataValidation, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - }, - Method_NotAllowed: { - customCode: ErrorCode.ConfigMethod001, - message: 'Method not allowed for this configuration', - severity: Severity.Err, - category: Category.Configuration, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, - sdkMessage: 'Method not allowed for this configuration', - }, - Method_PermissionsNotGranted: { - customCode: ErrorCode.ConfigPerm001, - message: 'Permissions not granted', - severity: Severity.Err, - category: Category.Configuration, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, - userMessage: - 'Required permissions were not granted. Please allow access to continue.', - sdkMessage: 'Permissions not granted', - }, Method_Cancel: { - customCode: ErrorCode.UserCancel002, + customCode: ErrorCode.UserCancelled, message: 'Method cancelled by user', severity: Severity.Warning, category: Category.UserAction, @@ -631,7 +303,7 @@ export const HARDWARE_MAPPINGS = { sdkMessage: 'Cancelled', }, Method_Interrupted: { - customCode: ErrorCode.UserCancel002, + customCode: ErrorCode.UserCancelled, message: 'Popup closed by user', severity: Severity.Warning, category: Category.UserAction, @@ -641,53 +313,44 @@ export const HARDWARE_MAPPINGS = { sdkMessage: 'Popup closed', }, Method_UnknownCoin: { - customCode: ErrorCode.DataNotfound003, + customCode: ErrorCode.Unknown, message: 'Coin not found', severity: Severity.Err, - category: Category.DataValidation, + category: Category.Unknown, retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'The requested cryptocurrency is not supported.', sdkMessage: 'Coin not found', }, Method_AddressNotMatch: { - customCode: ErrorCode.DataValidation001, + customCode: ErrorCode.Unknown, message: 'Addresses do not match', severity: Severity.Err, - category: Category.DataValidation, + category: Category.Unknown, retryStrategy: RetryStrategy.NoRetry, userActionable: true, userMessage: 'Address verification failed. The addresses do not match.', sdkMessage: 'Addresses do not match', }, Method_Discovery_BundleException: { - customCode: ErrorCode.SysInternal001, + customCode: ErrorCode.Unknown, message: 'Discovery bundle exception', severity: Severity.Err, - category: Category.System, + category: Category.Unknown, retryStrategy: RetryStrategy.Retry, userActionable: false, }, Method_Override: { - customCode: ErrorCode.ConfigMethod001, + customCode: ErrorCode.Unknown, message: 'Method override', severity: Severity.Warning, - category: Category.Configuration, + category: Category.Unknown, retryStrategy: RetryStrategy.NoRetry, userActionable: false, sdkMessage: 'override', }, - Method_NoResponse: { - customCode: ErrorCode.ProtoCmd003, - message: 'Call resolved without response', - severity: Severity.Err, - category: Category.Protocol, - retryStrategy: RetryStrategy.Retry, - userActionable: false, - sdkMessage: 'Call resolved without response', - }, Device_NotFound: { - customCode: ErrorCode.DeviceDetect001, + customCode: ErrorCode.DeviceNotFound, message: 'Device not found', severity: Severity.Err, category: Category.DeviceState, @@ -698,7 +361,7 @@ export const HARDWARE_MAPPINGS = { sdkMessage: 'Device not found', }, Device_InitializeFailed: { - customCode: ErrorCode.DeviceState001, + customCode: ErrorCode.DeviceNotReady, message: 'Device initialization failed', severity: Severity.Err, category: Category.DeviceState, @@ -707,18 +370,8 @@ export const HARDWARE_MAPPINGS = { userMessage: 'Failed to initialize device. Please reconnect and try again.', }, - Device_FwException: { - customCode: ErrorCode.SysFirmware001, - message: 'Firmware exception', - severity: Severity.Err, - category: Category.System, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, - userMessage: - 'Firmware error detected. Please update your device firmware.', - }, Device_ModeException: { - customCode: ErrorCode.DeviceMode001, + customCode: ErrorCode.DeviceIncompatibleMode, message: 'Device mode exception', severity: Severity.Err, category: Category.DeviceState, @@ -728,7 +381,7 @@ export const HARDWARE_MAPPINGS = { 'Device is in an incompatible mode. Please check your device settings.', }, Device_Disconnected: { - customCode: ErrorCode.DeviceState003, + customCode: ErrorCode.DeviceDisconnected, message: 'Device disconnected', severity: Severity.Err, category: Category.DeviceState, @@ -739,7 +392,7 @@ export const HARDWARE_MAPPINGS = { sdkMessage: 'Device disconnected', }, Device_UsedElsewhere: { - customCode: ErrorCode.DeviceState004, + customCode: ErrorCode.DeviceUsedElsewhere, message: 'Device is used in another window', severity: Severity.Err, category: Category.DeviceState, @@ -750,7 +403,7 @@ export const HARDWARE_MAPPINGS = { sdkMessage: 'Device is used in another window', }, Device_InvalidState: { - customCode: ErrorCode.AuthSec001, + customCode: ErrorCode.AuthFailed, message: 'Passphrase is incorrect', severity: Severity.Err, category: Category.Authentication, @@ -760,7 +413,7 @@ export const HARDWARE_MAPPINGS = { sdkMessage: 'Passphrase is incorrect', }, Device_CallInProgress: { - customCode: ErrorCode.DeviceState005, + customCode: ErrorCode.DeviceCallInProgress, message: 'Device call in progress', severity: Severity.Warning, category: Category.DeviceState, @@ -770,7 +423,7 @@ export const HARDWARE_MAPPINGS = { sdkMessage: 'Device call in progress', }, Device_MultipleNotSupported: { - customCode: ErrorCode.DeviceCap001, + customCode: ErrorCode.DeviceMultipleConnected, message: 'Multiple devices are not supported', severity: Severity.Err, category: Category.DeviceState, @@ -781,7 +434,7 @@ export const HARDWARE_MAPPINGS = { sdkMessage: 'Multiple devices are not supported', }, Device_MissingCapability: { - customCode: ErrorCode.DeviceCap001, + customCode: ErrorCode.DeviceMissingCapability, message: 'Device is missing required capability', severity: Severity.Err, category: Category.DeviceState, @@ -792,7 +445,7 @@ export const HARDWARE_MAPPINGS = { sdkMessage: 'Device is missing capability', }, Device_MissingCapabilityBtcOnly: { - customCode: ErrorCode.DeviceCap002, + customCode: ErrorCode.DeviceBtcOnlyFirmware, message: 'Device is BTC-only, operation not supported', severity: Severity.Err, category: Category.DeviceState, @@ -804,7 +457,7 @@ export const HARDWARE_MAPPINGS = { }, }, default: { - customCode: ErrorCode.Unknown001, + customCode: ErrorCode.Unknown, message: 'Unknown Trezor error', severity: Severity.Err, category: Category.Unknown, diff --git a/packages/keyring-utils/src/hardware-error.test.ts b/packages/keyring-utils/src/hardware-error.test.ts index 161da8639..89f673ec6 100644 --- a/packages/keyring-utils/src/hardware-error.test.ts +++ b/packages/keyring-utils/src/hardware-error.test.ts @@ -8,7 +8,7 @@ import { describe('HardwareWalletError', () => { const mockOptions = { - code: ErrorCode.UserCancel001, + code: ErrorCode.UserRejected, severity: Severity.Warning, category: Category.UserAction, retryStrategy: RetryStrategy.Retry, @@ -22,7 +22,7 @@ describe('HardwareWalletError', () => { expect(error.message).toBe('Test error'); expect(error.name).toBe('HardwareWalletError'); - expect(error.code).toBe(ErrorCode.UserCancel001); + expect(error.code).toBe(ErrorCode.UserRejected); expect(error.severity).toBe(Severity.Warning); expect(error.category).toBe(Category.UserAction); expect(error.retryStrategy).toBe(RetryStrategy.Retry); @@ -233,7 +233,7 @@ describe('HardwareWalletError', () => { expect(json.id).toBe(error.id); expect(json.name).toBe('HardwareWalletError'); expect(json.message).toBe('Test error'); - expect(json.code).toBe(ErrorCode.UserCancel001); + expect(json.code).toBe(ErrorCode.UserRejected); expect(json.severity).toBe(Severity.Warning); expect(json.category).toBe(Category.UserAction); expect(json.retryStrategy).toBe(RetryStrategy.Retry); @@ -281,20 +281,32 @@ describe('HardwareWalletError', () => { const result = error.toString(); expect(result).toBe( - 'HardwareWalletError [UserCancel001]: Transaction was rejected', + 'HardwareWalletError [UserRejected:2000]: Transaction was rejected', ); }); it('should work with different error codes and messages', () => { const error = new HardwareWalletError('Internal error', { ...mockOptions, - code: ErrorCode.SysInternal001, + code: ErrorCode.Unknown, userMessage: 'An internal error occurred', }); const result = error.toString(); expect(result).toBe( - 'HardwareWalletError [SysInternal001]: An internal error occurred', + 'HardwareWalletError [Unknown:99999]: An internal error occurred', + ); + }); + + it('should fall back to UNKNOWN name for unmapped numeric codes', () => { + const error = new HardwareWalletError('Weird error', { + ...mockOptions, + code: 123456 as unknown as ErrorCode, + userMessage: 'Something strange happened', + }); + + expect(error.toString()).toBe( + 'HardwareWalletError [UNKNOWN:123456]: Something strange happened', ); }); }); @@ -307,7 +319,7 @@ describe('HardwareWalletError', () => { const result = error.toDetailedString(); - expect(result).toContain('HardwareWalletError [UserCancel001]'); + expect(result).toContain('HardwareWalletError [UserRejected:2000]'); expect(result).toContain('Message: Test error'); expect(result).toContain('User Message: Transaction was rejected'); expect(result).toContain('Severity: Warning'); @@ -363,7 +375,7 @@ describe('HardwareWalletError', () => { describe('error scenarios', () => { it('should handle critical authentication errors', () => { const error = new HardwareWalletError('Device blocked', { - code: ErrorCode.AuthLock002, + code: ErrorCode.AuthDeviceBlocked, severity: Severity.Critical, category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, @@ -378,7 +390,7 @@ describe('HardwareWalletError', () => { it('should handle retryable connection errors', () => { const error = new HardwareWalletError('Connection timeout', { - code: ErrorCode.ConnTimeout001, + code: ErrorCode.ConnTimeout, severity: Severity.Err, category: Category.Connection, retryStrategy: RetryStrategy.ExponentialBackoff, @@ -393,7 +405,7 @@ describe('HardwareWalletError', () => { it('should handle user action warnings', () => { const error = new HardwareWalletError('User confirmation required', { - code: ErrorCode.UserConfirm001, + code: ErrorCode.UserConfirmationRequired, severity: Severity.Warning, category: Category.UserAction, retryStrategy: RetryStrategy.Retry, diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts index 1ffe458b9..dce937984 100644 --- a/packages/keyring-utils/src/hardware-error.ts +++ b/packages/keyring-utils/src/hardware-error.ts @@ -1,5 +1,5 @@ -import type { ErrorCode, Category } from './hardware-errors-enums'; -import { Severity, RetryStrategy } from './hardware-errors-enums'; +import type { Category } from './hardware-errors-enums'; +import { ErrorCode, Severity, RetryStrategy } from './hardware-errors-enums'; /** * Generates a unique error ID using timestamp and random values. @@ -12,6 +12,17 @@ function generateErrorId(): string { return `err_${timestamp}_${randomPart}`; } +/** + * Gets the human-readable name for an error code using enum reverse mapping. + * + * @param code - The error code enum value. + * @returns The string name of the error code, or 'UNKNOWN' if not found. + */ +function getErrorCodeName(code: ErrorCode): string { + // Numeric enums have a reverse mapping at runtime: ErrorCode[1000] => "AUTH_FAILED" + return ErrorCode[code] ?? 'UNKNOWN'; +} + export type HardwareWalletErrorOptions = { code: ErrorCode; severity: Severity; @@ -158,7 +169,8 @@ export class HardwareWalletError extends Error { * @returns A user-friendly string representation of the error. */ toString(): string { - return `${this.name} [${this.code}]: ${this.userMessage}`; + const codeName = getErrorCodeName(this.code); + return `${this.name} [${codeName}:${this.code}]: ${this.userMessage}`; } /** @@ -167,8 +179,9 @@ export class HardwareWalletError extends Error { * @returns A detailed string representation of the error for debugging. */ toDetailedString(): string { + const codeName = getErrorCodeName(this.code); const details = [ - `${this.name} [${this.code}]`, + `${this.name} [${codeName}:${this.code}]`, `Message: ${this.message}`, `User Message: ${this.userMessage}`, `Severity: ${this.severity}`, diff --git a/packages/keyring-utils/src/hardware-errors-enums.ts b/packages/keyring-utils/src/hardware-errors-enums.ts index 856e59332..7fe50284b 100644 --- a/packages/keyring-utils/src/hardware-errors-enums.ts +++ b/packages/keyring-utils/src/hardware-errors-enums.ts @@ -1,91 +1,55 @@ // Error Code Enum export enum ErrorCode { - // Authentication & Security - AuthPin001 = 'AuthPin001', - AuthPin002 = 'AuthPin002', - AuthPin003 = 'AuthPin003', - AuthPin004 = 'AuthPin004', - AuthLock001 = 'AuthLock001', - AuthLock002 = 'AuthLock002', - AuthSec001 = 'AuthSec001', - AuthSec002 = 'AuthSec002', - AuthWipe001 = 'AuthWipe001', - - // User Action - UserCancel001 = 'UserCancel001', - UserCancel002 = 'UserCancel002', - UserInput001 = 'UserInput001', - UserConfirm001 = 'UserConfirm001', - - // Device State - DeviceState001 = 'DeviceState001', - DeviceState002 = 'DeviceState002', - DeviceState003 = 'DeviceState003', - DeviceState004 = 'DeviceState004', - DeviceState005 = 'DeviceState005', - DeviceDetect001 = 'DeviceDetect001', - DeviceCap001 = 'DeviceCap001', - DeviceCap002 = 'DeviceCap002', - DeviceMode001 = 'DeviceMode001', - - // Connection & Transport - ConnTransport001 = 'ConnTransport001', - ConnClosed001 = 'ConnClosed001', - ConnIframe001 = 'ConnIframe001', - ConnSuite001 = 'ConnSuite001', - ConnTimeout001 = 'ConnTimeout001', - ConnBlocked001 = 'ConnBlocked001', + // Success + Success = 0, - // Data & Validation - DataFormat001 = 'DataFormat001', - DataFormat002 = 'DataFormat002', - DataFormat003 = 'DataFormat003', - DataMissing001 = 'DataMissing001', - DataValidation001 = 'DataValidation001', - DataValidation002 = 'DataValidation002', - DataNotfound001 = 'DataNotfound001', - DataNotfound002 = 'DataNotfound002', - DataNotfound003 = 'DataNotfound003', + // Authentication + AuthFailed = 1000, + AuthIncorrectPin = 1001, + AuthPinAttemptsRemaining = 1002, + AuthPinCancelled = 1003, + AuthPinMismatch = 1004, + AuthDeviceLocked = 1010, + AuthDeviceBlocked = 1011, + AuthSecurityCondition = 1020, + AuthWipeCodeMismatch = 1030, - // Cryptographic Operations - CryptoSign001 = 'CryptoSign001', - CryptoAlgo001 = 'CryptoAlgo001', - CryptoKey001 = 'CryptoKey001', - CryptoEntropy001 = 'CryptoEntropy001', + // User action + UserRejected = 2000, + UserCancelled = 2001, + UserConfirmationRequired = 2002, + UserInputRequired = 2003, - // System & Internal - SysInternal001 = 'SysInternal001', - SysMemory001 = 'SysMemory001', - SysMemory002 = 'SysMemory002', - SysFile001 = 'SysFile001', - SysFile002 = 'SysFile002', - SysLicense001 = 'SysLicense001', - SysFirmware001 = 'SysFirmware001', - SysFirmware002 = 'SysFirmware002', + // Device state + DeviceNotReady = 3000, + DeviceInvalidSession = 3001, + DeviceDisconnected = 3003, + DeviceUsedElsewhere = 3004, + DeviceCallInProgress = 3005, + DeviceNotFound = 3010, + DeviceMultipleConnected = 3011, + DeviceMissingCapability = 3020, + DeviceBtcOnlyFirmware = 3021, + DeviceIncompatibleMode = 3030, - // Command & Protocol - ProtoCmd001 = 'ProtoCmd001', - ProtoCmd002 = 'ProtoCmd002', - ProtoCmd003 = 'ProtoCmd003', - ProtoMsg001 = 'ProtoMsg001', - ProtoParam001 = 'ProtoParam001', + // Connection & transport + ConnTransportMissing = 4000, + ConnClosed = 4001, + ConnTimeout = 4002, + ConnBlocked = 4003, + ConnIframeMissing = 4010, + ConnSuiteMissing = 4011, - // Configuration & Initialization - ConfigInit001 = 'ConfigInit001', - ConfigInit002 = 'ConfigInit002', - ConfigInit003 = 'ConfigInit003', - ConfigPerm001 = 'ConfigPerm001', - ConfigMethod001 = 'ConfigMethod001', + // Protocol + ProtoUnexpectedMessage = 5000, + ProtoCommandError = 5001, + ProtoMessageError = 5002, // Transaction - TxFunds001 = 'TxFunds001', - TxFail001 = 'TxFail001', - - // Success - Success000 = 'Success000', + TxInsufficientFunds = 10000, - // Unknown/Fallback - Unknown001 = 'Unknown001', + // Unknown/fallback + Unknown = 99999, } // Severity Enum @@ -100,15 +64,10 @@ export enum Severity { export enum Category { Success = 'Success', Authentication = 'Authentication', - DataValidation = 'DataValidation', Protocol = 'Protocol', - System = 'System', - Cryptography = 'Cryptography', - Configuration = 'Configuration', Connection = 'Connection', UserAction = 'UserAction', DeviceState = 'DeviceState', - Transaction = 'Transaction', Unknown = 'Unknown', } From c4cd2407753d49cbd980b047200c8f48ce63b518 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Fri, 9 Jan 2026 21:56:13 +0800 Subject: [PATCH 08/24] fix: add code --- .../src/hardware-error-mappings.test.ts | 4 +- .../src/hardware-error-mappings.ts | 39 ++++++++++++++++++- .../src/hardware-errors-enums.ts | 6 +++ 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error-mappings.test.ts b/packages/keyring-utils/src/hardware-error-mappings.test.ts index 8e413ebfb..e7e12cb08 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.test.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.test.ts @@ -433,9 +433,7 @@ describe('HARDWARE_MAPPINGS', () => { ); criticalMappings.forEach((mapping) => { - expect([RetryStrategy.NoRetry, RetryStrategy.Retry]).toContain( - mapping.retryStrategy, - ); + expect(mapping.retryStrategy).toBe(RetryStrategy.NoRetry); }); }); }); diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/keyring-utils/src/hardware-error-mappings.ts index c69553404..a2bc9fc5b 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.ts @@ -58,7 +58,7 @@ export const HARDWARE_MAPPINGS = { }, '0x9804': { customCode: ErrorCode.AuthSecurityCondition, - message: 'Access condition not fulfilled', + message: 'App update required', severity: Severity.Err, category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, @@ -111,6 +111,43 @@ export const HARDWARE_MAPPINGS = { userMessage: 'Transaction was rejected. Please approve on your device to continue.', }, + '0x6a80': { + customCode: ErrorCode.DeviceStateBlindSignNotSupported, + message: 'Blind signing not supported', + severity: Severity.Err, + category: Category.DeviceState, + retryStrategy: RetryStrategy.NoRetry, + userActionable: true, + userMessage: 'Blind signing is not supported on this device.', + }, + '0x6d00': { + customCode: ErrorCode.DeviceStateOnlyV4Supported, + message: 'Ledger Only V4 supported', + severity: Severity.Err, + category: Category.DeviceState, + retryStrategy: RetryStrategy.NoRetry, + userActionable: true, + userMessage: 'Only V4 is supported on this device.', + }, + '0x6e00': { + customCode: ErrorCode.DeviceStateEthAppClosed, + message: 'Ethereum app closed', + severity: Severity.Err, + category: Category.DeviceState, + retryStrategy: RetryStrategy.NoRetry, + userActionable: true, + 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, + retryStrategy: RetryStrategy.NoRetry, + userActionable: true, + userMessage: + 'Ethereum app is out of date. Please update it to continue.', + }, }, }, trezor: { diff --git a/packages/keyring-utils/src/hardware-errors-enums.ts b/packages/keyring-utils/src/hardware-errors-enums.ts index 7fe50284b..1782970c9 100644 --- a/packages/keyring-utils/src/hardware-errors-enums.ts +++ b/packages/keyring-utils/src/hardware-errors-enums.ts @@ -45,6 +45,12 @@ export enum ErrorCode { ProtoCommandError = 5001, ProtoMessageError = 5002, + // Device Stae + DeviceStateBlindSignNotSupported = 6001, + DeviceStateOnlyV4Supported = 6002, + DeviceStateEthAppClosed = 6003, + DeviceStateEthAppOutOfDate = 6004, + // Transaction TxInsufficientFunds = 10000, From 8cf427b158207dc7900e569b7481d054d0d5d587 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Fri, 9 Jan 2026 22:51:40 +0800 Subject: [PATCH 09/24] fix: test --- .../src/hardware-error-mappings.test.ts | 26 +++++++------------ .../src/hardware-error-mappings.ts | 1 + 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error-mappings.test.ts b/packages/keyring-utils/src/hardware-error-mappings.test.ts index e7e12cb08..9f805d956 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.test.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.test.ts @@ -392,33 +392,27 @@ describe('HARDWARE_MAPPINGS', () => { const ledgerMappings = Object.values( HARDWARE_MAPPINGS.ledger.errorMappings, ).filter( - (mapping): mapping is typeof mapping & { userMessage: string } => - mapping.userActionable && - mapping.severity !== Severity.Info && - 'userMessage' in mapping && - typeof mapping.userMessage === 'string' && - mapping.userMessage.length > 0, + (mapping) => + mapping.userActionable && mapping.severity !== Severity.Info, ); ledgerMappings.forEach((mapping) => { - expect(mapping.userMessage).toBeDefined(); - expect(mapping.userMessage.length).toBeGreaterThan(0); + expect('userMessage' in mapping).toBe(true); + expect(typeof (mapping as any).userMessage).toBe('string'); + expect((mapping as any).userMessage.length).toBeGreaterThan(0); }); const trezorMappings = Object.values( HARDWARE_MAPPINGS.trezor.errorMappings, ).filter( - (mapping): mapping is typeof mapping & { userMessage: string } => - mapping.userActionable && - mapping.severity !== Severity.Info && - 'userMessage' in mapping && - typeof mapping.userMessage === 'string' && - mapping.userMessage.length > 0, + (mapping) => + mapping.userActionable && mapping.severity !== Severity.Info, ); trezorMappings.forEach((mapping) => { - expect(mapping.userMessage).toBeDefined(); - expect(mapping.userMessage.length).toBeGreaterThan(0); + expect('userMessage' in mapping).toBe(true); + expect(typeof (mapping as any).userMessage).toBe('string'); + expect((mapping as any).userMessage.length).toBeGreaterThan(0); }); }); diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/keyring-utils/src/hardware-error-mappings.ts index a2bc9fc5b..ec3896bb1 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.ts @@ -63,6 +63,7 @@ export const HARDWARE_MAPPINGS = { category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, userActionable: true, + userMessage: 'Please update your Ledger app to continue.', }, '0x9808': { customCode: ErrorCode.AuthFailed, From 928e0dec796a19b2a79a9af5a72157259e17ea02 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Mon, 12 Jan 2026 18:35:44 +0800 Subject: [PATCH 10/24] fix: use Error for value --- .../src/hardware-error-mappings.test.ts | 12 +-- .../src/hardware-error-mappings.ts | 86 ++++++++----------- .../keyring-utils/src/hardware-error.test.ts | 6 +- .../src/hardware-errors-enums.ts | 3 +- 4 files changed, 45 insertions(+), 62 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error-mappings.test.ts b/packages/keyring-utils/src/hardware-error-mappings.test.ts index 9f805d956..77f065486 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.test.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.test.ts @@ -43,7 +43,7 @@ describe('HARDWARE_MAPPINGS', () => { it('should map 0x6300 to authentication failed', () => { const mapping = errorMappings['0x6300']; expect(mapping.customCode).toBe(ErrorCode.AuthFailed); - expect(mapping.severity).toBe(Severity.Err); + expect(mapping.severity).toBe(Severity.Error); expect(mapping.category).toBe(Category.Authentication); expect(mapping.userActionable).toBe(true); expect(mapping.userMessage).toBeDefined(); @@ -59,7 +59,7 @@ describe('HARDWARE_MAPPINGS', () => { it('should map 0x5515 to device locked', () => { const mapping = errorMappings['0x5515']; expect(mapping.customCode).toBe(ErrorCode.AuthDeviceLocked); - expect(mapping.severity).toBe(Severity.Err); + expect(mapping.severity).toBe(Severity.Error); expect(mapping.userActionable).toBe(true); expect(mapping.userMessage).toContain('unlock'); }); @@ -146,7 +146,7 @@ describe('HARDWARE_MAPPINGS', () => { it('should map code 1 to unexpected message', () => { const mapping = errorMappings['1']; expect(mapping.customCode).toBe(ErrorCode.ProtoUnexpectedMessage); - expect(mapping.severity).toBe(Severity.Err); + expect(mapping.severity).toBe(Severity.Error); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.originalName).toBe('Failure_UnexpectedMessage'); }); @@ -175,12 +175,6 @@ describe('HARDWARE_MAPPINGS', () => { expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.originalName).toBe('Failure_PinInvalid'); }); - - it('should map code 12 to PIN mismatch', () => { - const mapping = errorMappings['12']; - expect(mapping.customCode).toBe(ErrorCode.AuthPinMismatch); - expect(mapping.originalName).toBe('Failure_PinMismatch'); - }); }); describe('device state errors', () => { diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/keyring-utils/src/hardware-error-mappings.ts index ec3896bb1..1ee685d5e 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.ts @@ -21,7 +21,7 @@ export const HARDWARE_MAPPINGS = { '0x6300': { customCode: ErrorCode.AuthFailed, message: 'Authentication failed', - severity: Severity.Err, + severity: Severity.Error, category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -39,7 +39,7 @@ export const HARDWARE_MAPPINGS = { '0x6982': { customCode: ErrorCode.AuthSecurityCondition, message: 'Security conditions not satisfied', - severity: Severity.Err, + severity: Severity.Error, category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -59,7 +59,7 @@ export const HARDWARE_MAPPINGS = { '0x9804': { customCode: ErrorCode.AuthSecurityCondition, message: 'App update required', - severity: Severity.Err, + severity: Severity.Error, category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -68,7 +68,7 @@ export const HARDWARE_MAPPINGS = { '0x9808': { customCode: ErrorCode.AuthFailed, message: 'Contradiction in secret code status', - severity: Severity.Err, + severity: Severity.Error, category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, userActionable: false, @@ -86,7 +86,7 @@ export const HARDWARE_MAPPINGS = { '0x650f': { customCode: ErrorCode.ConnClosed, message: 'App closed or connection issue', - severity: Severity.Err, + severity: Severity.Error, category: Category.Connection, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -96,7 +96,7 @@ export const HARDWARE_MAPPINGS = { '0x5515': { customCode: ErrorCode.AuthDeviceLocked, message: 'Device is locked', - severity: Severity.Err, + severity: Severity.Error, category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -115,7 +115,7 @@ export const HARDWARE_MAPPINGS = { '0x6a80': { customCode: ErrorCode.DeviceStateBlindSignNotSupported, message: 'Blind signing not supported', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -124,7 +124,7 @@ export const HARDWARE_MAPPINGS = { '0x6d00': { customCode: ErrorCode.DeviceStateOnlyV4Supported, message: 'Ledger Only V4 supported', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -133,7 +133,7 @@ export const HARDWARE_MAPPINGS = { '0x6e00': { customCode: ErrorCode.DeviceStateEthAppClosed, message: 'Ethereum app closed', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -142,7 +142,7 @@ export const HARDWARE_MAPPINGS = { '0x6501': { customCode: ErrorCode.DeviceStateEthAppOutOfDate, message: 'Ethereum app out of date', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -157,7 +157,7 @@ export const HARDWARE_MAPPINGS = { '1': { customCode: ErrorCode.ProtoUnexpectedMessage, message: 'Unexpected message received', - severity: Severity.Err, + severity: Severity.Error, category: Category.Protocol, retryStrategy: RetryStrategy.Retry, userActionable: false, @@ -207,7 +207,7 @@ export const HARDWARE_MAPPINGS = { '7': { customCode: ErrorCode.AuthIncorrectPin, message: 'PIN invalid', - severity: Severity.Err, + severity: Severity.Error, category: Category.Authentication, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -217,7 +217,7 @@ export const HARDWARE_MAPPINGS = { '11': { customCode: ErrorCode.DeviceNotReady, message: 'Device not initialized', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -225,20 +225,10 @@ export const HARDWARE_MAPPINGS = { 'Your Trezor device needs to be initialized. Please set it up first.', originalName: 'Failure_NotInitialized', }, - '12': { - customCode: ErrorCode.AuthPinMismatch, - message: 'PIN mismatch', - severity: Severity.Err, - category: Category.Authentication, - retryStrategy: RetryStrategy.Retry, - userActionable: true, - userMessage: 'PINs do not match. Please try again.', - originalName: 'Failure_PinMismatch', - }, '13': { customCode: ErrorCode.AuthWipeCodeMismatch, message: 'Wipe code mismatch', - severity: Severity.Err, + severity: Severity.Error, category: Category.Authentication, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -248,7 +238,7 @@ export const HARDWARE_MAPPINGS = { '14': { customCode: ErrorCode.DeviceInvalidSession, message: 'Invalid session', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.Retry, userActionable: false, @@ -268,7 +258,7 @@ export const HARDWARE_MAPPINGS = { UNKNOWN: { customCode: ErrorCode.Unknown, message: 'Unknown error', - severity: Severity.Err, + severity: Severity.Error, category: Category.Unknown, retryStrategy: RetryStrategy.NoRetry, userActionable: false, @@ -279,7 +269,7 @@ export const HARDWARE_MAPPINGS = { Init_IframeBlocked: { customCode: ErrorCode.ConnBlocked, message: 'Iframe blocked', - severity: Severity.Err, + severity: Severity.Error, category: Category.Connection, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -290,7 +280,7 @@ export const HARDWARE_MAPPINGS = { Init_IframeTimeout: { customCode: ErrorCode.ConnTimeout, message: 'Iframe connection timeout', - severity: Severity.Err, + severity: Severity.Error, category: Category.Connection, retryStrategy: RetryStrategy.Retry, userActionable: false, @@ -301,7 +291,7 @@ export const HARDWARE_MAPPINGS = { Popup_ConnectionMissing: { customCode: ErrorCode.ConnIframeMissing, message: 'Unable to establish connection with iframe', - severity: Severity.Err, + severity: Severity.Error, category: Category.Connection, retryStrategy: RetryStrategy.Retry, userActionable: false, @@ -311,7 +301,7 @@ export const HARDWARE_MAPPINGS = { Desktop_ConnectionMissing: { customCode: ErrorCode.ConnSuiteMissing, message: 'Unable to establish connection with Suite', - severity: Severity.Err, + severity: Severity.Error, category: Category.Connection, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -322,7 +312,7 @@ export const HARDWARE_MAPPINGS = { Transport_Missing: { customCode: ErrorCode.ConnTransportMissing, message: 'Transport is missing', - severity: Severity.Err, + severity: Severity.Error, category: Category.Connection, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -353,7 +343,7 @@ export const HARDWARE_MAPPINGS = { Method_UnknownCoin: { customCode: ErrorCode.Unknown, message: 'Coin not found', - severity: Severity.Err, + severity: Severity.Error, category: Category.Unknown, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -363,7 +353,7 @@ export const HARDWARE_MAPPINGS = { Method_AddressNotMatch: { customCode: ErrorCode.Unknown, message: 'Addresses do not match', - severity: Severity.Err, + severity: Severity.Error, category: Category.Unknown, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -373,7 +363,7 @@ export const HARDWARE_MAPPINGS = { Method_Discovery_BundleException: { customCode: ErrorCode.Unknown, message: 'Discovery bundle exception', - severity: Severity.Err, + severity: Severity.Error, category: Category.Unknown, retryStrategy: RetryStrategy.Retry, userActionable: false, @@ -390,7 +380,7 @@ export const HARDWARE_MAPPINGS = { Device_NotFound: { customCode: ErrorCode.DeviceNotFound, message: 'Device not found', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -401,7 +391,7 @@ export const HARDWARE_MAPPINGS = { Device_InitializeFailed: { customCode: ErrorCode.DeviceNotReady, message: 'Device initialization failed', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -411,7 +401,7 @@ export const HARDWARE_MAPPINGS = { Device_ModeException: { customCode: ErrorCode.DeviceIncompatibleMode, message: 'Device mode exception', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -421,7 +411,7 @@ export const HARDWARE_MAPPINGS = { Device_Disconnected: { customCode: ErrorCode.DeviceDisconnected, message: 'Device disconnected', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -432,7 +422,7 @@ export const HARDWARE_MAPPINGS = { Device_UsedElsewhere: { customCode: ErrorCode.DeviceUsedElsewhere, message: 'Device is used in another window', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -443,7 +433,7 @@ export const HARDWARE_MAPPINGS = { Device_InvalidState: { customCode: ErrorCode.AuthFailed, message: 'Passphrase is incorrect', - severity: Severity.Err, + severity: Severity.Error, category: Category.Authentication, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -463,7 +453,7 @@ export const HARDWARE_MAPPINGS = { Device_MultipleNotSupported: { customCode: ErrorCode.DeviceMultipleConnected, message: 'Multiple devices are not supported', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -474,7 +464,7 @@ export const HARDWARE_MAPPINGS = { Device_MissingCapability: { customCode: ErrorCode.DeviceMissingCapability, message: 'Device is missing required capability', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -485,7 +475,7 @@ export const HARDWARE_MAPPINGS = { Device_MissingCapabilityBtcOnly: { customCode: ErrorCode.DeviceBtcOnlyFirmware, message: 'Device is BTC-only, operation not supported', - severity: Severity.Err, + severity: Severity.Error, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -497,7 +487,7 @@ export const HARDWARE_MAPPINGS = { default: { customCode: ErrorCode.Unknown, message: 'Unknown Trezor error', - severity: Severity.Err, + severity: Severity.Error, category: Category.Unknown, retryStrategy: RetryStrategy.NoRetry, userActionable: false, @@ -509,25 +499,25 @@ export const HARDWARE_MAPPINGS = { pattern: '^Failure_.*', type: 'prefix', description: 'Device failure codes', - defaultSeverity: Severity.Err, + defaultSeverity: Severity.Error, }, { pattern: '^Init_.*', type: 'prefix', description: 'Initialization errors', - defaultSeverity: Severity.Err, + defaultSeverity: Severity.Error, }, { pattern: '^Method_.*', type: 'prefix', description: 'Method invocation errors', - defaultSeverity: Severity.Err, + defaultSeverity: Severity.Error, }, { pattern: '^Device_.*', type: 'prefix', description: 'Device state errors', - defaultSeverity: Severity.Err, + defaultSeverity: Severity.Error, }, ], }, diff --git a/packages/keyring-utils/src/hardware-error.test.ts b/packages/keyring-utils/src/hardware-error.test.ts index 89f673ec6..45e6b8a98 100644 --- a/packages/keyring-utils/src/hardware-error.test.ts +++ b/packages/keyring-utils/src/hardware-error.test.ts @@ -108,7 +108,7 @@ describe('HardwareWalletError', () => { }); it('should return false for non-CRITICAL severity', () => { - const severities = [Severity.Err, Severity.Warning, Severity.Info]; + const severities = [Severity.Error, Severity.Warning, Severity.Info]; severities.forEach((severity) => { const error = new HardwareWalletError('Test error', { ...mockOptions, @@ -129,7 +129,7 @@ describe('HardwareWalletError', () => { }); it('should return false for non-WARNING severity', () => { - const severities = [Severity.Err, Severity.Critical, Severity.Info]; + const severities = [Severity.Error, Severity.Critical, Severity.Info]; severities.forEach((severity) => { const error = new HardwareWalletError('Test error', { ...mockOptions, @@ -391,7 +391,7 @@ describe('HardwareWalletError', () => { it('should handle retryable connection errors', () => { const error = new HardwareWalletError('Connection timeout', { code: ErrorCode.ConnTimeout, - severity: Severity.Err, + severity: Severity.Error, category: Category.Connection, retryStrategy: RetryStrategy.ExponentialBackoff, userActionable: false, diff --git a/packages/keyring-utils/src/hardware-errors-enums.ts b/packages/keyring-utils/src/hardware-errors-enums.ts index 1782970c9..9a0a6b398 100644 --- a/packages/keyring-utils/src/hardware-errors-enums.ts +++ b/packages/keyring-utils/src/hardware-errors-enums.ts @@ -8,7 +8,6 @@ export enum ErrorCode { AuthIncorrectPin = 1001, AuthPinAttemptsRemaining = 1002, AuthPinCancelled = 1003, - AuthPinMismatch = 1004, AuthDeviceLocked = 1010, AuthDeviceBlocked = 1011, AuthSecurityCondition = 1020, @@ -61,7 +60,7 @@ export enum ErrorCode { // Severity Enum export enum Severity { Info = 'Info', - Err = 'Err', + Err = 'Error', Warning = 'Warning', Critical = 'Critical', } From 9a617f4496a5d1ad5fb2b3e1924910a1c00d54f4 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Mon, 12 Jan 2026 18:37:37 +0800 Subject: [PATCH 11/24] fix: unknown error code --- packages/keyring-utils/src/hardware-error.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts index dce937984..49067ee29 100644 --- a/packages/keyring-utils/src/hardware-error.ts +++ b/packages/keyring-utils/src/hardware-error.ts @@ -19,8 +19,8 @@ function generateErrorId(): string { * @returns The string name of the error code, or 'UNKNOWN' if not found. */ function getErrorCodeName(code: ErrorCode): string { - // Numeric enums have a reverse mapping at runtime: ErrorCode[1000] => "AUTH_FAILED" - return ErrorCode[code] ?? 'UNKNOWN'; + // Numeric enums have a reverse mapping at runtime: ErrorCode[1000] => "AuthFailed" + return ErrorCode[code] ?? ErrorCode.Unknown; } export type HardwareWalletErrorOptions = { From a82997589c92639bf4a0d69b4a38c1898716a06a Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Mon, 12 Jan 2026 18:39:14 +0800 Subject: [PATCH 12/24] feat: add erro prefix helper --- packages/keyring-utils/src/hardware-error.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts index 49067ee29..cfadf9f10 100644 --- a/packages/keyring-utils/src/hardware-error.ts +++ b/packages/keyring-utils/src/hardware-error.ts @@ -179,9 +179,8 @@ export class HardwareWalletError extends Error { * @returns A detailed string representation of the error for debugging. */ toDetailedString(): string { - const codeName = getErrorCodeName(this.code); const details = [ - `${this.name} [${codeName}:${this.code}]`, + this.#getErrorPrefix(), `Message: ${this.message}`, `User Message: ${this.userMessage}`, `Severity: ${this.severity}`, @@ -201,4 +200,9 @@ export class HardwareWalletError extends Error { return details.join('\n'); } + + #getErrorPrefix(): string { + const codeName = getErrorCodeName(this.code); + return `${this.name} [${codeName}:${this.code}]`; + } } From 6e59f1ff6da60866054608b1de6c19215c201c54 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Mon, 12 Jan 2026 18:53:25 +0800 Subject: [PATCH 13/24] fix; update subcategory --- packages/keyring-utils/src/hardware-error.ts | 1 + packages/keyring-utils/src/hardware-errors-enums.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts index cfadf9f10..b9cde1998 100644 --- a/packages/keyring-utils/src/hardware-error.ts +++ b/packages/keyring-utils/src/hardware-error.ts @@ -8,6 +8,7 @@ import { ErrorCode, Severity, RetryStrategy } from './hardware-errors-enums'; */ function generateErrorId(): string { const timestamp = Date.now().toString(36); + // Random string will be formatted as: 0.fa4dmg7flr8, so we skip 0. part. const randomPart = Math.random().toString(36).substring(2, 9); return `err_${timestamp}_${randomPart}`; } diff --git a/packages/keyring-utils/src/hardware-errors-enums.ts b/packages/keyring-utils/src/hardware-errors-enums.ts index 9a0a6b398..8d230ea04 100644 --- a/packages/keyring-utils/src/hardware-errors-enums.ts +++ b/packages/keyring-utils/src/hardware-errors-enums.ts @@ -8,10 +8,10 @@ export enum ErrorCode { AuthIncorrectPin = 1001, AuthPinAttemptsRemaining = 1002, AuthPinCancelled = 1003, - AuthDeviceLocked = 1010, - AuthDeviceBlocked = 1011, - AuthSecurityCondition = 1020, - AuthWipeCodeMismatch = 1030, + AuthDeviceLocked = 1100, + AuthDeviceBlocked = 1101, + AuthSecurityCondition = 1200, + AuthWipeCodeMismatch = 1300, // User action UserRejected = 2000, From 131e51355db03dbaa3b382e5269fb1c38f435e5c Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Mon, 12 Jan 2026 19:04:43 +0800 Subject: [PATCH 14/24] fix: lint and test --- .../src/hardware-error-mappings.test.ts | 6 +- .../src/hardware-error-mappings.ts | 76 +++++++++---------- .../keyring-utils/src/hardware-error.test.ts | 8 +- packages/keyring-utils/src/hardware-error.ts | 2 +- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error-mappings.test.ts b/packages/keyring-utils/src/hardware-error-mappings.test.ts index 77f065486..851b47caf 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.test.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.test.ts @@ -43,7 +43,7 @@ describe('HARDWARE_MAPPINGS', () => { it('should map 0x6300 to authentication failed', () => { const mapping = errorMappings['0x6300']; expect(mapping.customCode).toBe(ErrorCode.AuthFailed); - expect(mapping.severity).toBe(Severity.Error); + expect(mapping.severity).toBe(Severity.Err); expect(mapping.category).toBe(Category.Authentication); expect(mapping.userActionable).toBe(true); expect(mapping.userMessage).toBeDefined(); @@ -59,7 +59,7 @@ describe('HARDWARE_MAPPINGS', () => { it('should map 0x5515 to device locked', () => { const mapping = errorMappings['0x5515']; expect(mapping.customCode).toBe(ErrorCode.AuthDeviceLocked); - expect(mapping.severity).toBe(Severity.Error); + expect(mapping.severity).toBe(Severity.Err); expect(mapping.userActionable).toBe(true); expect(mapping.userMessage).toContain('unlock'); }); @@ -146,7 +146,7 @@ describe('HARDWARE_MAPPINGS', () => { it('should map code 1 to unexpected message', () => { const mapping = errorMappings['1']; expect(mapping.customCode).toBe(ErrorCode.ProtoUnexpectedMessage); - expect(mapping.severity).toBe(Severity.Error); + expect(mapping.severity).toBe(Severity.Err); expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.originalName).toBe('Failure_UnexpectedMessage'); }); diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/keyring-utils/src/hardware-error-mappings.ts index 1ee685d5e..3af83056e 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.ts @@ -21,7 +21,7 @@ export const HARDWARE_MAPPINGS = { '0x6300': { customCode: ErrorCode.AuthFailed, message: 'Authentication failed', - severity: Severity.Error, + severity: Severity.Err, category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -39,7 +39,7 @@ export const HARDWARE_MAPPINGS = { '0x6982': { customCode: ErrorCode.AuthSecurityCondition, message: 'Security conditions not satisfied', - severity: Severity.Error, + severity: Severity.Err, category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -59,7 +59,7 @@ export const HARDWARE_MAPPINGS = { '0x9804': { customCode: ErrorCode.AuthSecurityCondition, message: 'App update required', - severity: Severity.Error, + severity: Severity.Err, category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -68,7 +68,7 @@ export const HARDWARE_MAPPINGS = { '0x9808': { customCode: ErrorCode.AuthFailed, message: 'Contradiction in secret code status', - severity: Severity.Error, + severity: Severity.Err, category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, userActionable: false, @@ -86,7 +86,7 @@ export const HARDWARE_MAPPINGS = { '0x650f': { customCode: ErrorCode.ConnClosed, message: 'App closed or connection issue', - severity: Severity.Error, + severity: Severity.Err, category: Category.Connection, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -96,7 +96,7 @@ export const HARDWARE_MAPPINGS = { '0x5515': { customCode: ErrorCode.AuthDeviceLocked, message: 'Device is locked', - severity: Severity.Error, + severity: Severity.Err, category: Category.Authentication, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -115,7 +115,7 @@ export const HARDWARE_MAPPINGS = { '0x6a80': { customCode: ErrorCode.DeviceStateBlindSignNotSupported, message: 'Blind signing not supported', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -124,7 +124,7 @@ export const HARDWARE_MAPPINGS = { '0x6d00': { customCode: ErrorCode.DeviceStateOnlyV4Supported, message: 'Ledger Only V4 supported', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -133,7 +133,7 @@ export const HARDWARE_MAPPINGS = { '0x6e00': { customCode: ErrorCode.DeviceStateEthAppClosed, message: 'Ethereum app closed', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -142,7 +142,7 @@ export const HARDWARE_MAPPINGS = { '0x6501': { customCode: ErrorCode.DeviceStateEthAppOutOfDate, message: 'Ethereum app out of date', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -157,7 +157,7 @@ export const HARDWARE_MAPPINGS = { '1': { customCode: ErrorCode.ProtoUnexpectedMessage, message: 'Unexpected message received', - severity: Severity.Error, + severity: Severity.Err, category: Category.Protocol, retryStrategy: RetryStrategy.Retry, userActionable: false, @@ -207,7 +207,7 @@ export const HARDWARE_MAPPINGS = { '7': { customCode: ErrorCode.AuthIncorrectPin, message: 'PIN invalid', - severity: Severity.Error, + severity: Severity.Err, category: Category.Authentication, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -217,7 +217,7 @@ export const HARDWARE_MAPPINGS = { '11': { customCode: ErrorCode.DeviceNotReady, message: 'Device not initialized', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -228,7 +228,7 @@ export const HARDWARE_MAPPINGS = { '13': { customCode: ErrorCode.AuthWipeCodeMismatch, message: 'Wipe code mismatch', - severity: Severity.Error, + severity: Severity.Err, category: Category.Authentication, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -238,7 +238,7 @@ export const HARDWARE_MAPPINGS = { '14': { customCode: ErrorCode.DeviceInvalidSession, message: 'Invalid session', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.Retry, userActionable: false, @@ -258,7 +258,7 @@ export const HARDWARE_MAPPINGS = { UNKNOWN: { customCode: ErrorCode.Unknown, message: 'Unknown error', - severity: Severity.Error, + severity: Severity.Err, category: Category.Unknown, retryStrategy: RetryStrategy.NoRetry, userActionable: false, @@ -269,7 +269,7 @@ export const HARDWARE_MAPPINGS = { Init_IframeBlocked: { customCode: ErrorCode.ConnBlocked, message: 'Iframe blocked', - severity: Severity.Error, + severity: Severity.Err, category: Category.Connection, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -280,7 +280,7 @@ export const HARDWARE_MAPPINGS = { Init_IframeTimeout: { customCode: ErrorCode.ConnTimeout, message: 'Iframe connection timeout', - severity: Severity.Error, + severity: Severity.Err, category: Category.Connection, retryStrategy: RetryStrategy.Retry, userActionable: false, @@ -291,7 +291,7 @@ export const HARDWARE_MAPPINGS = { Popup_ConnectionMissing: { customCode: ErrorCode.ConnIframeMissing, message: 'Unable to establish connection with iframe', - severity: Severity.Error, + severity: Severity.Err, category: Category.Connection, retryStrategy: RetryStrategy.Retry, userActionable: false, @@ -301,7 +301,7 @@ export const HARDWARE_MAPPINGS = { Desktop_ConnectionMissing: { customCode: ErrorCode.ConnSuiteMissing, message: 'Unable to establish connection with Suite', - severity: Severity.Error, + severity: Severity.Err, category: Category.Connection, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -312,7 +312,7 @@ export const HARDWARE_MAPPINGS = { Transport_Missing: { customCode: ErrorCode.ConnTransportMissing, message: 'Transport is missing', - severity: Severity.Error, + severity: Severity.Err, category: Category.Connection, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -343,7 +343,7 @@ export const HARDWARE_MAPPINGS = { Method_UnknownCoin: { customCode: ErrorCode.Unknown, message: 'Coin not found', - severity: Severity.Error, + severity: Severity.Err, category: Category.Unknown, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -353,7 +353,7 @@ export const HARDWARE_MAPPINGS = { Method_AddressNotMatch: { customCode: ErrorCode.Unknown, message: 'Addresses do not match', - severity: Severity.Error, + severity: Severity.Err, category: Category.Unknown, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -363,7 +363,7 @@ export const HARDWARE_MAPPINGS = { Method_Discovery_BundleException: { customCode: ErrorCode.Unknown, message: 'Discovery bundle exception', - severity: Severity.Error, + severity: Severity.Err, category: Category.Unknown, retryStrategy: RetryStrategy.Retry, userActionable: false, @@ -380,7 +380,7 @@ export const HARDWARE_MAPPINGS = { Device_NotFound: { customCode: ErrorCode.DeviceNotFound, message: 'Device not found', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -391,7 +391,7 @@ export const HARDWARE_MAPPINGS = { Device_InitializeFailed: { customCode: ErrorCode.DeviceNotReady, message: 'Device initialization failed', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -401,7 +401,7 @@ export const HARDWARE_MAPPINGS = { Device_ModeException: { customCode: ErrorCode.DeviceIncompatibleMode, message: 'Device mode exception', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -411,7 +411,7 @@ export const HARDWARE_MAPPINGS = { Device_Disconnected: { customCode: ErrorCode.DeviceDisconnected, message: 'Device disconnected', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -422,7 +422,7 @@ export const HARDWARE_MAPPINGS = { Device_UsedElsewhere: { customCode: ErrorCode.DeviceUsedElsewhere, message: 'Device is used in another window', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -433,7 +433,7 @@ export const HARDWARE_MAPPINGS = { Device_InvalidState: { customCode: ErrorCode.AuthFailed, message: 'Passphrase is incorrect', - severity: Severity.Error, + severity: Severity.Err, category: Category.Authentication, retryStrategy: RetryStrategy.Retry, userActionable: true, @@ -453,7 +453,7 @@ export const HARDWARE_MAPPINGS = { Device_MultipleNotSupported: { customCode: ErrorCode.DeviceMultipleConnected, message: 'Multiple devices are not supported', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -464,7 +464,7 @@ export const HARDWARE_MAPPINGS = { Device_MissingCapability: { customCode: ErrorCode.DeviceMissingCapability, message: 'Device is missing required capability', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -475,7 +475,7 @@ export const HARDWARE_MAPPINGS = { Device_MissingCapabilityBtcOnly: { customCode: ErrorCode.DeviceBtcOnlyFirmware, message: 'Device is BTC-only, operation not supported', - severity: Severity.Error, + severity: Severity.Err, category: Category.DeviceState, retryStrategy: RetryStrategy.NoRetry, userActionable: true, @@ -487,7 +487,7 @@ export const HARDWARE_MAPPINGS = { default: { customCode: ErrorCode.Unknown, message: 'Unknown Trezor error', - severity: Severity.Error, + severity: Severity.Err, category: Category.Unknown, retryStrategy: RetryStrategy.NoRetry, userActionable: false, @@ -499,25 +499,25 @@ export const HARDWARE_MAPPINGS = { pattern: '^Failure_.*', type: 'prefix', description: 'Device failure codes', - defaultSeverity: Severity.Error, + defaultSeverity: Severity.Err, }, { pattern: '^Init_.*', type: 'prefix', description: 'Initialization errors', - defaultSeverity: Severity.Error, + defaultSeverity: Severity.Err, }, { pattern: '^Method_.*', type: 'prefix', description: 'Method invocation errors', - defaultSeverity: Severity.Error, + defaultSeverity: Severity.Err, }, { pattern: '^Device_.*', type: 'prefix', description: 'Device state errors', - defaultSeverity: Severity.Error, + defaultSeverity: Severity.Err, }, ], }, diff --git a/packages/keyring-utils/src/hardware-error.test.ts b/packages/keyring-utils/src/hardware-error.test.ts index 45e6b8a98..26d477d0e 100644 --- a/packages/keyring-utils/src/hardware-error.test.ts +++ b/packages/keyring-utils/src/hardware-error.test.ts @@ -108,7 +108,7 @@ describe('HardwareWalletError', () => { }); it('should return false for non-CRITICAL severity', () => { - const severities = [Severity.Error, Severity.Warning, Severity.Info]; + const severities = [Severity.Err, Severity.Warning, Severity.Info]; severities.forEach((severity) => { const error = new HardwareWalletError('Test error', { ...mockOptions, @@ -129,7 +129,7 @@ describe('HardwareWalletError', () => { }); it('should return false for non-WARNING severity', () => { - const severities = [Severity.Error, Severity.Critical, Severity.Info]; + const severities = [Severity.Err, Severity.Critical, Severity.Info]; severities.forEach((severity) => { const error = new HardwareWalletError('Test error', { ...mockOptions, @@ -306,7 +306,7 @@ describe('HardwareWalletError', () => { }); expect(error.toString()).toBe( - 'HardwareWalletError [UNKNOWN:123456]: Something strange happened', + 'HardwareWalletError [99999:123456]: Something strange happened', ); }); }); @@ -391,7 +391,7 @@ describe('HardwareWalletError', () => { it('should handle retryable connection errors', () => { const error = new HardwareWalletError('Connection timeout', { code: ErrorCode.ConnTimeout, - severity: Severity.Error, + severity: Severity.Err, category: Category.Connection, retryStrategy: RetryStrategy.ExponentialBackoff, userActionable: false, diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts index b9cde1998..824c3619c 100644 --- a/packages/keyring-utils/src/hardware-error.ts +++ b/packages/keyring-utils/src/hardware-error.ts @@ -17,7 +17,7 @@ function generateErrorId(): string { * Gets the human-readable name for an error code using enum reverse mapping. * * @param code - The error code enum value. - * @returns The string name of the error code, or 'UNKNOWN' if not found. + * @returns The string name of the error code, or '99999' if not found. */ function getErrorCodeName(code: ErrorCode): string { // Numeric enums have a reverse mapping at runtime: ErrorCode[1000] => "AuthFailed" From b3f140486d63b272709c91d9af5a4ab4b1dc3d7e Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Tue, 13 Jan 2026 17:16:00 +0800 Subject: [PATCH 15/24] fix: fallback --- packages/keyring-utils/src/hardware-error.test.ts | 2 +- packages/keyring-utils/src/hardware-error.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error.test.ts b/packages/keyring-utils/src/hardware-error.test.ts index 26d477d0e..a7f9fb337 100644 --- a/packages/keyring-utils/src/hardware-error.test.ts +++ b/packages/keyring-utils/src/hardware-error.test.ts @@ -306,7 +306,7 @@ describe('HardwareWalletError', () => { }); expect(error.toString()).toBe( - 'HardwareWalletError [99999:123456]: Something strange happened', + 'HardwareWalletError [Unknown:123456]: Something strange happened', ); }); }); diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts index 824c3619c..1daa918d7 100644 --- a/packages/keyring-utils/src/hardware-error.ts +++ b/packages/keyring-utils/src/hardware-error.ts @@ -17,11 +17,11 @@ function generateErrorId(): string { * Gets the human-readable name for an error code using enum reverse mapping. * * @param code - The error code enum value. - * @returns The string name of the error code, or '99999' if not found. + * @returns The string name of the error code, or 'Unknown' if not found. */ function getErrorCodeName(code: ErrorCode): string { // Numeric enums have a reverse mapping at runtime: ErrorCode[1000] => "AuthFailed" - return ErrorCode[code] ?? ErrorCode.Unknown; + return ErrorCode[code] ?? ErrorCode[ErrorCode.Unknown]; } export type HardwareWalletErrorOptions = { From de46ef74d27965ea6e308c436187f135740aa4c7 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Tue, 13 Jan 2026 18:29:20 +0800 Subject: [PATCH 16/24] Apply suggestions from code review Co-authored-by: Charly Chevalier --- packages/keyring-utils/src/hardware-error-mappings.ts | 4 ++-- packages/keyring-utils/src/hardware-error.ts | 2 +- packages/keyring-utils/src/hardware-errors-enums.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/keyring-utils/src/hardware-error-mappings.ts index 3af83056e..fd9723c68 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.ts @@ -6,7 +6,7 @@ import { RetryStrategy, } from './hardware-errors-enums'; -export const HARDWARE_MAPPINGS = { +export const HARDWARE_ERROR_MAPPINGS = { ledger: { vendorName: 'Ledger', errorMappings: { @@ -110,7 +110,7 @@ export const HARDWARE_MAPPINGS = { retryStrategy: RetryStrategy.Retry, userActionable: true, userMessage: - 'Transaction was rejected. Please approve on your device to continue.', + 'Operation was rejected. Please approve on your device to continue.', }, '0x6a80': { customCode: ErrorCode.DeviceStateBlindSignNotSupported, diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts index 1daa918d7..b2f71c34c 100644 --- a/packages/keyring-utils/src/hardware-error.ts +++ b/packages/keyring-utils/src/hardware-error.ts @@ -171,7 +171,7 @@ export class HardwareWalletError extends Error { */ toString(): string { const codeName = getErrorCodeName(this.code); - return `${this.name} [${codeName}:${this.code}]: ${this.userMessage}`; + return `${this.#getErrorPrefix()}: ${this.message}`; } /** diff --git a/packages/keyring-utils/src/hardware-errors-enums.ts b/packages/keyring-utils/src/hardware-errors-enums.ts index 8d230ea04..95079ad3f 100644 --- a/packages/keyring-utils/src/hardware-errors-enums.ts +++ b/packages/keyring-utils/src/hardware-errors-enums.ts @@ -44,7 +44,7 @@ export enum ErrorCode { ProtoCommandError = 5001, ProtoMessageError = 5002, - // Device Stae + // Device state DeviceStateBlindSignNotSupported = 6001, DeviceStateOnlyV4Supported = 6002, DeviceStateEthAppClosed = 6003, From a555c4b805a1af714f3bf0809db55d0f3461eaa0 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Tue, 13 Jan 2026 20:17:49 +0800 Subject: [PATCH 17/24] fix: remove retryStrategy and userActionable --- .../src/hardware-error-mappings.test.ts | 76 +------------ .../src/hardware-error-mappings.ts | 107 +----------------- .../keyring-utils/src/hardware-error.test.ts | 73 +----------- packages/keyring-utils/src/hardware-error.ts | 34 +----- 4 files changed, 8 insertions(+), 282 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error-mappings.test.ts b/packages/keyring-utils/src/hardware-error-mappings.test.ts index 851b47caf..dcb0a5945 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.test.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.test.ts @@ -1,10 +1,5 @@ import { HARDWARE_MAPPINGS } from './hardware-error-mappings'; -import { - ErrorCode, - Severity, - Category, - RetryStrategy, -} from './hardware-errors-enums'; +import { ErrorCode, Severity, Category } from './hardware-errors-enums'; describe('HARDWARE_MAPPINGS', () => { describe('structure', () => { @@ -34,8 +29,6 @@ describe('HARDWARE_MAPPINGS', () => { expect(mapping.customCode).toBe(ErrorCode.Success); expect(mapping.severity).toBe(Severity.Info); expect(mapping.category).toBe(Category.Success); - expect(mapping.retryStrategy).toBe(RetryStrategy.NoRetry); - expect(mapping.userActionable).toBe(false); }); }); @@ -45,7 +38,6 @@ describe('HARDWARE_MAPPINGS', () => { expect(mapping.customCode).toBe(ErrorCode.AuthFailed); expect(mapping.severity).toBe(Severity.Err); expect(mapping.category).toBe(Category.Authentication); - expect(mapping.userActionable).toBe(true); expect(mapping.userMessage).toBeDefined(); }); @@ -53,14 +45,12 @@ describe('HARDWARE_MAPPINGS', () => { const mapping = errorMappings['0x63c0']; expect(mapping.customCode).toBe(ErrorCode.AuthPinAttemptsRemaining); expect(mapping.severity).toBe(Severity.Warning); - expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); it('should map 0x5515 to device locked', () => { const mapping = errorMappings['0x5515']; expect(mapping.customCode).toBe(ErrorCode.AuthDeviceLocked); expect(mapping.severity).toBe(Severity.Err); - expect(mapping.userActionable).toBe(true); expect(mapping.userMessage).toContain('unlock'); }); @@ -68,7 +58,6 @@ describe('HARDWARE_MAPPINGS', () => { const mapping = errorMappings['0x9840']; expect(mapping.customCode).toBe(ErrorCode.AuthDeviceBlocked); expect(mapping.severity).toBe(Severity.Critical); - expect(mapping.retryStrategy).toBe(RetryStrategy.NoRetry); }); }); @@ -78,15 +67,12 @@ describe('HARDWARE_MAPPINGS', () => { expect(mapping.customCode).toBe(ErrorCode.UserRejected); expect(mapping.severity).toBe(Severity.Warning); expect(mapping.category).toBe(Category.UserAction); - expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); - expect(mapping.userActionable).toBe(true); }); it('should map 0x5501 to user refused', () => { const mapping = errorMappings['0x5501']; expect(mapping.customCode).toBe(ErrorCode.UserRejected); expect(mapping.severity).toBe(Severity.Warning); - expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); }); describe('connection errors', () => { @@ -94,7 +80,6 @@ describe('HARDWARE_MAPPINGS', () => { const mapping = errorMappings['0x650f']; expect(mapping.customCode).toBe(ErrorCode.ConnClosed); expect(mapping.category).toBe(Category.Connection); - expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); }); @@ -104,8 +89,6 @@ describe('HARDWARE_MAPPINGS', () => { expect(mapping).toHaveProperty('message'); expect(mapping).toHaveProperty('severity'); expect(mapping).toHaveProperty('category'); - expect(mapping).toHaveProperty('retryStrategy'); - expect(mapping).toHaveProperty('userActionable'); const numericErrorCodes = Object.values(ErrorCode).filter( (value): value is number => typeof value === 'number', @@ -113,8 +96,6 @@ describe('HARDWARE_MAPPINGS', () => { expect(numericErrorCodes).toContain(mapping.customCode); expect(Object.values(Severity)).toContain(mapping.severity); expect(Object.values(Category)).toContain(mapping.category); - expect(Object.values(RetryStrategy)).toContain(mapping.retryStrategy); - expect(typeof mapping.userActionable).toBe('boolean'); expect(typeof mapping.message).toBe('string'); }); }); @@ -147,7 +128,6 @@ describe('HARDWARE_MAPPINGS', () => { const mapping = errorMappings['1']; expect(mapping.customCode).toBe(ErrorCode.ProtoUnexpectedMessage); expect(mapping.severity).toBe(Severity.Err); - expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.originalName).toBe('Failure_UnexpectedMessage'); }); @@ -155,7 +135,6 @@ describe('HARDWARE_MAPPINGS', () => { const mapping = errorMappings['4']; expect(mapping.customCode).toBe(ErrorCode.UserCancelled); expect(mapping.category).toBe(Category.UserAction); - expect(mapping.userActionable).toBe(true); expect(mapping.originalName).toBe('Failure_ActionCancelled'); }); }); @@ -172,7 +151,6 @@ describe('HARDWARE_MAPPINGS', () => { const mapping = errorMappings['7']; expect(mapping.customCode).toBe(ErrorCode.AuthIncorrectPin); expect(mapping.category).toBe(Category.Authentication); - expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.originalName).toBe('Failure_PinInvalid'); }); }); @@ -189,21 +167,18 @@ describe('HARDWARE_MAPPINGS', () => { const mapping = errorMappings['15']; expect(mapping.customCode).toBe(ErrorCode.DeviceCallInProgress); expect(mapping.severity).toBe(Severity.Warning); - expect(mapping.retryStrategy).toBe(RetryStrategy.ExponentialBackoff); expect(mapping.originalName).toBe('Failure_Busy'); }); it('should map Device_Disconnected to device disconnected', () => { const mapping = errorMappings.Device_Disconnected; expect(mapping.customCode).toBe(ErrorCode.DeviceDisconnected); - expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); expect(mapping.sdkMessage).toBe('Device disconnected'); }); it('should map Device_UsedElsewhere correctly', () => { const mapping = errorMappings.Device_UsedElsewhere; expect(mapping.customCode).toBe(ErrorCode.DeviceUsedElsewhere); - expect(mapping.retryStrategy).toBe(RetryStrategy.NoRetry); expect(mapping.userMessage).toContain('another window'); }); }); @@ -219,7 +194,6 @@ describe('HARDWARE_MAPPINGS', () => { it('should map Init_IframeTimeout', () => { const mapping = errorMappings.Init_IframeTimeout; expect(mapping.customCode).toBe(ErrorCode.ConnTimeout); - expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); it('should map Transport_Missing', () => { @@ -234,7 +208,6 @@ describe('HARDWARE_MAPPINGS', () => { const mapping = errorMappings.Method_Cancel; expect(mapping.customCode).toBe(ErrorCode.UserCancelled); expect(mapping.category).toBe(Category.UserAction); - expect(mapping.retryStrategy).toBe(RetryStrategy.Retry); }); it('should map Method_UnknownCoin', () => { @@ -278,8 +251,6 @@ describe('HARDWARE_MAPPINGS', () => { expect(mapping).toHaveProperty('message'); expect(mapping).toHaveProperty('severity'); expect(mapping).toHaveProperty('category'); - expect(mapping).toHaveProperty('retryStrategy'); - expect(mapping).toHaveProperty('userActionable'); const numericErrorCodes = Object.values(ErrorCode).filter( (value): value is number => typeof value === 'number', @@ -287,8 +258,6 @@ describe('HARDWARE_MAPPINGS', () => { expect(numericErrorCodes).toContain(mapping.customCode); expect(Object.values(Severity)).toContain(mapping.severity); expect(Object.values(Category)).toContain(mapping.category); - expect(Object.values(RetryStrategy)).toContain(mapping.retryStrategy); - expect(typeof mapping.userActionable).toBe('boolean'); expect(typeof mapping.message).toBe('string'); }); }); @@ -381,48 +350,5 @@ describe('HARDWARE_MAPPINGS', () => { ); expect(trezorCustomCodes.length).toBeGreaterThan(0); }); - - it('should have user messages for user-actionable errors', () => { - const ledgerMappings = Object.values( - HARDWARE_MAPPINGS.ledger.errorMappings, - ).filter( - (mapping) => - mapping.userActionable && mapping.severity !== Severity.Info, - ); - - ledgerMappings.forEach((mapping) => { - expect('userMessage' in mapping).toBe(true); - expect(typeof (mapping as any).userMessage).toBe('string'); - expect((mapping as any).userMessage.length).toBeGreaterThan(0); - }); - - const trezorMappings = Object.values( - HARDWARE_MAPPINGS.trezor.errorMappings, - ).filter( - (mapping) => - mapping.userActionable && mapping.severity !== Severity.Info, - ); - - trezorMappings.forEach((mapping) => { - expect('userMessage' in mapping).toBe(true); - expect(typeof (mapping as any).userMessage).toBe('string'); - expect((mapping as any).userMessage.length).toBeGreaterThan(0); - }); - }); - - it('should use NO_RETRY for critical errors', () => { - const allMappings = [ - ...Object.values(HARDWARE_MAPPINGS.ledger.errorMappings), - ...Object.values(HARDWARE_MAPPINGS.trezor.errorMappings), - ]; - - const criticalMappings = allMappings.filter( - (mapping) => mapping.severity === Severity.Critical, - ); - - criticalMappings.forEach((mapping) => { - expect(mapping.retryStrategy).toBe(RetryStrategy.NoRetry); - }); - }); }); }); diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/keyring-utils/src/hardware-error-mappings.ts index fd9723c68..247f39900 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.ts @@ -1,10 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { - ErrorCode, - Severity, - Category, - RetryStrategy, -} from './hardware-errors-enums'; +import { ErrorCode, Severity, Category } from './hardware-errors-enums'; export const HARDWARE_ERROR_MAPPINGS = { ledger: { @@ -15,16 +10,12 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Operation successful', severity: Severity.Info, category: Category.Success, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, }, '0x6300': { customCode: ErrorCode.AuthFailed, message: 'Authentication failed', severity: Severity.Err, category: Category.Authentication, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Authentication failed. Please verify your credentials.', }, '0x63c0': { @@ -32,8 +23,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'PIN attempts remaining', severity: Severity.Warning, category: Category.Authentication, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Incorrect PIN. Please try again.', }, '0x6982': { @@ -41,8 +30,7 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Security conditions not satisfied', severity: Severity.Err, category: Category.Authentication, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, + userMessage: 'Device is locked or access rights are insufficient. Please unlock your device.', }, @@ -51,8 +39,7 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'User rejected action on device', severity: Severity.Warning, category: Category.UserAction, - retryStrategy: RetryStrategy.Retry, - userActionable: true, + userMessage: 'Transaction was rejected. Please approve on your device to continue.', }, @@ -61,8 +48,7 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'App update required', severity: Severity.Err, category: Category.Authentication, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, + userMessage: 'Please update your Ledger app to continue.', }, '0x9808': { @@ -70,16 +56,13 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Contradiction in secret code status', severity: Severity.Err, category: Category.Authentication, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, }, '0x9840': { customCode: ErrorCode.AuthDeviceBlocked, message: 'Code blocked', severity: Severity.Critical, category: Category.Authentication, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, + userMessage: 'Your device is blocked due to too many failed attempts. Please follow device recovery procedures.', }, @@ -88,8 +71,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'App closed or connection issue', severity: Severity.Err, category: Category.Connection, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Connection lost or app closed. Please open the corresponding app on your Ledger device.', }, @@ -98,8 +79,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Device is locked', severity: Severity.Err, category: Category.Authentication, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Please unlock your Ledger device to continue.', }, '0x5501': { @@ -107,8 +86,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'User refused on device', severity: Severity.Warning, category: Category.UserAction, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Operation was rejected. Please approve on your device to continue.', }, @@ -117,8 +94,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Blind signing not supported', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Blind signing is not supported on this device.', }, '0x6d00': { @@ -126,8 +101,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Ledger Only V4 supported', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Only V4 is supported on this device.', }, '0x6e00': { @@ -135,8 +108,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Ethereum app closed', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Ethereum app is closed. Please open it to continue.', }, '0x6501': { @@ -144,8 +115,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Ethereum app out of date', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Ethereum app is out of date. Please update it to continue.', }, @@ -159,8 +128,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Unexpected message received', severity: Severity.Err, category: Category.Protocol, - retryStrategy: RetryStrategy.Retry, - userActionable: false, originalName: 'Failure_UnexpectedMessage', }, '2': { @@ -168,8 +135,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Button confirmation required', severity: Severity.Warning, category: Category.UserAction, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Please confirm the action on your Trezor device.', originalName: 'Failure_ButtonExpected', }, @@ -178,8 +143,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Action cancelled by user', severity: Severity.Warning, category: Category.UserAction, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'You cancelled the operation. Please try again if this was unintentional.', originalName: 'Failure_ActionCancelled', @@ -189,8 +152,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'PIN entry expected', severity: Severity.Warning, category: Category.UserAction, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Please enter your PIN on the Trezor device.', originalName: 'Failure_PinExpected', }, @@ -199,8 +160,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'PIN cancelled by user', severity: Severity.Warning, category: Category.Authentication, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'PIN entry was cancelled. Please try again.', originalName: 'Failure_PinCancelled', }, @@ -209,8 +168,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'PIN invalid', severity: Severity.Err, category: Category.Authentication, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Incorrect PIN entered. Please try again.', originalName: 'Failure_PinInvalid', }, @@ -219,8 +176,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Device not initialized', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Your Trezor device needs to be initialized. Please set it up first.', originalName: 'Failure_NotInitialized', @@ -230,8 +185,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Wipe code mismatch', severity: Severity.Err, category: Category.Authentication, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Wipe codes do not match. Please try again.', originalName: 'Failure_WipeCodeMismatch', }, @@ -240,8 +193,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Invalid session', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.Retry, - userActionable: false, userMessage: 'Session expired. Please reconnect your device.', originalName: 'Failure_InvalidSession', }, @@ -250,8 +201,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Device busy', severity: Severity.Warning, category: Category.DeviceState, - retryStrategy: RetryStrategy.ExponentialBackoff, - userActionable: false, userMessage: 'Device is busy. Please wait and try again.', originalName: 'Failure_Busy', }, @@ -260,8 +209,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Unknown error', severity: Severity.Err, category: Category.Unknown, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, userMessage: 'An unknown error occurred. Please try again or contact support.', originalName: 'Failure_UnknownCode', @@ -271,8 +218,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Iframe blocked', severity: Severity.Err, category: Category.Connection, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Connection blocked. Please check your browser settings and allow iframes.', sdkMessage: 'Iframe blocked', @@ -282,8 +227,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Iframe connection timeout', severity: Severity.Err, category: Category.Connection, - retryStrategy: RetryStrategy.Retry, - userActionable: false, userMessage: 'Connection timed out. Please check your internet connection and try again.', sdkMessage: 'Iframe timeout', @@ -293,8 +236,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Unable to establish connection with iframe', severity: Severity.Err, category: Category.Connection, - retryStrategy: RetryStrategy.Retry, - userActionable: false, userMessage: 'Connection failed. Please try again.', sdkMessage: 'Unable to establish connection with iframe', }, @@ -303,8 +244,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Unable to establish connection with Suite', severity: Severity.Err, category: Category.Connection, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Cannot connect to Trezor Suite. Please ensure Trezor Suite is running.', sdkMessage: 'Unable to establish connection with Suite', @@ -314,8 +253,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Transport is missing', severity: Severity.Err, category: Category.Connection, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Transport layer not available. Please reconnect your device.', sdkMessage: 'Transport is missing', @@ -325,8 +262,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Method cancelled by user', severity: Severity.Warning, category: Category.UserAction, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Operation was cancelled.', sdkMessage: 'Cancelled', }, @@ -335,8 +270,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Popup closed by user', severity: Severity.Warning, category: Category.UserAction, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Operation interrupted. The popup was closed.', sdkMessage: 'Popup closed', }, @@ -345,8 +278,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Coin not found', severity: Severity.Err, category: Category.Unknown, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'The requested cryptocurrency is not supported.', sdkMessage: 'Coin not found', }, @@ -355,8 +286,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Addresses do not match', severity: Severity.Err, category: Category.Unknown, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Address verification failed. The addresses do not match.', sdkMessage: 'Addresses do not match', }, @@ -365,16 +294,12 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Discovery bundle exception', severity: Severity.Err, category: Category.Unknown, - retryStrategy: RetryStrategy.Retry, - userActionable: false, }, Method_Override: { customCode: ErrorCode.Unknown, message: 'Method override', severity: Severity.Warning, category: Category.Unknown, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, sdkMessage: 'override', }, Device_NotFound: { @@ -382,8 +307,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Device not found', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Trezor device not detected. Please connect your device and try again.', sdkMessage: 'Device not found', @@ -393,8 +316,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Device initialization failed', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Failed to initialize device. Please reconnect and try again.', }, @@ -403,8 +324,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Device mode exception', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Device is in an incompatible mode. Please check your device settings.', }, @@ -413,8 +332,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Device disconnected', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Device was disconnected. Please reconnect your Trezor device.', sdkMessage: 'Device disconnected', @@ -424,8 +341,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Device is used in another window', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Your Trezor is being used in another window or application. Please close other connections.', sdkMessage: 'Device is used in another window', @@ -435,8 +350,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Passphrase is incorrect', severity: Severity.Err, category: Category.Authentication, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Incorrect passphrase. Please try again.', sdkMessage: 'Passphrase is incorrect', }, @@ -445,8 +358,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Device call in progress', severity: Severity.Warning, category: Category.DeviceState, - retryStrategy: RetryStrategy.ExponentialBackoff, - userActionable: false, userMessage: 'Another operation is in progress. Please wait.', sdkMessage: 'Device call in progress', }, @@ -455,8 +366,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Multiple devices are not supported', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Multiple devices detected. Please connect only one Trezor device.', sdkMessage: 'Multiple devices are not supported', @@ -466,8 +375,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Device is missing required capability', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Your device does not support this feature. A firmware update may be required.', sdkMessage: 'Device is missing capability', @@ -477,8 +384,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Device is BTC-only, operation not supported', severity: Severity.Err, category: Category.DeviceState, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'This operation is not supported on Bitcoin-only firmware.', sdkMessage: 'Device is missing capability (BTC only)', @@ -489,8 +394,6 @@ export const HARDWARE_ERROR_MAPPINGS = { message: 'Unknown Trezor error', severity: Severity.Err, category: Category.Unknown, - retryStrategy: RetryStrategy.NoRetry, - userActionable: false, userMessage: 'An unexpected error occurred. Please try again or contact support.', }, diff --git a/packages/keyring-utils/src/hardware-error.test.ts b/packages/keyring-utils/src/hardware-error.test.ts index a7f9fb337..be9114d0c 100644 --- a/packages/keyring-utils/src/hardware-error.test.ts +++ b/packages/keyring-utils/src/hardware-error.test.ts @@ -1,18 +1,11 @@ import { HardwareWalletError } from './hardware-error'; -import { - ErrorCode, - Severity, - Category, - RetryStrategy, -} from './hardware-errors-enums'; +import { ErrorCode, Severity, Category } from './hardware-errors-enums'; describe('HardwareWalletError', () => { const mockOptions = { code: ErrorCode.UserRejected, severity: Severity.Warning, category: Category.UserAction, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Transaction was rejected', }; @@ -25,8 +18,6 @@ describe('HardwareWalletError', () => { expect(error.code).toBe(ErrorCode.UserRejected); expect(error.severity).toBe(Severity.Warning); expect(error.category).toBe(Category.UserAction); - expect(error.retryStrategy).toBe(RetryStrategy.Retry); - expect(error.userActionable).toBe(true); expect(error.userMessage).toBe('Transaction was rejected'); }); @@ -72,32 +63,6 @@ describe('HardwareWalletError', () => { }); }); - describe('isRetryable', () => { - it('should return true for RETRY strategy', () => { - const error = new HardwareWalletError('Test error', { - ...mockOptions, - retryStrategy: RetryStrategy.Retry, - }); - expect(error.isRetryable()).toBe(true); - }); - - it('should return true for EXPONENTIAL_BACKOFF strategy', () => { - const error = new HardwareWalletError('Test error', { - ...mockOptions, - retryStrategy: RetryStrategy.ExponentialBackoff, - }); - expect(error.isRetryable()).toBe(true); - }); - - it('should return false for NO_RETRY strategy', () => { - const error = new HardwareWalletError('Test error', { - ...mockOptions, - retryStrategy: RetryStrategy.NoRetry, - }); - expect(error.isRetryable()).toBe(false); - }); - }); - describe('isCritical', () => { it('should return true for CRITICAL severity', () => { const error = new HardwareWalletError('Test error', { @@ -140,24 +105,6 @@ describe('HardwareWalletError', () => { }); }); - describe('requiresUserAction', () => { - it('should return true when userActionable is true', () => { - const error = new HardwareWalletError('Test error', { - ...mockOptions, - userActionable: true, - }); - expect(error.requiresUserAction()).toBe(true); - }); - - it('should return false when userActionable is false', () => { - const error = new HardwareWalletError('Test error', { - ...mockOptions, - userActionable: false, - }); - expect(error.requiresUserAction()).toBe(false); - }); - }); - describe('withMetadata', () => { it('should create a new error with additional metadata', () => { const originalMetadata = { deviceId: '12345' }; @@ -210,8 +157,6 @@ describe('HardwareWalletError', () => { expect(newError.code).toBe(originalError.code); expect(newError.severity).toBe(originalError.severity); expect(newError.category).toBe(originalError.category); - expect(newError.retryStrategy).toBe(originalError.retryStrategy); - expect(newError.userActionable).toBe(originalError.userActionable); expect(newError.userMessage).toBe(originalError.userMessage); expect(newError.cause).toBe(originalError.cause); }); @@ -236,8 +181,6 @@ describe('HardwareWalletError', () => { expect(json.code).toBe(ErrorCode.UserRejected); expect(json.severity).toBe(Severity.Warning); expect(json.category).toBe(Category.UserAction); - expect(json.retryStrategy).toBe(RetryStrategy.Retry); - expect(json.userActionable).toBe(true); expect(json.userMessage).toBe('Transaction was rejected'); expect(json.timestamp).toBe(error.timestamp.toISOString()); expect(json.metadata).toStrictEqual(metadata); @@ -324,8 +267,6 @@ describe('HardwareWalletError', () => { expect(result).toContain('User Message: Transaction was rejected'); expect(result).toContain('Severity: Warning'); expect(result).toContain('Category: UserAction'); - expect(result).toContain('Retry Strategy: Retry'); - expect(result).toContain('User Actionable: true'); expect(result).toContain('Timestamp:'); }); @@ -378,14 +319,10 @@ describe('HardwareWalletError', () => { code: ErrorCode.AuthDeviceBlocked, severity: Severity.Critical, category: Category.Authentication, - retryStrategy: RetryStrategy.NoRetry, - userActionable: true, userMessage: 'Device is blocked due to too many failed attempts', }); expect(error.isCritical()).toBe(true); - expect(error.isRetryable()).toBe(false); - expect(error.requiresUserAction()).toBe(true); }); it('should handle retryable connection errors', () => { @@ -393,14 +330,10 @@ describe('HardwareWalletError', () => { code: ErrorCode.ConnTimeout, severity: Severity.Err, category: Category.Connection, - retryStrategy: RetryStrategy.ExponentialBackoff, - userActionable: false, userMessage: 'Connection timed out', }); expect(error.isCritical()).toBe(false); - expect(error.isRetryable()).toBe(true); - expect(error.requiresUserAction()).toBe(false); }); it('should handle user action warnings', () => { @@ -408,15 +341,11 @@ describe('HardwareWalletError', () => { code: ErrorCode.UserConfirmationRequired, severity: Severity.Warning, category: Category.UserAction, - retryStrategy: RetryStrategy.Retry, - userActionable: true, userMessage: 'Please confirm the action on your device', }); expect(error.isWarning()).toBe(true); expect(error.isCritical()).toBe(false); - expect(error.isRetryable()).toBe(true); - expect(error.requiresUserAction()).toBe(true); }); }); }); diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts index b2f71c34c..7caa61234 100644 --- a/packages/keyring-utils/src/hardware-error.ts +++ b/packages/keyring-utils/src/hardware-error.ts @@ -1,5 +1,5 @@ import type { Category } from './hardware-errors-enums'; -import { ErrorCode, Severity, RetryStrategy } from './hardware-errors-enums'; +import { ErrorCode, Severity } from './hardware-errors-enums'; /** * Generates a unique error ID using timestamp and random values. @@ -28,8 +28,6 @@ export type HardwareWalletErrorOptions = { code: ErrorCode; severity: Severity; category: Category; - retryStrategy: RetryStrategy; - userActionable: boolean; userMessage: string; cause?: Error; metadata?: Record; @@ -44,10 +42,6 @@ export class HardwareWalletError extends Error { public readonly category: Category; - public readonly retryStrategy: RetryStrategy; - - public readonly userActionable: boolean; - public readonly userMessage: string; public readonly timestamp: Date; @@ -63,23 +57,12 @@ export class HardwareWalletError extends Error { this.code = options.code; this.severity = options.severity; this.category = options.category; - this.retryStrategy = options.retryStrategy; - this.userActionable = options.userActionable; this.userMessage = options.userMessage; this.timestamp = new Date(); this.metadata = options.metadata; this.cause = options.cause; } - /** - * Checks if this error can be retried based on its retry strategy. - * - * @returns True if the error can be retried, false otherwise. - */ - isRetryable(): boolean { - return this.retryStrategy !== RetryStrategy.NoRetry; - } - /** * Checks if this error is critical. * @@ -98,15 +81,6 @@ export class HardwareWalletError extends Error { return this.severity === Severity.Warning; } - /** - * Checks if this error requires user action. - * - * @returns True if the error requires user action, false otherwise. - */ - requiresUserAction(): boolean { - return this.userActionable; - } - /** * Creates a new error instance with additional metadata. * @@ -120,8 +94,6 @@ export class HardwareWalletError extends Error { code: this.code, severity: this.severity, category: this.category, - retryStrategy: this.retryStrategy, - userActionable: this.userActionable, userMessage: this.userMessage, metadata: { ...(this.metadata ?? {}), ...additionalMetadata }, }; @@ -147,8 +119,6 @@ export class HardwareWalletError extends Error { code: this.code, severity: this.severity, category: this.category, - retryStrategy: this.retryStrategy, - userActionable: this.userActionable, userMessage: this.userMessage, timestamp: this.timestamp.toISOString(), metadata: this.metadata, @@ -186,8 +156,6 @@ export class HardwareWalletError extends Error { `User Message: ${this.userMessage}`, `Severity: ${this.severity}`, `Category: ${this.category}`, - `Retry Strategy: ${this.retryStrategy}`, - `User Actionable: ${this.userActionable}`, `Timestamp: ${this.timestamp.toISOString()}`, ]; From 4c01d43754602eccbae1b4a8ca1d1e9d50df5c7d Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Tue, 13 Jan 2026 20:33:40 +0800 Subject: [PATCH 18/24] fix: remove trezor --- .../src/hardware-error-mappings.test.ts | 245 +------------- .../src/hardware-error-mappings.ts | 304 ------------------ packages/keyring-utils/src/hardware-error.ts | 2 +- .../src/hardware-errors-enums.ts | 7 - 4 files changed, 10 insertions(+), 548 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error-mappings.test.ts b/packages/keyring-utils/src/hardware-error-mappings.test.ts index dcb0a5945..b2446d1d5 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.test.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.test.ts @@ -1,21 +1,19 @@ -import { HARDWARE_MAPPINGS } from './hardware-error-mappings'; +import { HARDWARE_ERROR_MAPPINGS } from './hardware-error-mappings'; import { ErrorCode, Severity, Category } from './hardware-errors-enums'; -describe('HARDWARE_MAPPINGS', () => { +describe('HARDWARE_ERROR_MAPPINGS', () => { describe('structure', () => { - it('should have ledger and trezor vendors', () => { - expect(HARDWARE_MAPPINGS).toHaveProperty('ledger'); - expect(HARDWARE_MAPPINGS).toHaveProperty('trezor'); + it('should have ledger vendor', () => { + expect(HARDWARE_ERROR_MAPPINGS).toHaveProperty('ledger'); }); it('should have vendor names', () => { - expect(HARDWARE_MAPPINGS.ledger.vendorName).toBe('Ledger'); - expect(HARDWARE_MAPPINGS.trezor.vendorName).toBe('Trezor'); + expect(HARDWARE_ERROR_MAPPINGS.ledger.vendorName).toBe('Ledger'); }); }); describe('Ledger mappings', () => { - const { errorMappings } = HARDWARE_MAPPINGS.ledger; + const { errorMappings } = HARDWARE_ERROR_MAPPINGS.ledger; it('should have errorMappings object', () => { expect(errorMappings).toBeDefined(); @@ -115,240 +113,15 @@ describe('HARDWARE_MAPPINGS', () => { }); }); - describe('Trezor mappings', () => { - const { errorMappings } = HARDWARE_MAPPINGS.trezor; - - it('should have errorMapping object', () => { - expect(errorMappings).toBeDefined(); - expect(typeof errorMappings).toBe('object'); - }); - - describe('failure codes', () => { - it('should map code 1 to unexpected message', () => { - const mapping = errorMappings['1']; - expect(mapping.customCode).toBe(ErrorCode.ProtoUnexpectedMessage); - expect(mapping.severity).toBe(Severity.Err); - expect(mapping.originalName).toBe('Failure_UnexpectedMessage'); - }); - - it('should map code 4 to action cancelled', () => { - const mapping = errorMappings['4']; - expect(mapping.customCode).toBe(ErrorCode.UserCancelled); - expect(mapping.category).toBe(Category.UserAction); - expect(mapping.originalName).toBe('Failure_ActionCancelled'); - }); - }); - - describe('PIN errors', () => { - it('should map code 5 to PIN expected', () => { - const mapping = errorMappings['5']; - expect(mapping.customCode).toBe(ErrorCode.UserInputRequired); - expect(mapping.category).toBe(Category.UserAction); - expect(mapping.originalName).toBe('Failure_PinExpected'); - }); - - it('should map code 7 to PIN invalid', () => { - const mapping = errorMappings['7']; - expect(mapping.customCode).toBe(ErrorCode.AuthIncorrectPin); - expect(mapping.category).toBe(Category.Authentication); - expect(mapping.originalName).toBe('Failure_PinInvalid'); - }); - }); - - describe('device state errors', () => { - it('should map code 11 to device not initialized', () => { - const mapping = errorMappings['11']; - expect(mapping.customCode).toBe(ErrorCode.DeviceNotReady); - expect(mapping.category).toBe(Category.DeviceState); - expect(mapping.originalName).toBe('Failure_NotInitialized'); - }); - - it('should map code 15 to device busy', () => { - const mapping = errorMappings['15']; - expect(mapping.customCode).toBe(ErrorCode.DeviceCallInProgress); - expect(mapping.severity).toBe(Severity.Warning); - expect(mapping.originalName).toBe('Failure_Busy'); - }); - - it('should map Device_Disconnected to device disconnected', () => { - const mapping = errorMappings.Device_Disconnected; - expect(mapping.customCode).toBe(ErrorCode.DeviceDisconnected); - expect(mapping.sdkMessage).toBe('Device disconnected'); - }); - - it('should map Device_UsedElsewhere correctly', () => { - const mapping = errorMappings.Device_UsedElsewhere; - expect(mapping.customCode).toBe(ErrorCode.DeviceUsedElsewhere); - expect(mapping.userMessage).toContain('another window'); - }); - }); - - describe('connection errors', () => { - it('should map Init_IframeBlocked', () => { - const mapping = errorMappings.Init_IframeBlocked; - expect(mapping.customCode).toBe(ErrorCode.ConnBlocked); - expect(mapping.category).toBe(Category.Connection); - expect(mapping.userMessage).toContain('browser settings'); - }); - - it('should map Init_IframeTimeout', () => { - const mapping = errorMappings.Init_IframeTimeout; - expect(mapping.customCode).toBe(ErrorCode.ConnTimeout); - }); - - it('should map Transport_Missing', () => { - const mapping = errorMappings.Transport_Missing; - expect(mapping.customCode).toBe(ErrorCode.ConnTransportMissing); - expect(mapping.category).toBe(Category.Connection); - }); - }); - - describe('method errors', () => { - it('should map Method_Cancel', () => { - const mapping = errorMappings.Method_Cancel; - expect(mapping.customCode).toBe(ErrorCode.UserCancelled); - expect(mapping.category).toBe(Category.UserAction); - }); - - it('should map Method_UnknownCoin', () => { - const mapping = errorMappings.Method_UnknownCoin; - expect(mapping.customCode).toBe(ErrorCode.Unknown); - expect(mapping.userMessage).toContain('not supported'); - }); - }); - - describe('device capability errors', () => { - it('should map Device_MultipleNotSupported', () => { - const mapping = errorMappings.Device_MultipleNotSupported; - expect(mapping.customCode).toBe(ErrorCode.DeviceMultipleConnected); - expect(mapping.userMessage).toContain('only one'); - }); - - it('should map Device_MissingCapability', () => { - const mapping = errorMappings.Device_MissingCapability; - expect(mapping.customCode).toBe(ErrorCode.DeviceMissingCapability); - expect(mapping.userMessage).toContain('firmware update'); - }); - - it('should map Device_MissingCapabilityBtcOnly', () => { - const mapping = errorMappings.Device_MissingCapabilityBtcOnly; - expect(mapping.customCode).toBe(ErrorCode.DeviceBtcOnlyFirmware); - expect(mapping.userMessage).toContain('Bitcoin-only'); - }); - }); - describe('special codes', () => { - it('should have UNKNOWN fallback', () => { - const mapping = errorMappings.UNKNOWN; - expect(mapping.customCode).toBe(ErrorCode.Unknown); - expect(mapping.category).toBe(Category.Unknown); - expect(mapping.originalName).toBe('Failure_UnknownCode'); - }); - }); - - it('should have valid structure for all mappings', () => { - Object.entries(errorMappings).forEach(([_code, 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('should have valid optional fields 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, - ); - mappingsWithUserMessage.forEach((mapping) => { - expect(typeof mapping.userMessage).toBe('string'); - expect(mapping.userMessage.length).toBeGreaterThan(0); - }); - - const mappingsWithOriginalName = Object.values(errorMappings).filter( - (mapping): mapping is typeof mapping & { originalName: string } => - 'originalName' in mapping && - typeof mapping.originalName === 'string' && - mapping.originalName.length > 0, - ); - mappingsWithOriginalName.forEach((mapping) => { - expect(typeof mapping.originalName).toBe('string'); - expect(mapping.originalName.length).toBeGreaterThan(0); - }); - - const mappingsWithSdkMessage = Object.values(errorMappings).filter( - (mapping): mapping is typeof mapping & { sdkMessage: string } => - 'sdkMessage' in mapping && - typeof mapping.sdkMessage === 'string' && - mapping.sdkMessage.length > 0, - ); - mappingsWithSdkMessage.forEach((mapping) => { - expect(typeof mapping.sdkMessage).toBe('string'); - expect(mapping.sdkMessage.length).toBeGreaterThan(0); - }); - }); - }); - - describe('Trezor default and patterns', () => { - it('should have default error mapping', () => { - const { default: defaultMapping } = HARDWARE_MAPPINGS.trezor; - expect(defaultMapping).toBeDefined(); - expect(defaultMapping.customCode).toBe(ErrorCode.Unknown); - expect(defaultMapping.category).toBe(Category.Unknown); - }); - - it('should have error_patterns array', () => { - const { error_patterns: errorPatterns } = HARDWARE_MAPPINGS.trezor; - expect(Array.isArray(errorPatterns)).toBe(true); - expect(errorPatterns.length).toBeGreaterThan(0); - }); - - it('should have valid pattern structure', () => { - const { error_patterns: errorPatterns } = HARDWARE_MAPPINGS.trezor; - errorPatterns.forEach((pattern) => { - expect(pattern).toHaveProperty('pattern'); - expect(pattern).toHaveProperty('type'); - expect(pattern).toHaveProperty('description'); - expect(pattern).toHaveProperty('defaultSeverity'); - expect(typeof pattern.pattern).toBe('string'); - expect(typeof pattern.type).toBe('string'); - expect(typeof pattern.description).toBe('string'); - expect(Object.values(Severity)).toContain(pattern.defaultSeverity); - }); - }); - - it('should have patterns for common error prefixes', () => { - const { error_patterns: errorPatterns } = HARDWARE_MAPPINGS.trezor; - const patterns = errorPatterns.map((patternObj) => patternObj.pattern); - expect(patterns).toContain('^Failure_.*'); - expect(patterns).toContain('^Init_.*'); - expect(patterns).toContain('^Method_.*'); - expect(patterns).toContain('^Device_.*'); - }); - }); - describe('consistency checks', () => { it('should have unique error codes within each vendor', () => { - const ledgerCodes = Object.values(HARDWARE_MAPPINGS.ledger.errorMappings); + const ledgerCodes = Object.values( + HARDWARE_ERROR_MAPPINGS.ledger.errorMappings, + ); const ledgerCustomCodes = ledgerCodes.map( (mapping) => mapping.customCode, ); expect(ledgerCustomCodes.length).toBeGreaterThan(0); - - const trezorCodes = Object.values(HARDWARE_MAPPINGS.trezor.errorMappings); - const trezorCustomCodes = trezorCodes.map( - (mapping) => mapping.customCode, - ); - expect(trezorCustomCodes.length).toBeGreaterThan(0); }); }); }); diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/keyring-utils/src/hardware-error-mappings.ts index 247f39900..a4fd87536 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.ts @@ -120,308 +120,4 @@ export const HARDWARE_ERROR_MAPPINGS = { }, }, }, - trezor: { - vendorName: 'Trezor', - errorMappings: { - '1': { - customCode: ErrorCode.ProtoUnexpectedMessage, - message: 'Unexpected message received', - severity: Severity.Err, - category: Category.Protocol, - originalName: 'Failure_UnexpectedMessage', - }, - '2': { - customCode: ErrorCode.UserConfirmationRequired, - message: 'Button confirmation required', - severity: Severity.Warning, - category: Category.UserAction, - userMessage: 'Please confirm the action on your Trezor device.', - originalName: 'Failure_ButtonExpected', - }, - '4': { - customCode: ErrorCode.UserCancelled, - message: 'Action cancelled by user', - severity: Severity.Warning, - category: Category.UserAction, - userMessage: - 'You cancelled the operation. Please try again if this was unintentional.', - originalName: 'Failure_ActionCancelled', - }, - '5': { - customCode: ErrorCode.UserInputRequired, - message: 'PIN entry expected', - severity: Severity.Warning, - category: Category.UserAction, - userMessage: 'Please enter your PIN on the Trezor device.', - originalName: 'Failure_PinExpected', - }, - '6': { - customCode: ErrorCode.AuthPinCancelled, - message: 'PIN cancelled by user', - severity: Severity.Warning, - category: Category.Authentication, - userMessage: 'PIN entry was cancelled. Please try again.', - originalName: 'Failure_PinCancelled', - }, - '7': { - customCode: ErrorCode.AuthIncorrectPin, - message: 'PIN invalid', - severity: Severity.Err, - category: Category.Authentication, - userMessage: 'Incorrect PIN entered. Please try again.', - originalName: 'Failure_PinInvalid', - }, - '11': { - customCode: ErrorCode.DeviceNotReady, - message: 'Device not initialized', - severity: Severity.Err, - category: Category.DeviceState, - userMessage: - 'Your Trezor device needs to be initialized. Please set it up first.', - originalName: 'Failure_NotInitialized', - }, - '13': { - customCode: ErrorCode.AuthWipeCodeMismatch, - message: 'Wipe code mismatch', - severity: Severity.Err, - category: Category.Authentication, - userMessage: 'Wipe codes do not match. Please try again.', - originalName: 'Failure_WipeCodeMismatch', - }, - '14': { - customCode: ErrorCode.DeviceInvalidSession, - message: 'Invalid session', - severity: Severity.Err, - category: Category.DeviceState, - userMessage: 'Session expired. Please reconnect your device.', - originalName: 'Failure_InvalidSession', - }, - '15': { - customCode: ErrorCode.DeviceCallInProgress, - message: 'Device busy', - severity: Severity.Warning, - category: Category.DeviceState, - userMessage: 'Device is busy. Please wait and try again.', - originalName: 'Failure_Busy', - }, - UNKNOWN: { - customCode: ErrorCode.Unknown, - message: 'Unknown error', - severity: Severity.Err, - category: Category.Unknown, - userMessage: - 'An unknown error occurred. Please try again or contact support.', - originalName: 'Failure_UnknownCode', - }, - Init_IframeBlocked: { - customCode: ErrorCode.ConnBlocked, - message: 'Iframe blocked', - severity: Severity.Err, - category: Category.Connection, - userMessage: - 'Connection blocked. Please check your browser settings and allow iframes.', - sdkMessage: 'Iframe blocked', - }, - Init_IframeTimeout: { - customCode: ErrorCode.ConnTimeout, - message: 'Iframe connection timeout', - severity: Severity.Err, - category: Category.Connection, - userMessage: - 'Connection timed out. Please check your internet connection and try again.', - sdkMessage: 'Iframe timeout', - }, - Popup_ConnectionMissing: { - customCode: ErrorCode.ConnIframeMissing, - message: 'Unable to establish connection with iframe', - severity: Severity.Err, - category: Category.Connection, - userMessage: 'Connection failed. Please try again.', - sdkMessage: 'Unable to establish connection with iframe', - }, - Desktop_ConnectionMissing: { - customCode: ErrorCode.ConnSuiteMissing, - message: 'Unable to establish connection with Suite', - severity: Severity.Err, - category: Category.Connection, - userMessage: - 'Cannot connect to Trezor Suite. Please ensure Trezor Suite is running.', - sdkMessage: 'Unable to establish connection with Suite', - }, - Transport_Missing: { - customCode: ErrorCode.ConnTransportMissing, - message: 'Transport is missing', - severity: Severity.Err, - category: Category.Connection, - userMessage: - 'Transport layer not available. Please reconnect your device.', - sdkMessage: 'Transport is missing', - }, - Method_Cancel: { - customCode: ErrorCode.UserCancelled, - message: 'Method cancelled by user', - severity: Severity.Warning, - category: Category.UserAction, - userMessage: 'Operation was cancelled.', - sdkMessage: 'Cancelled', - }, - Method_Interrupted: { - customCode: ErrorCode.UserCancelled, - message: 'Popup closed by user', - severity: Severity.Warning, - category: Category.UserAction, - userMessage: 'Operation interrupted. The popup was closed.', - sdkMessage: 'Popup closed', - }, - Method_UnknownCoin: { - customCode: ErrorCode.Unknown, - message: 'Coin not found', - severity: Severity.Err, - category: Category.Unknown, - userMessage: 'The requested cryptocurrency is not supported.', - sdkMessage: 'Coin not found', - }, - Method_AddressNotMatch: { - customCode: ErrorCode.Unknown, - message: 'Addresses do not match', - severity: Severity.Err, - category: Category.Unknown, - userMessage: 'Address verification failed. The addresses do not match.', - sdkMessage: 'Addresses do not match', - }, - Method_Discovery_BundleException: { - customCode: ErrorCode.Unknown, - message: 'Discovery bundle exception', - severity: Severity.Err, - category: Category.Unknown, - }, - Method_Override: { - customCode: ErrorCode.Unknown, - message: 'Method override', - severity: Severity.Warning, - category: Category.Unknown, - sdkMessage: 'override', - }, - Device_NotFound: { - customCode: ErrorCode.DeviceNotFound, - message: 'Device not found', - severity: Severity.Err, - category: Category.DeviceState, - userMessage: - 'Trezor device not detected. Please connect your device and try again.', - sdkMessage: 'Device not found', - }, - Device_InitializeFailed: { - customCode: ErrorCode.DeviceNotReady, - message: 'Device initialization failed', - severity: Severity.Err, - category: Category.DeviceState, - userMessage: - 'Failed to initialize device. Please reconnect and try again.', - }, - Device_ModeException: { - customCode: ErrorCode.DeviceIncompatibleMode, - message: 'Device mode exception', - severity: Severity.Err, - category: Category.DeviceState, - userMessage: - 'Device is in an incompatible mode. Please check your device settings.', - }, - Device_Disconnected: { - customCode: ErrorCode.DeviceDisconnected, - message: 'Device disconnected', - severity: Severity.Err, - category: Category.DeviceState, - userMessage: - 'Device was disconnected. Please reconnect your Trezor device.', - sdkMessage: 'Device disconnected', - }, - Device_UsedElsewhere: { - customCode: ErrorCode.DeviceUsedElsewhere, - message: 'Device is used in another window', - severity: Severity.Err, - category: Category.DeviceState, - userMessage: - 'Your Trezor is being used in another window or application. Please close other connections.', - sdkMessage: 'Device is used in another window', - }, - Device_InvalidState: { - customCode: ErrorCode.AuthFailed, - message: 'Passphrase is incorrect', - severity: Severity.Err, - category: Category.Authentication, - userMessage: 'Incorrect passphrase. Please try again.', - sdkMessage: 'Passphrase is incorrect', - }, - Device_CallInProgress: { - customCode: ErrorCode.DeviceCallInProgress, - message: 'Device call in progress', - severity: Severity.Warning, - category: Category.DeviceState, - userMessage: 'Another operation is in progress. Please wait.', - sdkMessage: 'Device call in progress', - }, - Device_MultipleNotSupported: { - customCode: ErrorCode.DeviceMultipleConnected, - message: 'Multiple devices are not supported', - severity: Severity.Err, - category: Category.DeviceState, - userMessage: - 'Multiple devices detected. Please connect only one Trezor device.', - sdkMessage: 'Multiple devices are not supported', - }, - Device_MissingCapability: { - customCode: ErrorCode.DeviceMissingCapability, - message: 'Device is missing required capability', - severity: Severity.Err, - category: Category.DeviceState, - userMessage: - 'Your device does not support this feature. A firmware update may be required.', - sdkMessage: 'Device is missing capability', - }, - Device_MissingCapabilityBtcOnly: { - customCode: ErrorCode.DeviceBtcOnlyFirmware, - message: 'Device is BTC-only, operation not supported', - severity: Severity.Err, - category: Category.DeviceState, - userMessage: - 'This operation is not supported on Bitcoin-only firmware.', - sdkMessage: 'Device is missing capability (BTC only)', - }, - }, - default: { - customCode: ErrorCode.Unknown, - message: 'Unknown Trezor error', - severity: Severity.Err, - category: Category.Unknown, - userMessage: - 'An unexpected error occurred. Please try again or contact support.', - }, - error_patterns: [ - { - pattern: '^Failure_.*', - type: 'prefix', - description: 'Device failure codes', - defaultSeverity: Severity.Err, - }, - { - pattern: '^Init_.*', - type: 'prefix', - description: 'Initialization errors', - defaultSeverity: Severity.Err, - }, - { - pattern: '^Method_.*', - type: 'prefix', - description: 'Method invocation errors', - defaultSeverity: Severity.Err, - }, - { - pattern: '^Device_.*', - type: 'prefix', - description: 'Device state errors', - defaultSeverity: Severity.Err, - }, - ], - }, }; diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts index 7caa61234..8ad66e530 100644 --- a/packages/keyring-utils/src/hardware-error.ts +++ b/packages/keyring-utils/src/hardware-error.ts @@ -141,7 +141,7 @@ export class HardwareWalletError extends Error { */ toString(): string { const codeName = getErrorCodeName(this.code); - return `${this.#getErrorPrefix()}: ${this.message}`; + return `${this.#getErrorPrefix()}: ${this.userMessage}`; } /** diff --git a/packages/keyring-utils/src/hardware-errors-enums.ts b/packages/keyring-utils/src/hardware-errors-enums.ts index 95079ad3f..4529fb06a 100644 --- a/packages/keyring-utils/src/hardware-errors-enums.ts +++ b/packages/keyring-utils/src/hardware-errors-enums.ts @@ -75,10 +75,3 @@ export enum Category { DeviceState = 'DeviceState', Unknown = 'Unknown', } - -// Retry Strategy Enum -export enum RetryStrategy { - NoRetry = 'NoRetry', - Retry = 'Retry', - ExponentialBackoff = 'ExponentialBackoff', -} From f5b22307f8a4e19b01d8265b56c269d614cfe218 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Tue, 13 Jan 2026 21:04:07 +0800 Subject: [PATCH 19/24] fix: lint --- packages/keyring-utils/src/hardware-error.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts index 8ad66e530..7d85c18cf 100644 --- a/packages/keyring-utils/src/hardware-error.ts +++ b/packages/keyring-utils/src/hardware-error.ts @@ -140,7 +140,6 @@ export class HardwareWalletError extends Error { * @returns A user-friendly string representation of the error. */ toString(): string { - const codeName = getErrorCodeName(this.code); return `${this.#getErrorPrefix()}: ${this.userMessage}`; } From 85fe69814ef1d0927e1aa7d05050fabd9b26f770 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Tue, 13 Jan 2026 21:10:22 +0800 Subject: [PATCH 20/24] fix: remove unused --- packages/keyring-utils/src/hardware-errors-enums.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/keyring-utils/src/hardware-errors-enums.ts b/packages/keyring-utils/src/hardware-errors-enums.ts index 4529fb06a..c681379c4 100644 --- a/packages/keyring-utils/src/hardware-errors-enums.ts +++ b/packages/keyring-utils/src/hardware-errors-enums.ts @@ -36,8 +36,6 @@ export enum ErrorCode { ConnClosed = 4001, ConnTimeout = 4002, ConnBlocked = 4003, - ConnIframeMissing = 4010, - ConnSuiteMissing = 4011, // Protocol ProtoUnexpectedMessage = 5000, From ce66cea54e3c3646e6cbc214b8a63a402faba4b0 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Tue, 13 Jan 2026 21:25:28 +0800 Subject: [PATCH 21/24] fix test --- .../keyring-utils/src/hardware-error.test.ts | 66 ++++++++++--------- packages/keyring-utils/src/hardware-error.ts | 5 +- .../src/hardware-errors-enums.ts | 6 +- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error.test.ts b/packages/keyring-utils/src/hardware-error.test.ts index be9114d0c..862f404db 100644 --- a/packages/keyring-utils/src/hardware-error.test.ts +++ b/packages/keyring-utils/src/hardware-error.test.ts @@ -10,7 +10,7 @@ describe('HardwareWalletError', () => { }; describe('constructor', () => { - it('should create an error with required properties', () => { + it('creates an error with required properties', () => { const error = new HardwareWalletError('Test error', mockOptions); expect(error.message).toBe('Test error'); @@ -21,7 +21,7 @@ describe('HardwareWalletError', () => { expect(error.userMessage).toBe('Transaction was rejected'); }); - it('should generate a unique error ID', () => { + it('generates a unique error ID', () => { const error1 = new HardwareWalletError('Test error 1', mockOptions); const error2 = new HardwareWalletError('Test error 2', mockOptions); @@ -31,7 +31,7 @@ describe('HardwareWalletError', () => { expect(error1.id).toMatch(/^err_[a-z0-9]+_[a-z0-9]+$/u); }); - it('should set timestamp to current date', () => { + it('sets timestamp to current date', () => { const before = new Date(); const error = new HardwareWalletError('Test error', mockOptions); const after = new Date(); @@ -42,7 +42,7 @@ describe('HardwareWalletError', () => { expect(error.timestamp.getTime()).toBeLessThanOrEqual(after.getTime()); }); - it('should set optional properties when provided', () => { + it('sets optional properties when provided', () => { const cause = new Error('Original error'); const metadata = { deviceId: '12345', attempt: 1 }; @@ -56,7 +56,7 @@ describe('HardwareWalletError', () => { expect(error.metadata).toStrictEqual(metadata); }); - it('should work with instanceof checks', () => { + it('works with instanceof checks', () => { const error = new HardwareWalletError('Test error', mockOptions); expect(error instanceof HardwareWalletError).toBe(true); expect(error instanceof Error).toBe(true); @@ -64,7 +64,7 @@ describe('HardwareWalletError', () => { }); describe('isCritical', () => { - it('should return true for CRITICAL severity', () => { + it('returns true for CRITICAL severity', () => { const error = new HardwareWalletError('Test error', { ...mockOptions, severity: Severity.Critical, @@ -72,7 +72,7 @@ describe('HardwareWalletError', () => { expect(error.isCritical()).toBe(true); }); - it('should return false for non-CRITICAL severity', () => { + it('returns false for non-CRITICAL severity', () => { const severities = [Severity.Err, Severity.Warning, Severity.Info]; severities.forEach((severity) => { const error = new HardwareWalletError('Test error', { @@ -85,7 +85,7 @@ describe('HardwareWalletError', () => { }); describe('isWarning', () => { - it('should return true for WARNING severity', () => { + it('returns true for WARNING severity', () => { const error = new HardwareWalletError('Test error', { ...mockOptions, severity: Severity.Warning, @@ -93,7 +93,7 @@ describe('HardwareWalletError', () => { expect(error.isWarning()).toBe(true); }); - it('should return false for non-WARNING severity', () => { + it('returns false for non-WARNING severity', () => { const severities = [Severity.Err, Severity.Critical, Severity.Info]; severities.forEach((severity) => { const error = new HardwareWalletError('Test error', { @@ -106,7 +106,7 @@ describe('HardwareWalletError', () => { }); describe('withMetadata', () => { - it('should create a new error with additional metadata', () => { + it('creates a new error with additional metadata', () => { const originalMetadata = { deviceId: '12345' }; const originalError = new HardwareWalletError('Test error', { ...mockOptions, @@ -124,7 +124,7 @@ describe('HardwareWalletError', () => { expect(newError).not.toBe(originalError); // New instance }); - it('should create metadata when original has none', () => { + it('creates metadata when original has none', () => { const originalError = new HardwareWalletError('Test error', mockOptions); const metadata = { deviceId: '12345' }; const newError = originalError.withMetadata(metadata); @@ -132,7 +132,7 @@ describe('HardwareWalletError', () => { expect(newError.metadata).toStrictEqual(metadata); }); - it('should override existing metadata keys', () => { + it('overrides existing metadata keys', () => { const originalError = new HardwareWalletError('Test error', { ...mockOptions, metadata: { key: 'old', other: 'value' }, @@ -143,7 +143,7 @@ describe('HardwareWalletError', () => { expect(newError.metadata).toStrictEqual({ key: 'new', other: 'value' }); }); - it('should preserve all other properties', () => { + it('preserves all other properties', () => { const cause = new Error('Original error'); const originalError = new HardwareWalletError('Test error', { @@ -163,7 +163,7 @@ describe('HardwareWalletError', () => { }); describe('toJSON', () => { - it('should serialize all properties to JSON', () => { + it('serializes all properties to JSON', () => { const cause = new Error('Original error'); const metadata = { deviceId: '12345' }; @@ -186,7 +186,7 @@ describe('HardwareWalletError', () => { expect(json.metadata).toStrictEqual(metadata); }); - it('should serialize cause when present', () => { + it('serializes cause when present', () => { const cause = new Error('Original error'); const error = new HardwareWalletError('Test error', { @@ -202,14 +202,14 @@ describe('HardwareWalletError', () => { }); }); - it('should not include cause when not present', () => { + it('does not include cause when not present', () => { const error = new HardwareWalletError('Test error', mockOptions); const json = error.toJSON(); expect(json.cause).toBeUndefined(); }); - it('should handle undefined optional properties', () => { + it('handles undefined optional properties', () => { const error = new HardwareWalletError('Test error', mockOptions); const json = error.toJSON(); @@ -219,16 +219,16 @@ describe('HardwareWalletError', () => { }); describe('toString', () => { - it('should return a user-friendly string representation', () => { + it('returns a user-friendly string representation', () => { const error = new HardwareWalletError('Test error', mockOptions); const result = error.toString(); expect(result).toBe( - 'HardwareWalletError [UserRejected:2000]: Transaction was rejected', + 'HardwareWalletError [UserRejected:2000]: Test error', ); }); - it('should work with different error codes and messages', () => { + it('works with different error codes and messages', () => { const error = new HardwareWalletError('Internal error', { ...mockOptions, code: ErrorCode.Unknown, @@ -237,11 +237,11 @@ describe('HardwareWalletError', () => { const result = error.toString(); expect(result).toBe( - 'HardwareWalletError [Unknown:99999]: An internal error occurred', + 'HardwareWalletError [Unknown:99999]: Internal error', ); }); - it('should fall back to UNKNOWN name for unmapped numeric codes', () => { + it('falls back to UNKNOWN name for unmapped numeric codes', () => { const error = new HardwareWalletError('Weird error', { ...mockOptions, code: 123456 as unknown as ErrorCode, @@ -249,13 +249,13 @@ describe('HardwareWalletError', () => { }); expect(error.toString()).toBe( - 'HardwareWalletError [Unknown:123456]: Something strange happened', + 'HardwareWalletError [Unknown:123456]: Weird error', ); }); }); describe('toDetailedString', () => { - it('should return a detailed string with all information', () => { + it('returns a detailed string with all information', () => { const error = new HardwareWalletError('Test error', { ...mockOptions, }); @@ -263,14 +263,16 @@ describe('HardwareWalletError', () => { const result = error.toDetailedString(); expect(result).toContain('HardwareWalletError [UserRejected:2000]'); - expect(result).toContain('Message: Test error'); + expect(result).toContain( + 'HardwareWalletError [UserRejected:2000]: Test error', + ); expect(result).toContain('User Message: Transaction was rejected'); expect(result).toContain('Severity: Warning'); expect(result).toContain('Category: UserAction'); expect(result).toContain('Timestamp:'); }); - it('should include metadata when present', () => { + it('includes metadata when present', () => { const metadata = { deviceId: '12345', attempt: 1 }; const error = new HardwareWalletError('Test error', { ...mockOptions, @@ -283,7 +285,7 @@ describe('HardwareWalletError', () => { expect(result).toContain('"attempt": 1'); }); - it('should include cause when present', () => { + it('includes cause when present', () => { const cause = new Error('Original error'); const error = new HardwareWalletError('Test error', { ...mockOptions, @@ -294,7 +296,7 @@ describe('HardwareWalletError', () => { expect(result).toContain('Caused by: Original error'); }); - it('should not include optional fields when not present', () => { + it('does not include optional fields when not present', () => { const error = new HardwareWalletError('Test error', mockOptions); const result = error.toDetailedString(); @@ -302,7 +304,7 @@ describe('HardwareWalletError', () => { expect(result).not.toContain('Caused by:'); }); - it('should not include metadata section when metadata is empty', () => { + it('does not include metadata section when metadata is empty', () => { const error = new HardwareWalletError('Test error', { ...mockOptions, metadata: {}, @@ -314,7 +316,7 @@ describe('HardwareWalletError', () => { }); describe('error scenarios', () => { - it('should handle critical authentication errors', () => { + it('handles critical authentication errors', () => { const error = new HardwareWalletError('Device blocked', { code: ErrorCode.AuthDeviceBlocked, severity: Severity.Critical, @@ -325,7 +327,7 @@ describe('HardwareWalletError', () => { expect(error.isCritical()).toBe(true); }); - it('should handle retryable connection errors', () => { + it('handles retryable connection errors', () => { const error = new HardwareWalletError('Connection timeout', { code: ErrorCode.ConnTimeout, severity: Severity.Err, @@ -336,7 +338,7 @@ describe('HardwareWalletError', () => { expect(error.isCritical()).toBe(false); }); - it('should handle user action warnings', () => { + it('handles user action warnings', () => { const error = new HardwareWalletError('User confirmation required', { code: ErrorCode.UserConfirmationRequired, severity: Severity.Warning, diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/keyring-utils/src/hardware-error.ts index 7d85c18cf..78cc24fe3 100644 --- a/packages/keyring-utils/src/hardware-error.ts +++ b/packages/keyring-utils/src/hardware-error.ts @@ -140,7 +140,7 @@ export class HardwareWalletError extends Error { * @returns A user-friendly string representation of the error. */ toString(): string { - return `${this.#getErrorPrefix()}: ${this.userMessage}`; + return `${this.#getErrorPrefix()}: ${this.message}`; } /** @@ -150,8 +150,7 @@ export class HardwareWalletError extends Error { */ toDetailedString(): string { const details = [ - this.#getErrorPrefix(), - `Message: ${this.message}`, + this.toString(), `User Message: ${this.userMessage}`, `Severity: ${this.severity}`, `Category: ${this.category}`, diff --git a/packages/keyring-utils/src/hardware-errors-enums.ts b/packages/keyring-utils/src/hardware-errors-enums.ts index c681379c4..3df55d8e9 100644 --- a/packages/keyring-utils/src/hardware-errors-enums.ts +++ b/packages/keyring-utils/src/hardware-errors-enums.ts @@ -38,9 +38,9 @@ export enum ErrorCode { ConnBlocked = 4003, // Protocol - ProtoUnexpectedMessage = 5000, - ProtoCommandError = 5001, - ProtoMessageError = 5002, + ProtocolUnexpectedMessage = 5000, + ProtocolCommandError = 5001, + ProtocolMessageError = 5002, // Device state DeviceStateBlindSignNotSupported = 6001, From 954c66a65ef8faeeb445be3f3dcebe833cba5e80 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Wed, 14 Jan 2026 07:46:30 +0800 Subject: [PATCH 22/24] fix: refactor mapping and update enum key --- .../src/hardware-error-mappings.test.ts | 52 ++-- .../src/hardware-error-mappings.ts | 222 +++++++++--------- .../keyring-utils/src/hardware-error.test.ts | 2 +- .../src/hardware-errors-enums.ts | 16 +- 4 files changed, 138 insertions(+), 154 deletions(-) diff --git a/packages/keyring-utils/src/hardware-error-mappings.test.ts b/packages/keyring-utils/src/hardware-error-mappings.test.ts index b2446d1d5..1d26ab78a 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.test.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.test.ts @@ -1,27 +1,17 @@ -import { HARDWARE_ERROR_MAPPINGS } from './hardware-error-mappings'; +import { LEDGER_ERROR_MAPPINGS } from './hardware-error-mappings'; import { ErrorCode, Severity, Category } from './hardware-errors-enums'; describe('HARDWARE_ERROR_MAPPINGS', () => { - describe('structure', () => { - it('should have ledger vendor', () => { - expect(HARDWARE_ERROR_MAPPINGS).toHaveProperty('ledger'); - }); - - it('should have vendor names', () => { - expect(HARDWARE_ERROR_MAPPINGS.ledger.vendorName).toBe('Ledger'); - }); - }); - describe('Ledger mappings', () => { - const { errorMappings } = HARDWARE_ERROR_MAPPINGS.ledger; + const errorMappings = LEDGER_ERROR_MAPPINGS; - it('should have errorMappings object', () => { + it('have errorMappings object', () => { expect(errorMappings).toBeDefined(); expect(typeof errorMappings).toBe('object'); }); describe('success codes', () => { - it('should map 0x9000 to success', () => { + it('map 0x9000 to success', () => { const mapping = errorMappings['0x9000']; expect(mapping).toBeDefined(); expect(mapping.customCode).toBe(ErrorCode.Success); @@ -31,57 +21,59 @@ describe('HARDWARE_ERROR_MAPPINGS', () => { }); describe('authentication errors', () => { - it('should map 0x6300 to authentication failed', () => { + it('map 0x6300 to authentication failed', () => { const mapping = errorMappings['0x6300']; - expect(mapping.customCode).toBe(ErrorCode.AuthFailed); + expect(mapping.customCode).toBe(ErrorCode.AuthenticationFailed); expect(mapping.severity).toBe(Severity.Err); expect(mapping.category).toBe(Category.Authentication); expect(mapping.userMessage).toBeDefined(); }); - it('should map 0x63c0 to PIN attempts remaining', () => { + it('map 0x63c0 to PIN attempts remaining', () => { const mapping = errorMappings['0x63c0']; - expect(mapping.customCode).toBe(ErrorCode.AuthPinAttemptsRemaining); + expect(mapping.customCode).toBe( + ErrorCode.AuthenticationPinAttemptsRemaining, + ); expect(mapping.severity).toBe(Severity.Warning); }); - it('should map 0x5515 to device locked', () => { + it('map 0x5515 to device locked', () => { const mapping = errorMappings['0x5515']; - expect(mapping.customCode).toBe(ErrorCode.AuthDeviceLocked); + expect(mapping.customCode).toBe(ErrorCode.AuthenticationDeviceLocked); expect(mapping.severity).toBe(Severity.Err); expect(mapping.userMessage).toContain('unlock'); }); - it('should map 0x9840 to device blocked', () => { + it('map 0x9840 to device blocked', () => { const mapping = errorMappings['0x9840']; - expect(mapping.customCode).toBe(ErrorCode.AuthDeviceBlocked); + expect(mapping.customCode).toBe(ErrorCode.AuthenticationDeviceBlocked); expect(mapping.severity).toBe(Severity.Critical); }); }); describe('user action errors', () => { - it('should map 0x6985 to user rejected', () => { + 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('should map 0x5501 to user refused', () => { + 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('should map 0x650f to connection issue', () => { + it('map 0x650f to connection issue', () => { const mapping = errorMappings['0x650f']; expect(mapping.customCode).toBe(ErrorCode.ConnClosed); expect(mapping.category).toBe(Category.Connection); }); }); - it('should have valid structure for all mappings', () => { + it('have valid structure for all mappings', () => { Object.entries(errorMappings).forEach(([_, mapping]) => { expect(mapping).toHaveProperty('customCode'); expect(mapping).toHaveProperty('message'); @@ -98,7 +90,7 @@ describe('HARDWARE_ERROR_MAPPINGS', () => { }); }); - it('should have valid userMessage when present', () => { + it('have valid userMessage when present', () => { const mappingsWithUserMessage = Object.values(errorMappings).filter( (mapping): mapping is typeof mapping & { userMessage: string } => 'userMessage' in mapping && @@ -114,10 +106,8 @@ describe('HARDWARE_ERROR_MAPPINGS', () => { }); describe('consistency checks', () => { - it('should have unique error codes within each vendor', () => { - const ledgerCodes = Object.values( - HARDWARE_ERROR_MAPPINGS.ledger.errorMappings, - ); + it('have unique error codes', () => { + const ledgerCodes = Object.values(LEDGER_ERROR_MAPPINGS); const ledgerCustomCodes = ledgerCodes.map( (mapping) => mapping.customCode, ); diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/keyring-utils/src/hardware-error-mappings.ts index a4fd87536..59c7028a2 100644 --- a/packages/keyring-utils/src/hardware-error-mappings.ts +++ b/packages/keyring-utils/src/hardware-error-mappings.ts @@ -1,123 +1,117 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { ErrorCode, Severity, Category } from './hardware-errors-enums'; -export const HARDWARE_ERROR_MAPPINGS = { - ledger: { - vendorName: 'Ledger', - errorMappings: { - '0x9000': { - customCode: ErrorCode.Success, - message: 'Operation successful', - severity: Severity.Info, - category: Category.Success, - }, - '0x6300': { - customCode: ErrorCode.AuthFailed, - message: 'Authentication failed', - severity: Severity.Err, - category: Category.Authentication, - userMessage: 'Authentication failed. Please verify your credentials.', - }, - '0x63c0': { - customCode: ErrorCode.AuthPinAttemptsRemaining, - message: 'PIN attempts remaining', - severity: Severity.Warning, - category: Category.Authentication, - userMessage: 'Incorrect PIN. Please try again.', - }, - '0x6982': { - customCode: ErrorCode.AuthSecurityCondition, - message: 'Security conditions not satisfied', - severity: Severity.Err, - category: Category.Authentication, +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: + '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.AuthSecurityCondition, - message: 'App update required', - severity: Severity.Err, - category: Category.Authentication, + 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.AuthFailed, - message: 'Contradiction in secret code status', - severity: Severity.Err, - category: Category.Authentication, - }, - '0x9840': { - customCode: ErrorCode.AuthDeviceBlocked, - message: 'Code blocked', - severity: Severity.Critical, - 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.AuthDeviceLocked, - 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.', - }, - }, + 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.', }, }; diff --git a/packages/keyring-utils/src/hardware-error.test.ts b/packages/keyring-utils/src/hardware-error.test.ts index 862f404db..b2db94fe8 100644 --- a/packages/keyring-utils/src/hardware-error.test.ts +++ b/packages/keyring-utils/src/hardware-error.test.ts @@ -318,7 +318,7 @@ describe('HardwareWalletError', () => { describe('error scenarios', () => { it('handles critical authentication errors', () => { const error = new HardwareWalletError('Device blocked', { - code: ErrorCode.AuthDeviceBlocked, + code: ErrorCode.AuthenticationDeviceBlocked, severity: Severity.Critical, category: Category.Authentication, userMessage: 'Device is blocked due to too many failed attempts', diff --git a/packages/keyring-utils/src/hardware-errors-enums.ts b/packages/keyring-utils/src/hardware-errors-enums.ts index 3df55d8e9..ba49d951d 100644 --- a/packages/keyring-utils/src/hardware-errors-enums.ts +++ b/packages/keyring-utils/src/hardware-errors-enums.ts @@ -4,14 +4,14 @@ export enum ErrorCode { Success = 0, // Authentication - AuthFailed = 1000, - AuthIncorrectPin = 1001, - AuthPinAttemptsRemaining = 1002, - AuthPinCancelled = 1003, - AuthDeviceLocked = 1100, - AuthDeviceBlocked = 1101, - AuthSecurityCondition = 1200, - AuthWipeCodeMismatch = 1300, + AuthenticationFailed = 1000, + AuthenticationIncorrectPin = 1001, + AuthenticationPinAttemptsRemaining = 1002, + AuthenticationPinCancelled = 1003, + AuthenticationDeviceLocked = 1100, + AuthenticationDeviceBlocked = 1101, + AuthenticationSecurityCondition = 1200, + AuthenticationWipeCodeMismatch = 1300, // User action UserRejected = 2000, From 414fef039459ba96a93888fd04414a11dc1b106f Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Wed, 14 Jan 2026 07:55:57 +0800 Subject: [PATCH 23/24] refactor: move to new package --- packages/hw-device-sdk/package.json | 3 +++ .../src/hardware-error-mappings.test.ts | 0 .../src/hardware-error-mappings.ts | 0 .../src/hardware-error.test.ts | 0 .../src/hardware-error.ts | 0 .../src/hardware-errors-enums.ts | 0 packages/hw-device-sdk/src/index.test.ts | 8 -------- packages/hw-device-sdk/src/index.ts | 5 +++-- packages/hw-device-sdk/tsconfig.build.json | 1 + packages/keyring-utils/src/index.ts | 3 --- yarn.lock | 1 + 11 files changed, 8 insertions(+), 13 deletions(-) rename packages/{keyring-utils => hw-device-sdk}/src/hardware-error-mappings.test.ts (100%) rename packages/{keyring-utils => hw-device-sdk}/src/hardware-error-mappings.ts (100%) rename packages/{keyring-utils => hw-device-sdk}/src/hardware-error.test.ts (100%) rename packages/{keyring-utils => hw-device-sdk}/src/hardware-error.ts (100%) rename packages/{keyring-utils => hw-device-sdk}/src/hardware-errors-enums.ts (100%) delete mode 100644 packages/hw-device-sdk/src/index.test.ts diff --git a/packages/hw-device-sdk/package.json b/packages/hw-device-sdk/package.json index 9b6961cc1..643c1c871 100644 --- a/packages/hw-device-sdk/package.json +++ b/packages/hw-device-sdk/package.json @@ -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", diff --git a/packages/keyring-utils/src/hardware-error-mappings.test.ts b/packages/hw-device-sdk/src/hardware-error-mappings.test.ts similarity index 100% rename from packages/keyring-utils/src/hardware-error-mappings.test.ts rename to packages/hw-device-sdk/src/hardware-error-mappings.test.ts diff --git a/packages/keyring-utils/src/hardware-error-mappings.ts b/packages/hw-device-sdk/src/hardware-error-mappings.ts similarity index 100% rename from packages/keyring-utils/src/hardware-error-mappings.ts rename to packages/hw-device-sdk/src/hardware-error-mappings.ts diff --git a/packages/keyring-utils/src/hardware-error.test.ts b/packages/hw-device-sdk/src/hardware-error.test.ts similarity index 100% rename from packages/keyring-utils/src/hardware-error.test.ts rename to packages/hw-device-sdk/src/hardware-error.test.ts diff --git a/packages/keyring-utils/src/hardware-error.ts b/packages/hw-device-sdk/src/hardware-error.ts similarity index 100% rename from packages/keyring-utils/src/hardware-error.ts rename to packages/hw-device-sdk/src/hardware-error.ts diff --git a/packages/keyring-utils/src/hardware-errors-enums.ts b/packages/hw-device-sdk/src/hardware-errors-enums.ts similarity index 100% rename from packages/keyring-utils/src/hardware-errors-enums.ts rename to packages/hw-device-sdk/src/hardware-errors-enums.ts diff --git a/packages/hw-device-sdk/src/index.test.ts b/packages/hw-device-sdk/src/index.test.ts deleted file mode 100644 index fe9ea7803..000000000 --- a/packages/hw-device-sdk/src/index.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -// TODO: Remove this once the package has actual exports. -/* eslint-disable import-x/unambiguous */ - -describe('index', () => { - it('does nothing', () => { - expect(true).toBe(true); - }); -}); diff --git a/packages/hw-device-sdk/src/index.ts b/packages/hw-device-sdk/src/index.ts index 5162399cd..2263a01d0 100644 --- a/packages/hw-device-sdk/src/index.ts +++ b/packages/hw-device-sdk/src/index.ts @@ -1,2 +1,3 @@ -// TODO: Remove this once the package has actual exports. -/* eslint-disable import-x/unambiguous */ +export * from './hardware-errors-enums'; +export * from './hardware-error-mappings'; +export * from './hardware-error'; diff --git a/packages/hw-device-sdk/tsconfig.build.json b/packages/hw-device-sdk/tsconfig.build.json index dee2ad8c3..b3a9f7371 100644 --- a/packages/hw-device-sdk/tsconfig.build.json +++ b/packages/hw-device-sdk/tsconfig.build.json @@ -5,6 +5,7 @@ "outDir": "dist", "rootDir": "src" }, + "references": [{ "path": "../keyring-utils/tsconfig.build.json" }], "include": ["./src/**/*.ts"], "exclude": ["./src/**/*.test.ts", "./src/**/*.test-d.ts"] } diff --git a/packages/keyring-utils/src/index.ts b/packages/keyring-utils/src/index.ts index 7d657dd6d..e84e6cc96 100644 --- a/packages/keyring-utils/src/index.ts +++ b/packages/keyring-utils/src/index.ts @@ -5,6 +5,3 @@ export * from './scopes'; export * from './superstruct'; export * from './JsonRpcRequest'; export type * from './keyring'; -export * from './hardware-errors-enums'; -export * from './hardware-error-mappings'; -export * from './hardware-error'; diff --git a/yarn.lock b/yarn.lock index 64e0a4177..63fb4fbf7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1941,6 +1941,7 @@ __metadata: "@lavamoat/allow-scripts": "npm:^3.2.1" "@lavamoat/preinstall-always-fail": "npm:^2.1.0" "@metamask/auto-changelog": "npm:^3.4.4" + "@metamask/keyring-utils": "workspace:^" "@ts-bridge/cli": "npm:^0.6.3" "@types/jest": "npm:^29.5.12" "@types/node": "npm:^20.12.12" From b865d0aa5cb4da83aba71d5a052c0d52b1d5ada0 Mon Sep 17 00:00:00 2001 From: Monte Lai Date: Wed, 14 Jan 2026 08:09:33 +0800 Subject: [PATCH 24/24] fix: lint --- packages/hw-device-sdk/CHANGELOG.md | 6 +++++- packages/hw-device-sdk/tsconfig.build.json | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/hw-device-sdk/CHANGELOG.md b/packages/hw-device-sdk/CHANGELOG.md index f4eb4815c..9c275ca84 100644 --- a/packages/hw-device-sdk/CHANGELOG.md +++ b/packages/hw-device-sdk/CHANGELOG.md @@ -7,4 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -[Unreleased]: https://github.com/MetaMask/accounts/ +### Added + +- Add hardwared related error mappings and custom hardware error. + +[Unreleased]: https://github.com/MetaMask/accounts/@0.0.0...HEAD diff --git a/packages/hw-device-sdk/tsconfig.build.json b/packages/hw-device-sdk/tsconfig.build.json index b3a9f7371..dee2ad8c3 100644 --- a/packages/hw-device-sdk/tsconfig.build.json +++ b/packages/hw-device-sdk/tsconfig.build.json @@ -5,7 +5,6 @@ "outDir": "dist", "rootDir": "src" }, - "references": [{ "path": "../keyring-utils/tsconfig.build.json" }], "include": ["./src/**/*.ts"], "exclude": ["./src/**/*.test.ts", "./src/**/*.test-d.ts"] }