TradingAdvancedMEV

Building Arbitrage Bots on Solana

Architecture, implementation, and optimization strategies for cross-DEX arbitrage on Solana.

Updated March 202525 min read

Arbitrage on Solana

Arbitrage — profiting from price differences across markets — is one of the most technically demanding and competitive applications on Solana. The combination of fast block times, low fees, and a liquid DEX ecosystem creates abundant arbitrage opportunities, but the competition is fierce. Successful arbitrage bots require sub-millisecond decision making, optimal transaction construction, and sophisticated infrastructure.

Types of Arbitrage

TypeDescriptionComplexityCapital Required
Simple DEX arbitrageBuy on DEX A, sell on DEX BLowMedium
Triangular arbitrageA→B→C→A cycleMediumMedium
Flash loan arbitrageBorrow, arb, repay in one txHighNone
CEX-DEX arbitrageExploit CEX/DEX price gapsVery HighHigh
Liquidation arbitrageLiquidate undercollateralized positionsHighHigh

Architecture Overview

A production arbitrage bot consists of several components working in concert. The price monitor tracks prices across all relevant DEXes in real time using gRPC streaming. The opportunity detector calculates potential profits from price differences, accounting for fees and slippage. The transaction builder constructs optimized transactions. The executor submits transactions with appropriate priority fees.

Price Monitoring with gRPC

price-monitor.tstypescript
import Client, { CommitmentLevel } from "@triton-one/yellowstone-grpc";

const RAYDIUM_AMM = "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8";
const ORCA_WHIRLPOOL = "whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc";

interface PoolState {
  address: string;
  tokenA: string;
  tokenB: string;
  reserveA: bigint;
  reserveB: bigint;
  lastUpdate: number;
}

const pools = new Map<string, PoolState>();

async function startPriceMonitor(grpcEndpoint: string, token: string) {
  const client = new Client(grpcEndpoint, token, {});
  const stream = await client.subscribe();

  stream.write({
    accounts: {
      poolAccounts: {
        owner: [RAYDIUM_AMM, ORCA_WHIRLPOOL],
        filters: [],
      },
    },
    commitment: CommitmentLevel.PROCESSED,
  });

  stream.on("data", (data) => {
    if (data.account) {
      const update = data.account.account;
      const poolAddress = update?.pubkey?.toString();
      if (poolAddress) {
        const state = parsePoolState(update.data);
        pools.set(poolAddress, state);
        checkArbitrageOpportunity(poolAddress, state);
      }
    }
  });
}

function checkArbitrageOpportunity(updatedPool: string, state: PoolState) {
  // Find pools with the same token pair
  for (const [address, pool] of pools) {
    if (address === updatedPool) continue;
    if (pool.tokenA === state.tokenA && pool.tokenB === state.tokenB) {
      const priceA = Number(state.reserveB) / Number(state.reserveA);
      const priceB = Number(pool.reserveB) / Number(pool.reserveA);
      const spread = Math.abs(priceA - priceB) / Math.min(priceA, priceB);
      
      if (spread > 0.003) { // 0.3% minimum spread after fees
        executeArbitrage(updatedPool, address, state, pool, spread);
      }
    }
  }
}

Jupiter Integration for Optimal Routing

jupiter-arb.tstypescript
import { createJupiterApiClient } from "@jup-ag/api";

const jupiterApi = createJupiterApiClient();

async function getArbitrageRoute(
  inputMint: string,
  outputMint: string,
  amount: number
) {
  // Get quote for forward swap
  const forwardQuote = await jupiterApi.quoteGet({
    inputMint,
    outputMint,
    amount,
    slippageBps: 50, // 0.5% slippage
    onlyDirectRoutes: false,
  });

  // Get quote for reverse swap
  const reverseQuote = await jupiterApi.quoteGet({
    inputMint: outputMint,
    outputMint: inputMint,
    amount: Number(forwardQuote.outAmount),
    slippageBps: 50,
    onlyDirectRoutes: false,
  });

  const profit = Number(reverseQuote.outAmount) - amount;
  const profitBps = (profit / amount) * 10000;

  return { forwardQuote, reverseQuote, profit, profitBps };
}

Priority Fees and Transaction Optimization

In competitive arbitrage, transaction inclusion speed is critical. Solana's priority fee mechanism allows transactions to pay more to jump ahead in the queue. Setting the right priority fee is a balance: too low and your transaction may be delayed or dropped; too high and you erode your profit margin.

priority-fees.tstypescript
import { ComputeBudgetProgram, TransactionMessage, VersionedTransaction } from "@solana/web3.js";

async function buildOptimizedTransaction(
  instructions: TransactionInstruction[],
  payer: PublicKey,
  connection: Connection
) {
  // Get recent priority fees for similar transactions
  const recentFees = await connection.getRecentPrioritizationFees({
    lockedWritableAccounts: instructions.flatMap(ix => 
      ix.keys.filter(k => k.isWritable).map(k => k.pubkey)
    ),
  });

  // Use 75th percentile fee for competitive inclusion
  const sortedFees = recentFees.map(f => f.prioritizationFee).sort((a, b) => a - b);
  const p75Fee = sortedFees[Math.floor(sortedFees.length * 0.75)] || 1000;

  const computeBudgetIx = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: p75Fee,
  });

  const computeLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
    units: 200_000, // Estimate compute units needed
  });

  const { blockhash } = await connection.getLatestBlockhash('processed');
  
  const message = new TransactionMessage({
    payerKey: payer,
    recentBlockhash: blockhash,
    instructions: [computeBudgetIx, computeLimitIx, ...instructions],
  }).compileToV0Message();

  return new VersionedTransaction(message);
}

Jito Bundles for Atomic Execution

Jito's block engine allows submitting bundles of transactions that execute atomically — either all succeed or all fail. This is essential for arbitrage strategies that require multiple transactions to be profitable. Jito also provides MEV protection and allows tipping validators for priority inclusion.

ℹ️
Jito bundles are submitted to Jito-Solana validators, which represent approximately 60-70% of Solana's stake weight. Transactions in bundles are guaranteed to execute in order and atomically, eliminating sandwich attack risk.

Risk Management

Arbitrage bots face several risks beyond technical execution. Slippage can eliminate profits if pool state changes between opportunity detection and execution. Failed transactions still consume priority fees. Network congestion can delay execution. Implement strict profit thresholds, maximum position sizes, and circuit breakers to protect capital.