diff --git a/docs/FAST_MVP_PLAN.md b/docs/FAST_MVP_PLAN.md new file mode 100644 index 0000000..2f3f2b4 --- /dev/null +++ b/docs/FAST_MVP_PLAN.md @@ -0,0 +1,829 @@ +# MEV Bot - Fast MVP Plan (4-5 Weeks) + +**Objective**: Deploy profitable arbitrage bot in 4-5 weeks with minimal viable features +**Strategy**: Validate business model quickly, then decide whether to scale +**Capital**: Start with 0.5-1 ETH, scale if profitable + +--- + +## 🎯 Core Philosophy + +**Ship fast, validate profitability, iterate based on real data.** + +We're cutting everything that's not essential: +- ❌ No 13+ protocols (just UniswapV2 + UniswapV3) +- ❌ No 4-hop arbitrage (just 2-hop) +- ❌ No sequencer integration yet (regular RPC first) +- ❌ No batch execution (single trades only) +- ❌ No fancy dashboard (basic metrics only) + +**We CAN add these later if the bot is profitable.** + +--- + +## 📅 4-Week Timeline + +### **Week 1: Core Parsers** (Dec 1-7) +**Goal**: Parse UniswapV2 and UniswapV3 swaps accurately + +#### Day 1-2: UniswapV2 Parser +- [ ] Implement Swap event parsing +- [ ] Handle Mint/Burn events +- [ ] Extract tokens from pool cache +- [ ] Decimal scaling (USDC 6, WBTC 8, WETH 18) +- [ ] 100% test coverage +- [ ] Integration test with real Arbitrum tx + +**Success Criteria**: +- Parse any UniswapV2 swap on Arbitrum +- No zero addresses +- Correct decimal handling +- Tests pass: `make test-coverage` + +#### Day 3-5: UniswapV3 Parser +- [ ] Parse V3 Swap events (signed amounts) +- [ ] Handle sqrtPriceX96 and tick +- [ ] Concentrated liquidity basics +- [ ] Multi-hop swap support +- [ ] 100% test coverage +- [ ] Integration tests + +**Success Criteria**: +- Parse any UniswapV3 swap on Arbitrum +- Handle negative amounts correctly +- Proper tick/liquidity tracking + +#### Day 6-7: Pool Discovery +- [ ] Fetch all UniswapV2 pools on Arbitrum +- [ ] Fetch all UniswapV3 pools on Arbitrum +- [ ] Populate pool cache +- [ ] Get initial reserves +- [ ] ~500-1000 pools total + +**Success Criteria**: +- Pool cache has major trading pairs (WETH/USDC, WETH/ARB, etc.) +- Reserves are accurate +- Cache lookup is fast (<1ms) + +--- + +### **Week 2: Arbitrage Detection** (Dec 8-14) +**Goal**: Find profitable 2-hop arbitrage opportunities + +#### Day 1-2: Market Graph +- [ ] Build graph from pool cache +- [ ] Nodes = tokens, Edges = pools +- [ ] Efficient adjacency list representation +- [ ] Unit tests + +**Example**: +``` +WETH --[UniV2 Pool]-- USDC +WETH --[UniV3 Pool]-- USDC +ARB --[UniV2 Pool]-- WETH +``` + +#### Day 3-4: Path Finder (2-Hop Only) +- [ ] Find circular paths: Token A → Token B → Token A +- [ ] BFS algorithm (simple, fast) +- [ ] Limit to 2 hops maximum +- [ ] Filter by minimum liquidity ($10k+) +- [ ] Unit tests with mock graph + +**Example Arbitrage**: +``` +Buy USDC with WETH on UniV2 (cheaper) +Sell USDC for WETH on UniV3 (more expensive) +Profit = difference - gas +``` + +#### Day 5-6: Profitability Calculator +- [ ] Calculate swap output (AMM formula) +- [ ] Account for fees (0.3% UniV2, 0.05-1% UniV3) +- [ ] Estimate gas cost (~150k gas per hop) +- [ ] Calculate net profit +- [ ] Filter: only opportunities >0.01 ETH profit + +**Formula**: +``` +Gross Profit = Output Amount - Input Amount +Gas Cost = Gas Used × Gas Price +Net Profit = Gross Profit - Gas Cost + +Only execute if Net Profit > 0.01 ETH +``` + +#### Day 7: Integration & Testing +- [ ] End-to-end test: Pool cache → Graph → Paths → Profit +- [ ] Test with historical Arbitrum data +- [ ] Verify calculations match real outcomes +- [ ] Performance: <50ms per detection + +--- + +### **Week 3: Execution Engine** (Dec 15-21) +**Goal**: Execute profitable arbitrage trades + +#### Day 1-2: Transaction Builder +- [ ] Build swap transaction for UniswapV2 +- [ ] Build swap transaction for UniswapV3 +- [ ] Calculate exact input/output amounts +- [ ] Add slippage tolerance (0.5%) +- [ ] Unit tests + +#### Day 3-4: Execution Logic +- [ ] Connect to Arbitrum RPC (Alchemy/Infura) +- [ ] Get wallet nonce +- [ ] Estimate gas +- [ ] Set gas price (current + 10%) +- [ ] Sign transaction +- [ ] Submit to network +- [ ] Wait for confirmation +- [ ] Handle reverts gracefully + +**Simple Flow**: +```go +func (e *Executor) Execute(opportunity *Opportunity) error { + // 1. Build transaction + tx := e.buildArbitrageTx(opportunity) + + // 2. Estimate gas + gas, err := e.client.EstimateGas(tx) + if err != nil { + return err + } + tx.Gas = gas * 1.1 // 10% buffer + + // 3. Get gas price + gasPrice, _ := e.client.SuggestGasPrice() + tx.GasPrice = gasPrice * 1.1 // 10% higher to ensure inclusion + + // 4. Check still profitable after gas + netProfit := opportunity.GrossProfit - (gas * gasPrice) + if netProfit < minProfit { + return ErrNotProfitable + } + + // 5. Sign and send + signedTx, _ := types.SignTx(tx, e.signer, e.privateKey) + err = e.client.SendTransaction(ctx, signedTx) + + // 6. Wait for confirmation + receipt, err := e.waitForReceipt(signedTx.Hash()) + + // 7. Record result + e.recordTrade(opportunity, receipt) + + return err +} +``` + +#### Day 5-6: Basic Risk Management +- [ ] Circuit breaker: Stop after 3 failed trades in a row +- [ ] Max loss limit: Stop after -0.1 ETH total loss +- [ ] Slippage protection: Revert if output < expected × 99.5% +- [ ] Position limit: Max 0.5 ETH per trade +- [ ] Cooldown: Wait 1 minute after circuit breaker trips + +#### Day 7: Testnet Testing +- [ ] Deploy to Arbitrum Sepolia testnet +- [ ] Test full flow: Detect → Execute → Confirm +- [ ] Verify trades succeed +- [ ] Check profit calculations +- [ ] Fix any bugs + +--- + +### **Week 4: Monitoring & Deployment** (Dec 22-28) +**Goal**: Deploy to mainnet with real capital + +#### Day 1-2: Basic Metrics +- [ ] Count opportunities found +- [ ] Count trades executed +- [ ] Count successful vs failed +- [ ] Track total profit/loss +- [ ] Track gas spent +- [ ] Simple logging to file + +**Metrics to track**: +``` +Opportunities Found: 127 +Trades Executed: 23 +Successful: 18 (78%) +Failed: 5 (22%) + +Gross Profit: 0.42 ETH +Gas Spent: 0.15 ETH +Net Profit: 0.27 ETH +ROI: 54% (on 0.5 ETH capital) +``` + +#### Day 3: Mainnet Preparation +- [ ] Security audit of wallet handling +- [ ] Create dedicated bot wallet +- [ ] Fund with 0.5 ETH test capital +- [ ] Set conservative limits: + - Max 0.1 ETH per trade + - Max 10 trades per day + - Stop after -0.05 ETH loss +- [ ] Backup private key securely + +#### Day 4-5: Shadow Mode (24 hours) +- [ ] Run bot in shadow mode (detect but don't execute) +- [ ] Log all opportunities +- [ ] Calculate theoretical profit +- [ ] Verify no false positives +- [ ] Check for edge cases + +**Example Log**: +``` +[2024-12-22 10:15:23] OPPORTUNITY FOUND +Path: WETH → USDC (UniV2) → WETH (UniV3) +Input: 0.1 ETH +Expected Output: 0.1023 ETH +Gross Profit: 0.0023 ETH ($4.60) +Gas Cost: 0.0008 ETH ($1.60) +Net Profit: 0.0015 ETH ($3.00) +Action: WOULD EXECUTE (shadow mode) +``` + +#### Day 6-7: Live Deployment (Carefully!) +- [ ] Switch to live mode +- [ ] Start with VERY conservative limits +- [ ] Monitor constantly (first 24 hours) +- [ ] Be ready to kill switch if needed + +**First Day Checklist**: +- [ ] Bot detects opportunities ✅ +- [ ] Profit calculations are accurate ✅ +- [ ] Trades execute successfully ✅ +- [ ] No unexpected reverts ✅ +- [ ] Gas costs as expected ✅ +- [ ] Net profit is positive ✅ + +--- + +## 🎯 Success Criteria for Fast MVP + +### **Minimum Viable Success** (Worth continuing): +- ✅ Bot runs for 7 days without crashes +- ✅ Executes at least 5 trades +- ✅ Success rate >50% +- ✅ Net profit >0 (even $10 is validation) +- ✅ No major bugs or losses + +### **Strong Success** (Scale up immediately): +- ✅ Net profit >10% in first week +- ✅ Success rate >70% +- ✅ Consistent daily opportunities +- ✅ No circuit breaker trips +- ✅ Clear path to scaling + +### **Failure** (Pivot or abandon): +- ❌ Net loss after 7 days +- ❌ Success rate <30% +- ❌ Circuit breaker trips repeatedly +- ❌ No arbitrage opportunities +- ❌ Competition too fierce + +--- + +## 🔍 What We're NOT Building (Yet) + +These are explicitly OUT OF SCOPE for Fast MVP: + +### Deferred to "Scale-Up Phase" (if MVP is profitable): +1. **More DEX Protocols** + - Curve, Balancer, SushiSwap, Camelot + - Add in Week 5-6 if MVP works + +2. **Sequencer Integration** + - Front-running via sequencer feed + - Add in Week 6-7 if needed for competitiveness + +3. **Multi-Hop Arbitrage** + - 3-hop and 4-hop paths + - Add if 2-hop is saturated + +4. **Batch Execution** + - Multicall for gas savings + - Add when trade volume justifies it + +5. **Advanced Gas Optimization** + - Dynamic gas strategies + - EIP-1559 optimization + - Add if gas is eating profits + +6. **Fancy Dashboard** + - Grafana, Prometheus + - Real-time monitoring + - Add when operating at scale + +7. **Flashbots Integration** + - Not available on Arbitrum anyway + - May never be needed + +--- + +## 📊 Realistic Expectations + +### Week 1 Projections (Very Conservative): +``` +Capital: 0.5 ETH +Trades: 3-5 per day +Success Rate: 50% (learning phase) +Avg Profit per Success: 0.005 ETH +Avg Gas per Trade: 0.002 ETH + +Daily Net: + Success: 2 × 0.005 = 0.01 ETH + Failed Gas: 3 × 0.002 = 0.006 ETH + Net: 0.004 ETH per day + +Weekly: 0.028 ETH (5.6% ROI) +``` + +### If This Works, Week 2-4: +``` +Capital: 1.0 ETH (increase after validation) +Trades: 10 per day (more confidence) +Success Rate: 70% (optimization) +Avg Profit: 0.008 ETH + +Daily Net: 0.035 ETH +Weekly: 0.245 ETH (24.5% ROI) +``` + +### If This REALLY Works (Month 2-3): +- Add more DEX protocols → more opportunities +- Add sequencer → better execution +- Scale capital to 5-10 ETH +- Target: 50-100% monthly ROI + +--- + +## 🚨 Risk Management + +### Hard Limits (Circuit Breakers): +```yaml +max_loss_per_day: 0.05 ETH +max_loss_per_week: 0.1 ETH +max_consecutive_failures: 3 +max_trade_size: 0.1 ETH +max_trades_per_day: 20 +max_gas_price: 0.5 gwei (Arbitrum is cheap) + +circuit_breaker_cooldown: 1 hour +emergency_stop_loss: 0.2 ETH total +``` + +### Manual Oversight: +- [ ] Check metrics every 6 hours (first week) +- [ ] Review all failed trades +- [ ] Adjust limits based on results +- [ ] Be ready to pause if needed + +### Emergency Stop: +```bash +# Kill switch command +pkill -f mev-bot + +# Or via API +curl -X POST http://localhost:8080/emergency-stop +``` + +--- + +## 📦 Minimal Tech Stack + +### Core Dependencies: +- **Language**: Go 1.21+ +- **Ethereum Client**: go-ethereum (geth) +- **RPC Provider**: Alchemy or Infura (free tier is fine) +- **Database**: SQLite (simple, no postgres needed yet) +- **Logging**: Standard Go `log` package +- **Metrics**: Simple file-based logs + +### Infrastructure: +- **Hosting**: Local machine or cheap VPS ($5/month) +- **Monitoring**: Tail logs + manual checks +- **Alerts**: None (you'll check manually) + +### Later (if profitable): +- Upgrade to dedicated server +- Add Prometheus + Grafana +- Set up PagerDuty alerts +- Use PostgreSQL for analytics + +--- + +## 🎬 Implementation Order (Detailed) + +### Week 1: Days 1-2 (UniswapV2 Parser) +```bash +# Create feature branch +git checkout -b feature/v2/parsers/uniswap-v2-mvp + +# Implement +touch pkg/parsers/uniswap_v2.go +touch pkg/parsers/uniswap_v2_test.go + +# Focus areas: +1. Swap event signature: 0xd78ad95f... +2. ABI decoding: amount0In, amount1In, amount0Out, amount1Out +3. Token extraction from pool cache +4. Decimal scaling (critical!) +5. Validation (no zero addresses) + +# Test with real data +# Example: https://arbiscan.io/tx/0x... +# Parse real Uniswap V2 swap on Arbitrum + +# Achieve 100% coverage +go test ./pkg/parsers/... -coverprofile=coverage.out +go tool cover -html=coverage.out +``` + +### Week 1: Days 3-5 (UniswapV3 Parser) +```bash +git checkout -b feature/v2/parsers/uniswap-v3-mvp + +# Key differences from V2: +1. Signed amounts (int256, not uint256) +2. sqrtPriceX96 (Q64.96 fixed point) +3. Tick and liquidity +4. Fee tiers (0.05%, 0.3%, 1%) + +# Math helpers needed: +func sqrtPriceX96ToPrice(sqrtPriceX96 *big.Int) *big.Float +func calculateSwapOutput(pool *Pool, amountIn *big.Int) *big.Int +``` + +### Week 2: Days 1-4 (Arbitrage Detection) +```bash +git checkout -b feature/v2/arbitrage/basic-detection + +# File structure: +pkg/arbitrage/ +├── graph.go # Market graph +├── pathfinder.go # 2-hop BFS +├── profitability.go # Profit calculation +└── detector.go # Main detector + +# Key algorithm: +For each token pair (A, B): + Find all pools: A → B + For each pool P1: + For each other pool P2: + If P1.price != P2.price: + Calculate arbitrage profit + If profit > minProfit: + Emit opportunity +``` + +### Week 3: Days 1-6 (Execution Engine) +```bash +git checkout -b feature/v2/execution/basic-executor + +# Critical path: +1. Build swap calldata +2. Estimate gas +3. Calculate gas cost +4. Verify still profitable +5. Sign transaction +6. Send to network +7. Wait for receipt +8. Handle success/failure + +# Test on testnet FIRST! +ARBITRUM_RPC=https://sepolia-rollup.arbitrum.io/rpc +``` + +### Week 4: Days 1-7 (Deployment) +```bash +# Shadow mode config: +SHADOW_MODE=true +LOG_LEVEL=debug +MIN_PROFIT=0.01 + +# Go live: +SHADOW_MODE=false +MAX_TRADE_SIZE=0.1 +MAX_TRADES_PER_DAY=10 +CIRCUIT_BREAKER_LOSS=0.05 +``` + +--- + +## 🎯 Decision Points + +### After Week 2 (Arbitrage Detection): +**Question**: Are there enough profitable opportunities? + +Run detection against historical data: +```bash +go run cmd/historical-analysis/main.go \ + --start-block 150000000 \ + --end-block 150001000 \ + --min-profit 0.01 +``` + +**If <5 opportunities per day**: +- ❌ Stop and reconsider strategy +- Maybe try different DEXs +- Maybe lower profit threshold + +**If >10 opportunities per day**: +- ✅ Continue to execution phase + +### After Week 3 (Testnet): +**Question**: Do trades execute successfully? + +**If success rate <50%**: +- Debug execution logic +- Check gas estimation +- Verify slippage calculations + +**If success rate >70%**: +- ✅ Proceed to mainnet + +### After Week 4 Day 7 (First Week Live): +**Question**: Is this profitable? + +**If net profit >0**: +- ✅ Continue for another week +- Consider scaling capital + +**If net profit <0**: +- Analyze why: + - Competition too fierce? + - Gas too expensive? + - Calculations wrong? +- Decide: Fix and retry, or pivot? + +--- + +## 🔄 What Happens After 4 Weeks? + +### Scenario A: MVP is Profitable ✅ +**Next Steps**: +1. Increase capital to 2-5 ETH +2. Add more DEX protocols (Curve, Balancer) +3. Implement 3-hop arbitrage +4. Add sequencer integration (for speed) +5. Build proper monitoring dashboard +6. Scale to 20-50% monthly ROI + +**Timeline**: 4 more weeks to "Full MVP" + +### Scenario B: MVP is Break-Even ⚖️ +**Next Steps**: +1. Optimize for 2 more weeks +2. Add sequencer (may be the missing piece) +3. Reduce gas costs (batch execution) +4. If still break-even, reconsider + +### Scenario C: MVP is Unprofitable ❌ +**Analysis**: +- Is Arbitrum too competitive? +- Are opportunities too rare? +- Is our execution too slow? + +**Options**: +1. Pivot to different chain (Polygon? Base?) +2. Try different MEV strategy (liquidations?) +3. Abandon and move on + +**Key**: We only invested 4 weeks, not 10! + +--- + +## 📋 Week-by-Week Checklist + +### Week 1 Checklist: +- [ ] UniswapV2 parser complete (100% coverage) +- [ ] UniswapV3 parser complete (100% coverage) +- [ ] Pool cache populated with major pairs +- [ ] Can parse any swap on Arbitrum +- [ ] All tests passing + +### Week 2 Checklist: +- [ ] Market graph built from pools +- [ ] 2-hop pathfinder working +- [ ] Profitability calculator accurate +- [ ] Finding >5 opportunities per day (historical) +- [ ] Detection latency <100ms + +### Week 3 Checklist: +- [ ] Can execute swaps on testnet +- [ ] Gas estimation accurate +- [ ] Slippage protection working +- [ ] Circuit breaker tested +- [ ] Success rate >70% on testnet + +### Week 4 Checklist: +- [ ] Metrics collection working +- [ ] Shadow mode validated (24 hours) +- [ ] First live trade successful +- [ ] Circuit breaker hasn't tripped +- [ ] Net profit >0 after 7 days + +--- + +## 💡 Key Principles for Fast MVP + +### 1. **Simple Over Perfect** +``` +❌ Don't: Build a sophisticated gas optimization system +✅ Do: Just use current gas price + 10% + +❌ Don't: Support 13 DEX protocols +✅ Do: Start with 2, add more if profitable + +❌ Don't: Build a ML model for profit prediction +✅ Do: Simple math: output - input - gas +``` + +### 2. **Validate Assumptions Fast** +``` +Week 1: Can we parse swaps correctly? +Week 2: Are there arbitrage opportunities? +Week 3: Can we execute trades? +Week 4: Is it profitable? + +Each week answers ONE key question. +``` + +### 3. **Fail Fast, Pivot Faster** +``` +If Week 2 shows no opportunities → STOP +If Week 3 shows trades fail → FIX or STOP +If Week 4 shows losses → PIVOT or STOP + +Don't throw good time after bad. +``` + +### 4. **Real Data Over Assumptions** +``` +Don't assume profitability → TEST IT +Don't assume opportunities exist → MEASURE THEM +Don't assume execution works → VERIFY IT + +Shadow mode + small capital = real data +``` + +--- + +## 🚀 Let's Start! + +Your next immediate action: + +```bash +# 1. Review this plan +# 2. If approved, start Week 1 Day 1: + +git checkout -b feature/v2/parsers/uniswap-v2-mvp + +# Create the parser +touch pkg/parsers/uniswap_v2.go +touch pkg/parsers/uniswap_v2_test.go + +# Let's build! 🏗️ +``` + +--- + +**This plan gets you to profitability validation in 4 weeks with minimal capital risk. After that, you have REAL DATA to decide whether to scale, pivot, or stop.** + +**Ready to start Week 1? 🎬** + +--- + +## 🚨 CRITICAL BUG DISCOVERED (Nov 2024) + +### Problem: Bot Uses STALE Reserve Data + +**Symptom**: Bot ran for 17+ hours, found ZERO arbitrage opportunities. + +**Root Cause**: The arbitrage detector uses pool reserves fetched ONCE at startup and never refreshes them. + +```go +// CURRENT (BROKEN) FLOW: +1. Bot starts → DiscoverMajorPools() fetches reserves ONCE +2. Bot scans every 30s → Uses SAME stale reserves +3. Real prices change constantly → Bot sees old data +4. Result: ZERO opportunities found (markets look balanced with old data) +``` + +**Why This Matters**: +- Pool reserves change with EVERY swap on-chain +- Arbitrage opportunities exist for seconds/milliseconds +- Using stale data = guaranteed to miss every opportunity +- 17 hours of runtime = 17 hours of wasted scanning + +### Solution: Live Reserve Refresh + +**MUST IMPLEMENT** before bot can find real opportunities: + +```go +// pkg/discovery/reserve_refresh.go + +// RefreshReserves fetches latest reserves from chain for all cached pools +func (d *UniswapV2PoolDiscovery) RefreshReserves(ctx context.Context) error { + pools, _ := d.poolCache.GetByLiquidity(ctx, big.NewInt(0), 10000) + + for _, pool := range pools { + // Call getReserves() on each pool contract + reserves, err := d.fetchReservesFromChain(ctx, pool.Address) + if err != nil { + continue // Skip failed pools + } + + // Update cache with fresh reserves + pool.Reserve0 = reserves.Reserve0 + pool.Reserve1 = reserves.Reserve1 + pool.LastUpdated = time.Now() + + d.poolCache.Update(ctx, pool) + } + return nil +} +``` + +**Integration in main.go**: +```go +// BEFORE each scan, refresh reserves +for range ticker.C { + // NEW: Refresh reserves from chain + if err := poolDiscovery.RefreshReserves(ctx); err != nil { + logger.Error("failed to refresh reserves", "error", err) + } + + // Then scan for opportunities (with fresh data!) + opportunities, err := detector.ScanForOpportunities(ctx, blockNumber) + // ... +} +``` + +### Implementation Priority: HIGHEST + +| Task | Priority | Estimated Time | +|------|----------|----------------| +| Create `reserve_refresh.go` | P0 | 30 min | +| Add `RefreshReserves()` method | P0 | 30 min | +| Call refresh before each scan | P0 | 15 min | +| Test with live data | P0 | 30 min | +| **Total** | **CRITICAL** | **~2 hours** | + +### Verification Test + +After implementing reserve refresh: + +```bash +# Run bot with verbose logging +./bin/mev-flashloan --min-profit 5 --interval 10s --verbose + +# Expected output (after fix): +# - Reserve values should CHANGE between scans +# - Some opportunities should be found (even if small) +# - If still 0 opportunities after 1 hour, lower min-profit further +``` + +### Why This Wasn't Caught Earlier + +1. Unit tests use mock data (don't need live reserves) +2. Integration tests check parsing, not live detection +3. Bot "worked" (no crashes) but with stale data +4. Need live mainnet testing with reserve updates + +### Lesson Learned + +**Always test with LIVE data before declaring production-ready.** + +Static test data can hide critical bugs like stale caches. + +--- + +## 📋 Updated Implementation Checklist + +### Immediate Fixes Required: + +- [ ] **P0**: Implement `RefreshReserves()` in pool discovery +- [ ] **P0**: Call reserve refresh before each arbitrage scan +- [ ] **P0**: Add logging to show reserve changes between scans +- [ ] **P0**: Test on mainnet - verify opportunities are found +- [ ] **P1**: Add reserve age check (skip pools not updated in >60s) +- [ ] **P1**: Batch RPC calls for efficiency (multicall) +- [ ] **P2**: Add WebSocket subscription for real-time reserve updates + +### Performance Considerations: + +With 9 pools, refreshing reserves adds: +- ~9 RPC calls per scan (one per pool) +- ~500ms latency (with public RPC) +- Acceptable for 30s scan interval + +For scaling to 100+ pools: +- Use multicall to batch reserve fetches +- Consider WebSocket subscriptions +- Target <100ms refresh time