diff --git a/packages/hw-device-sdk/CHANGELOG.md b/packages/hw-device-sdk/CHANGELOG.md index f4eb4815..9c275ca8 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/package.json b/packages/hw-device-sdk/package.json index 9b6961cc..643c1c87 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/hw-device-sdk/src/hardware-error-mappings.test.ts b/packages/hw-device-sdk/src/hardware-error-mappings.test.ts new file mode 100644 index 00000000..1d26ab78 --- /dev/null +++ b/packages/hw-device-sdk/src/hardware-error-mappings.test.ts @@ -0,0 +1,117 @@ +import { LEDGER_ERROR_MAPPINGS } from './hardware-error-mappings'; +import { ErrorCode, Severity, Category } from './hardware-errors-enums'; + +describe('HARDWARE_ERROR_MAPPINGS', () => { + describe('Ledger mappings', () => { + const errorMappings = LEDGER_ERROR_MAPPINGS; + + it('have errorMappings object', () => { + expect(errorMappings).toBeDefined(); + expect(typeof errorMappings).toBe('object'); + }); + + describe('success codes', () => { + it('map 0x9000 to success', () => { + const mapping = errorMappings['0x9000']; + expect(mapping).toBeDefined(); + expect(mapping.customCode).toBe(ErrorCode.Success); + expect(mapping.severity).toBe(Severity.Info); + expect(mapping.category).toBe(Category.Success); + }); + }); + + describe('authentication errors', () => { + it('map 0x6300 to authentication failed', () => { + const mapping = errorMappings['0x6300']; + expect(mapping.customCode).toBe(ErrorCode.AuthenticationFailed); + expect(mapping.severity).toBe(Severity.Err); + expect(mapping.category).toBe(Category.Authentication); + expect(mapping.userMessage).toBeDefined(); + }); + + it('map 0x63c0 to PIN attempts remaining', () => { + const mapping = errorMappings['0x63c0']; + expect(mapping.customCode).toBe( + ErrorCode.AuthenticationPinAttemptsRemaining, + ); + expect(mapping.severity).toBe(Severity.Warning); + }); + + it('map 0x5515 to device locked', () => { + const mapping = errorMappings['0x5515']; + expect(mapping.customCode).toBe(ErrorCode.AuthenticationDeviceLocked); + expect(mapping.severity).toBe(Severity.Err); + expect(mapping.userMessage).toContain('unlock'); + }); + + it('map 0x9840 to device blocked', () => { + const mapping = errorMappings['0x9840']; + expect(mapping.customCode).toBe(ErrorCode.AuthenticationDeviceBlocked); + expect(mapping.severity).toBe(Severity.Critical); + }); + }); + + describe('user action errors', () => { + it('map 0x6985 to user rejected', () => { + const mapping = errorMappings['0x6985']; + expect(mapping.customCode).toBe(ErrorCode.UserRejected); + expect(mapping.severity).toBe(Severity.Warning); + expect(mapping.category).toBe(Category.UserAction); + }); + + it('map 0x5501 to user refused', () => { + const mapping = errorMappings['0x5501']; + expect(mapping.customCode).toBe(ErrorCode.UserRejected); + expect(mapping.severity).toBe(Severity.Warning); + }); + }); + describe('connection errors', () => { + it('map 0x650f to connection issue', () => { + const mapping = errorMappings['0x650f']; + expect(mapping.customCode).toBe(ErrorCode.ConnClosed); + expect(mapping.category).toBe(Category.Connection); + }); + }); + + it('have valid structure for all mappings', () => { + Object.entries(errorMappings).forEach(([_, mapping]) => { + expect(mapping).toHaveProperty('customCode'); + expect(mapping).toHaveProperty('message'); + expect(mapping).toHaveProperty('severity'); + expect(mapping).toHaveProperty('category'); + + const numericErrorCodes = Object.values(ErrorCode).filter( + (value): value is number => typeof value === 'number', + ); + expect(numericErrorCodes).toContain(mapping.customCode); + expect(Object.values(Severity)).toContain(mapping.severity); + expect(Object.values(Category)).toContain(mapping.category); + expect(typeof mapping.message).toBe('string'); + }); + }); + + it('have valid userMessage when present', () => { + const mappingsWithUserMessage = Object.values(errorMappings).filter( + (mapping): mapping is typeof mapping & { userMessage: string } => + 'userMessage' in mapping && + typeof mapping.userMessage === 'string' && + mapping.userMessage.length > 0, + ); + expect(mappingsWithUserMessage.length).toBeGreaterThan(0); + mappingsWithUserMessage.forEach((mapping) => { + expect(typeof mapping.userMessage).toBe('string'); + expect(mapping.userMessage.length).toBeGreaterThan(0); + }); + }); + }); + + describe('consistency checks', () => { + it('have unique error codes', () => { + const ledgerCodes = Object.values(LEDGER_ERROR_MAPPINGS); + const ledgerCustomCodes = ledgerCodes.map( + (mapping) => mapping.customCode, + ); + expect(ledgerCustomCodes.length).toBeGreaterThan(0); + }); + }); +}); diff --git a/packages/hw-device-sdk/src/hardware-error-mappings.ts b/packages/hw-device-sdk/src/hardware-error-mappings.ts new file mode 100644 index 00000000..59c7028a --- /dev/null +++ b/packages/hw-device-sdk/src/hardware-error-mappings.ts @@ -0,0 +1,117 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { ErrorCode, Severity, Category } from './hardware-errors-enums'; + +export const LEDGER_ERROR_MAPPINGS = { + '0x9000': { + customCode: ErrorCode.Success, + message: 'Operation successful', + severity: Severity.Info, + category: Category.Success, + }, + '0x6300': { + customCode: ErrorCode.AuthenticationFailed, + message: 'Authentication failed', + severity: Severity.Err, + category: Category.Authentication, + userMessage: 'Authentication failed. Please verify your credentials.', + }, + '0x63c0': { + customCode: ErrorCode.AuthenticationPinAttemptsRemaining, + message: 'PIN attempts remaining', + severity: Severity.Warning, + category: Category.Authentication, + userMessage: 'Incorrect PIN. Please try again.', + }, + '0x6982': { + customCode: ErrorCode.AuthenticationSecurityCondition, + message: 'Security conditions not satisfied', + severity: Severity.Err, + category: Category.Authentication, + + userMessage: + 'Device is locked or access rights are insufficient. Please unlock your device.', + }, + '0x6985': { + customCode: ErrorCode.UserRejected, + message: 'User rejected action on device', + severity: Severity.Warning, + category: Category.UserAction, + + userMessage: + 'Transaction was rejected. Please approve on your device to continue.', + }, + '0x9804': { + customCode: ErrorCode.AuthenticationSecurityCondition, + message: 'App update required', + severity: Severity.Err, + category: Category.Authentication, + + userMessage: 'Please update your Ledger app to continue.', + }, + '0x9808': { + customCode: ErrorCode.AuthenticationFailed, + message: 'Contradiction in secret code status', + severity: Severity.Err, + category: Category.Authentication, + }, + '0x9840': { + customCode: ErrorCode.AuthenticationDeviceBlocked, + message: 'Code blocked', + severity: Severity.Critical, + category: Category.Authentication, + + userMessage: + 'Your device is blocked due to too many failed attempts. Please follow device recovery procedures.', + }, + '0x650f': { + customCode: ErrorCode.ConnClosed, + message: 'App closed or connection issue', + severity: Severity.Err, + category: Category.Connection, + userMessage: + 'Connection lost or app closed. Please open the corresponding app on your Ledger device.', + }, + '0x5515': { + customCode: ErrorCode.AuthenticationDeviceLocked, + message: 'Device is locked', + severity: Severity.Err, + category: Category.Authentication, + userMessage: 'Please unlock your Ledger device to continue.', + }, + '0x5501': { + customCode: ErrorCode.UserRejected, + message: 'User refused on device', + severity: Severity.Warning, + category: Category.UserAction, + userMessage: + 'Operation was rejected. Please approve on your device to continue.', + }, + '0x6a80': { + customCode: ErrorCode.DeviceStateBlindSignNotSupported, + message: 'Blind signing not supported', + severity: Severity.Err, + category: Category.DeviceState, + userMessage: 'Blind signing is not supported on this device.', + }, + '0x6d00': { + customCode: ErrorCode.DeviceStateOnlyV4Supported, + message: 'Ledger Only V4 supported', + severity: Severity.Err, + category: Category.DeviceState, + userMessage: 'Only V4 is supported on this device.', + }, + '0x6e00': { + customCode: ErrorCode.DeviceStateEthAppClosed, + message: 'Ethereum app closed', + severity: Severity.Err, + category: Category.DeviceState, + userMessage: 'Ethereum app is closed. Please open it to continue.', + }, + '0x6501': { + customCode: ErrorCode.DeviceStateEthAppOutOfDate, + message: 'Ethereum app out of date', + severity: Severity.Err, + category: Category.DeviceState, + userMessage: 'Ethereum app is out of date. Please update it to continue.', + }, +}; diff --git a/packages/hw-device-sdk/src/hardware-error.test.ts b/packages/hw-device-sdk/src/hardware-error.test.ts new file mode 100644 index 00000000..b2db94fe --- /dev/null +++ b/packages/hw-device-sdk/src/hardware-error.test.ts @@ -0,0 +1,353 @@ +import { HardwareWalletError } from './hardware-error'; +import { ErrorCode, Severity, Category } from './hardware-errors-enums'; + +describe('HardwareWalletError', () => { + const mockOptions = { + code: ErrorCode.UserRejected, + severity: Severity.Warning, + category: Category.UserAction, + userMessage: 'Transaction was rejected', + }; + + describe('constructor', () => { + it('creates 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.UserRejected); + expect(error.severity).toBe(Severity.Warning); + expect(error.category).toBe(Category.UserAction); + expect(error.userMessage).toBe('Transaction was rejected'); + }); + + it('generates 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('sets 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('sets 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, + }); + + expect(error.cause).toBe(cause); + expect(error.metadata).toStrictEqual(metadata); + }); + + it('works with instanceof checks', () => { + const error = new HardwareWalletError('Test error', mockOptions); + expect(error instanceof HardwareWalletError).toBe(true); + expect(error instanceof Error).toBe(true); + }); + }); + + describe('isCritical', () => { + it('returns true for CRITICAL severity', () => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + severity: Severity.Critical, + }); + expect(error.isCritical()).toBe(true); + }); + + it('returns false for non-CRITICAL severity', () => { + const severities = [Severity.Err, Severity.Warning, Severity.Info]; + severities.forEach((severity) => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + severity, + }); + expect(error.isCritical()).toBe(false); + }); + }); + }); + + describe('isWarning', () => { + it('returns true for WARNING severity', () => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + severity: Severity.Warning, + }); + expect(error.isWarning()).toBe(true); + }); + + it('returns false for non-WARNING severity', () => { + const severities = [Severity.Err, Severity.Critical, Severity.Info]; + severities.forEach((severity) => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + severity, + }); + expect(error.isWarning()).toBe(false); + }); + }); + }); + + describe('withMetadata', () => { + it('creates 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('creates 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('overrides 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('preserves all other properties', () => { + const cause = new Error('Original error'); + + const originalError = new HardwareWalletError('Test error', { + ...mockOptions, + cause, + }); + + 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.userMessage).toBe(originalError.userMessage); + expect(newError.cause).toBe(originalError.cause); + }); + }); + + describe('toJSON', () => { + it('serializes all properties to JSON', () => { + const cause = new Error('Original error'); + const metadata = { deviceId: '12345' }; + + const error = new HardwareWalletError('Test error', { + ...mockOptions, + cause, + metadata, + }); + + 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.UserRejected); + expect(json.severity).toBe(Severity.Warning); + expect(json.category).toBe(Category.UserAction); + expect(json.userMessage).toBe('Transaction was rejected'); + expect(json.timestamp).toBe(error.timestamp.toISOString()); + expect(json.metadata).toStrictEqual(metadata); + }); + + it('serializes cause when present', () => { + const cause = new Error('Original error'); + + const error = new HardwareWalletError('Test error', { + ...mockOptions, + cause, + }); + + const json = error.toJSON(); + + expect(json.cause).toStrictEqual({ + name: 'Error', + message: 'Original error', + }); + }); + + it('does not include cause when not present', () => { + const error = new HardwareWalletError('Test error', mockOptions); + const json = error.toJSON(); + + expect(json.cause).toBeUndefined(); + }); + + it('handles 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('returns a user-friendly string representation', () => { + const error = new HardwareWalletError('Test error', mockOptions); + const result = error.toString(); + + expect(result).toBe( + 'HardwareWalletError [UserRejected:2000]: Test error', + ); + }); + + it('works with different error codes and messages', () => { + const error = new HardwareWalletError('Internal error', { + ...mockOptions, + code: ErrorCode.Unknown, + userMessage: 'An internal error occurred', + }); + const result = error.toString(); + + expect(result).toBe( + 'HardwareWalletError [Unknown:99999]: Internal error', + ); + }); + + it('falls 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]: Weird error', + ); + }); + }); + + describe('toDetailedString', () => { + it('returns a detailed string with all information', () => { + const error = new HardwareWalletError('Test error', { + ...mockOptions, + }); + + const result = error.toDetailedString(); + + expect(result).toContain('HardwareWalletError [UserRejected:2000]'); + 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('includes 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('includes 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('does 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('does 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('handles critical authentication errors', () => { + const error = new HardwareWalletError('Device blocked', { + code: ErrorCode.AuthenticationDeviceBlocked, + severity: Severity.Critical, + category: Category.Authentication, + userMessage: 'Device is blocked due to too many failed attempts', + }); + + expect(error.isCritical()).toBe(true); + }); + + it('handles retryable connection errors', () => { + const error = new HardwareWalletError('Connection timeout', { + code: ErrorCode.ConnTimeout, + severity: Severity.Err, + category: Category.Connection, + userMessage: 'Connection timed out', + }); + + expect(error.isCritical()).toBe(false); + }); + + it('handles user action warnings', () => { + const error = new HardwareWalletError('User confirmation required', { + code: ErrorCode.UserConfirmationRequired, + severity: Severity.Warning, + category: Category.UserAction, + userMessage: 'Please confirm the action on your device', + }); + + expect(error.isWarning()).toBe(true); + expect(error.isCritical()).toBe(false); + }); + }); +}); diff --git a/packages/hw-device-sdk/src/hardware-error.ts b/packages/hw-device-sdk/src/hardware-error.ts new file mode 100644 index 00000000..78cc24fe --- /dev/null +++ b/packages/hw-device-sdk/src/hardware-error.ts @@ -0,0 +1,175 @@ +import type { Category } from './hardware-errors-enums'; +import { ErrorCode, Severity } 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); + // 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}`; +} + +/** + * 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] => "AuthFailed" + return ErrorCode[code] ?? ErrorCode[ErrorCode.Unknown]; +} + +export type HardwareWalletErrorOptions = { + code: ErrorCode; + severity: Severity; + category: Category; + userMessage: string; + cause?: Error; + metadata?: Record; +}; + +export class HardwareWalletError extends Error { + public readonly id: string; + + public readonly code: ErrorCode; + + public readonly severity: Severity; + + public readonly category: Category; + + public readonly userMessage: string; + + public readonly timestamp: Date; + + public readonly metadata: Record | undefined; + + 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.userMessage = options.userMessage; + this.timestamp = new Date(); + this.metadata = options.metadata; + this.cause = options.cause; + } + + /** + * 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; + } + + /** + * 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, + userMessage: this.userMessage, + metadata: { ...(this.metadata ?? {}), ...additionalMetadata }, + }; + + 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, + userMessage: this.userMessage, + timestamp: this.timestamp.toISOString(), + metadata: this.metadata, + }; + + if (this.cause !== undefined) { + json.cause = { + name: this.cause.name, + message: this.cause.message, + }; + } + + return json; + } + + /** + * Returns a user-friendly string representation of the error. + * + * @returns A user-friendly string representation of the error. + */ + toString(): string { + return `${this.#getErrorPrefix()}: ${this.message}`; + } + + /** + * Returns a detailed string representation for debugging. + * + * @returns A detailed string representation of the error for debugging. + */ + toDetailedString(): string { + const details = [ + this.toString(), + `User Message: ${this.userMessage}`, + `Severity: ${this.severity}`, + `Category: ${this.category}`, + `Timestamp: ${this.timestamp.toISOString()}`, + ]; + + 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'); + } + + #getErrorPrefix(): string { + const codeName = getErrorCodeName(this.code); + return `${this.name} [${codeName}:${this.code}]`; + } +} diff --git a/packages/hw-device-sdk/src/hardware-errors-enums.ts b/packages/hw-device-sdk/src/hardware-errors-enums.ts new file mode 100644 index 00000000..ba49d951 --- /dev/null +++ b/packages/hw-device-sdk/src/hardware-errors-enums.ts @@ -0,0 +1,75 @@ +// Error Code Enum +export enum ErrorCode { + // Success + Success = 0, + + // Authentication + AuthenticationFailed = 1000, + AuthenticationIncorrectPin = 1001, + AuthenticationPinAttemptsRemaining = 1002, + AuthenticationPinCancelled = 1003, + AuthenticationDeviceLocked = 1100, + AuthenticationDeviceBlocked = 1101, + AuthenticationSecurityCondition = 1200, + AuthenticationWipeCodeMismatch = 1300, + + // User action + UserRejected = 2000, + UserCancelled = 2001, + UserConfirmationRequired = 2002, + UserInputRequired = 2003, + + // Device state + DeviceNotReady = 3000, + DeviceInvalidSession = 3001, + DeviceDisconnected = 3003, + DeviceUsedElsewhere = 3004, + DeviceCallInProgress = 3005, + DeviceNotFound = 3010, + DeviceMultipleConnected = 3011, + DeviceMissingCapability = 3020, + DeviceBtcOnlyFirmware = 3021, + DeviceIncompatibleMode = 3030, + + // Connection & transport + ConnTransportMissing = 4000, + ConnClosed = 4001, + ConnTimeout = 4002, + ConnBlocked = 4003, + + // Protocol + ProtocolUnexpectedMessage = 5000, + ProtocolCommandError = 5001, + ProtocolMessageError = 5002, + + // Device state + DeviceStateBlindSignNotSupported = 6001, + DeviceStateOnlyV4Supported = 6002, + DeviceStateEthAppClosed = 6003, + DeviceStateEthAppOutOfDate = 6004, + + // Transaction + TxInsufficientFunds = 10000, + + // Unknown/fallback + Unknown = 99999, +} + +// Severity Enum +export enum Severity { + Info = 'Info', + Err = 'Error', + Warning = 'Warning', + Critical = 'Critical', +} + +// Category Enum +export enum Category { + Success = 'Success', + Authentication = 'Authentication', + Protocol = 'Protocol', + Connection = 'Connection', + UserAction = 'UserAction', + DeviceState = 'DeviceState', + Unknown = 'Unknown', +} 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 fe9ea780..00000000 --- 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 5162399c..2263a01d 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/yarn.lock b/yarn.lock index 64e0a417..63fb4fbf 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"