Error Handling
This guide covers error handling strategies and best practices for the SatsTerminal Borrow SDK.Error Types Overview
The SDK provides typed errors for precise handling:| Error Class | When Thrown |
|---|---|
WalletNotConnectedError | Wallet operations without connected wallet |
SmartAccountError | Smart account initialization/operation failures |
ApiError | API request failures |
ConfigValidationError | Invalid SDK configuration |
QuoteError | Quote-related failures |
WorkflowError | Workflow execution failures |
Basic Error Handling
Copy
import {
BorrowSDK,
BorrowSDKError,
WalletNotConnectedError,
SmartAccountError,
ApiError,
ConfigValidationError,
QuoteError,
WorkflowError
} from '@satsterminal-sdk/borrow';
try {
await sdk.getLoan({
collateralBTC: 0.1,
loanAmountUSD: 5000
});
} catch (error) {
if (error instanceof WalletNotConnectedError) {
// Handle wallet not connected
showConnectWalletPrompt();
} else if (error instanceof SmartAccountError) {
// Handle smart account issues
console.error('Smart account error:', error.message);
} else if (error instanceof ApiError) {
// Handle API errors
handleApiError(error);
} else if (error instanceof QuoteError) {
// Handle quote errors
showMessage('No quotes available. Try different parameters.');
} else if (error instanceof WorkflowError) {
// Handle workflow errors
console.error('Workflow failed:', error.workflowId);
} else {
// Handle unknown errors
console.error('Unexpected error:', error);
}
}
Handling API Errors
API errors include HTTP status codes for precise handling:Copy
async function handleApiError(error: ApiError) {
switch (error.statusCode) {
case 400:
// Bad request - validation error
showValidationError(error.message);
break;
case 401:
// Unauthorized - invalid API key
showError('Invalid API key. Please check your configuration.');
break;
case 403:
// Forbidden
showError('Access denied.');
break;
case 404:
// Not found
showError('Resource not found.');
break;
case 429:
// Rate limited
showError('Too many requests. Please wait.');
await delay(5000);
// Retry operation
break;
case 500:
case 502:
case 503:
case 504:
// Server errors
showError('Server error. Please try again later.');
break;
default:
showError(`Error: ${error.message}`);
}
}
Retry Strategies
Automatic Retry Configuration
Copy
const sdk = new BorrowSDK({
// ...config
retryConfig: {
maxRetries: 3,
retryDelay: 1000,
retryableStatusCodes: [408, 429, 500, 502, 503, 504]
}
});
Manual Retry with Backoff
Copy
async function retryWithBackoff<T>(
operation: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000
): Promise<T> {
let lastError: Error | undefined;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
// Check if error is retryable
if (error instanceof ApiError) {
const retryable = [408, 429, 500, 502, 503, 504];
if (!retryable.includes(error.statusCode || 0)) {
throw error; // Not retryable
}
} else if (!(error instanceof BorrowSDKError)) {
throw error; // Unknown error, don't retry
}
// Exponential backoff
const delay = baseDelay * Math.pow(2, attempt);
console.log(`Retry ${attempt + 1}/${maxRetries} in ${delay}ms...`);
await new Promise(r => setTimeout(r, delay));
}
}
throw lastError;
}
// Usage
const quotes = await retryWithBackoff(() => sdk.getQuotes(params));
Workflow Error Handling
Workflows have their own error handling through callbacks:Copy
await sdk.getLoan({
collateralBTC: 0.1,
loanAmountUSD: 5000,
onError: (error) => {
// Error during workflow execution
console.error('Workflow error:', error);
if (error.includes('timeout')) {
showError('Operation timed out. Your deposit may still be processing.');
} else if (error.includes('deposit')) {
showError('Deposit not received. Please check your transaction.');
} else if (error.includes('bridge')) {
showError('Bridge operation failed. Please contact support.');
} else {
showError(`Operation failed: ${error}`);
}
}
});
Session Error Recovery
Copy
async function executeWithSession<T>(
sdk: BorrowSDK,
operation: () => Promise<T>
): Promise<T> {
try {
return await operation();
} catch (error) {
if (error instanceof SmartAccountError) {
if (error.message.includes('session') ||
error.message.includes('expired')) {
// Session expired, refresh it
console.log('Session expired, refreshing...');
await sdk.setup();
return await operation(); // Retry
}
}
throw error;
}
}
// Usage
const history = await executeWithSession(sdk, () =>
sdk.getLoanHistory({ status: 'active' })
);
Global Error Handler
Copy
class SDKErrorHandler {
private sdk: BorrowSDK;
private onError?: (error: BorrowSDKError) => void;
private onSessionExpired?: () => void;
constructor(sdk: BorrowSDK) {
this.sdk = sdk;
}
setErrorHandler(handler: (error: BorrowSDKError) => void) {
this.onError = handler;
}
setSessionExpiredHandler(handler: () => void) {
this.onSessionExpired = handler;
}
async execute<T>(operation: () => Promise<T>): Promise<T> {
try {
return await operation();
} catch (error) {
if (error instanceof BorrowSDKError) {
this.handleSDKError(error);
throw error;
}
throw error;
}
}
private handleSDKError(error: BorrowSDKError) {
// Log error
console.error('SDK Error:', error.toJSON());
// Check for session expiry
if (error instanceof SmartAccountError &&
error.message.includes('session')) {
this.onSessionExpired?.();
}
// Call global handler
this.onError?.(error);
}
}
// Usage
const errorHandler = new SDKErrorHandler(sdk);
errorHandler.setErrorHandler((error) => {
// Send to error tracking service
errorTracker.capture(error);
});
errorHandler.setSessionExpiredHandler(() => {
// Redirect to login
router.push('/connect');
});
// Execute operations
const quotes = await errorHandler.execute(() => sdk.getQuotes(params));
Error Logging
Copy
function logError(error: unknown, context: Record<string, any> = {}) {
if (error instanceof BorrowSDKError) {
const errorData = {
type: error.constructor.name,
code: error.code,
message: error.message,
context: { ...error.context, ...context },
timestamp: new Date().toISOString(),
stack: error.stack
};
// Log locally
console.error('SDK Error:', JSON.stringify(errorData, null, 2));
// Send to monitoring service
monitoring.logError(errorData);
} else if (error instanceof Error) {
console.error('Error:', error.message, context);
monitoring.logError({
type: 'Error',
message: error.message,
context,
timestamp: new Date().toISOString(),
stack: error.stack
});
} else {
console.error('Unknown error:', error);
}
}
User-Friendly Error Messages
Copy
function getUserMessage(error: unknown): string {
if (error instanceof WalletNotConnectedError) {
return 'Please connect your wallet to continue.';
}
if (error instanceof SmartAccountError) {
if (error.message.includes('session')) {
return 'Your session has expired. Please reconnect.';
}
return 'There was a problem with your account. Please try again.';
}
if (error instanceof ApiError) {
switch (error.statusCode) {
case 401:
return 'Authentication failed. Please check your API key.';
case 429:
return 'Too many requests. Please wait a moment and try again.';
case 500:
return 'Server error. Our team has been notified.';
default:
return 'Something went wrong. Please try again.';
}
}
if (error instanceof QuoteError) {
return 'No quotes available for these parameters. Try adjusting your loan amount or collateral.';
}
if (error instanceof WorkflowError) {
return 'The operation could not be completed. Please check your transaction status.';
}
if (error instanceof ConfigValidationError) {
return 'Configuration error. Please contact support.';
}
return 'An unexpected error occurred. Please try again.';
}
Best Practices
1. Always Catch Errors
Copy
// Bad
await sdk.getLoan(options);
// Good
try {
await sdk.getLoan(options);
} catch (error) {
handleError(error);
}
2. Use Type Guards
Copy
function isBorrowSDKError(error: unknown): error is BorrowSDKError {
return error instanceof BorrowSDKError;
}
function isApiError(error: unknown): error is ApiError {
return error instanceof ApiError;
}
3. Provide Context
Copy
try {
await sdk.repay(loanId, amount);
} catch (error) {
logError(error, {
operation: 'repay',
loanId,
amount,
userId: currentUser.id
});
throw error;
}
4. Don’t Swallow Errors
Copy
// Bad
try {
await sdk.setup();
} catch (error) {
// Silent failure
}
// Good
try {
await sdk.setup();
} catch (error) {
logError(error);
showUserError(getUserMessage(error));
// Optionally re-throw
}
5. Handle Workflow Errors Separately
Copy
await sdk.getLoan({
// ...options
onError: (error) => {
// Workflow-specific error handling
handleWorkflowError(error);
}
}).catch((error) => {
// Setup/initialization errors
handleSetupError(error);
});