# 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 4. **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 5. **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 6. **Balancer V3** (if deployed on Arbitrum) - Next-gen weighted pools - Event: Monitor for deployment - Pool info: TBD ### Kyber Network 7. **Kyber Classic** - Dynamic reserve AMM - Event: `KyberTrade(address indexed src, address indexed dest, uint srcAmount, uint dstAmount)` - Pool info: reserveId, tokens, rate 8. **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) 9. **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 10. **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 11. **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 12. **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 13. **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 ```go 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 ```go 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** ```go // 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go 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 ```go // 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 ```go 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.