Problem Statement
Raw transaction errors returned from Stellar nodes output confusing low-level exception codes like tx_failed, op_underfunded, op_no_trust, tx_bad_seq, and Soroban-specific HostError codes such as HostError: ValueUnknown or HostError: HostObjectError(ContractError(1)). These error messages are opaque to non-expert node operators who lack context about the Stellar transaction model, causing frustration and unnecessary support tickets.
Technical Bounds & Invariants
- Error source domains: Stellar Horizon (REST), Soroban RPC (JSON-RPC), Wallet extension (Freighter/Lobstr SDK), local validation
- Translation coverage target: < 2% "unrecognized error" rate in production
- Localization: English only (v1), i18n-ready architecture for v2
- Dictionary entries: 50-100 error code patterns initially, extensible via config file
- Performance: translation lookup must complete in under 0.1ms per error
Codebase Navigation Guide
- Primary target:
/src/utils/errorDecoder.ts
- Error display component:
/src/components/shared/ErrorDisplay.tsx
- Transaction submission flow:
/src/hooks/useSorobanStaking.ts — error handling at line 120
- RPC client:
/src/lib/stellar/rpcClient.ts
Step-by-Step Resolution Blueprint
- Define an
ErrorCatalog as a strongly-typed Record<string, ErrorDefinition> where each key is a regex pattern matching an error code or message, and each value contains: { category: 'balance' | 'auth' | 'network' | 'contract' | 'wallet', severity: 'info' | 'warning' | 'error', humanTitle: string, humanDescription: string, troubleshootingSteps: string[], docsUrl?: string }
- Implement
decodeTransactionError(rawError: unknown): DecodedError that: (a) normalizes the error to a string via extractErrorMessage(rawError) (handles Error objects, string, Fetch errors, RPC response shapes), (b) iterates through the catalog patterns with RegExp.test, (c) returns the first match with interpolated parameters (e.g., {minBalance} from op_underfunded is extracted and inserted into the human description), (d) falls back to a generic "Unknown Error" entry with the raw error code and a "Copy error details" button
- Catalog entries must include mappings like:
tx_bad_seq -> "Sequence number mismatch. Your account's transaction sequence has moved ahead of the submitted transaction. Please refresh the dashboard and try again."; HostError: ValueUnknown -> "Contract storage value not found. This usually means the contract data entry has expired. The system will automatically attempt to restore it."
- Create a React component
<DecodedError error={rawError} onDismiss /> that renders: (a) the humanTitle with a severity icon, (b) humanDescription in a muted paragraph, (c) troubleshootingSteps as an ordered list, (d) a "Learn More" link to docsUrl if available, (e) a "Copy Raw Error" button for support contexts
- Integrate the decoder into all transaction submission hooks by wrapping the
.catch() handler: catch (e) { setError(decodeTransactionError(e)); } — this ensures every error presented to the user goes through the translation pipeline
- Add a Sentry feedback loop: when a
DecodedError falls through to the "Unknown" fallback, capture the raw error in Sentry with fingerprint: ['error-decoder-unknown', rawErrorCode] to identify gaps in the catalog that need new entries
- Write tests for all catalog entries using
vitest: inject each raw error pattern and assert the decoded output contains the expected humanTitle and that troubleshootingSteps is non-empty; also test edge cases: null, undefined, empty string, and non-Error objects
Problem Statement
Raw transaction errors returned from Stellar nodes output confusing low-level exception codes like
tx_failed,op_underfunded,op_no_trust,tx_bad_seq, and Soroban-specificHostErrorcodes such asHostError: ValueUnknownorHostError: HostObjectError(ContractError(1)). These error messages are opaque to non-expert node operators who lack context about the Stellar transaction model, causing frustration and unnecessary support tickets.Technical Bounds & Invariants
Codebase Navigation Guide
/src/utils/errorDecoder.ts/src/components/shared/ErrorDisplay.tsx/src/hooks/useSorobanStaking.ts— error handling at line 120/src/lib/stellar/rpcClient.tsStep-by-Step Resolution Blueprint
ErrorCatalogas a strongly-typedRecord<string, ErrorDefinition>where each key is a regex pattern matching an error code or message, and each value contains:{ category: 'balance' | 'auth' | 'network' | 'contract' | 'wallet', severity: 'info' | 'warning' | 'error', humanTitle: string, humanDescription: string, troubleshootingSteps: string[], docsUrl?: string }decodeTransactionError(rawError: unknown): DecodedErrorthat: (a) normalizes the error to a string viaextractErrorMessage(rawError)(handles Error objects, string, Fetch errors, RPC response shapes), (b) iterates through the catalog patterns withRegExp.test, (c) returns the first match with interpolated parameters (e.g.,{minBalance}fromop_underfundedis extracted and inserted into the human description), (d) falls back to a generic "Unknown Error" entry with the raw error code and a "Copy error details" buttontx_bad_seq-> "Sequence number mismatch. Your account's transaction sequence has moved ahead of the submitted transaction. Please refresh the dashboard and try again.";HostError: ValueUnknown-> "Contract storage value not found. This usually means the contract data entry has expired. The system will automatically attempt to restore it."<DecodedError error={rawError} onDismiss />that renders: (a) the humanTitle with a severity icon, (b) humanDescription in a muted paragraph, (c) troubleshootingSteps as an ordered list, (d) a "Learn More" link todocsUrlif available, (e) a "Copy Raw Error" button for support contexts.catch()handler:catch (e) { setError(decodeTransactionError(e)); }— this ensures every error presented to the user goes through the translation pipelineDecodedErrorfalls through to the "Unknown" fallback, capture the raw error in Sentry withfingerprint: ['error-decoder-unknown', rawErrorCode]to identify gaps in the catalog that need new entriesvitest: inject each raw error pattern and assert the decoded output contains the expected humanTitle and thattroubleshootingStepsis non-empty; also test edge cases:null,undefined, empty string, and non-Error objects