package arbitrum import ( "encoding/json" "fmt" "os" "sync" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/fraktal/mev-beta/internal/logger" arbitrumCommon "github.com/fraktal/mev-beta/pkg/arbitrum/common" ) // ArbitrumProtocolRegistry manages all DEX protocols on Arbitrum type ArbitrumProtocolRegistry struct { logger *logger.Logger protocols map[string]*DEXProtocol mu sync.RWMutex // Event logging swapLogger *os.File liquidationLogger *os.File liquidityLogger *os.File } // DEXProtocol represents a complete DEX protocol configuration type DEXProtocol struct { Name string `json:"name"` Type string `json:"type"` // "uniswap_v2", "uniswap_v3", "curve", "balancer", etc. Routers []common.Address `json:"routers"` Factories []common.Address `json:"factories"` SwapFunctions map[string]string `json:"swap_functions"` EventSignatures map[string]common.Hash `json:"event_signatures"` PoolTypes []string `json:"pool_types"` FeeStructure map[string]interface{} `json:"fee_structure"` Active bool `json:"active"` Priority int `json:"priority"` // Higher = more important for MEV } // SwapEvent represents a detected swap for logging type SwapEvent struct { Timestamp time.Time `json:"timestamp"` BlockNumber uint64 `json:"block_number"` TxHash string `json:"tx_hash"` Protocol string `json:"protocol"` Router string `json:"router"` Pool string `json:"pool"` TokenIn string `json:"token_in"` TokenOut string `json:"token_out"` AmountIn string `json:"amount_in"` AmountOut string `json:"amount_out"` Sender string `json:"sender"` Recipient string `json:"recipient"` GasPrice string `json:"gas_price"` GasUsed uint64 `json:"gas_used"` PriceImpact float64 `json:"price_impact"` MEVScore float64 `json:"mev_score"` Profitable bool `json:"profitable"` } // LiquidationEvent represents a detected liquidation type LiquidationEvent struct { Timestamp time.Time `json:"timestamp"` BlockNumber uint64 `json:"block_number"` TxHash string `json:"tx_hash"` Protocol string `json:"protocol"` Liquidator string `json:"liquidator"` Borrower string `json:"borrower"` CollateralToken string `json:"collateral_token"` DebtToken string `json:"debt_token"` CollateralAmount string `json:"collateral_amount"` DebtAmount string `json:"debt_amount"` Bonus string `json:"liquidation_bonus"` HealthFactor float64 `json:"health_factor"` MEVOpportunity bool `json:"mev_opportunity"` EstimatedProfit string `json:"estimated_profit"` } // LiquidityEvent represents a liquidity change event type LiquidityEvent struct { Timestamp time.Time `json:"timestamp"` BlockNumber uint64 `json:"block_number"` TxHash string `json:"tx_hash"` Protocol string `json:"protocol"` Pool string `json:"pool"` EventType string `json:"event_type"` // "add", "remove", "sync" Token0 string `json:"token0"` Token1 string `json:"token1"` Amount0 string `json:"amount0"` Amount1 string `json:"amount1"` Liquidity string `json:"liquidity"` PriceAfter string `json:"price_after"` ImpactSize float64 `json:"impact_size"` ArbitrageOpp bool `json:"arbitrage_opportunity"` } // NewArbitrumProtocolRegistry creates a new protocol registry func NewArbitrumProtocolRegistry(logger *logger.Logger) (*ArbitrumProtocolRegistry, error) { registry := &ArbitrumProtocolRegistry{ logger: logger, protocols: make(map[string]*DEXProtocol), } // Initialize event logging files if err := registry.initializeEventLogging(); err != nil { return nil, fmt.Errorf("failed to initialize event logging: %w", err) } // Load all Arbitrum DEX protocols if err := registry.loadArbitrumProtocols(); err != nil { return nil, fmt.Errorf("failed to load protocols: %w", err) } return registry, nil } // initializeEventLogging sets up JSONL logging files func (r *ArbitrumProtocolRegistry) initializeEventLogging() error { // Create logs directory if it doesn't exist if err := os.MkdirAll("logs", 0755); err != nil { return fmt.Errorf("failed to create logs directory: %w", err) } // Open swap events log file swapFile, err := os.OpenFile("logs/swaps.jsonl", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return fmt.Errorf("failed to open swap log file: %w", err) } r.swapLogger = swapFile // Open liquidation events log file liquidationFile, err := os.OpenFile("logs/liquidations.jsonl", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return fmt.Errorf("failed to open liquidation log file: %w", err) } r.liquidationLogger = liquidationFile // Open liquidity events log file liquidityFile, err := os.OpenFile("logs/liquidity.jsonl", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return fmt.Errorf("failed to open liquidity log file: %w", err) } r.liquidityLogger = liquidityFile return nil } // loadArbitrumProtocols loads all major DEX protocols on Arbitrum func (r *ArbitrumProtocolRegistry) loadArbitrumProtocols() error { // Uniswap V3 - Highest priority for MEV r.protocols["uniswap_v3"] = &DEXProtocol{ Name: "Uniswap V3", Type: "uniswap_v3", Routers: []common.Address{ common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"), // SwapRouter common.HexToAddress("0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"), // SwapRouter02 }, Factories: []common.Address{ common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"), // Factory }, SwapFunctions: map[string]string{ "0x414bf389": "exactInputSingle", "0xc04b8d59": "exactInput", "0xdb3e2198": "exactOutputSingle", "0xf28c0498": "exactOutput", "0x5ae401dc": "multicall", "0x1f0464d1": "multicall", }, EventSignatures: map[string]common.Hash{ "Swap": crypto.Keccak256Hash([]byte("Swap(address,address,int256,int256,uint160,uint128,int24)")), "Mint": crypto.Keccak256Hash([]byte("Mint(address,address,int24,int24,uint128,uint256,uint256)")), "Burn": crypto.Keccak256Hash([]byte("Burn(address,int24,int24,uint128,uint256,uint256)")), }, PoolTypes: []string{"concentrated"}, FeeStructure: map[string]interface{}{"type": "tiered", "fees": []int{500, 3000, 10000}}, Active: true, Priority: 100, } // Uniswap V2 - High MEV potential r.protocols["uniswap_v2"] = &DEXProtocol{ Name: "Uniswap V2", Type: "uniswap_v2", Routers: []common.Address{ common.HexToAddress("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"), // Router02 }, Factories: []common.Address{ common.HexToAddress("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"), // Factory }, SwapFunctions: map[string]string{ "0x38ed1739": "swapExactTokensForTokens", "0x8803dbee": "swapTokensForExactTokens", "0x7ff36ab5": "swapExactETHForTokens", "0x4a25d94a": "swapTokensForExactETH", "0x791ac947": "swapExactTokensForETH", "0xfb3bdb41": "swapETHForExactTokens", }, EventSignatures: map[string]common.Hash{ "Swap": crypto.Keccak256Hash([]byte("Swap(address,uint256,uint256,uint256,uint256,address)")), "Mint": crypto.Keccak256Hash([]byte("Mint(address,uint256,uint256)")), "Burn": crypto.Keccak256Hash([]byte("Burn(address,uint256,uint256,address)")), "Sync": crypto.Keccak256Hash([]byte("Sync(uint112,uint112)")), }, PoolTypes: []string{"constant_product"}, FeeStructure: map[string]interface{}{"type": "fixed", "fee": 3000}, Active: true, Priority: 90, } // SushiSwap - High volume on Arbitrum r.protocols["sushiswap"] = &DEXProtocol{ Name: "SushiSwap", Type: "uniswap_v2", Routers: []common.Address{ common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"), // SushiRouter }, Factories: []common.Address{ common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4"), // Factory }, SwapFunctions: map[string]string{ "0x38ed1739": "swapExactTokensForTokens", "0x8803dbee": "swapTokensForExactTokens", "0x7ff36ab5": "swapExactETHForTokens", "0x4a25d94a": "swapTokensForExactETH", "0x791ac947": "swapExactTokensForETH", "0xfb3bdb41": "swapETHForExactTokens", }, EventSignatures: map[string]common.Hash{ "Swap": crypto.Keccak256Hash([]byte("Swap(address,uint256,uint256,uint256,uint256,address)")), "Mint": crypto.Keccak256Hash([]byte("Mint(address,uint256,uint256)")), "Burn": crypto.Keccak256Hash([]byte("Burn(address,uint256,uint256,address)")), "Sync": crypto.Keccak256Hash([]byte("Sync(uint112,uint112)")), }, PoolTypes: []string{"constant_product"}, FeeStructure: map[string]interface{}{"type": "fixed", "fee": 3000}, Active: true, Priority: 85, } // Camelot V3 - Arbitrum native DEX with high activity r.protocols["camelot_v3"] = &DEXProtocol{ Name: "Camelot V3", Type: "algebra", // Camelot uses Algebra protocol Routers: []common.Address{ common.HexToAddress("0x1F721E2E82F6676FCE4eA07A5958cF098D339e18"), // SwapRouter }, Factories: []common.Address{ common.HexToAddress("0x1a3c9B1d2F0529D97f2afC5136Cc23e58f1FD35B"), // Factory }, SwapFunctions: map[string]string{ "0x414bf389": "exactInputSingle", "0xc04b8d59": "exactInput", "0xdb3e2198": "exactOutputSingle", "0xf28c0498": "exactOutput", }, EventSignatures: map[string]common.Hash{ "Swap": crypto.Keccak256Hash([]byte("Swap(address,address,int256,int256,uint160,uint128,int24)")), "Mint": crypto.Keccak256Hash([]byte("Mint(address,int24,int24,uint128,uint256,uint256)")), "Burn": crypto.Keccak256Hash([]byte("Burn(address,int24,int24,uint128,uint256,uint256)")), }, PoolTypes: []string{"concentrated"}, FeeStructure: map[string]interface{}{"type": "dynamic", "base_fee": 500}, Active: true, Priority: 80, } // Balancer V2 - Good for large swaps r.protocols["balancer_v2"] = &DEXProtocol{ Name: "Balancer V2", Type: "balancer_v2", Routers: []common.Address{ common.HexToAddress("0xBA12222222228d8Ba445958a75a0704d566BF2C8"), // Vault }, Factories: []common.Address{ common.HexToAddress("0x8E9aa87E45f6a460D4448f8154F1CA8C5C8a63b5"), // WeightedPoolFactory common.HexToAddress("0x751A0bC0e3f75b38e01Cf25bFCE7fF36DE1C87DE"), // StablePoolFactory }, SwapFunctions: map[string]string{ "0x52bbbe29": "swap", "0x945bcec9": "batchSwap", }, EventSignatures: map[string]common.Hash{ "Swap": crypto.Keccak256Hash([]byte("Swap(bytes32,address,address,uint256,uint256)")), "PoolBalanceChanged": crypto.Keccak256Hash([]byte("PoolBalanceChanged(bytes32,address,address[],int256[],uint256[])")), }, PoolTypes: []string{"weighted", "stable", "meta_stable"}, FeeStructure: map[string]interface{}{"type": "variable", "min": 100, "max": 10000}, Active: true, Priority: 70, } // Curve Finance - Stablecoins and similar assets r.protocols["curve"] = &DEXProtocol{ Name: "Curve Finance", Type: "curve", Routers: []common.Address{ common.HexToAddress("0xA72C85C258A81761433B4e8da60505Fe3Dd551CC"), // 2Pool common.HexToAddress("0x960ea3e3C7FB317332d990873d354E18d7645590"), // Tricrypto common.HexToAddress("0x85E8Fc6B3cb1aB51E13A1Eb7b22b3F42E66B1BBB"), // CurveAavePool }, Factories: []common.Address{ common.HexToAddress("0xb17b674D9c5CB2e441F8e196a2f048A81355d031"), // StableFactory common.HexToAddress("0x9AF14D26075f142eb3F292D5065EB3faa646167b"), // CryptoFactory }, SwapFunctions: map[string]string{ "0x3df02124": "exchange", "0xa6417ed6": "exchange_underlying", "0x5b41b908": "exchange(int128,int128,uint256,uint256)", }, EventSignatures: map[string]common.Hash{ "TokenExchange": crypto.Keccak256Hash([]byte("TokenExchange(address,int128,uint256,int128,uint256)")), "TokenExchangeUnderlying": crypto.Keccak256Hash([]byte("TokenExchangeUnderlying(address,int128,uint256,int128,uint256)")), "AddLiquidity": crypto.Keccak256Hash([]byte("AddLiquidity(address,uint256[],uint256[],uint256,uint256)")), "RemoveLiquidity": crypto.Keccak256Hash([]byte("RemoveLiquidity(address,uint256[],uint256)")), }, PoolTypes: []string{"stable", "crypto", "meta"}, FeeStructure: map[string]interface{}{"type": "fixed", "fee": 400}, Active: true, Priority: 65, } // 1inch - Aggregator with MEV opportunities r.protocols["1inch"] = &DEXProtocol{ Name: "1inch", Type: "aggregator", Routers: []common.Address{ common.HexToAddress("0x1111111254EEB25477B68fb85Ed929f73A960582"), // AggregationRouterV5 common.HexToAddress("0x111111125421cA6dc452d289314280a0f8842A65"), // AggregationRouterV4 }, Factories: []common.Address{}, SwapFunctions: map[string]string{ "0x7c025200": "swap", "0x12aa3caf": "unoswap", "0x0502b1c5": "uniswapV3Swap", }, EventSignatures: map[string]common.Hash{ "Swapped": crypto.Keccak256Hash([]byte("Swapped(address,address,address,uint256,uint256)")), }, PoolTypes: []string{"aggregated"}, FeeStructure: map[string]interface{}{"type": "variable"}, Active: true, Priority: 75, } // GMX - Perpetuals with liquidation opportunities r.protocols["gmx"] = &DEXProtocol{ Name: "GMX", Type: "perpetual", Routers: []common.Address{ common.HexToAddress("0xaBBc5F99639c9B6bCb58544ddf04EFA6802F4064"), // Router common.HexToAddress("0xA906F338CB21815cBc4Bc87ace9e68c87eF8d8F1"), // PositionRouter }, Factories: []common.Address{ common.HexToAddress("0x489ee077994B6658eAfA855C308275EAd8097C4A"), // Vault }, SwapFunctions: map[string]string{ "0x0a5bc6f8": "swap", "0x7fc0c104": "increasePosition", "0xf3bf7c12": "decreasePosition", }, EventSignatures: map[string]common.Hash{ "Swap": crypto.Keccak256Hash([]byte("Swap(address,address,address,uint256,uint256,uint256,uint256)")), "LiquidatePosition": crypto.Keccak256Hash([]byte("LiquidatePosition(bytes32,address,address,address,bool,uint256,uint256,uint256,int256,uint256)")), "IncreasePosition": crypto.Keccak256Hash([]byte("IncreasePosition(bytes32,address,address,address,uint256,uint256,bool,uint256,uint256)")), "DecreasePosition": crypto.Keccak256Hash([]byte("DecreasePosition(bytes32,address,address,address,uint256,uint256,bool,uint256,uint256)")), }, PoolTypes: []string{"perpetual"}, FeeStructure: map[string]interface{}{"type": "position_based"}, Active: true, Priority: 90, // High priority due to liquidation MEV } // Radiant Capital - Lending protocol with liquidations r.protocols["radiant"] = &DEXProtocol{ Name: "Radiant Capital", Type: "lending", Routers: []common.Address{ common.HexToAddress("0x2032b9A8e9F7e76768CA9271003d3e43E1616B1F"), // LendingPool }, Factories: []common.Address{}, SwapFunctions: map[string]string{ "0x630d4904": "liquidationCall", "0xa415bcad": "deposit", "0x69328dec": "withdraw", "0xc858f5f9": "borrow", "0x573ade81": "repay", }, EventSignatures: map[string]common.Hash{ "LiquidationCall": crypto.Keccak256Hash([]byte("LiquidationCall(address,address,address,uint256,uint256,address,bool)")), "Deposit": crypto.Keccak256Hash([]byte("Deposit(address,address,address,uint256,uint16)")), "Withdraw": crypto.Keccak256Hash([]byte("Withdraw(address,address,address,uint256)")), "Borrow": crypto.Keccak256Hash([]byte("Borrow(address,address,address,uint256,uint256,uint16)")), "Repay": crypto.Keccak256Hash([]byte("Repay(address,address,address,uint256)")), }, PoolTypes: []string{"lending"}, FeeStructure: map[string]interface{}{"type": "interest_based"}, Active: true, Priority: 85, // High priority for liquidation MEV } r.logger.Info(fmt.Sprintf("Loaded %d DEX protocols for Arbitrum MEV detection", len(r.protocols))) return nil } // LogSwapEvent logs a swap event to JSONL file func (r *ArbitrumProtocolRegistry) LogSwapEvent(event *SwapEvent) error { r.mu.Lock() defer r.mu.Unlock() data, err := json.Marshal(event) if err != nil { return fmt.Errorf("failed to marshal swap event: %w", err) } if _, err := r.swapLogger.Write(append(data, '\n')); err != nil { return fmt.Errorf("failed to write swap event: %w", err) } return r.swapLogger.Sync() } // LogLiquidationEvent logs a liquidation event to JSONL file func (r *ArbitrumProtocolRegistry) LogLiquidationEvent(event *LiquidationEvent) error { r.mu.Lock() defer r.mu.Unlock() data, err := json.Marshal(event) if err != nil { return fmt.Errorf("failed to marshal liquidation event: %w", err) } if _, err := r.liquidationLogger.Write(append(data, '\n')); err != nil { return fmt.Errorf("failed to write liquidation event: %w", err) } return r.liquidationLogger.Sync() } // LogLiquidityEvent logs a liquidity event to JSONL file func (r *ArbitrumProtocolRegistry) LogLiquidityEvent(event *LiquidityEvent) error { r.mu.Lock() defer r.mu.Unlock() data, err := json.Marshal(event) if err != nil { return fmt.Errorf("failed to marshal liquidity event: %w", err) } if _, err := r.liquidityLogger.Write(append(data, '\n')); err != nil { return fmt.Errorf("failed to write liquidity event: %w", err) } return r.liquidityLogger.Sync() } // GetProtocol returns a protocol by name func (r *ArbitrumProtocolRegistry) GetProtocol(name string) (*DEXProtocol, bool) { r.mu.RLock() defer r.mu.RUnlock() protocol, exists := r.protocols[name] return protocol, exists } // GetActiveProtocols returns all active protocols sorted by priority func (r *ArbitrumProtocolRegistry) GetActiveProtocols() []*DEXProtocol { r.mu.RLock() defer r.mu.RUnlock() var protocols []*DEXProtocol for _, protocol := range r.protocols { if protocol.Active { protocols = append(protocols, protocol) } } // Sort by priority (highest first) for i := 0; i < len(protocols)-1; i++ { for j := i + 1; j < len(protocols); j++ { if protocols[i].Priority < protocols[j].Priority { protocols[i], protocols[j] = protocols[j], protocols[i] } } } return protocols } // GetFactoryAddresses returns all factory addresses for a protocol func (r *ArbitrumProtocolRegistry) GetFactoryAddresses(protocol arbitrumCommon.Protocol) []common.Address { r.mu.RLock() defer r.mu.RUnlock() if p, exists := r.protocols[string(protocol)]; exists { return p.Factories } return []common.Address{} } // GetContractAddresses returns all contract addresses for a protocol func (r *ArbitrumProtocolRegistry) GetContractAddresses(protocol arbitrumCommon.Protocol) []common.Address { r.mu.RLock() defer r.mu.RUnlock() if p, exists := r.protocols[string(protocol)]; exists { return p.Routers } return []common.Address{} } // IsKnownRouter checks if an address is a known DEX router func (r *ArbitrumProtocolRegistry) IsKnownRouter(address common.Address) (string, bool) { r.mu.RLock() defer r.mu.RUnlock() for name, protocol := range r.protocols { if !protocol.Active { continue } for _, router := range protocol.Routers { if router == address { return name, true } } } return "", false } // IsSwapFunction checks if a function signature is a known swap function func (r *ArbitrumProtocolRegistry) IsSwapFunction(sig string) (string, string, bool) { r.mu.RLock() defer r.mu.RUnlock() for protocolName, protocol := range r.protocols { if !protocol.Active { continue } if functionName, exists := protocol.SwapFunctions[sig]; exists { return protocolName, functionName, true } } return "", "", false } // LogArbitrageExecution logs arbitrage execution results (success or failure) func (r *ArbitrumProtocolRegistry) LogArbitrageExecution(executionData map[string]interface{}) error { // Add to arbitrage execution log file (reusing liquidation logger for now) data, err := json.Marshal(executionData) if err != nil { return fmt.Errorf("failed to marshal arbitrage execution data: %w", err) } // Write to log file with newline if r.liquidationLogger != nil { _, err = r.liquidationLogger.Write(append(data, '\n')) if err != nil { return fmt.Errorf("failed to write arbitrage execution log: %w", err) } return r.liquidationLogger.Sync() } return nil } // Close closes all log files func (r *ArbitrumProtocolRegistry) Close() error { var errors []error if r.swapLogger != nil { if err := r.swapLogger.Close(); err != nil { errors = append(errors, err) } } if r.liquidationLogger != nil { if err := r.liquidationLogger.Close(); err != nil { errors = append(errors, err) } } if r.liquidityLogger != nil { if err := r.liquidityLogger.Close(); err != nil { errors = append(errors, err) } } if len(errors) > 0 { return fmt.Errorf("errors closing log files: %v", errors) } return nil }