Skip to content
SwapKit is a powerful suite of tools for building blockchain applications.

Production Best Practices

Building production-ready applications with SwapKit requires careful attention to security, performance, and user experience. This guide covers essential best practices.

For frontend applications, use a backend proxy to keep API keys secure:

// Frontend
const quote = await fetch('/api/swap-quote', {
method: 'POST',
body: JSON.stringify(quoteParams)
}).then(r => r.json());
// Backend API route
import { 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);
}

Always use secure wallet connections instead of handling private keys directly:

import { createSwapKit, Chain } from '@swapkit/sdk';
const swapKit = createSwapKit();
// ✅ Use secure wallet connections
await swapKit.connectEVMWallet([Chain.Ethereum]); // MetaMask
await swapKit.connectLedger([Chain.Bitcoin]); // Hardware wallet
await swapKit.connectTrezor([Chain.Bitcoin]); // Trezor wallet

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%'
});
}
}

Use the modular approach for smaller bundles:

// Import only what you need
import { SwapKit, ThorchainPlugin, evmWallet } from '@swapkit/sdk';
const swapKit = SwapKit({
plugins: { ...ThorchainPlugin },
wallets: { ...evmWallet }
});

Load toolboxes only when needed:

import { Chain } from '@swapkit/sdk';
// Lazy load toolboxes
async 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);
}
}

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 prices
const price = await cache.get(
`price_${asset}`,
'price',
async () => {
const { SwapKitApi } = await import('@swapkit/helpers/api');
return SwapKitApi.getPrice(asset);
}
);

Batch multiple operations for efficiency:

import { Chain } from '@swapkit/sdk';
// ❌ Inefficient - multiple sequential calls
const ethBalance = await swapKit.getBalance(Chain.Ethereum);
const btcBalance = await swapKit.getBalance(Chain.Bitcoin);
const avaxBalance = await swapKit.getBalance(Chain.Avalanche);
// ✅ Efficient - parallel calls
const chains = [Chain.Ethereum, Chain.Bitcoin, Chain.Avalanche];
const balances = await Promise.all(
chains.map(chain => swapKit.getBalance(chain))
);

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' };
}
}

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!;
}
// Usage
const quote = await withRetry(() =>
SwapKitApi.getSwapQuote(params)
);

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)
});
}
}

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');
}

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 logic
async 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}`);
}

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)
};
}

Track errors for debugging:

import * as Sentry from '@sentry/browser';
// Initialize error tracking
Sentry.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 errors
try {
await swapKit.swap({ route });
} catch (error) {
Sentry.captureException(error, {
tags: {
chain: route.sellAsset.split('.')[0],
provider: route.providers[0]
}
});
throw error;
}

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 performance
const txHash = await tracker.track('swap', () =>
swapKit.swap({ route })
);

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
// Feature flags for gradual rollout
const features = {
newSwapFlow: process.env.ENABLE_NEW_SWAP_FLOW === 'true',
thorchainSwaps: process.env.ENABLE_THORCHAIN === 'true'
};
if (features.newSwapFlow) {
// New implementation
} else {
// Stable implementation
}
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'
};
}