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:

// @noErrorValidation
const quote = await fetch("/api/swap-quote", {
method: "POST",
body: JSON.stringify(quoteParams),
}).then((r) => r.json());
import { SwapKitApi } from "@swapkit/helpers/api";
export async function POST(request: Request) {
const params = await request.json();
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:

// @noErrorValidation
import { createSwapKit, Chain } from "@swapkit/sdk";
const swapKit = createSwapKit();
await swapKit.connectEVMWallet([Chain.Ethereum]);
await swapKit.connectLedger([Chain.Bitcoin]);
await swapKit.connectTrezor([Chain.Bitcoin]);

Always validate user inputs:

// @noErrorValidation
import { createSwapKit, SwapKitError } from "@swapkit/sdk";
const swapKit = createSwapKit();
function validateSwapInput(params: any): void {
if (!swapKit.validateAddress(params.recipient, params.chain)) {
throw new SwapKitError("core_swap_invalid_params", {
message: "Invalid recipient address",
});
}
const amount = Number(params.amount);
if (isNaN(amount) || amount <= 0) {
throw new SwapKitError("core_swap_invalid_params", {
message: "Invalid amount",
});
}
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 { SwapKit, ThorchainPlugin, evmWallet } from '@swapkit/sdk';
const swapKit = SwapKit({
plugins: { ...ThorchainPlugin },
wallets: { ...evmWallet }
});

Load toolboxes only when needed:

// @noErrorValidation
import { Chain } from "@swapkit/sdk";
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:

// @noErrorValidation
class SwapKitCache {
private cache = new Map<string, { data: any; timestamp: number }>();
private ttl: Record<string, number> = {
price: 60_000,
balance: 300_000,
quote: 30_000,
};
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();
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:

// @noErrorValidation
import { Chain } from "@swapkit/sdk";
const ethBalance = await swapKit.getBalance(Chain.Ethereum);
const btcBalance = await swapKit.getBalance(Chain.Bitcoin);
const avaxBalance = await swapKit.getBalance(Chain.Avalanche);
const chains = [Chain.Ethereum, Chain.Bitcoin, Chain.Avalanche];
const balances = await Promise.all(
chains.map((chain) => swapKit.getBalance(chain))
);

Handle all error scenarios gracefully:

// @noErrorValidation
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) {
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 };
}
}
if (error instanceof Error && error.message.includes("network")) {
return { success: false, error: "Network error. Please try again." };
}
console.error("Unexpected error:", error);
return { success: false, error: "An unexpected error occurred" };
}
}

Implement retry logic for transient failures:

// @noErrorValidation
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;
if (error instanceof SwapKitError && error.code === "user_rejected") {
throw error;
}
if (i < options.maxRetries) {
await new Promise((r) => setTimeout(r, options.delay * (i + 1)));
}
}
}
throw lastError!;
}
const quote = await withRetry(() => SwapKitApi.getSwapQuote(params));

Provide clear feedback during operations:

// @noErrorValidation
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>) => {};
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:

// @noErrorValidation
import { Chain, SwapKitApi } from "@swapkit/sdk";
async function trackTransaction(
txHash: string,
chain: Chain,
onUpdate: (status: string) => void
) {
let attempts = 0;
const maxAttempts = 60;
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) {}
await new Promise((r) => setTimeout(r, 5000));
attempts++;
}
throw new Error("Transaction tracking timeout");
}

Configure multiple RPC endpoints for reliability:

// @noErrorValidation
import { Chain } from '@swapkit/sdk';
const rpcEndpoints = {
[Chain.Ethereum]: [
process.env.ALCHEMY_ETH_RPC,
process.env.INFURA_ETH_RPC,
'https:
].filter(Boolean),
[Chain.Avalanche]: [
process.env.AVALANCHE_RPC,
'https:
].filter(Boolean)
};
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:

// @noErrorValidation
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:

// @noErrorValidation
import * as Sentry from "@sentry/browser";
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
beforeSend(event, hint) {
if (event.request?.data) {
delete event.request.data.privateKey;
delete event.request.data.mnemonic;
}
return event;
},
});
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:

// @noErrorValidation
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) {
analytics.track("operation_performance", {
operation,
duration,
status,
});
}
}
const tracker = new PerformanceTracker();
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
// @noErrorValidation
const features = {
newSwapFlow: process.env.ENABLE_NEW_SWAP_FLOW === "true",
thorchainSwaps: process.env.ENABLE_THORCHAIN === "true",
};
if (features.newSwapFlow) {
} else {
}
// @noErrorValidation
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",
};
}