This document describes the math and algorithmic details of ArbitrageV's implementation, matching the codebase in src/graph.ts. It covers data structures, graph construction, CPMM swap formulas, dynamic programming arbitrage detection, and profit optimization.
export type PairInfo = {
pairAddress: Address; // AMM pool contract address
token0: Address; // Address of token0
token1: Address; // Address of token1
reserve0: bigint; // Liquidity reserve of token0
reserve1: bigint; // Liquidity reserve of token1
fee: number; // Trading fee in basis points (e.g., 30 for 0.3%)
};
interface Edge {
to: Address; // Destination token
pairAddress: Address;
direction: 'token0ToToken1' | 'token1ToToken0';
fee: number;
reserveIn: bigint;
reserveOut: bigint;
}ArbitrageV maintains a directed graph of AMM pools:
- For each
PairInfo, two edges are added:token0 → token1token1 → token0
Each edge stores the current reserves and fee for computing swap outputs.
The updateGraphEdges method dynamically adds or updates edges for a given PairInfo:
- Converts reserves to numbers and skips zero-liquidity pools.
- Tracks highest-reserve pair for each token using
tokenToHighestReservePair. - Maintains
edgeIndex(Map<${token}-${pairAddress}, Edge>) for O(1) updates. - Updates existing edges’
reserveIn,reserveOut, andfee, or creates newEdgeinstances.
The updatePairReservesBatch method processes multiple reserve updates efficiently:
- Accepts an array of
{ pairAddress, reserve0, reserve1 }updates. - Updates stored
PairInfoobjects and logs missing pairs. - Collects unique updated pairs and invokes
updateGraphEdgesper pair to refresh the graph.
For a given input amount amountIn, reserves reserveIn, reserveOut, and fee f (basis points):
-
Convert fee to multiplier:
feeMultiplier = 1 - f / 10000 -
Amount after fee:
amountInAfterFee = amountIn * feeMultiplier -
Output amount (constant-product formula):
amountOut = (amountInAfterFee * reserveOut) / (reserveIn + amountInAfterFee)
Arbitrage cycles are found via a DP-based method in findArbitrageOpportunities:
-
DP Table:
dp[step]maps each token to up toMAX_ENTRIES_PER_TOKENbest entries (DPEntry), where each entry has:amountOut: maximum output afterstepswaps starting from input 1path,pairs,directions: tracking the route
-
Initialization:
dp[0].set(startToken, [{ amountOut: 1.0, path: [startToken], pairs: [], directions: [] }]) -
Relaxation: For
step = 1..maxHops:- For each token in
dp[step-1], and each outgoing edge:- Skip if same pair used
- Compute new
amountOutvia CPMM formula - Insert into
dp[step][edge.to], keep top entries byamountOut
- For each token in
-
Recording Opportunities:
- Whenever
step ≥ 2andedge.to === startToken, record a circular arbitrage route. - The
findMultiTokenArbitrageOpportunitiesmethod generalizes this approach by seedingdp[0]with multiplestartTokens, tracking each entry’s origin (entry.path[0]), and recording cycles that return to their respective origin.
- Whenever
After raw cycles are found, each is validated and optimized:
-
Profit Function:
calculateProfit(inputAmount)simulates swaps along the cycle. -
Derivatives:
calculateJacobian: first derivative of profit w.r.t. inputcalculateHessian: second derivative
-
Optimal Input: A Newton–Raphson iteration finds
inputAmountmaximizing profit. -
Profit Condition: Only cycles with
profit > minProfitare kept.
For each validated opportunity, we return:
path: array of token addressespairs: array of pool addressesdirections: swap directionsoptimalInput: computed input amountprofit: max profit amount
src/graph.ts:findArbitrageOpportunities,createProfitFunctions- CPMM constant-product invariant:
x * y = k - DP pruning and maximum hops settings in
constants.ts