Production Best Practices
Building production-ready applications with SwapKit requires careful attention to security, performance, and user experience. This guide covers essential best practices.
Security
Section titled “Security”API Key Management
Section titled “API Key Management”For frontend applications, use a backend proxy to keep API keys secure:
// Frontendconst quote = await fetch('/api/swap-quote', { method: 'POST', body: JSON.stringify(quoteParams)}).then(r => r.json());
// Backend API routeimport { SwapKitApi } from '@swapkit/helpers/api';
export async function POST(request: Request) { const params = await request.json();
// Add API key server-side const quote = await SwapKitApi.getSwapQuote({ ...params, apiKey: process.env.SWAPKIT_API_KEY });
return Response.json(quote);}
Private Key Handling
Section titled “Private Key Handling”Always use secure wallet connections instead of handling private keys directly:
import { createSwapKit, Chain } from '@swapkit/sdk';
const swapKit = createSwapKit();
// ✅ Use secure wallet connectionsawait swapKit.connectEVMWallet([Chain.Ethereum]); // MetaMaskawait swapKit.connectLedger([Chain.Bitcoin]); // Hardware walletawait swapKit.connectTrezor([Chain.Bitcoin]); // Trezor wallet
Input Validation
Section titled “Input Validation”Always validate user inputs:
import { createSwapKit, SwapKitError } from '@swapkit/sdk';
const swapKit = createSwapKit();
function validateSwapInput(params: any): void { // Validate addresses if (!swapKit.validateAddress(params.recipient, params.chain)) { throw new SwapKitError('core_swap_invalid_params', { message: 'Invalid recipient address' }); }
// Validate amounts const amount = Number(params.amount); if (isNaN(amount) || amount <= 0) { throw new SwapKitError('core_swap_invalid_params', { message: 'Invalid amount' }); }
// Validate slippage if (params.slippage < 0 || params.slippage > 50) { throw new SwapKitError('core_swap_invalid_params', { message: 'Slippage must be between 0 and 50%' }); }}
Performance Optimization
Section titled “Performance Optimization”Bundle Size Reduction
Section titled “Bundle Size Reduction”Use the modular approach for smaller bundles:
// Import only what you needimport { SwapKit, ThorchainPlugin, evmWallet } from '@swapkit/sdk';
const swapKit = SwapKit({ plugins: { ...ThorchainPlugin }, wallets: { ...evmWallet }});
// Imports everything - larger bundleimport { createSwapKit } from '@swapkit/sdk';
const swapKit = createSwapKit();
Lazy Loading Toolboxes
Section titled “Lazy Loading Toolboxes”Load toolboxes only when needed:
import { Chain } from '@swapkit/sdk';
// Lazy load toolboxesasync function getToolboxForChain(chain: Chain) { switch (chain) { case Chain.Ethereum: const { getEvmToolbox } = await import('@swapkit/sdk'); return getEvmToolbox(chain);
case Chain.Bitcoin: const { getUtxoToolbox } = await import('@swapkit/sdk'); return getUtxoToolbox(chain);
default: const { getToolbox } = await import('@swapkit/sdk'); return getToolbox(chain); }}
Request Caching
Section titled “Request Caching”Implement caching for frequently accessed data:
class SwapKitCache { private cache = new Map<string, { data: any; timestamp: number }>(); private ttl: Record<string, number> = { price: 60_000, // 1 minute balance: 300_000, // 5 minutes quote: 30_000, // 30 seconds };
async get<T>( key: string, type: keyof typeof this.ttl, fetcher: () => Promise<T> ): Promise<T> { const cached = this.cache.get(key); const now = Date.now();
if (cached && now - cached.timestamp < this.ttl[type]) { return cached.data; }
const data = await fetcher(); this.cache.set(key, { data, timestamp: now }); return data; }}
const cache = new SwapKitCache();
// Use cache for pricesconst price = await cache.get( `price_${asset}`, 'price', async () => { const { SwapKitApi } = await import('@swapkit/helpers/api'); return SwapKitApi.getPrice(asset); });
Batch Operations
Section titled “Batch Operations”Batch multiple operations for efficiency:
import { Chain } from '@swapkit/sdk';
// ❌ Inefficient - multiple sequential callsconst ethBalance = await swapKit.getBalance(Chain.Ethereum);const btcBalance = await swapKit.getBalance(Chain.Bitcoin);const avaxBalance = await swapKit.getBalance(Chain.Avalanche);
// ✅ Efficient - parallel callsconst chains = [Chain.Ethereum, Chain.Bitcoin, Chain.Avalanche];const balances = await Promise.all( chains.map(chain => swapKit.getBalance(chain)));
Error Handling
Section titled “Error Handling”Comprehensive Error Handling
Section titled “Comprehensive Error Handling”Handle all error scenarios gracefully:
import { SwapKitError } from '@swapkit/sdk';
async function executeSwap(route: SwapRoute) { try { const txHash = await swapKit.swap({ route }); return { success: true, txHash }; } catch (error) { if (error instanceof SwapKitError) { // Handle SwapKit-specific errors switch (error.code) { case 'wallet_not_connected': return { success: false, error: 'Please connect your wallet' };
case 'insufficient_balance': return { success: false, error: 'Insufficient balance' };
case 'user_rejected': return { success: false, error: 'Transaction cancelled' };
default: return { success: false, error: error.message }; } }
// Handle network errors if (error instanceof Error && error.message.includes('network')) { return { success: false, error: 'Network error. Please try again.' }; }
// Unknown error console.error('Unexpected error:', error); return { success: false, error: 'An unexpected error occurred' }; }}
Retry Logic
Section titled “Retry Logic”Implement retry logic for transient failures:
import { SwapKitError, SwapKitApi } from '@swapkit/sdk';
async function withRetry<T>( fn: () => Promise<T>, options = { maxRetries: 3, delay: 1000 }): Promise<T> { let lastError: Error;
for (let i = 0; i <= options.maxRetries; i++) { try { return await fn(); } catch (error) { lastError = error as Error;
// Don't retry user rejections if (error instanceof SwapKitError && error.code === 'user_rejected') { throw error; }
// Wait before retry if (i < options.maxRetries) { await new Promise(r => setTimeout(r, options.delay * (i + 1))); } } }
throw lastError!;}
// Usageconst quote = await withRetry(() => SwapKitApi.getSwapQuote(params));
User Experience
Section titled “User Experience”Loading States
Section titled “Loading States”Provide clear feedback during operations:
import { SwapKitApi } from '@swapkit/sdk';
interface SwapState { status: 'idle' | 'quoting' | 'approving' | 'swapping' | 'success' | 'error'; message?: string; txHash?: string;}
async function performSwap(params: SwapParams) { const setState = (update: Partial<SwapState>) => { // Update UI state };
try { setState({ status: 'quoting', message: 'Getting best price...' }); const quote = await SwapKitApi.getSwapQuote(params);
setState({ status: 'approving', message: 'Approving tokens...' }); await swapKit.approveAssetValue(assetValue);
setState({ status: 'swapping', message: 'Executing swap...' }); const txHash = await swapKit.swap({ route: quote.routes[0] });
setState({ status: 'success', txHash }); } catch (error) { setState({ status: 'error', message: getErrorMessage(error) }); }}
Transaction Tracking
Section titled “Transaction Tracking”Track transaction status for better UX:
import { Chain, SwapKitApi } from '@swapkit/sdk';
async function trackTransaction( txHash: string, chain: Chain, onUpdate: (status: string) => void) { let attempts = 0; const maxAttempts = 60; // 5 minutes with 5s intervals
while (attempts < maxAttempts) { try { const status = await SwapKitApi.getTrackerDetails({ txHash, chain });
onUpdate(status.status);
if (status.status === 'completed' || status.status === 'failed') { return status; } } catch (error) { // Continue polling }
await new Promise(r => setTimeout(r, 5000)); attempts++; }
throw new Error('Transaction tracking timeout');}
Network Configuration
Section titled “Network Configuration”RPC Failover
Section titled “RPC Failover”Configure multiple RPC endpoints for reliability:
import { Chain } from '@swapkit/sdk';
const rpcEndpoints = { [Chain.Ethereum]: [ process.env.ALCHEMY_ETH_RPC, process.env.INFURA_ETH_RPC, 'https://eth.llamarpc.com' ].filter(Boolean),
[Chain.Avalanche]: [ process.env.AVALANCHE_RPC, 'https://api.avax.network/ext/bc/C/rpc' ].filter(Boolean)};
// Failover logicasync function getRpcUrl(chain: Chain): Promise<string> { const endpoints = rpcEndpoints[chain] || [];
for (const url of endpoints) { try { const response = await fetch(url, { method: 'POST', body: JSON.stringify({ method: 'eth_blockNumber', params: [], id: 1 }) });
if (response.ok) return url; } catch { continue; } }
throw new Error(`No working RPC for ${chain}`);}
Gas Price Management
Section titled “Gas Price Management”Implement dynamic gas pricing:
import { SwapKitApi } from '@swapkit/sdk';
async function getOptimalGasPrice(chain: Chain, priority: 'low' | 'medium' | 'high') { const gasRates = await SwapKitApi.getGasRate(chain);
const multipliers = { low: 0.9, medium: 1.0, high: 1.5 };
return { maxFeePerGas: Math.floor(Number(gasRates) * multipliers[priority]), maxPriorityFeePerGas: Math.floor(Number(gasRates) * 0.1) };}
Monitoring & Analytics
Section titled “Monitoring & Analytics”Error Tracking
Section titled “Error Tracking”Track errors for debugging:
import * as Sentry from '@sentry/browser';
// Initialize error trackingSentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV, beforeSend(event, hint) { // Don't send sensitive data if (event.request?.data) { delete event.request.data.privateKey; delete event.request.data.mnemonic; } return event; }});
// Track SwapKit errorstry { await swapKit.swap({ route });} catch (error) { Sentry.captureException(error, { tags: { chain: route.sellAsset.split('.')[0], provider: route.providers[0] } }); throw error;}
Performance Monitoring
Section titled “Performance Monitoring”Track key metrics:
class PerformanceTracker { track(operation: string, fn: () => Promise<any>) { const start = performance.now();
return fn() .then(result => { const duration = performance.now() - start; this.report(operation, duration, 'success'); return result; }) .catch(error => { const duration = performance.now() - start; this.report(operation, duration, 'error'); throw error; }); }
private report(operation: string, duration: number, status: string) { // Send to analytics analytics.track('operation_performance', { operation, duration, status }); }}
const tracker = new PerformanceTracker();
// Track swap performanceconst txHash = await tracker.track('swap', () => swapKit.swap({ route }));
Deployment Checklist
Section titled “Deployment Checklist”Before deploying to production:
- Environment Variables: All sensitive data in env vars
- API Keys: Production keys configured, not exposed
- Error Handling: Comprehensive error handling implemented
- Monitoring: Error tracking and analytics configured
- Performance: Bundle optimized, lazy loading implemented
- Security: Input validation, secure wallet handling
- Testing: All features tested on mainnet
- Documentation: User guides and FAQs prepared
- Support: Support channels established
- Compliance: Legal requirements addressed
Testing in Production
Section titled “Testing in Production”Gradual Rollout
Section titled “Gradual Rollout”// Feature flags for gradual rolloutconst features = { newSwapFlow: process.env.ENABLE_NEW_SWAP_FLOW === 'true', thorchainSwaps: process.env.ENABLE_THORCHAIN === 'true'};
if (features.newSwapFlow) { // New implementation} else { // Stable implementation}
Health Checks
Section titled “Health Checks”import { SwapKitApi, Chain } from '@swapkit/sdk';
async function healthCheck(): Promise<HealthStatus> { const checks = await Promise.allSettled([ SwapKitApi.getPrice('ETH'), swapKit.validateAddress('0x...', Chain.Ethereum), fetch(rpcUrls[Chain.Ethereum]) ]);
return { api: checks[0].status === 'fulfilled', sdk: checks[1].status === 'fulfilled', rpc: checks[2].status === 'fulfilled' };}
Next Steps
Section titled “Next Steps”- Review Security considerations
- Implement Advanced Features
- Monitor performance with API Reference