Build a Solana Arbitrage Quote Engine

Build a simple arbitrage quote engine that detects circular arbitrage opportunities by quoting token-to-same-token routes through the Carbium DEX API.

Overview

Circular arbitrage finds profit opportunities by routing a token through intermediate tokens and back to itself. For example: SOL → USDC → SOL. If you receive more SOL than you started with, there's an arbitrage opportunity.

Prerequisites

API Reference

Quote Endpoint

GET https://api.carbium.io/api/v2/quote

ParameterTypeRequiredDescription
src_mintstringYesSource token mint address
dst_mintstringYesDestination token mint address
amount_innumberYesInput amount in smallest units (lamports)
slippage_bpsnumberNoSlippage tolerance in basis points (default: 10)
user_accountstringNoWallet address to receive executable transaction

Response

{
  "inAmount": "1000000000",
  "outAmount": "1005000000",
  "priceImpact": 0.01,
  "routePlan": [
    {
      "swap": "Raydium",
      "percent": 100
    }
  ]
}

Implementation

JavaScript (Node.js)

This script checks for a profit loop between SOL and USDC.

import fetch from 'node-fetch';
import dotenv from 'dotenv';

dotenv.config();

const API_KEY = process.env.CARBIUM_API_KEY;

// Token Mints
const SOL_MINT = 'So11111111111111111111111111111111111111112';
const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';

/**
 * Check for arbitrage opportunity
 * Route: SOL -> USDC -> SOL
 */
async function checkArbitrage(amountIn) {
  try {
    console.log(`Checking arb for ${amountIn} lamports...`);

    // Leg 1: SOL -> USDC
    const leg1 = await getQuote(SOL_MINT, USDC_MINT, amountIn);
    if (!leg1) return;
    const usdcAmount = leg1.outAmount;

    // Leg 2: USDC -> SOL
    const leg2 = await getQuote(USDC_MINT, SOL_MINT, usdcAmount);
    if (!leg2) return;
    const finalSolAmount = leg2.outAmount;

    // Calculate Profit
    const profit = finalSolAmount - amountIn;
    const isProfitable = profit > 0;

    console.log(`
    --- ARBITRAGE CHECK ---
    Input:  ${amountIn / 1e9} SOL
    Output: ${finalSolAmount / 1e9} SOL
    Profit: ${profit / 1e9} SOL
    Profitable: ${isProfitable ? '✅ YES' : '❌ NO'}
    `);

  } catch (error) {
    console.error('Arb check failed:', error.message);
  }
}

async function getQuote(src, dst, amount) {
  const params = new URLSearchParams({
    src_mint: src,
    dst_mint: dst,
    amount_in: amount
  });

  const response = await fetch(
    `https://api.carbium.io/api/v2/quote?${params}`,
    { headers: { 'X-API-KEY': API_KEY } }
  );

  if (!response.ok) return null;
  return await response.json();
}

// Run check for 1 SOL
checkArbitrage(1000000000);

Python

This script performs the same check using Python's requests library.

import os
import requests
from dotenv import load_dotenv

load_dotenv()

API_KEY = os.getenv('CARBIUM_API_KEY')
BASE_URL = "[https://api.carbium.io/api/v2/quote](https://api.carbium.io/api/v2/quote)"

SOL_MINT = "So11111111111111111111111111111111111111112"
USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"

def get_quote(src_mint, dst_mint, amount_in):
    params = {
        "src_mint": src_mint,
        "dst_mint": dst_mint,
        "amount_in": amount_in
    }
    headers = {"X-API-KEY": API_KEY}
    
    try:
        response = requests.get(BASE_URL, params=params, headers=headers)
        response.raise_for_status()
        return response.json()
    except requests.exceptions.RequestException as e:
        print(f"Quote failed: {e}")
        return None

def check_arbitrage(amount_sol_lamports):
    print(f"Checking arb for {amount_sol_lamports / 1e9} SOL...")

    # Leg 1: SOL -> USDC
    leg1 = get_quote(SOL_MINT, USDC_MINT, amount_sol_lamports)
    if not leg1: return
    
    usdc_amount = leg1['outAmount']
    
    # Leg 2: USDC -> SOL
    leg2 = get_quote(USDC_MINT, SOL_MINT, usdc_amount)
    if not leg2: return
    
    final_sol = int(leg2['outAmount'])
    start_sol = int(amount_sol_lamports)
    
    profit = final_sol - start_sol
    
    print(f"""
    --- ARBITRAGE RESULT ---
    Start: {start_sol / 1e9} SOL
    End:   {final_sol / 1e9} SOL
    Profit: {profit / 1e9} SOL
    Result: {'✅ PROFIT' if profit > 0 else '❌ LOSS'}
    """)

if __name__ == "__main__":
    # Check 1 SOL
    check_arbitrage(1000000000)

Strategy Tips

  1. Latency Matters: Run this script as close to the Carbium server region as possible (Switzerland)
  2. Slippage: For arbitrage, set slippage_bps very low (e.g., 5 or 10) to ensure you don't lose profit to price movement during execution.
  3. Parallel Execution: You can check multiple routes (SOL->USDC, SOL->USDT, SOL->SPDR) simultaneously using Promise.all in Node.js or asyncio in Python.

One-Step Closer to Decentralized MEV: Unlocking Circular Swaps
CARBIUM
Blog Post

One-Step Closer to Decentralized MEV: Unlocking Circular Swaps

Why Carbium supports circular arbitrage swaps ((TokenA) -> (TokenB) -> (TokenA)) and why other providers gatekeep them to protect their own MEV revenue.

Read article