Fixed compilation errors in integration code: Type System Fixes: - Add types.Logger type alias (*slog.Logger) - Add PoolInfo.LiquidityUSD field - Add ProtocolSushiSwap and ProtocolCamelot constants - Fix time.Now() call in arbiscan_validator.go Pool Discovery Fixes: - Change cache from *cache.PoolCache to cache.PoolCache (interface) - Add context.Context parameters to cache.Add() and cache.Count() calls - Fix protocol type from string to ProtocolType Docker Fixes: - Add .dockerignore to exclude test files and docs - Add go mod tidy step in Dockerfile - Add //go:build examples tag to example_usage.go Still Remaining: - Arbitrage package needs similar interface fixes - SwapEvent.TokenIn/TokenOut field name issues - More cache interface method calls need context Progress: Parser and pool discovery packages now compile correctly. Integration code (main.go, sequencer, pools) partially working. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
338 lines
10 KiB
Go
338 lines
10 KiB
Go
//go:build examples
|
|
// +build examples
|
|
|
|
package parsers
|
|
|
|
// This file demonstrates how to use the parser factory with multiple protocol parsers,
|
|
// swap logging, and Arbiscan validation for MEV bot operations.
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"math/big"
|
|
"os"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
|
|
"github.com/your-org/mev-bot/pkg/cache"
|
|
"github.com/your-org/mev-bot/pkg/observability"
|
|
mevtypes "github.com/your-org/mev-bot/pkg/types"
|
|
"github.com/your-org/mev-bot/pkg/validation"
|
|
)
|
|
|
|
// ExampleSetup demonstrates complete parser setup with all supported protocols
|
|
func ExampleSetup() {
|
|
ctx := context.Background()
|
|
|
|
// 1. Create logger
|
|
logger := observability.NewLogger(slog.LevelInfo)
|
|
|
|
// 2. Create pool cache
|
|
poolCache := cache.NewPoolCache()
|
|
|
|
// 3. Populate cache with known pools (would come from pool discovery in production)
|
|
populatePoolCache(ctx, poolCache)
|
|
|
|
// 4. Create parser factory
|
|
factory := NewFactory()
|
|
|
|
// 5. Register all protocol parsers
|
|
uniswapV2Parser := NewUniswapV2Parser(poolCache, logger)
|
|
uniswapV3Parser := NewUniswapV3Parser(poolCache, logger)
|
|
curveParser := NewCurveParser(poolCache, logger)
|
|
|
|
factory.RegisterParser(mevtypes.ProtocolUniswapV2, uniswapV2Parser)
|
|
factory.RegisterParser(mevtypes.ProtocolUniswapV3, uniswapV3Parser)
|
|
factory.RegisterParser(mevtypes.ProtocolCurve, curveParser)
|
|
|
|
// 6. Create swap logger for testing and validation
|
|
swapLogger, _ := NewSwapLogger("./logs/swaps", logger)
|
|
|
|
// 7. Create Arbiscan validator
|
|
arbiscanAPIKey := os.Getenv("ARBISCAN_API_KEY")
|
|
arbiscanValidator := NewArbiscanValidator(arbiscanAPIKey, logger, swapLogger)
|
|
|
|
// 8. Create validator with rules
|
|
validationRules := validation.DefaultValidationRules()
|
|
validator := validation.NewValidator(validationRules)
|
|
|
|
// Now ready to parse transactions
|
|
fmt.Println("✅ Parser factory initialized with 3 protocols")
|
|
fmt.Println("✅ Swap logging enabled")
|
|
fmt.Println("✅ Arbiscan validation enabled")
|
|
|
|
// Example usage (see ExampleParseTransaction)
|
|
_ = factory
|
|
_ = validator
|
|
_ = swapLogger
|
|
_ = arbiscanValidator
|
|
}
|
|
|
|
// ExampleParseTransaction shows how to parse a transaction with multiple swaps
|
|
func ExampleParseTransaction(
|
|
factory *factory,
|
|
tx *types.Transaction,
|
|
receipt *types.Receipt,
|
|
validator validation.Validator,
|
|
swapLogger *SwapLogger,
|
|
) ([]*mevtypes.SwapEvent, error) {
|
|
ctx := context.Background()
|
|
|
|
// 1. Parse all swap events from the transaction
|
|
events, err := factory.ParseTransaction(ctx, tx, receipt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse transaction: %w", err)
|
|
}
|
|
|
|
// 2. Validate each event
|
|
validEvents := validator.FilterValid(ctx, events)
|
|
|
|
// 3. Log valid swaps for testing/analysis
|
|
if len(validEvents) > 0 {
|
|
swapLogger.LogSwapBatch(ctx, validEvents, "multi-protocol")
|
|
}
|
|
|
|
// 4. Return valid events for arbitrage detection
|
|
return validEvents, nil
|
|
}
|
|
|
|
// ExampleArbitrageDetection shows how to detect arbitrage opportunities
|
|
func ExampleArbitrageDetection(events []*mevtypes.SwapEvent, poolCache cache.PoolCache) {
|
|
ctx := context.Background()
|
|
|
|
// Group events by token pairs
|
|
type TokenPair struct {
|
|
Token0, Token1 common.Address
|
|
}
|
|
|
|
eventsByPair := make(map[TokenPair][]*mevtypes.SwapEvent)
|
|
|
|
for _, event := range events {
|
|
pair := TokenPair{
|
|
Token0: event.Token0,
|
|
Token1: event.Token1,
|
|
}
|
|
eventsByPair[pair] = append(eventsByPair[pair], event)
|
|
}
|
|
|
|
// For each token pair, compare prices across protocols
|
|
for pair, pairEvents := range eventsByPair {
|
|
if len(pairEvents) < 2 {
|
|
continue // Need at least 2 events to compare
|
|
}
|
|
|
|
// Compare V2 vs V3 prices
|
|
for i, event1 := range pairEvents {
|
|
for j, event2 := range pairEvents {
|
|
if i >= j {
|
|
continue
|
|
}
|
|
|
|
// Check if protocols are different
|
|
if event1.Protocol == event2.Protocol {
|
|
continue
|
|
}
|
|
|
|
// Calculate implied prices
|
|
price1 := calculateImpliedPrice(event1)
|
|
price2 := calculateImpliedPrice(event2)
|
|
|
|
// Calculate price difference
|
|
priceDiff := new(big.Float).Sub(price1, price2)
|
|
priceDiff.Abs(priceDiff)
|
|
|
|
// If price difference > threshold, we have an arbitrage opportunity
|
|
threshold := big.NewFloat(0.001) // 0.1%
|
|
if priceDiff.Cmp(threshold) > 0 {
|
|
fmt.Printf("🎯 Arbitrage opportunity found!\n")
|
|
fmt.Printf(" Pair: %s/%s\n", pair.Token0.Hex()[:10], pair.Token1.Hex()[:10])
|
|
fmt.Printf(" %s price: %s\n", event1.Protocol, price1.Text('f', 6))
|
|
fmt.Printf(" %s price: %s\n", event2.Protocol, price2.Text('f', 6))
|
|
fmt.Printf(" Difference: %s\n", priceDiff.Text('f', 6))
|
|
|
|
// Calculate potential profit
|
|
profit := simulateArbitrage(ctx, event1, event2, poolCache)
|
|
if profit.Sign() > 0 {
|
|
fmt.Printf(" 💰 Estimated profit: %s ETH\n", profit.Text('f', 6))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ExampleMultiHopArbitrage shows how to detect multi-hop arbitrage (A→B→C→A)
|
|
func ExampleMultiHopArbitrage(poolCache cache.PoolCache) {
|
|
ctx := context.Background()
|
|
|
|
// Example: WETH → USDC → DAI → WETH arbitrage on Uniswap V3
|
|
|
|
// Pool 1: WETH/USDC
|
|
poolWETH_USDC, _ := poolCache.GetByAddress(ctx, common.HexToAddress("0x1111"))
|
|
|
|
// Pool 2: USDC/DAI
|
|
poolUSDC_DAI, _ := poolCache.GetByAddress(ctx, common.HexToAddress("0x2222"))
|
|
|
|
// Pool 3: DAI/WETH
|
|
poolDAI_WETH, _ := poolCache.GetByAddress(ctx, common.HexToAddress("0x3333"))
|
|
|
|
// Simulate route: 1 WETH → USDC → DAI → WETH
|
|
startAmount := big.NewInt(1000000000000000000) // 1 WETH
|
|
|
|
// Step 1: WETH → USDC
|
|
usdcAmount, priceAfter1, _ := CalculateSwapAmounts(
|
|
poolWETH_USDC.SqrtPriceX96,
|
|
poolWETH_USDC.Liquidity,
|
|
startAmount,
|
|
true, // WETH = token0
|
|
3000, // 0.3% fee
|
|
)
|
|
|
|
// Step 2: USDC → DAI
|
|
daiAmount, priceAfter2, _ := CalculateSwapAmounts(
|
|
poolUSDC_DAI.SqrtPriceX96,
|
|
poolUSDC_DAI.Liquidity,
|
|
usdcAmount,
|
|
true, // USDC = token0
|
|
500, // 0.05% fee (Curve-like)
|
|
)
|
|
|
|
// Step 3: DAI → WETH
|
|
finalWETH, priceAfter3, _ := CalculateSwapAmounts(
|
|
poolDAI_WETH.SqrtPriceX96,
|
|
poolDAI_WETH.Liquidity,
|
|
daiAmount,
|
|
false, // WETH = token1
|
|
3000, // 0.3% fee
|
|
)
|
|
|
|
// Calculate profit
|
|
profit := new(big.Int).Sub(finalWETH, startAmount)
|
|
|
|
if profit.Sign() > 0 {
|
|
fmt.Printf("🚀 Multi-hop arbitrage opportunity!\n")
|
|
fmt.Printf(" Route: WETH → USDC → DAI → WETH\n")
|
|
fmt.Printf(" Input: %s WETH\n", formatAmount(startAmount, 18))
|
|
fmt.Printf(" Output: %s WETH\n", formatAmount(finalWETH, 18))
|
|
fmt.Printf(" 💰 Profit: %s WETH\n", formatAmount(profit, 18))
|
|
fmt.Printf(" Prices: %v → %v → %v\n", priceAfter1, priceAfter2, priceAfter3)
|
|
} else {
|
|
fmt.Printf("❌ No profit: %s WETH loss\n", formatAmount(new(big.Int).Abs(profit), 18))
|
|
}
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func populatePoolCache(ctx context.Context, poolCache cache.PoolCache) {
|
|
// Example pools (would come from discovery service in production)
|
|
|
|
// Uniswap V2: WETH/USDC
|
|
poolCache.Add(ctx, &mevtypes.PoolInfo{
|
|
Address: common.HexToAddress("0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443"),
|
|
Protocol: mevtypes.ProtocolUniswapV2,
|
|
Token0: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH
|
|
Token1: common.HexToAddress("0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8"), // USDC
|
|
Token0Decimals: 18,
|
|
Token1Decimals: 6,
|
|
Fee: 30, // 0.3%
|
|
IsActive: true,
|
|
})
|
|
|
|
// Uniswap V3: WETH/USDC 0.05%
|
|
poolCache.Add(ctx, &mevtypes.PoolInfo{
|
|
Address: common.HexToAddress("0xC31E54c7a869B9FcBEcc14363CF510d1c41fa444"),
|
|
Protocol: mevtypes.ProtocolUniswapV3,
|
|
Token0: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH
|
|
Token1: common.HexToAddress("0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8"), // USDC
|
|
Token0Decimals: 18,
|
|
Token1Decimals: 6,
|
|
Fee: 500, // 0.05%
|
|
SqrtPriceX96: new(big.Int).Lsh(big.NewInt(1), 96),
|
|
Liquidity: big.NewInt(1000000000000),
|
|
IsActive: true,
|
|
})
|
|
|
|
// Curve: USDC/USDT
|
|
poolCache.Add(ctx, &mevtypes.PoolInfo{
|
|
Address: common.HexToAddress("0x7f90122BF0700F9E7e1F688fe926940E8839F353"),
|
|
Protocol: mevtypes.ProtocolCurve,
|
|
Token0: common.HexToAddress("0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8"), // USDC
|
|
Token1: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"), // USDT
|
|
Token0Decimals: 6,
|
|
Token1Decimals: 6,
|
|
Fee: 4, // 0.04%
|
|
AmpCoefficient: big.NewInt(2000),
|
|
IsActive: true,
|
|
})
|
|
}
|
|
|
|
func calculateImpliedPrice(event *mevtypes.SwapEvent) *big.Float {
|
|
// Calculate price as amountOut / amountIn
|
|
var amountIn, amountOut *big.Int
|
|
|
|
if event.Amount0In.Sign() > 0 {
|
|
amountIn = event.Amount0In
|
|
amountOut = event.Amount1Out
|
|
} else {
|
|
amountIn = event.Amount1In
|
|
amountOut = event.Amount0Out
|
|
}
|
|
|
|
if amountIn.Sign() == 0 {
|
|
return big.NewFloat(0)
|
|
}
|
|
|
|
amountInFloat := new(big.Float).SetInt(amountIn)
|
|
amountOutFloat := new(big.Float).SetInt(amountOut)
|
|
|
|
price := new(big.Float).Quo(amountOutFloat, amountInFloat)
|
|
return price
|
|
}
|
|
|
|
func simulateArbitrage(
|
|
ctx context.Context,
|
|
event1, event2 *mevtypes.SwapEvent,
|
|
poolCache cache.PoolCache,
|
|
) *big.Float {
|
|
// Simplified arbitrage simulation
|
|
// In production, this would:
|
|
// 1. Calculate optimal trade size
|
|
// 2. Account for gas costs
|
|
// 3. Account for slippage
|
|
// 4. Check liquidity constraints
|
|
|
|
// For now, return mock profit
|
|
return big.NewFloat(0.05) // 0.05 ETH profit
|
|
}
|
|
|
|
func formatAmount(amount *big.Int, decimals uint8) string {
|
|
// Convert to float and format
|
|
amountFloat := new(big.Float).SetInt(amount)
|
|
divisor := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil))
|
|
result := new(big.Float).Quo(amountFloat, divisor)
|
|
return result.Text('f', 6)
|
|
}
|
|
|
|
// ExampleRealTimeMonitoring shows how to monitor pending transactions
|
|
func ExampleRealTimeMonitoring() {
|
|
fmt.Println("📡 Real-time MEV bot monitoring pattern:")
|
|
fmt.Println("")
|
|
fmt.Println("1. Subscribe to pending transactions (mempool)")
|
|
fmt.Println("2. Parse swaps using factory.ParseTransaction()")
|
|
fmt.Println("3. Validate using validator.FilterValid()")
|
|
fmt.Println("4. Detect arbitrage across protocols")
|
|
fmt.Println("5. Calculate profitability (profit - gas)")
|
|
fmt.Println("6. Execute if profitable (front-run, sandwich, or arbitrage)")
|
|
fmt.Println("7. Log results with swapLogger for analysis")
|
|
fmt.Println("8. Validate accuracy with arbiscanValidator")
|
|
fmt.Println("")
|
|
fmt.Println("Performance targets:")
|
|
fmt.Println(" - Parse: < 5ms")
|
|
fmt.Println(" - Validate: < 2ms")
|
|
fmt.Println(" - Detect: < 10ms")
|
|
fmt.Println(" - Execute: < 30ms")
|
|
fmt.Println(" - Total: < 50ms end-to-end")
|
|
}
|