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
- Carbium API key (Get one here)
- Node.js 18+ or Python 3.8+
API Reference
Quote Endpoint
GET https://api.carbium.io/api/v2/quote
| Parameter | Type | Required | Description |
|---|---|---|---|
src_mint | string | Yes | Source token mint address |
dst_mint | string | Yes | Destination token mint address |
amount_in | number | Yes | Input amount in smallest units (lamports) |
slippage_bps | number | No | Slippage tolerance in basis points (default: 10) |
user_account | string | No | Wallet 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
- Latency Matters: Run this script as close to the Carbium server region as possible (Switzerland)
- 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.
- Parallel Execution: You can check multiple routes (SOL->USDC, SOL->USDT, SOL->SPDR) simultaneously using Promise.all in Node.js or asyncio in Python.
Updated about 5 hours ago
