package monitor import ( "context" "fmt" "math/big" "sync" "time" "github.com/fraktal/mev-beta/internal/config" "github.com/fraktal/mev-beta/internal/logger" "github.com/fraktal/mev-beta/internal/ratelimit" "github.com/fraktal/mev-beta/pkg/market" "github.com/fraktal/mev-beta/pkg/scanner" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "golang.org/x/time/rate" ) // ArbitrumMonitor monitors the Arbitrum sequencer for transactions with concurrency support type ArbitrumMonitor struct { config *config.ArbitrumConfig botConfig *config.BotConfig client *ethclient.Client logger *logger.Logger rateLimiter *ratelimit.LimiterManager marketMgr *market.MarketManager scanner *scanner.MarketScanner pipeline *market.Pipeline fanManager *market.FanManager limiter *rate.Limiter pollInterval time.Duration running bool mu sync.RWMutex } // NewArbitrumMonitor creates a new Arbitrum monitor with rate limiting func NewArbitrumMonitor( arbCfg *config.ArbitrumConfig, botCfg *config.BotConfig, logger *logger.Logger, rateLimiter *ratelimit.LimiterManager, marketMgr *market.MarketManager, scanner *scanner.MarketScanner, ) (*ArbitrumMonitor, error) { // Create Ethereum client client, err := ethclient.Dial(arbCfg.RPCEndpoint) if err != nil { return nil, fmt.Errorf("failed to connect to Arbitrum node: %v", err) } // Create rate limiter based on config limiter := rate.NewLimiter( rate.Limit(arbCfg.RateLimit.RequestsPerSecond), arbCfg.RateLimit.Burst, ) // Create pipeline pipeline := market.NewPipeline(botCfg, logger, marketMgr, scanner) // Add default stages pipeline.AddDefaultStages() // Create fan manager fanManager := market.NewFanManager( &config.Config{ Arbitrum: *arbCfg, Bot: *botCfg, }, logger, rateLimiter, ) return &ArbitrumMonitor{ config: arbCfg, botConfig: botCfg, client: client, logger: logger, rateLimiter: rateLimiter, marketMgr: marketMgr, scanner: scanner, pipeline: pipeline, fanManager: fanManager, limiter: limiter, pollInterval: time.Duration(botCfg.PollingInterval) * time.Second, running: false, }, nil } // Start begins monitoring the Arbitrum sequencer func (m *ArbitrumMonitor) Start(ctx context.Context) error { m.mu.Lock() m.running = true m.mu.Unlock() m.logger.Info("Starting Arbitrum sequencer monitoring...") // Get the latest block to start from if err := m.rateLimiter.WaitForLimit(ctx, m.config.RPCEndpoint); err != nil { return fmt.Errorf("rate limit error: %v", err) } header, err := m.client.HeaderByNumber(ctx, nil) if err != nil { return fmt.Errorf("failed to get latest block header: %v", err) } lastBlock := header.Number.Uint64() m.logger.Info(fmt.Sprintf("Starting from block: %d", lastBlock)) for { m.mu.RLock() running := m.running m.mu.RUnlock() if !running { break } select { case <-ctx.Done(): m.Stop() return nil case <-time.After(m.pollInterval): // Get the latest block if err := m.rateLimiter.WaitForLimit(ctx, m.config.RPCEndpoint); err != nil { m.logger.Error(fmt.Sprintf("Rate limit error: %v", err)) continue } header, err := m.client.HeaderByNumber(ctx, nil) if err != nil { m.logger.Error(fmt.Sprintf("Failed to get latest block header: %v", err)) continue } currentBlock := header.Number.Uint64() // Process blocks from lastBlock+1 to currentBlock for blockNum := lastBlock + 1; blockNum <= currentBlock; blockNum++ { if err := m.processBlock(ctx, blockNum); err != nil { m.logger.Error(fmt.Sprintf("Failed to process block %d: %v", blockNum, err)) } } lastBlock = currentBlock } } return nil } // Stop stops the monitor func (m *ArbitrumMonitor) Stop() { m.mu.Lock() defer m.mu.Unlock() m.running = false m.logger.Info("Stopping Arbitrum monitor...") } // processBlock processes a single block for potential swap transactions func (m *ArbitrumMonitor) processBlock(ctx context.Context, blockNumber uint64) error { m.logger.Debug(fmt.Sprintf("Processing block %d", blockNumber)) // Wait for rate limiter if err := m.rateLimiter.WaitForLimit(ctx, m.config.RPCEndpoint); err != nil { return fmt.Errorf("rate limit error: %v", err) } // Get block by number block, err := m.client.BlockByNumber(ctx, big.NewInt(int64(blockNumber))) if err != nil { return fmt.Errorf("failed to get block %d: %v", blockNumber, err) } // Process transactions through the pipeline transactions := block.Transactions() // Process transactions through the pipeline with block number and timestamp if err := m.pipeline.ProcessTransactions(ctx, transactions, blockNumber, block.Time()); err != nil { m.logger.Error(fmt.Sprintf("Pipeline processing error: %v", err)) } return nil } // processTransaction analyzes a transaction for potential swap opportunities func (m *ArbitrumMonitor) processTransaction(ctx context.Context, tx *types.Transaction) error { // Check if this is a potential swap transaction // This is a simplified check - in practice, you would check for // specific function signatures of Uniswap-like contracts // For now, we'll just log all transactions from, err := m.client.TransactionSender(ctx, tx, common.Hash{}, 0) if err != nil { // This can happen for pending transactions from = common.HexToAddress("0x0") } m.logger.Debug(fmt.Sprintf("Transaction: %s, From: %s, To: %s, Value: %s ETH", tx.Hash().Hex(), from.Hex(), func() string { if tx.To() != nil { return tx.To().Hex() } return "contract creation" }(), new(big.Float).Quo(new(big.Float).SetInt(tx.Value()), big.NewFloat(1e18)).String(), )) // TODO: Add logic to detect swap transactions and analyze them // This would involve: // 1. Checking if the transaction is calling a Uniswap-like contract // 2. Decoding the swap function call // 3. Extracting the token addresses and amounts // 4. Calculating potential price impact return nil } // GetPendingTransactions retrieves pending transactions from the mempool func (m *ArbitrumMonitor) GetPendingTransactions(ctx context.Context) ([]*types.Transaction, error) { // This is a simplified implementation // In practice, you might need to use a different approach to access pending transactions // Query for pending transactions txs := make([]*types.Transaction, 0) // Note: ethclient doesn't directly expose pending transactions // You might need to use a different approach or a custom RPC call return txs, nil }