15 KiB
MEV Bot Profitability Audit - Critical Blockers Identified
Date: November 4, 2025
Status: ZERO EXECUTIONS - 100% Blocker Rate
Executive Summary: The MEV bot detects opportunities but fails to execute ANY arbitrage trades. Analysis reveals 10 critical blockers preventing profitability.
CRITICAL FINDINGS
Primary Issue: Zero Execution Rate
- Detected Opportunities: Yes (multiple per minute)
- Executed Opportunities: 0 (ZERO - 100% failure rate)
- Success Rate: 0.00%
- Net Profit: 0.000000 ETH (no successful trades)
From logs:
2025/11/02 15:22:38 [INFO] Arbitrage Service Stats - Detected: 0, Executed: 0, Successful: 0, Success Rate: 0.00%, Total Profit: 0.000000 ETH
All detected opportunities show: isExecutable:false rejectReason:negative profit after gas and slippage costs
RANKED BLOCKERS BY IMPACT (Most Critical First)
BLOCKER #1: Zero Amount Detection - CATASTROPHIC
Impact: Blocks 95%+ of all opportunity attempts
Location: pkg/profitcalc/profit_calc.go lines 104-134
Problem:
// Lines 108-118: Rejects all zero/nil amounts
if amountIn == nil || amountOut == nil || amountIn.Sign() <= 0 || amountOut.Sign() <= 0 {
opportunity.IsExecutable = false
opportunity.RejectReason = "invalid swap amounts (nil or zero)"
opportunity.Confidence = 0.0
return opportunity
}
// Lines 121-134: Rejects dust amounts below 0.0001 ETH
if amountIn.Cmp(minAmount) < 0 || amountOut.Cmp(minAmount) < 0 {
opportunity.IsExecutable = false
opportunity.RejectReason = "dust amounts below threshold..."
return opportunity
}
Log Evidence:
Amount In: 0.000000 tokens
Amount Out: 0.000000 tokens
rejectReason:negative profit after gas and slippage costs
Root Cause: Token amount extraction from swap events is broken. The AnalyzeSwapOpportunity method receives ZERO amounts even when swaps are clearly happening (e.g., "Amount Out: 1611.982004 tokens" logged but marked as invalid).
Data Flow Issue:
- Swap events are parsed → amounts extracted wrong
- Amounts are zero or dust when passed to
AnalyzeSwapOpportunity - Opportunity immediately rejected
- Never reaches execution logic
Why Not Fixed: The token decimals or amount parsing is broken in the event parser (pkg/arbitrum/parser/core.go or pkg/events/parser.go). Amounts are being lost or miscalculated during extraction.
BLOCKER #2: Token Graph Empty - CRITICAL
Impact: No arbitrage paths can be found
Location: pkg/arbitrage/multihop.go lines 167-173
Problem:
adjacent := mhs.tokenGraph.GetAdjacentTokens(startToken)
if len(adjacent) == 0 {
mhs.logger.Warn(fmt.Sprintf("⚠️ Start token %s has no adjacent tokens in graph!", startToken.Hex()))
}
Analysis: The token graph is manually populated with hardcoded pool addresses (lines 522-594) that:
- Use PLACEHOLDER liquidity values (all set to
uint256.NewInt(1000000000000000000)) - Placeholder
SqrtPriceX96values (never updated from actual pools) - Never connect to the actual Arbitrum mainnet state
- Have LastUpdated = time.Zero (fails
isPoolUsable()check at line 703)
Why Not Fixed: Pool discovery is DISABLED intentionally:
// From cmd/mev-bot/main.go line 15:
// "github.com/fraktal/mev-beta/internal/tokens" // Not used - pool discovery disabled
The system:
- Loads 314 pools from cache (
logs: Loaded 314 pools from cache) - But NEVER adds them to the token graph used for arbitrage scanning
- Token graph is manually populated with 8 hardcoded pools (all with stale data)
- No connection between pool cache and token graph
BLOCKER #3: Batch Fetcher Contract Call Reverts - HIGH
Impact: Cannot fetch pool state data for ANY pool
Location: pkg/scanner/market/scanner.go line 139-142
Problem:
2025/11/02 15:22:49 [WARN] Failed to fetch batch 0-1: batch fetch V3 data failed: execution reverted
(repeated 25+ times)
Root Cause: The DataFetcher contract call reverts on Arbitrum:
- Contract address:
0xC6BD82306943c0F3104296a46113ca0863723cBD - Method:
batchFetchV3Data()(fixed from brokenbatchFetchAllData) - Error: execution reverted (likely contract is not deployed or wrong address)
Impact Chain:
- Cannot fetch real pool liquidity
- Falls back to placeholder values
- Profit calculations use placeholder data
- All calculations are mathematically invalid
From logs:
// pkg/scanner/market/scanner.go lines 145-150:
dataFetcherAddrStr := os.Getenv("CONTRACT_DATA_FETCHER")
if dataFetcherAddrStr == "" {
dataFetcherAddrStr = "0x42105682F819891698E76cfE6897F10b75f8aabc" // Fallback
}
The fallback address (0x421056...) doesn't match the one in config (0xC6BD82...). Contract may not be deployed at this address.
BLOCKER #4: Profit Margin Calculation Invalid - HIGH
Impact: All profitable opportunities marked as unprofitable
Location: pkg/profitcalc/profit_calc.go lines 261-294
Problem:
profitMargin:-4.466551104702248e-09 (EXTREME NEGATIVE VALUE)
profitMargin:-38365.84743877249 (EXTREME NEGATIVE VALUE)
Log Evidence from actual opportunities:
rejectReason:negative profit after gas and slippage costs
estimatedProfitETH:0.000000
gasCostETH:0.000007
netProfitETH:-0.000007
profitMargin:-330178.9776420681
Root Cause: When amounts are zero or near-zero:
- All calculations use zero amounts
- Net profit = 0 - 0 - 0.000007 = NEGATIVE
- Profit margin = negative / 0 = UNDEFINED EXTREME VALUE
- Circuit breaker rejects it
Mathematical Issue:
- Line 264:
profitMargin := new(big.Float).Quo(netProfit, amountOut) - When amountOut is near-zero, Quo operation produces extreme values
- Lines 270-283: Bounds checking is BACKWARDS:
The check rejects BOTH extreme values (correct) BUT the logic doesn't prevent -330,000 from being calculated
if profitMarginFloat > 1.0 { // reject if > 100% opportunity.IsExecutable = false } else if profitMarginFloat < -1.0 { // reject if < -100% opportunity.IsExecutable = false }
BLOCKER #5: Unknown Token Filtering - MEDIUM-HIGH
Impact: 95% of tokens cannot be priced
Location: pkg/profitcalc/profit_calc.go lines 152-161
Problem:
if arbitrageRoute == nil {
opportunity.IsExecutable = false
opportunity.RejectReason = fmt.Sprintf("unknown token - cannot price %s or %s", tokenA.Hex()[:10], tokenB.Hex()[:10])
opportunity.Confidence = 0.05 // Lower confidence for unknown token
return opportunity
}
Analysis: The PriceFeed doesn't have pricing for most Arbitrum tokens:
- Only 6 tokens cached:
logs: Loaded 6 tokens from cache - Arbitrum has 100+ tradeable tokens
- Any token pair missing from PriceFeed = immediate rejection
- Falls back to simplified spread calculation (~30 bps, unrealistic)
Impact:
- Cannot determine actual profitability
- Uses fallback calculation:
profit = amountOut * 0.003 - amountIn * 0.003 - This is mathematically incorrect for arbitrage
BLOCKER #6: Execution Code Path Unreachable - CRITICAL
Impact: Even if opportunities were valid, they wouldn't execute
Location: pkg/arbitrage/service.go (ExecuteArbitrage method)
Problem:
The arbitrage execution chain is:
- Market scanner detects opportunity →
SetOpportunityForwarderroutes to MultiHopScanner - MultiHopScanner.ScanForArbitrage finds paths → returns paths
- DEAD END: Nobody calls the executor with the paths
From logs and code review:
- No evidence of
arb.Executor.Execute()orarb.FlashExecutor.Execute()being called - The opportunities are detected but never forwarded to actual execution
ExecuteArbitragemethod in service.go exists but is never invoked
Missing Link: The opportunity forwarder interface is set but:
// pkg/scanner/market/scanner.go line 86-88:
func (s *MarketScanner) SetOpportunityForwarder(forwarder OpportunityForwarder) {
s.opportunityForwarder = forwarder
s.logger.Info("✅ Opportunity forwarder set - will route to multi-hop scanner")
}
But nothing actually calls s.opportunityForwarder.ExecuteArbitrage() with valid opportunities.
BLOCKER #7: Minimum Profit Threshold Too High - MEDIUM
Impact: Rejects legitimate opportunities
Location: pkg/arbitrage/detection_engine.go line 188-190
Problem:
engine.config.MinProfitThreshold, _ = engine.decimalConverter.FromString("0.001", 18, "ETH")
// = 0.001 ETH minimum (~$2 at current prices)
Analysis: The minimum is actually reasonable (0.001 ETH = ~$2), BUT:
- Gas cost on Arbitrum = 100-300k gas @ 0.1-0.5 gwei = $0.0002-0.001
- Minimum 0.5% spread opportunity (across DEXs) = rare
- With ZERO amounts being detected, threshold becomes irrelevant
Sub-blocker: Configuration also has:
# config/arbitrum_production.yaml line 263:
min_profit_wei: 1000000000000000 # $2.00 minimum profit
min_roi_percent: 0.05 # Minimum 0.05% ROI
min_confidence_score: 0.6 # Minimum 60% confidence
But all detected opportunities have confidence: 0.1 (10%), so they fail this check.
BLOCKER #8: RPC BatchFetch Contract Wrong/Not Deployed - MEDIUM
Impact: Cannot fetch pool state in batches (heavy RPC usage)
Location: pkg/datafetcher/batch_fetcher.go
Problem:
- Config points to
0xC6BD82306943c0F3104296a46113ca0863723cBD - Code fallback:
0x42105682F819891698E76cfE6897F10b75f8aabc - Neither address works (contract reverts or doesn't exist)
- System falls back to individual RPC calls for each pool
Impact:
- RPC rate limits hit immediately
- Cannot fetch data for 314 cached pools
- System stalls on rate limiting
BLOCKER #9: No Live Execution Framework Integration - CRITICAL
Impact: Flash loan executor never triggers
Location: pkg/arbitrage/flash_executor.go and pkg/arbitrage/executor.go
Problem:
Flash executors are initialized but NEVER called:
// pkg/arbitrage/service.go line 42-43:
flashExecutor *FlashSwapExecutor
liveFramework *LiveExecutionFramework
Both are initialized in NewArbitrageService() but:
- No goroutines call their Execute methods
- No event listeners forward opportunities to them
- They sit dormant while opportunities pass by
Missing: The actual execution loop that should:
- Listen for valid opportunities
- Call
flashExecutor.ExecuteFlashSwap() - Monitor transaction result
- Track profit/loss
Instead, the code only logs opportunities and never executes.
BLOCKER #10: Event Parser Extracts Zero Amounts - HIGH
Impact: All swap events show 0 token amounts
Location: pkg/arbitrum/parser/core.go or pkg/events/parser.go
Problem:
Swap events are being parsed with ZERO amounts when they contain data:
Log entry shows: "Amount Out: 1611.982004 tokens"
But parsed as: amount0Out:0, amount1Out:0
Evidence from logs:
Amount In: 0.000000 tokens
Amount Out: 0.000000 tokens
Yet the same transaction logs show real amounts being swapped. The parser is either:
- Extracting wrong event fields
- Not handling token decimals correctly
- Using wrong event signature for Swap events
- Losing data during amount sign conversion
This is the FIRST failure point in the entire pipeline.
SECONDARY ISSUES
Issue A: Pool Blacklist Prevents Scanning
- Valid pools are being blacklisted due to transient RPC errors
- Once blacklisted, never attempted again
- System self-limits to fewer and fewer pools
Issue B: Configuration Mismatch
- Multiple min profit thresholds at different levels
- Different enabled/disabled features in different configs
- Some features marked as DISABLED in code but ENABLED in config
Issue C: No Actual Wallet Funding Check
- Bot generates keys but never verifies wallet has funds
- Tries to execute trades with 0 balance wallet
- Transactions fail but error handling swallows the message
Issue D: Rate Limiting Too Aggressive
- Max 5 concurrent RPC calls (
config/arbitrum_production.yamlline 336) - 20 requests/second on growth plan (very conservative)
- System rate limits itself before hitting Chainstack limits
ROOT CAUSE ANALYSIS
The system has ARCHITECTURAL SEPARATION:
- Detection Pipeline (WORKING): Finds opportunities ✅
- Data Fetch Pipeline (BROKEN): Cannot get pool state ❌
- Profit Calculation (BROKEN): Uses invalid amounts ❌
- Execution Pipeline (DISCONNECTED): Never triggered ❌
Why No Executions:
Event Parsed → Amounts = ZERO → Profit Rejected → Execution Never Attempted
↑
ROOT CAUSE
The token amount extraction is broken at the event parser level. Everything downstream (profit calc, execution decisions) becomes irrelevant because the input data is invalid.
RECOMMENDATIONS (Priority Order)
P0: Fix Event Parser Amount Extraction
- Review
pkg/events/parser.go- validate Swap event parsing - Check token decimal handling in amount conversion
- Verify event signature matching (Uniswap V3 Swap vs V2)
- Test with real swap transactions to verify amount extraction
P1: Connect Token Graph to Pool Cache
- Link the 314 cached pools to the token graph
- Implement pool discovery loop that updates token graph
- Verify graph has minimum 20+ tokens with 50+ pools
P2: Fix Batch Fetcher Contract
- Deploy actual DataFetcher contract or find correct address
- Test
batchFetchV3Data()call on Arbitrum - Implement fallback to individual RPC calls with proper caching
P3: Connect Execution Pipeline
- Add event listener that forwards VALID opportunities to executor
- Implement execution goroutine
- Add transaction monitoring and result tracking
P4: Verify Wallet Funding
- Check wallet balance before any execution attempt
- Require minimum balance (0.05 ETH) for flash loan execution
- Add pre-flight validation
TESTING CHECKLIST
- Swap event parser extracts amounts correctly
- Profit calculator receives non-zero amounts
- 50+ opportunities marked as
isExecutable:true - TokenGraph has 20+ tokens, 50+ pool connections
- DataFetcher contract calls succeed
- First profitable arbitrage executes successfully
- Transaction confirmed on-chain
- Profit > gas cost
ESTIMATED FIX EFFORT
| Blocker | Effort | Time |
|---|---|---|
| #1 - Event Parser Fix | 2-4 hours | Debug + unit test + integration test |
| #2 - Token Graph Link | 1-2 hours | Wire up pool cache to graph |
| #3 - Batch Fetcher | 2-3 hours | Deploy/fix contract or remove |
| #6 - Execution Connection | 2-3 hours | Wire event → executor pipeline |
| #10 - Validation | 1-2 hours | Add amount validation in parser |
| Total | 8-14 hours | Full system operational |
Expected Outcome: System should achieve first profitable execution within 24 hours of fixing blockers #1-2.