Build Cross-Protocol Arbitrage Bot with Saros
Goal: Create a profitable arbitrage bot that automatically captures price differences between Saros and other DEXsArbitrage opportunities exist constantly across Solana DEXs due to fragmented liquidity. This guide builds a production-ready bot that monitors price differences and executes profitable trades automatically.

Arbitrage Strategy Overview
Core Arbitrage Bot Implementation
1. Price Monitor Service
Copy
// services/PriceMonitor.ts
import { Connection, PublicKey } from '@solana/web3.js';
import { LiquidityBookServices } from '@saros-finance/dlmm-sdk';
interface PriceQuote {
protocol: string;
price: number;
amountOut: number;
priceImpact: number;
fees: number;
timestamp: number;
}
interface ArbitrageOpportunity {
buyProtocol: string;
sellProtocol: string;
buyPrice: number;
sellPrice: number;
spread: number;
profitUSD: number;
tradeAmount: number;
confidence: number;
}
class PriceMonitor {
private connection: Connection;
private sarosService: LiquidityBookServices;
private priceHistory: Map<string, PriceQuote[]> = new Map();
constructor(connection: Connection) {
this.connection = connection;
this.sarosService = new LiquidityBookServices(connection);
}
async getAllPrices(
tokenA: PublicKey,
tokenB: PublicKey,
amount: number
): Promise<PriceQuote[]> {
const quotes: PriceQuote[] = [];
// Get Saros DLMM price
try {
const sarosQuote = await this.getSarosPrice(tokenA, tokenB, amount);
quotes.push(sarosQuote);
} catch (error) {
console.warn('Saros price fetch failed:', error.message);
}
// Get Jupiter aggregated price
try {
const jupiterQuote = await this.getJupiterPrice(tokenA, tokenB, amount);
quotes.push(jupiterQuote);
} catch (error) {
console.warn('Jupiter price fetch failed:', error.message);
}
// Get Raydium price
try {
const raydiumQuote = await this.getRaydiumPrice(tokenA, tokenB, amount);
quotes.push(raydiumQuote);
} catch (error) {
console.warn('Raydium price fetch failed:', error.message);
}
// Get Orca price
try {
const orcaQuote = await this.getOrcaPrice(tokenA, tokenB, amount);
quotes.push(orcaQuote);
} catch (error) {
console.warn('Orca price fetch failed:', error.message);
}
return quotes;
}
private async getSarosPrice(
tokenA: PublicKey,
tokenB: PublicKey,
amount: number
): Promise<PriceQuote> {
const pairAddress = await this.sarosService.findBestPair(tokenA, tokenB);
const quote = await this.sarosService.getQuote({
pair: pairAddress,
tokenBase: tokenA,
tokenQuote: tokenB,
amount: BigInt(amount * 1e9), // Convert to lamports
swapForY: true,
isExactInput: true,
tokenBaseDecimal: 9,
tokenQuoteDecimal: 6,
slippage: 0.5
});
return {
protocol: 'saros',
price: Number(quote.amountOut) / amount,
amountOut: Number(quote.amountOut),
priceImpact: quote.priceImpact,
fees: Number(quote.fee || 0),
timestamp: Date.now()
};
}
private async getJupiterPrice(
tokenA: PublicKey,
tokenB: PublicKey,
amount: number
): Promise<PriceQuote> {
// Jupiter API integration
const response = await fetch(`https://quote-api.jup.ag/v6/quote?inputMint=${tokenA.toString()}&outputMint=${tokenB.toString()}&amount=${amount * 1e9}&slippageBps=50`);
const data = await response.json();
return {
protocol: 'jupiter',
price: Number(data.outAmount) / amount,
amountOut: Number(data.outAmount),
priceImpact: data.priceImpactPct || 0,
fees: 0, // Jupiter handles fees internally
timestamp: Date.now()
};
}
private async getRaydiumPrice(
tokenA: PublicKey,
tokenB: PublicKey,
amount: number
): Promise<PriceQuote> {
// Raydium SDK integration
// This is a simplified example - implement actual Raydium SDK calls
const mockPrice = 100 + (Math.random() - 0.5) * 2; // Simulated price variation
return {
protocol: 'raydium',
price: mockPrice,
amountOut: amount * mockPrice,
priceImpact: 0.1,
fees: amount * 0.0025, // 0.25% fee
timestamp: Date.now()
};
}
private async getOrcaPrice(
tokenA: PublicKey,
tokenB: PublicKey,
amount: number
): Promise<PriceQuote> {
// Orca SDK integration
// This is a simplified example - implement actual Orca SDK calls
const mockPrice = 100 + (Math.random() - 0.5) * 1.5; // Simulated price variation
return {
protocol: 'orca',
price: mockPrice,
amountOut: amount * mockPrice,
priceImpact: 0.15,
fees: amount * 0.003, // 0.3% fee
timestamp: Date.now()
};
}
}
## Arbitrage Detection Algorithm
<img
src="/images/arbitrage-algorithm.png"
alt="Cross-DEX arbitrage detection algorithm showing real-time monitoring and execution workflow"
width="1200"
height="800"
className="rounded-lg shadow-lg"
/>
*Detailed algorithm flow for cross-DEX opportunities - this visualizes the logic implemented below*
```typescript
class ArbitrageDetector {
findArbitrageOpportunities(quotes: PriceQuote[], tradeAmount: number): ArbitrageOpportunity[] {
const opportunities: ArbitrageOpportunity[] = [];
// Compare all pairs of protocols
for (let i = 0; i < quotes.length; i++) {
for (let j = 0; j < quotes.length; j++) {
if (i === j) continue;
const buyQuote = quotes[i];
const sellQuote = quotes[j];
// Check if it's profitable to buy on protocol i and sell on protocol j
const spread = sellQuote.price - buyQuote.price;
const spreadPercentage = (spread / buyQuote.price) * 100;
// Account for fees and slippage
const totalFees = buyQuote.fees + sellQuote.fees;
const netSpread = spread - totalFees;
const profitUSD = netSpread * tradeAmount;
// Only consider opportunities with meaningful profit and low price impact
if (profitUSD > 5 && // Minimum $5 profit
buyQuote.priceImpact < 2 && // Max 2% price impact
sellQuote.priceImpact < 2) {
opportunities.push({
buyProtocol: buyQuote.protocol,
sellProtocol: sellQuote.protocol,
buyPrice: buyQuote.price,
sellPrice: sellQuote.price,
spread: spreadPercentage,
profitUSD,
tradeAmount,
confidence: this.calculateConfidence(buyQuote, sellQuote)
});
}
}
}
// Sort by profit descending
return opportunities.sort((a, b) => b.profitUSD - a.profitUSD);
}
private calculateConfidence(buyQuote: PriceQuote, sellQuote: PriceQuote): number {
// Calculate confidence score based on various factors
let confidence = 100;
// Reduce confidence for high price impact
confidence -= Math.max(0, (buyQuote.priceImpact - 0.5) * 20);
confidence -= Math.max(0, (sellQuote.priceImpact - 0.5) * 20);
// Reduce confidence for stale quotes
const now = Date.now();
const buyAge = (now - buyQuote.timestamp) / 1000; // seconds
const sellAge = (now - sellQuote.timestamp) / 1000;
confidence -= Math.max(0, (buyAge - 5) * 2); // Reduce after 5 seconds
confidence -= Math.max(0, (sellAge - 5) * 2);
return Math.max(0, Math.min(100, confidence));
}
// Store price history for analysis
updatePriceHistory(tokenPair: string, quotes: PriceQuote[]) {
if (!this.priceHistory.has(tokenPair)) {
this.priceHistory.set(tokenPair, []);
}
const history = this.priceHistory.get(tokenPair)!;
history.push(...quotes);
// Keep only last 1000 quotes
if (history.length > 1000) {
history.splice(0, history.length - 1000);
}
}
getHistoricalSpread(tokenPair: string, protocol1: string, protocol2: string): number {
const history = this.priceHistory.get(tokenPair) || [];
const recentQuotes = history.slice(-20); // Last 20 quotes
const spreads: number[] = [];
for (const quote1 of recentQuotes.filter(q => q.protocol === protocol1)) {
const quote2 = recentQuotes
.filter(q => q.protocol === protocol2)
.find(q => Math.abs(q.timestamp - quote1.timestamp) < 10000); // Within 10 seconds
if (quote2) {
const spread = Math.abs(quote2.price - quote1.price) / quote1.price * 100;
spreads.push(spread);
}
}
return spreads.length > 0
? spreads.reduce((sum, spread) => sum + spread, 0) / spreads.length
: 0;
}
}
2. Arbitrage Executor
Copy
// services/ArbitrageExecutor.ts
import { Connection, Keypair, PublicKey, Transaction } from '@solana/web3.js';
import { PriceMonitor, ArbitrageOpportunity } from './PriceMonitor';
interface TradeResult {
success: boolean;
profit: number;
error?: string;
buyTxHash?: string;
sellTxHash?: string;
executionTime: number;
}
class ArbitrageExecutor {
private connection: Connection;
private wallet: Keypair;
private priceMonitor: PriceMonitor;
private isExecuting: boolean = false;
private maxPositionSize: number = 1000; // Max $1000 per trade
private minProfitThreshold: number = 10; // Min $10 profit
constructor(connection: Connection, wallet: Keypair) {
this.connection = connection;
this.wallet = wallet;
this.priceMonitor = new PriceMonitor(connection);
}
async executeArbitrage(opportunity: ArbitrageOpportunity): Promise<TradeResult> {
if (this.isExecuting) {
return { success: false, error: 'Already executing trade', profit: 0, executionTime: 0 };
}
this.isExecuting = true;
const startTime = Date.now();
try {
// Pre-execution validation
await this.validateOpportunity(opportunity);
// Execute buy trade first (usually the faster one)
const buyResult = await this.executeBuyTrade(opportunity);
if (!buyResult.success) {
throw new Error(`Buy trade failed: ${buyResult.error}`);
}
// Execute sell trade
const sellResult = await this.executeSellTrade(opportunity);
if (!sellResult.success) {
// If sell fails, we're stuck with tokens - implement recovery logic
console.error('Sell trade failed after successful buy - manual intervention needed');
await this.handleFailedArbitrage(opportunity, buyResult.txHash);
throw new Error(`Sell trade failed: ${sellResult.error}`);
}
const executionTime = Date.now() - startTime;
const actualProfit = await this.calculateActualProfit(buyResult, sellResult);
console.log(`✅ Arbitrage completed: $${actualProfit.toFixed(2)} profit in ${executionTime}ms`);
return {
success: true,
profit: actualProfit,
buyTxHash: buyResult.txHash,
sellTxHash: sellResult.txHash,
executionTime
};
} catch (error: any) {
console.error('Arbitrage execution failed:', error.message);
return {
success: false,
error: error.message,
profit: 0,
executionTime: Date.now() - startTime
};
} finally {
this.isExecuting = false;
}
}
private async validateOpportunity(opportunity: ArbitrageOpportunity): Promise<void> {
// Check wallet balances
const balance = await this.connection.getBalance(this.wallet.publicKey);
if (balance < opportunity.tradeAmount * 1.1) { // 10% buffer for fees
throw new Error('Insufficient balance for trade');
}
// Revalidate prices (opportunities can disappear quickly)
const currentQuotes = await this.priceMonitor.getAllPrices(
new PublicKey('So11111111111111111111111111111111111111112'), // SOL
new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'), // USDC
opportunity.tradeAmount
);
const buyQuote = currentQuotes.find(q => q.protocol === opportunity.buyProtocol);
const sellQuote = currentQuotes.find(q => q.protocol === opportunity.sellProtocol);
if (!buyQuote || !sellQuote) {
throw new Error('Price quotes unavailable - opportunity expired');
}
const currentSpread = sellQuote.price - buyQuote.price;
const expectedSpread = opportunity.sellPrice - opportunity.buyPrice;
// Allow 5% tolerance for price movement
if (currentSpread < expectedSpread * 0.95) {
throw new Error('Opportunity expired - spread decreased');
}
// Check for excessive slippage
if (buyQuote.priceImpact > 3 || sellQuote.priceImpact > 3) {
throw new Error('Price impact too high');
}
}
private async executeBuyTrade(opportunity: ArbitrageOpportunity): Promise<{ success: boolean, txHash?: string, error?: string, amountReceived?: number }> {
try {
console.log(`Executing buy on ${opportunity.buyProtocol}...`);
switch (opportunity.buyProtocol) {
case 'saros':
return await this.executeSarosTrade(opportunity.tradeAmount, 'buy');
case 'jupiter':
return await this.executeJupiterTrade(opportunity.tradeAmount, 'buy');
case 'raydium':
return await this.executeRaydiumTrade(opportunity.tradeAmount, 'buy');
case 'orca':
return await this.executeOrcaTrade(opportunity.tradeAmount, 'buy');
default:
throw new Error(`Unsupported protocol: ${opportunity.buyProtocol}`);
}
} catch (error: any) {
return { success: false, error: error.message };
}
}
private async executeSellTrade(opportunity: ArbitrageOpportunity): Promise<{ success: boolean, txHash?: string, error?: string, amountReceived?: number }> {
try {
console.log(`Executing sell on ${opportunity.sellProtocol}...`);
switch (opportunity.sellProtocol) {
case 'saros':
return await this.executeSarosTrade(opportunity.tradeAmount, 'sell');
case 'jupiter':
return await this.executeJupiterTrade(opportunity.tradeAmount, 'sell');
case 'raydium':
return await this.executeRaydiumTrade(opportunity.tradeAmount, 'sell');
case 'orca':
return await this.executeOrcaTrade(opportunity.tradeAmount, 'sell');
default:
throw new Error(`Unsupported protocol: ${opportunity.sellProtocol}`);
}
} catch (error: any) {
return { success: false, error: error.message };
}
}
private async executeSarosTrade(amount: number, side: 'buy' | 'sell'): Promise<{ success: boolean, txHash?: string, error?: string, amountReceived?: number }> {
try {
const sarosService = new LiquidityBookServices(this.connection);
const pairAddress = await sarosService.findBestPair(
new PublicKey('So11111111111111111111111111111111111111112'), // SOL
new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v') // USDC
);
const transaction = await sarosService.swap({
pair: pairAddress,
tokenBase: side === 'buy'
? new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v') // USDC
: new PublicKey('So11111111111111111111111111111111111111112'), // SOL
tokenQuote: side === 'buy'
? new PublicKey('So11111111111111111111111111111111111111112') // SOL
: new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'), // USDC
amount: BigInt(amount * (side === 'buy' ? 1e6 : 1e9)), // Adjust for decimals
swapForY: side === 'buy',
isExactInput: true,
wallet: this.wallet.publicKey,
slippage: 0.5
});
// Sign and send transaction
transaction.sign([this.wallet]);
const txHash = await this.connection.sendRawTransaction(transaction.serialize());
// Confirm transaction
const confirmation = await this.connection.confirmTransaction(txHash, 'confirmed');
if (confirmation.value.err) {
throw new Error(`Transaction failed: ${confirmation.value.err}`);
}
return {
success: true,
txHash,
amountReceived: amount // Simplified - would parse actual amount from logs
};
} catch (error: any) {
return { success: false, error: error.message };
}
}
private async executeJupiterTrade(amount: number, side: 'buy' | 'sell'): Promise<{ success: boolean, txHash?: string, error?: string, amountReceived?: number }> {
try {
// Jupiter swap implementation
const inputMint = side === 'buy'
? 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' // USDC
: 'So11111111111111111111111111111111111111112'; // SOL
const outputMint = side === 'buy'
? 'So11111111111111111111111111111111111111112' // SOL
: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'; // USDC
// Get Jupiter quote
const quoteResponse = await fetch(
`https://quote-api.jup.ag/v6/quote?inputMint=${inputMint}&outputMint=${outputMint}&amount=${amount * (side === 'buy' ? 1e6 : 1e9)}&slippageBps=50`
);
const quoteData = await quoteResponse.json();
// Get Jupiter swap transaction
const swapResponse = await fetch('https://quote-api.jup.ag/v6/swap', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
quoteResponse: quoteData,
userPublicKey: this.wallet.publicKey.toString(),
wrapAndUnwrapSol: true
})
});
const swapData = await swapResponse.json();
// Execute transaction
const transaction = Transaction.from(Buffer.from(swapData.swapTransaction, 'base64'));
transaction.sign(this.wallet);
const txHash = await this.connection.sendRawTransaction(transaction.serialize());
const confirmation = await this.connection.confirmTransaction(txHash, 'confirmed');
if (confirmation.value.err) {
throw new Error(`Jupiter swap failed: ${confirmation.value.err}`);
}
return {
success: true,
txHash,
amountReceived: Number(quoteData.outAmount) / (side === 'buy' ? 1e9 : 1e6)
};
} catch (error: any) {
return { success: false, error: error.message };
}
}
private async executeRaydiumTrade(amount: number, side: 'buy' | 'sell'): Promise<{ success: boolean, txHash?: string, error?: string, amountReceived?: number }> {
// Implement Raydium trade execution
// This would require Raydium SDK integration
// For now, return mock successful execution
return {
success: true,
txHash: 'mock_raydium_tx_' + Date.now(),
amountReceived: amount * (side === 'buy' ? 100 : 0.01) // Mock conversion
};
}
private async executeOrcaTrade(amount: number, side: 'buy' | 'sell'): Promise<{ success: boolean, txHash?: string, error?: string, amountReceived?: number }> {
// Implement Orca trade execution
// This would require Orca SDK integration
// For now, return mock successful execution
return {
success: true,
txHash: 'mock_orca_tx_' + Date.now(),
amountReceived: amount * (side === 'buy' ? 100 : 0.01) // Mock conversion
};
}
private async calculateActualProfit(buyResult: any, sellResult: any): Promise<number> {
// Calculate actual profit based on transaction results
// This would parse transaction logs to get exact amounts
const buyAmount = buyResult.amountReceived || 0;
const sellAmount = sellResult.amountReceived || 0;
// Simplified profit calculation
return sellAmount - buyAmount;
}
private async handleFailedArbitrage(opportunity: ArbitrageOpportunity, buyTxHash?: string) {
// Implement recovery logic for failed arbitrage
// This might involve:
// 1. Attempting to sell tokens at market price
// 2. Holding tokens and trying again later
// 3. Sending alert to operator
console.error('Arbitrage failed - implementing recovery strategy');
// Send alert about manual intervention needed
// This would integrate with your alerting system
}
}
3. Main Arbitrage Bot
Copy
// ArbitrageBot.ts
import { Connection, Keypair } from '@solana/web3.js';
import { PriceMonitor } from './services/PriceMonitor';
import { ArbitrageExecutor } from './services/ArbitrageExecutor';
interface BotConfig {
rpcEndpoint: string;
privateKey: string;
monitoringInterval: number; // milliseconds
minProfitUSD: number;
maxPositionSize: number;
enabledProtocols: string[];
}
class ArbitrageBot {
private connection: Connection;
private wallet: Keypair;
private priceMonitor: PriceMonitor;
private executor: ArbitrageExecutor;
private config: BotConfig;
private isRunning: boolean = false;
private stats = {
totalTrades: 0,
successfulTrades: 0,
totalProfit: 0,
startTime: Date.now()
};
constructor(config: BotConfig) {
this.config = config;
this.connection = new Connection(config.rpcEndpoint);
this.wallet = Keypair.fromSecretKey(
Uint8Array.from(JSON.parse(config.privateKey))
);
this.priceMonitor = new PriceMonitor(this.connection);
this.executor = new ArbitrageExecutor(this.connection, this.wallet);
console.log(`🤖 Arbitrage Bot initialized`);
console.log(`📍 Wallet: ${this.wallet.publicKey.toString()}`);
console.log(`⚙️ Min Profit: $${config.minProfitUSD}`);
console.log(`📊 Protocols: ${config.enabledProtocols.join(', ')}`);
}
async start() {
if (this.isRunning) {
console.log('Bot is already running');
return;
}
this.isRunning = true;
console.log('🚀 Starting arbitrage bot...');
// Monitor key trading pairs
const tradingPairs = [
{
tokenA: 'So11111111111111111111111111111111111111112', // SOL
tokenB: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
symbol: 'SOL/USDC',
tradeSize: 100 // $100 trades
},
{
tokenA: 'So11111111111111111111111111111111111111112', // SOL
tokenB: 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So', // mSOL
symbol: 'SOL/mSOL',
tradeSize: 50 // $50 trades
}
];
// Start monitoring loop
while (this.isRunning) {
try {
for (const pair of tradingPairs) {
await this.monitorPair(pair);
// Small delay between pairs to avoid overwhelming RPCs
await this.sleep(100);
}
// Display stats periodically
if (this.stats.totalTrades % 100 === 0 && this.stats.totalTrades > 0) {
this.displayStats();
}
// Wait before next monitoring cycle
await this.sleep(this.config.monitoringInterval);
} catch (error) {
console.error('Bot error:', error);
// Wait longer on errors to avoid spam
await this.sleep(this.config.monitoringInterval * 2);
}
}
}
async stop() {
console.log('🛑 Stopping arbitrage bot...');
this.isRunning = false;
this.displayFinalStats();
}
private async monitorPair(pair: any) {
try {
// Get prices from all protocols
const quotes = await this.priceMonitor.getAllPrices(
pair.tokenA,
pair.tokenB,
pair.tradeSize
);
// Filter quotes from enabled protocols
const enabledQuotes = quotes.filter(q =>
this.config.enabledProtocols.includes(q.protocol)
);
if (enabledQuotes.length < 2) {
return; // Need at least 2 protocols for arbitrage
}
// Find arbitrage opportunities
const opportunities = this.priceMonitor.findArbitrageOpportunities(
enabledQuotes,
pair.tradeSize
);
// Store price history
this.priceMonitor.updatePriceHistory(pair.symbol, quotes);
// Execute the best opportunity if profitable
for (const opportunity of opportunities) {
if (opportunity.profitUSD >= this.config.minProfitUSD &&
opportunity.confidence > 70) {
console.log(`💰 Opportunity found: ${pair.symbol}`);
console.log(` Buy: ${opportunity.buyProtocol} @ $${opportunity.buyPrice.toFixed(4)}`);
console.log(` Sell: ${opportunity.sellProtocol} @ $${opportunity.sellPrice.toFixed(4)}`);
console.log(` Profit: $${opportunity.profitUSD.toFixed(2)} (${opportunity.spread.toFixed(2)}%)`);
console.log(` Confidence: ${opportunity.confidence.toFixed(0)}%`);
// Execute arbitrage
const result = await this.executor.executeArbitrage(opportunity);
this.recordTradeResult(result);
if (result.success) {
console.log(`✅ Arbitrage executed: $${result.profit.toFixed(2)} profit`);
break; // Only execute one opportunity per cycle
} else {
console.log(`❌ Arbitrage failed: ${result.error}`);
}
}
}
} catch (error) {
console.error(`Error monitoring ${pair.symbol}:`, error.message);
}
}
private recordTradeResult(result: any) {
this.stats.totalTrades++;
if (result.success) {
this.stats.successfulTrades++;
this.stats.totalProfit += result.profit;
}
}
private displayStats() {
const runtime = (Date.now() - this.stats.startTime) / 1000 / 60; // minutes
const successRate = (this.stats.successfulTrades / Math.max(this.stats.totalTrades, 1)) * 100;
console.log('\n📊 Bot Statistics:');
console.log(`⏱️ Runtime: ${runtime.toFixed(1)} minutes`);
console.log(`📈 Total Trades: ${this.stats.totalTrades}`);
console.log(`✅ Successful: ${this.stats.successfulTrades} (${successRate.toFixed(1)}%)`);
console.log(`💰 Total Profit: $${this.stats.totalProfit.toFixed(2)}`);
console.log(`📊 Profit/Min: $${(this.stats.totalProfit / Math.max(runtime, 1)).toFixed(2)}`);
console.log('');
}
private displayFinalStats() {
console.log('\n🏁 Final Bot Statistics:');
this.displayStats();
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage
const botConfig: BotConfig = {
rpcEndpoint: process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com',
privateKey: process.env.WALLET_PRIVATE_KEY || '',
monitoringInterval: 5000, // 5 seconds
minProfitUSD: 15, // $15 minimum profit
maxPositionSize: 1000, // $1000 max position
enabledProtocols: ['saros', 'jupiter', 'raydium', 'orca']
};
// Start the bot
const bot = new ArbitrageBot(botConfig);
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('\n⚠️ Received SIGINT, shutting down gracefully...');
await bot.stop();
process.exit(0);
});
process.on('SIGTERM', async () => {
console.log('\n⚠️ Received SIGTERM, shutting down gracefully...');
await bot.stop();
process.exit(0);
});
// Start the bot
bot.start().catch(console.error);
Production Deployment Configuration
1. Environment Variables
Copy
# .env.production
SOLANA_RPC_URL=https://your-premium-rpc-endpoint.com
WALLET_PRIVATE_KEY=[1,2,3,...] # Your wallet's private key array
MIN_PROFIT_USD=20
MAX_POSITION_SIZE=500
MONITORING_INTERVAL=3000
ENABLED_PROTOCOLS=saros,jupiter,raydium
# Monitoring and alerts
SLACK_WEBHOOK_URL=https://hooks.slack.com/...
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
# Database for logging trades
DATABASE_URL=postgresql://user:pass@host:5432/arbitrage_bot
2. Docker Configuration
Copy
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# Install dependencies
COPY package*.json ./
RUN npm ci --only=production
# Copy source code
COPY . .
# Build TypeScript
RUN npm run build
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
# Start the bot
CMD ["npm", "start"]
3. Monitoring Dashboard
Copy
// monitoring/dashboard.ts
app.get('/dashboard', (req, res) => {
const stats = bot.getStats();
res.json({
status: 'running',
uptime: process.uptime(),
trades: {
total: stats.totalTrades,
successful: stats.successfulTrades,
successRate: stats.successRate
},
profit: {
total: stats.totalProfit,
perHour: stats.profitPerHour,
perTrade: stats.avgProfitPerTrade
},
protocols: stats.protocolBreakdown,
lastOpportunity: stats.lastOpportunity
});
});
Risk Management & Safety
Position Sizing: Never risk more than 1% of capital per trade Stop Losses: Implement automatic stop losses if positions move against youMonitoring: Set up alerts for failed trades and unusual activity Diversification: Spread trades across multiple token pairs Testing: Always test on devnet before mainnet deployment This arbitrage bot provides a foundation for capturing cross-protocol price differences on Solana. The concentrated liquidity advantages of Saros DLMM often provide superior pricing, making it an excellent protocol for both buying and selling in arbitrage strategies.