Files
mev-beta/docs/planning/02_PROTOCOL_SUPPORT_REQUIREMENTS.md
Administrator f41adbe575 docs: add comprehensive V2 requirements documentation
- Created MODULARITY_REQUIREMENTS.md with component independence rules
- Created PROTOCOL_SUPPORT_REQUIREMENTS.md covering 13+ protocols
- Created TESTING_REQUIREMENTS.md enforcing 100% coverage
- Updated CLAUDE.md with strict feature/v2/* branch strategy

Requirements documented:
- Component modularity (standalone + integrated)
- 100% test coverage enforcement (non-negotiable)
- All DEX protocols (Uniswap V2/V3/V4, Curve, Balancer V2/V3, Kyber Classic/Elastic, Camelot V2/V3 with all Algebra variants)
- Proper decimal handling (critical for calculations)
- Pool caching with multi-index and O(1) mappings
- Market building with essential arbitrage detection values
- Price movement detection with decimal precision
- Transaction building (single and batch execution)
- Pool discovery and caching
- Comprehensive validation at all layers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 14:26:56 +01:00

19 KiB

V2 Protocol Support Requirements

Critical Requirement: Complete Protocol Coverage

Every protocol MUST be parsed correctly with 100% accuracy and 100% test coverage.

Supported DEX Protocols (Complete List)

Uniswap Family

  1. Uniswap V2

    • Constant product AMM (x * y = k)
    • Event: Swap(address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to)
    • Pool info: token0, token1, reserves
    • Fee: 0.3% (30 basis points)
  2. Uniswap V3

    • Concentrated liquidity AMM
    • Event: Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)
    • Pool info: token0, token1, fee (500/3000/10000), tickSpacing, sqrtPriceX96, liquidity, tick
    • CRITICAL: Amounts are signed (int256), handle negative values correctly
  3. Uniswap V4 (planned)

    • Hooks-based architecture
    • Event: TBD (monitor for mainnet deployment)
    • Pool info: Dynamic based on hooks

Curve Finance

  1. Curve StableSwap
    • Stable asset AMM
    • Event: TokenExchange(address indexed buyer, int128 sold_id, uint256 tokens_sold, int128 bought_id, uint256 tokens_bought)
    • Pool info: coins array, A (amplification coefficient), fee
    • CRITICAL: Use int128 for token IDs, proper decimal handling

Balancer

  1. Balancer V2

    • Weighted pool AMM
    • Event: Swap(bytes32 indexed poolId, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut)
    • Pool info: poolId, tokens array, weights, swapFee
    • CRITICAL: Uses poolId instead of pool address
  2. Balancer V3 (if deployed on Arbitrum)

    • Next-gen weighted pools
    • Event: Monitor for deployment
    • Pool info: TBD

Kyber Network

  1. Kyber Classic

    • Dynamic reserve AMM
    • Event: KyberTrade(address indexed src, address indexed dest, uint srcAmount, uint dstAmount)
    • Pool info: reserveId, tokens, rate
  2. Kyber Elastic

    • Concentrated liquidity (similar to Uniswap V3)
    • Event: Swap(address indexed sender, address indexed recipient, int256 deltaQty0, int256 deltaQty1, uint160 sqrtP, uint128 liquidity, int24 currentTick)
    • Pool info: token0, token1, swapFeeUnits, tickDistance
    • CRITICAL: Different field names than Uniswap V3 but similar math

Camelot (Arbitrum Native)

  1. Camelot V2

    • Uniswap V2 fork with dynamic fees
    • Event: Swap(address indexed sender, uint amount0In, uint amount1In, uint amount0Out, uint amount1Out, address indexed to)
    • Pool info: token0, token1, stableSwap (boolean), fee0, fee1
    • CRITICAL: Fees can be different for token0 and token1
  2. Camelot V3 (Algebra V1)

    • Event: Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 price, uint128 liquidity, int24 tick)
    • Pool info: token0, token1, fee, tickSpacing (from factory)
    • Algebra V1 specific
  3. Camelot V3 (Algebra V1.9)

    • Enhanced Algebra with adaptive fees
    • Event: Same as Algebra V1 but with communityFee field
    • Pool info: token0, token1, fee, communityFee, tickSpacing
    • CRITICAL: Fee can be dynamic
  4. Camelot V3 (Algebra Integral)

    • Latest Algebra version with plugins
    • Event: Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 price, uint128 liquidity, int24 tick, uint16 fee)
    • Pool info: token0, token1, fee (in event!), tickSpacing, plugin address
    • CRITICAL: Fee is emitted in event, not stored in pool
  5. Camelot V3 (Algebra Directional - All Versions)

    • Directional liquidity (different fees for buy/sell)
    • Event: Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 price, uint128 liquidity, int24 tick, uint16 feeZeroToOne, uint16 feeOneToZero)
    • Pool info: token0, token1, feeZeroToOne, feeOneToZero, tickSpacing
    • CRITICAL: Two separate fees based on direction

Required Pool Information Extraction

For EVERY pool discovered, we MUST extract:

Essential Fields

  • address - Pool contract address
  • token0 - First token address (MUST NOT be zero address)
  • token1 - Second token address (MUST NOT be zero address)
  • protocol - Protocol type (UniswapV2, UniswapV3, etc.)
  • poolType - Pool type (ConstantProduct, Concentrated, StableSwap, etc.)

Protocol-Specific Fields

V2-Style (Uniswap V2, SushiSwap, Camelot V2)

  • reserve0 - Token0 reserves
  • reserve1 - Token1 reserves
  • fee - Fee in basis points (usually 30 = 0.3%)

V3-Style (Uniswap V3, Kyber Elastic, Camelot V3)

  • sqrtPriceX96 - Current price (Q64.96 format)
  • liquidity - Current liquidity
  • tick - Current tick
  • tickSpacing - Tick spacing (from factory)
  • fee - Fee tier (500/3000/10000) OR dynamic fee

Curve

  • A - Amplification coefficient
  • fee - Fee in basis points
  • coins - Array of coin addresses (can be > 2)

Balancer

  • poolId - Vault pool ID (bytes32)
  • tokens - Array of token addresses
  • weights - Array of token weights
  • swapFee - Swap fee percentage

Metadata Fields

  • factory - Factory contract that created this pool
  • createdBlock - Block number when pool was created
  • createdTx - Transaction hash of pool creation
  • lastUpdated - Timestamp of last update
  • token0Decimals - Decimals for token0 (CRITICAL for calculations)
  • token1Decimals - Decimals for token1 (CRITICAL for calculations)
  • token0Symbol - Symbol for token0 (for logging)
  • token1Symbol - Symbol for token1 (for logging)

Parsing Requirements

1. Sequencer Event Reading

type SequencerReader interface {
    // Subscribe to new blocks
    Subscribe(ctx context.Context) (<-chan *types.Block, error)

    // Get full transaction receipts
    GetReceipts(ctx context.Context, txHashes []common.Hash) ([]*types.Receipt, error)

    // Parse block for DEX transactions
    ParseBlock(block *types.Block) ([]*Transaction, error)
}

2. Multi-Protocol Parser

type ProtocolParser interface {
    // Identify if transaction is for this protocol
    IsProtocolTransaction(tx *types.Transaction) bool

    // Parse swap event
    ParseSwapEvent(log *types.Log) (*SwapEvent, error)

    // Parse mint/burn events
    ParseLiquidityEvent(log *types.Log) (*LiquidityEvent, error)

    // Extract pool info from logs
    ExtractPoolInfo(logs []*types.Log) (*PoolInfo, error)

    // Validate parsed data
    Validate(event *SwapEvent) error
}

type SwapEvent struct {
    PoolAddress    common.Address
    Token0         common.Address  // MUST NOT be zero
    Token1         common.Address  // MUST NOT be zero
    Amount0In      *big.Int       // MUST NOT be nil or zero (one of In/Out)
    Amount0Out     *big.Int
    Amount1In      *big.Int       // MUST NOT be nil or zero (one of In/Out)
    Amount1Out     *big.Int
    Sender         common.Address
    Recipient      common.Address
    TxHash         common.Hash
    BlockNumber    uint64
    LogIndex       uint
    Timestamp      uint64

    // V3-specific
    SqrtPriceX96   *big.Int
    Liquidity      *big.Int
    Tick           int24

    // Protocol identification
    Protocol       Protocol
    PoolType       PoolType
}

3. Amount Parsing Rules

CRITICAL: Proper Decimal Handling

// Example: Parse Uniswap V2 swap
func (p *UniswapV2Parser) ParseSwap(log *types.Log) (*SwapEvent, error) {
    // Decode event
    event := new(UniswapV2SwapEvent)
    err := p.abi.UnpackIntoInterface(event, "Swap", log.Data)

    // Get token decimals (CRITICAL!)
    poolInfo := p.cache.GetPool(log.Address)
    token0Decimals := poolInfo.Token0Decimals
    token1Decimals := poolInfo.Token1Decimals

    // MUST use proper decimal scaling
    amount0In := ScaleAmount(event.Amount0In, token0Decimals)
    amount0Out := ScaleAmount(event.Amount0Out, token0Decimals)
    amount1In := ScaleAmount(event.Amount1In, token1Decimals)
    amount1Out := ScaleAmount(event.Amount1Out, token1Decimals)

    return &SwapEvent{
        Amount0In: amount0In,
        Amount0Out: amount0Out,
        Amount1In: amount1In,
        Amount1Out: amount1Out,
    }
}

// Decimal scaling helper
func ScaleAmount(amount *big.Int, decimals uint8) *big.Int {
    // Scale to 18 decimals for internal representation
    scale := new(big.Int).Exp(
        big.NewInt(10),
        big.NewInt(int64(18 - decimals)),
        nil,
    )
    return new(big.Int).Mul(amount, scale)
}

Pool Discovery Requirements

1. Factory Event Monitoring

type PoolDiscovery interface {
    // Monitor factory for pool creation
    MonitorFactory(ctx context.Context, factoryAddress common.Address) error

    // Discover pools from transaction
    DiscoverFromTransaction(tx *types.Transaction, receipt *types.Receipt) ([]*PoolInfo, error)

    // Verify pool exists and get info
    VerifyPool(ctx context.Context, poolAddress common.Address) (*PoolInfo, error)

    // Save discovered pool
    SavePool(pool *PoolInfo) error
}

2. Pool Caching Strategy

type PoolCache interface {
    // Add pool to cache
    Add(pool *PoolInfo) error

    // Get pool by address (O(1))
    Get(address common.Address) (*PoolInfo, error)

    // Get pools by token pair (O(1))
    GetByTokenPair(token0, token1 common.Address) ([]*PoolInfo, error)

    // Get pools by protocol (O(1))
    GetByProtocol(protocol Protocol) ([]*PoolInfo, error)

    // Get top pools by liquidity
    GetTopByLiquidity(limit int) ([]*PoolInfo, error)

    // Update pool data
    Update(address common.Address, updates *PoolUpdates) error

    // Save to persistent storage
    SaveToDisk(path string) error

    // Load from persistent storage
    LoadFromDisk(path string) error
}

3. Market Building with Mapping

type MarketBuilder interface {
    // Build market from pools
    BuildMarket(pools []*PoolInfo) (*Market, error)

    // Update market on new swap
    UpdateOnSwap(market *Market, swap *SwapEvent) (*PriceMovement, error)

    // Get market by token pair (using mapping for O(1) access)
    GetMarket(token0, token1 common.Address) (*Market, error)
}

type Market struct {
    Token0    common.Address
    Token1    common.Address
    Pools     map[common.Address]*PoolState  // Mapping for O(1) access
    BestBid   *big.Float                      // Best price to buy token0
    BestAsk   *big.Float                      // Best price to sell token0
    MidPrice  *big.Float                      // Mid-market price
    Liquidity *big.Int                        // Total liquidity
    LastUpdate uint64                         // Timestamp
}

type PoolState struct {
    Address      common.Address
    Protocol     Protocol
    CurrentPrice *big.Float  // With proper decimals
    Reserve0     *big.Int
    Reserve1     *big.Int
    Fee          uint32

    // V3-specific
    SqrtPriceX96 *big.Int
    Liquidity    *big.Int
    Tick         int24
}

4. Price Movement Detection

type PriceMovement struct {
    Market       *Market
    OldPrice     *big.Float  // Before swap
    NewPrice     *big.Float  // After swap
    PriceChange  *big.Float  // Absolute change
    PercentMove  float64     // Percentage movement
    TriggeredBy  *SwapEvent
    Timestamp    uint64

    // Arbitrage opportunity flag
    IsArbitrageOpportunity bool
    ExpectedProfit        *big.Float
}

// CRITICAL: Proper decimal handling in price calculation
func CalculatePriceMovement(market *Market, swap *SwapEvent) (*PriceMovement, error) {
    oldPrice := market.MidPrice

    // Update pool state with proper decimals
    pool := market.Pools[swap.PoolAddress]
    pool.Reserve0 = new(big.Int).Sub(pool.Reserve0, swap.Amount0Out)
    pool.Reserve0 = new(big.Int).Add(pool.Reserve0, swap.Amount0In)
    pool.Reserve1 = new(big.Int).Sub(pool.Reserve1, swap.Amount1Out)
    pool.Reserve1 = new(big.Int).Add(pool.Reserve1, swap.Amount1In)

    // Calculate new price with EXACT decimal precision
    newPrice := CalculatePrice(pool.Reserve0, pool.Reserve1,
        market.Token0Decimals, market.Token1Decimals)

    // Calculate percentage movement
    priceChange := new(big.Float).Sub(newPrice, oldPrice)
    percentMove := new(big.Float).Quo(priceChange, oldPrice)
    percentMove.Mul(percentMove, big.NewFloat(100))

    percent, _ := percentMove.Float64()

    return &PriceMovement{
        Market:      market,
        OldPrice:    oldPrice,
        NewPrice:    newPrice,
        PriceChange: priceChange,
        PercentMove: percent,
        TriggeredBy: swap,
        Timestamp:   swap.Timestamp,
    }
}

Arbitrage Detection Requirements

1. Essential Market Values

type ArbitrageMarket struct {
    // Token pair
    TokenA common.Address
    TokenB common.Address

    // All pools for this pair
    Pools map[common.Address]*PoolState  // O(1) access

    // Price quotes from each pool
    Quotes map[common.Address]*Quote

    // Liquidity depth
    LiquidityDepth map[common.Address]*LiquidityBracket

    // Best execution path
    BestBuyPool  common.Address
    BestSellPool common.Address

    // Arbitrage opportunity
    SpreadPercent float64
    ExpectedProfit *big.Float
    OptimalAmount  *big.Int
}

type Quote struct {
    Pool         common.Address
    InputAmount  *big.Int
    OutputAmount *big.Int
    Price        *big.Float  // With exact decimals
    Fee          uint32
    Slippage     float64     // Expected slippage %
}

type LiquidityBracket struct {
    Pool      common.Address
    Amounts   []*big.Int  // Different trade sizes
    Outputs   []*big.Int  // Expected outputs
    Slippages []float64   // Slippage at each amount
}

2. Arbitrage Calculator

type ArbitrageCalculator interface {
    // Find arbitrage opportunities
    FindOpportunities(market *ArbitrageMarket) ([]*Opportunity, error)

    // Calculate optimal trade size
    CalculateOptimalSize(opp *Opportunity) (*big.Int, error)

    // Calculate expected profit (after gas)
    CalculateProfit(opp *Opportunity, tradeSize *big.Int) (*big.Float, error)

    // Build execution transaction
    BuildTransaction(opp *Opportunity, tradeSize *big.Int) (*types.Transaction, error)
}

type Opportunity struct {
    Market        *ArbitrageMarket
    BuyPool       common.Address
    SellPool      common.Address
    BuyPrice      *big.Float  // Exact decimals
    SellPrice     *big.Float  // Exact decimals
    Spread        float64     // Percentage
    OptimalAmount *big.Int
    ExpectedProfit *big.Float // After fees and gas
    GasCost       *big.Int
    NetProfit     *big.Float  // After ALL costs
    Confidence    float64     // 0-1 confidence score
}

Transaction Building Requirements

1. Single Execution

type SingleExecutor interface {
    // Execute single arbitrage trade
    Execute(ctx context.Context, opp *Opportunity) (*types.Transaction, error)

    // Build transaction data
    BuildTxData(opp *Opportunity) ([]byte, error)

    // Estimate gas
    EstimateGas(ctx context.Context, txData []byte) (uint64, error)

    // Sign and send
    SignAndSend(ctx context.Context, tx *types.Transaction) (common.Hash, error)
}

2. Batch Execution

type BatchExecutor interface {
    // Execute multiple arbitrage trades in one transaction
    BatchExecute(ctx context.Context, opps []*Opportunity) (*types.Transaction, error)

    // Build multicall data
    BuildMulticall(opps []*Opportunity) ([]byte, error)

    // Optimize batch order for maximum profit
    OptimizeBatchOrder(opps []*Opportunity) []*Opportunity

    // Calculate batch gas savings
    CalculateGasSavings(opps []*Opportunity) (*big.Int, error)
}

// Example multicall structure
type Multicall struct {
    Targets   []common.Address  // Contract addresses
    Calldatas [][]byte         // Call data for each
    Values    []*big.Int       // ETH value for each
}

Validation Requirements

1. Pool Data Validation

// MUST validate ALL fields
func ValidatePoolInfo(pool *PoolInfo) error {
    if pool.Address == (common.Address{}) {
        return errors.New("pool address is zero")
    }
    if pool.Token0 == (common.Address{}) {
        return errors.New("token0 is zero address")
    }
    if pool.Token1 == (common.Address{}) {
        return errors.New("token1 is zero address")
    }
    if pool.Token0 == pool.Token1 {
        return errors.New("token0 and token1 are the same")
    }
    if pool.Token0Decimals == 0 || pool.Token0Decimals > 18 {
        return errors.New("invalid token0 decimals")
    }
    if pool.Token1Decimals == 0 || pool.Token1Decimals > 18 {
        return errors.New("invalid token1 decimals")
    }

    // Protocol-specific validation
    switch pool.PoolType {
    case PoolTypeConstantProduct:
        if pool.Reserve0 == nil || pool.Reserve0.Sign() <= 0 {
            return errors.New("invalid reserve0")
        }
        if pool.Reserve1 == nil || pool.Reserve1.Sign() <= 0 {
            return errors.New("invalid reserve1")
        }
    case PoolTypeConcentrated:
        if pool.SqrtPriceX96 == nil || pool.SqrtPriceX96.Sign() <= 0 {
            return errors.New("invalid sqrtPriceX96")
        }
        if pool.Liquidity == nil || pool.Liquidity.Sign() < 0 {
            return errors.New("invalid liquidity")
        }
    }

    return nil
}

2. Swap Event Validation

func ValidateSwapEvent(event *SwapEvent) error {
    // Zero address checks
    if event.Token0 == (common.Address{}) {
        return errors.New("token0 is zero address")
    }
    if event.Token1 == (common.Address{}) {
        return errors.New("token1 is zero address")
    }
    if event.PoolAddress == (common.Address{}) {
        return errors.New("pool address is zero")
    }

    // Amount validation (at least one must be non-zero)
    hasAmount0 := (event.Amount0In != nil && event.Amount0In.Sign() > 0) ||
                  (event.Amount0Out != nil && event.Amount0Out.Sign() > 0)
    hasAmount1 := (event.Amount1In != nil && event.Amount1In.Sign() > 0) ||
                  (event.Amount1Out != nil && event.Amount1Out.Sign() > 0)

    if !hasAmount0 {
        return errors.New("both amount0In and amount0Out are zero")
    }
    if !hasAmount1 {
        return errors.New("both amount1In and amount1Out are zero")
    }

    // Logical validation (can't have both in and out for same token)
    if event.Amount0In != nil && event.Amount0In.Sign() > 0 &&
       event.Amount0Out != nil && event.Amount0Out.Sign() > 0 {
        return errors.New("amount0In and amount0Out both positive")
    }

    return nil
}

Testing Requirements

See 03_TESTING_REQUIREMENTS.md for comprehensive testing strategy.

Each parser MUST have:

  • Unit tests for all event types (100% coverage)
  • Integration tests with real Arbiscan data
  • Edge case tests (zero amounts, max values, etc.)
  • Decimal precision tests
  • Gas estimation tests

CRITICAL: All protocols must be supported. All decimals must be handled correctly. All validation must pass. No exceptions.