Skip to main content

Build Cross-Protocol Arbitrage Bot with Saros

Goal: Create a profitable arbitrage bot that automatically captures price differences between Saros and other DEXs
Arbitrage 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. Complete automated DLMM trading bot architecture showing data layer, strategy engine, and execution components Complete system architecture for advanced developers - this shows the full automated trading bot that we’ll build

Arbitrage Strategy Overview

Core Arbitrage Bot Implementation

1. Price Monitor Service

// 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

// 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

// 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

# .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

# 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

// 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 you
Monitoring: 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.