The classifyStellarRPCFailure function provides deterministic classification of Stellar network failures into client-safe error categories. This ensures that raw upstream error messages never cross the API trust boundary, maintaining security while providing meaningful error responses.
export function classifyStellarRPCFailure(error: unknown): StellarRPCFailureClass| Class | Description | HTTP Status Code | Use Case |
|---|---|---|---|
TIMEOUT |
Request timed out or was aborted | 504 Gateway Timeout | Network latency, server overload |
RATE_LIMIT |
Too many requests to Stellar services | 429 Too Many Requests | API rate limiting exceeded |
UPSTREAM_ERROR |
Stellar server error (5xx) | 502 Bad Gateway | Horizon/Soroban server issues |
MALFORMED_RESPONSE |
Invalid response format from Stellar | 502 Bad Gateway | JSON parsing errors, unexpected format |
UNAUTHORIZED |
Authentication/authorization failure | 401 Unauthorized | Invalid credentials, permissions |
UNKNOWN |
Unclassified error | 503 Service Unavailable | Fallback for unknown errors |
export function classifyStellarRPCFailure(error: unknown): StellarRPCFailureClass {
// Timeout detection
if (
error instanceof Error &&
(error.name === 'AbortError' || error.message.toLowerCase().includes('timeout'))
) {
return StellarRPCFailureClass.TIMEOUT;
}
// HTTP status code classification
if (typeof error === 'object' && error !== null) {
const status = (error as { status?: number }).status;
if (status === 429) return StellarRPCFailureClass.RATE_LIMIT;
if (status === 401 || status === 403) return StellarRPCFailureClass.UNAUTHORIZED;
if (typeof status === 'number' && status >= 500) return StellarRPCFailureClass.UPSTREAM_ERROR;
}
// Response parsing errors
if (error instanceof SyntaxError) {
return StellarRPCFailureClass.MALFORMED_RESPONSE;
}
// Default fallback
return StellarRPCFailureClass.UNKNOWN;
}The offering sync service uses this classification to:
- Log appropriate error levels based on failure class
- Return suitable HTTP status codes to clients
- Provide safe error messages without exposing upstream details
- Enable monitoring and alerting for specific failure types
try {
const onChainState = await this.stellarClient.getOfferingState(contractAddress);
// Process successful response
} catch (error) {
const failureClass = classifyStellarRPCFailure(error);
this.logger.error('Stellar RPC failure', {
contractAddress,
failureClass,
// Never log raw error messages in production
});
// Return appropriate HTTP response
const statusCode = getStatusCodeForFailureClass(failureClass);
const clientMessage = getClientSafeMessage(failureClass);
return {
success: false,
error: clientMessage,
failureClass,
};
}- Raw error messages are never exposed to clients
- Only predefined failure classes are returned in API responses
- Error details are logged securely without sensitive information
function getClientSafeMessage(failureClass: StellarRPCFailureClass): string {
switch (failureClass) {
case StellarRPCFailureClass.TIMEOUT:
return 'Stellar network request timed out';
case StellarRPCFailureClass.RATE_LIMIT:
return 'Rate limit exceeded, please try again later';
case StellarRPCFailureClass.UPSTREAM_ERROR:
return 'Stellar service temporarily unavailable';
case StellarRPCFailureClass.MALFORMED_RESPONSE:
return 'Invalid response from Stellar network';
case StellarRPCFailureClass.UNAUTHORIZED:
return 'Authentication failed';
default:
return 'Stellar service error';
}
}The failure classes enable proper monitoring:
// Metrics collection
metrics.counter('stellar_rpc_failures_total').inc({
failure_class: failureClass,
contract_address: contractAddress,
});
// Alerting rules
if (failureClass === StellarRPCFailureClass.UPSTREAM_ERROR) {
alertManager.trigger('stellar_service_degraded', {
severity: 'warning',
details: { failureClass, contractAddress },
});
}- Test each classification path with appropriate error inputs
- Verify edge cases (null, undefined, custom errors)
- Ensure no raw error messages leak through
- Test with actual Stellar network failures
- Verify HTTP status code mapping
- Test error propagation through service layers
describe('classifyStellarRPCFailure', () => {
it('classifies timeout errors', () => {
const timeoutError = new Error('Request timeout');
timeoutError.name = 'AbortError';
expect(classifyStellarRPCFailure(timeoutError))
.toBe(StellarRPCFailureClass.TIMEOUT);
});
it('classifies rate limit errors', () => {
const rateLimitError = { status: 429 };
expect(classifyStellarRPCFailure(rateLimitError))
.toBe(StellarRPCFailureClass.RATE_LIMIT);
});
it('handles unknown errors', () => {
const unknownError = new Error('Something went wrong');
expect(classifyStellarRPCFailure(unknownError))
.toBe(StellarRPCFailureClass.UNKNOWN);
});
});- Always classify errors before exposing them to clients
- Log failure classes for monitoring and debugging
- Use appropriate HTTP status codes based on failure class
- Never expose raw upstream error messages in API responses
- Implement retry logic for transient failures (TIMEOUT, RATE_LIMIT)
- Set up alerts for persistent UPSTREAM_ERROR failures
The classification integrates seamlessly with the existing lib/errors system:
// In route handlers
if (result.failureClass) {
const statusCode = getStatusCodeForFailureClass(result.failureClass);
const error = Errors.serviceUnavailable('Stellar service temporarily unavailable', {
failureClass: result.failureClass,
});
return res.status(statusCode).json(error.toResponse(requestId));
}This ensures consistent error responses across the entire API while maintaining security and observability.