feat: create v2-prep branch with comprehensive planning
Restructured project for V2 refactor: **Structure Changes:** - Moved all V1 code to orig/ folder (preserved with git mv) - Created docs/planning/ directory - Added orig/README_V1.md explaining V1 preservation **Planning Documents:** - 00_V2_MASTER_PLAN.md: Complete architecture overview - Executive summary of critical V1 issues - High-level component architecture diagrams - 5-phase implementation roadmap - Success metrics and risk mitigation - 07_TASK_BREAKDOWN.md: Atomic task breakdown - 99+ hours of detailed tasks - Every task < 2 hours (atomic) - Clear dependencies and success criteria - Organized by implementation phase **V2 Key Improvements:** - Per-exchange parsers (factory pattern) - Multi-layer strict validation - Multi-index pool cache - Background validation pipeline - Comprehensive observability **Critical Issues Addressed:** - Zero address tokens (strict validation + cache enrichment) - Parsing accuracy (protocol-specific parsers) - No audit trail (background validation channel) - Inefficient lookups (multi-index cache) - Stats disconnection (event-driven metrics) Next Steps: 1. Review planning documents 2. Begin Phase 1: Foundation (P1-001 through P1-010) 3. Implement parsers in Phase 2 4. Build cache system in Phase 3 5. Add validation pipeline in Phase 4 6. Migrate and test in Phase 5 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
365
orig/pkg/exchanges/exchanges.go
Normal file
365
orig/pkg/exchanges/exchanges.go
Normal file
@@ -0,0 +1,365 @@
|
||||
package exchanges
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
|
||||
"github.com/fraktal/mev-beta/internal/logger"
|
||||
"github.com/fraktal/mev-beta/pkg/math"
|
||||
)
|
||||
|
||||
// TokenInfo represents token information
|
||||
type TokenInfo struct {
|
||||
Address string
|
||||
Symbol string
|
||||
Name string
|
||||
Decimals uint8
|
||||
}
|
||||
|
||||
// TokenPair represents a pair of tokens
|
||||
type TokenPair struct {
|
||||
Token0 TokenInfo
|
||||
Token1 TokenInfo
|
||||
}
|
||||
|
||||
// Use ExchangeType from math package to avoid duplication
|
||||
|
||||
// ExchangeConfig holds configuration for an exchange
|
||||
type ExchangeConfig struct {
|
||||
Type math.ExchangeType
|
||||
Name string
|
||||
FactoryAddress common.Address
|
||||
RouterAddress common.Address
|
||||
PoolInitCodeHash string
|
||||
SwapSelector []byte
|
||||
StableSwapSelector []byte
|
||||
ChainID int64
|
||||
SupportsFlashSwaps bool
|
||||
RequiresApproval bool
|
||||
MaxHops int
|
||||
DefaultSlippagePercent float64
|
||||
Url string
|
||||
ApiUrl string
|
||||
}
|
||||
|
||||
// PoolDetector interface for detecting pools
|
||||
type PoolDetector interface {
|
||||
GetAllPools(token0, token1 common.Address) ([]common.Address, error)
|
||||
GetPoolForPair(token0, token1 common.Address) (common.Address, error)
|
||||
GetSupportedFeeTiers() []int64
|
||||
GetPoolType() string
|
||||
}
|
||||
|
||||
// LiquidityFetcher interface for fetching liquidity data
|
||||
type LiquidityFetcher interface {
|
||||
GetPoolData(poolAddress common.Address) (*math.PoolData, error)
|
||||
GetTokenReserves(poolAddress, token0, token1 common.Address) (*big.Int, *big.Int, error)
|
||||
GetPoolPrice(poolAddress common.Address) (*big.Float, error)
|
||||
GetLiquidityDepth(poolAddress, tokenIn common.Address, amount *big.Int) (*big.Int, error)
|
||||
}
|
||||
|
||||
// SwapRouter interface for executing swaps
|
||||
type SwapRouter interface {
|
||||
CalculateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) (*big.Int, error)
|
||||
GenerateSwapData(tokenIn, tokenOut common.Address, amountIn, minAmountOut *big.Int, deadline *big.Int) ([]byte, error)
|
||||
GetSwapRoute(tokenIn, tokenOut common.Address) ([]common.Address, error)
|
||||
ValidateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) error
|
||||
}
|
||||
|
||||
// ExchangeRegistry manages multiple exchanges
|
||||
type ExchangeRegistry struct {
|
||||
client *ethclient.Client
|
||||
logger *logger.Logger
|
||||
exchanges map[math.ExchangeType]*ExchangeConfig
|
||||
poolDetectors map[math.ExchangeType]PoolDetector
|
||||
liquidityFetchers map[math.ExchangeType]LiquidityFetcher
|
||||
swapRouters map[math.ExchangeType]SwapRouter
|
||||
marketScanner *MarketScanner
|
||||
}
|
||||
|
||||
// MarketScanner interface for scanning markets
|
||||
type MarketScanner interface {
|
||||
GetPoolsForTokenPair(token0, token1 common.Address) []common.Address
|
||||
GetPoolInfo(poolAddress common.Address) (*math.PoolData, error)
|
||||
}
|
||||
|
||||
// NewExchangeRegistry creates a new exchange registry
|
||||
func NewExchangeRegistry(client *ethclient.Client, logger *logger.Logger) *ExchangeRegistry {
|
||||
return &ExchangeRegistry{
|
||||
client: client,
|
||||
logger: logger,
|
||||
exchanges: make(map[math.ExchangeType]*ExchangeConfig),
|
||||
poolDetectors: make(map[math.ExchangeType]PoolDetector),
|
||||
liquidityFetchers: make(map[math.ExchangeType]LiquidityFetcher),
|
||||
swapRouters: make(map[math.ExchangeType]SwapRouter),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadArbitrumExchanges loads all supported exchanges on Arbitrum
|
||||
func (er *ExchangeRegistry) LoadArbitrumExchanges() error {
|
||||
// Load Uniswap V3
|
||||
uniswapV3Config := &ExchangeConfig{
|
||||
Type: math.ExchangeUniswapV3,
|
||||
Name: "Uniswap V3",
|
||||
FactoryAddress: common.HexToAddress("0x1F98431c8aD98523631AE4a59f267346ea31F984"),
|
||||
RouterAddress: common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564"),
|
||||
PoolInitCodeHash: "0xe34f199b19b2b4f47d6b5b39c7bde33e2a0a4c36149e248e847a490f543b7f3f",
|
||||
SwapSelector: []byte{0x38, 0xed, 0x17, 0x39}, // exactInputSingle
|
||||
StableSwapSelector: []byte{},
|
||||
ChainID: 42161,
|
||||
SupportsFlashSwaps: true,
|
||||
RequiresApproval: true,
|
||||
MaxHops: 2,
|
||||
DefaultSlippagePercent: 0.5,
|
||||
Url: "https://uniswap.org",
|
||||
ApiUrl: "https://api.uniswap.org",
|
||||
}
|
||||
|
||||
er.exchanges[math.ExchangeUniswapV3] = uniswapV3Config
|
||||
|
||||
// Load SushiSwap
|
||||
sushiSwapConfig := &ExchangeConfig{
|
||||
Type: math.ExchangeSushiSwap,
|
||||
Name: "SushiSwap",
|
||||
FactoryAddress: common.HexToAddress("0xc35DADB65012eC5796536bD9864eD8773aBc74C4"),
|
||||
RouterAddress: common.HexToAddress("0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506"),
|
||||
PoolInitCodeHash: "0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303",
|
||||
SwapSelector: []byte{0x18, 0x2d, 0x2e, 0xdb}, // swapExactTokensForTokens
|
||||
StableSwapSelector: []byte{},
|
||||
ChainID: 42161,
|
||||
SupportsFlashSwaps: true,
|
||||
RequiresApproval: true,
|
||||
MaxHops: 3,
|
||||
DefaultSlippagePercent: 0.5,
|
||||
Url: "https://sushi.com",
|
||||
ApiUrl: "https://api.sushi.com",
|
||||
}
|
||||
|
||||
er.exchanges[math.ExchangeSushiSwap] = sushiSwapConfig
|
||||
|
||||
// Load Curve
|
||||
curveConfig := &ExchangeConfig{
|
||||
Type: math.ExchangeCurve,
|
||||
Name: "Curve",
|
||||
FactoryAddress: common.HexToAddress("0xb1748c7970d18f7d0ebf0f89e1c3f3b98e65e8ad"),
|
||||
RouterAddress: common.HexToAddress("0x4d7c19704623c38f54ec0d144c0ac63d0b76106e"),
|
||||
PoolInitCodeHash: "",
|
||||
SwapSelector: []byte{0x5b, 0x40, 0x2d, 0x3c}, // exchange
|
||||
StableSwapSelector: []byte{0x79, 0x1a, 0xc9, 0x47}, // exchange_underlying
|
||||
ChainID: 42161,
|
||||
SupportsFlashSwaps: false,
|
||||
RequiresApproval: true,
|
||||
MaxHops: 2,
|
||||
DefaultSlippagePercent: 0.1,
|
||||
Url: "https://curve.fi",
|
||||
ApiUrl: "https://api.curve.fi",
|
||||
}
|
||||
|
||||
er.exchanges[math.ExchangeCurve] = curveConfig
|
||||
|
||||
// Load Balancer
|
||||
balancerConfig := &ExchangeConfig{
|
||||
Type: math.ExchangeBalancer,
|
||||
Name: "Balancer",
|
||||
FactoryAddress: common.HexToAddress("0xb33c178f5e23f3abf72a0dbfe955f1e69f2e47a6"),
|
||||
RouterAddress: common.HexToAddress("0xba12222222228d8ba445958a75a0704d566bf2c8"),
|
||||
PoolInitCodeHash: "",
|
||||
SwapSelector: []byte{0x3e, 0x2, 0x76, 0x5e}, // swap
|
||||
StableSwapSelector: []byte{},
|
||||
ChainID: 42161,
|
||||
SupportsFlashSwaps: true,
|
||||
RequiresApproval: true,
|
||||
MaxHops: 3,
|
||||
DefaultSlippagePercent: 0.3,
|
||||
Url: "https://balancer.fi",
|
||||
ApiUrl: "https://api.balancer.fi",
|
||||
}
|
||||
|
||||
er.exchanges[math.ExchangeBalancer] = balancerConfig
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetExchangeByType returns an exchange configuration by type
|
||||
func (er *ExchangeRegistry) GetExchangeByType(exchangeType math.ExchangeType) *ExchangeConfig {
|
||||
config, exists := er.exchanges[exchangeType]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
// GetExchangesForPair returns exchanges that support a token pair
|
||||
func (er *ExchangeRegistry) GetExchangesForPair(token0, token1 common.Address) []*ExchangeConfig {
|
||||
var supported []*ExchangeConfig
|
||||
|
||||
for _, config := range er.exchanges {
|
||||
// In a real implementation, this would check if pools exist for this pair
|
||||
// For now, we'll assume all exchanges support all pairs for testing
|
||||
supported = append(supported, config)
|
||||
}
|
||||
|
||||
return supported
|
||||
}
|
||||
|
||||
// GetAllExchanges returns all registered exchanges
|
||||
func (er *ExchangeRegistry) GetAllExchanges() []*ExchangeConfig {
|
||||
var configs []*ExchangeConfig
|
||||
for _, config := range er.exchanges {
|
||||
configs = append(configs, config)
|
||||
}
|
||||
return configs
|
||||
}
|
||||
|
||||
// GetAllExchangesMap returns all registered exchanges as a map
|
||||
func (er *ExchangeRegistry) GetAllExchangesMap() map[math.ExchangeType]*ExchangeConfig {
|
||||
return er.exchanges
|
||||
}
|
||||
|
||||
// GetHighPriorityTokens returns high-priority tokens for scanning
|
||||
func (er *ExchangeRegistry) GetHighPriorityTokens(limit int) []TokenInfo {
|
||||
// Define high-priority tokens (ETH, USDC, USDT, WBTC, etc.)
|
||||
// CRITICAL FIX: Use correct Arbitrum token addresses (not Ethereum/other chains)
|
||||
highPriorityTokens := []TokenInfo{
|
||||
{Address: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", Symbol: "WETH", Name: "Wrapped Ether", Decimals: 18},
|
||||
{Address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", Symbol: "USDC", Name: "USD Coin", Decimals: 6}, // FIXED: Correct Arbitrum USDC
|
||||
{Address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", Symbol: "USDT", Name: "Tether USD", Decimals: 6},
|
||||
{Address: "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f", Symbol: "WBTC", Name: "Wrapped BTC", Decimals: 8}, // FIXED: Correct Arbitrum WBTC
|
||||
{Address: "0xf97f4df75117a78c1A5a0DBb814Af92458539FB4", Symbol: "LINK", Name: "ChainLink Token", Decimals: 18}, // FIXED: Correct Arbitrum LINK
|
||||
{Address: "0x11cDb42B0EB46D95f990BeDD4695A6e3fA034978", Symbol: "CRV", Name: "Curve DAO Token", Decimals: 18}, // FIXED: Correct Arbitrum CRV
|
||||
{Address: "0x912CE59144191C1204E64559FE8253a0e49E6548", Symbol: "ARB", Name: "Arbitrum", Decimals: 18}, // ADDED: Arbitrum native token
|
||||
{Address: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", Symbol: "DAI", Name: "Dai Stablecoin", Decimals: 18}, // ADDED: DAI
|
||||
{Address: "0xfc5A1A6EB076a2C7aD06eD22C90d7E710E35ad0a", Symbol: "GMX", Name: "GMX", Decimals: 18}, // ADDED: GMX
|
||||
{Address: "0x9623063377AD1B27544C965cCd7342f7EA7e88C7", Symbol: "GRT", Name: "The Graph", Decimals: 18}, // ADDED: GRT
|
||||
}
|
||||
|
||||
if limit > len(highPriorityTokens) {
|
||||
limit = len(highPriorityTokens)
|
||||
}
|
||||
|
||||
return highPriorityTokens[:limit]
|
||||
}
|
||||
|
||||
// IsPairSupported checks if a token pair is supported by any exchange
|
||||
func (er *ExchangeRegistry) IsPairSupported(token0, token1 common.Address) bool {
|
||||
supportedExchanges := er.GetExchangesForPair(token0, token1)
|
||||
return len(supportedExchanges) > 0
|
||||
}
|
||||
|
||||
// ArbitragePath represents a simple arbitrage path for the exchanges package
|
||||
type ArbitragePath struct {
|
||||
TokenIn common.Address
|
||||
TokenOut common.Address
|
||||
Exchanges []math.ExchangeType
|
||||
Pools []common.Address
|
||||
}
|
||||
|
||||
// GetPoolDetector returns the pool detector for a specific exchange type
|
||||
func (er *ExchangeRegistry) GetPoolDetector(exchangeType math.ExchangeType) PoolDetector {
|
||||
return er.poolDetectors[exchangeType]
|
||||
}
|
||||
|
||||
// GetLiquidityFetcher returns the liquidity fetcher for a specific exchange type
|
||||
func (er *ExchangeRegistry) GetLiquidityFetcher(exchangeType math.ExchangeType) LiquidityFetcher {
|
||||
return er.liquidityFetchers[exchangeType]
|
||||
}
|
||||
|
||||
// GetSwapRouter returns the swap router for a specific exchange type
|
||||
func (er *ExchangeRegistry) GetSwapRouter(exchangeType math.ExchangeType) SwapRouter {
|
||||
return er.swapRouters[exchangeType]
|
||||
}
|
||||
|
||||
// FindAllPaths finds all possible arbitrage paths between two tokens
|
||||
func (er *ExchangeRegistry) FindAllPaths(tokenA, tokenB common.Address, maxHops int) ([]*ArbitragePath, error) {
|
||||
// Simplified implementation for compilation - would be enhanced with actual path finding
|
||||
paths := []*ArbitragePath{}
|
||||
|
||||
// For now, create a simple direct path
|
||||
if er.IsPairSupported(tokenA, tokenB) {
|
||||
path := &ArbitragePath{
|
||||
TokenIn: tokenA,
|
||||
TokenOut: tokenB,
|
||||
Exchanges: []math.ExchangeType{math.ExchangeUniswapV3},
|
||||
Pools: []common.Address{}, // Would be populated with actual pool addresses
|
||||
}
|
||||
paths = append(paths, path)
|
||||
}
|
||||
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
// RegisterExchangeComponents registers pool detector, liquidity fetcher, and swap router for an exchange
|
||||
func (er *ExchangeRegistry) RegisterExchangeComponents(
|
||||
exchangeType math.ExchangeType,
|
||||
poolDetector PoolDetector,
|
||||
liquidityFetcher LiquidityFetcher,
|
||||
swapRouter SwapRouter,
|
||||
) {
|
||||
if er.poolDetectors == nil {
|
||||
er.poolDetectors = make(map[math.ExchangeType]PoolDetector)
|
||||
}
|
||||
if er.liquidityFetchers == nil {
|
||||
er.liquidityFetchers = make(map[math.ExchangeType]LiquidityFetcher)
|
||||
}
|
||||
if er.swapRouters == nil {
|
||||
er.swapRouters = make(map[math.ExchangeType]SwapRouter)
|
||||
}
|
||||
|
||||
er.poolDetectors[exchangeType] = poolDetector
|
||||
er.liquidityFetchers[exchangeType] = liquidityFetcher
|
||||
er.swapRouters[exchangeType] = swapRouter
|
||||
}
|
||||
|
||||
// InitializeExchangeComponents initializes all exchange components for an arbitrum chain
|
||||
func (er *ExchangeRegistry) InitializeExchangeComponents(engine *math.ExchangePricingEngine) error {
|
||||
if er.client == nil {
|
||||
return fmt.Errorf("ethclient is required to initialize exchange components")
|
||||
}
|
||||
|
||||
// Initialize Uniswap V2 components
|
||||
uniswapV2PoolDetector := NewUniswapV2PoolDetector(er.client, er.logger, er.exchanges[math.ExchangeUniswapV2])
|
||||
uniswapV2LiquidityFetcher := NewUniswapV2LiquidityFetcher(er.client, er.logger, er.exchanges[math.ExchangeUniswapV2], engine)
|
||||
uniswapV2Router := NewUniswapV2SwapRouter(er.client, er.logger, er.exchanges[math.ExchangeUniswapV2], engine)
|
||||
er.RegisterExchangeComponents(math.ExchangeUniswapV2, uniswapV2PoolDetector, uniswapV2LiquidityFetcher, uniswapV2Router)
|
||||
|
||||
// Initialize SushiSwap components (similar to Uniswap V2)
|
||||
sushiPoolDetector := NewSushiSwapPoolDetector(er.client, er.logger, er.exchanges[math.ExchangeSushiSwap])
|
||||
sushiLiquidityFetcher := NewSushiSwapLiquidityFetcher(er.client, er.logger, er.exchanges[math.ExchangeSushiSwap], engine)
|
||||
sushiRouter := NewSushiSwapSwapRouter(er.client, er.logger, er.exchanges[math.ExchangeSushiSwap], engine)
|
||||
er.RegisterExchangeComponents(math.ExchangeSushiSwap, sushiPoolDetector, sushiLiquidityFetcher, sushiRouter)
|
||||
|
||||
// Initialize Curve components
|
||||
curvePoolDetector := NewCurvePoolDetector(er.client, er.logger, er.exchanges[math.ExchangeCurve])
|
||||
curveLiquidityFetcher := NewCurveLiquidityFetcher(er.client, er.logger, er.exchanges[math.ExchangeCurve], engine)
|
||||
curveRouter := NewCurveSwapRouter(er.client, er.logger, er.exchanges[math.ExchangeCurve], engine)
|
||||
er.RegisterExchangeComponents(math.ExchangeCurve, curvePoolDetector, curveLiquidityFetcher, curveRouter)
|
||||
|
||||
// Initialize Balancer components
|
||||
balancerPoolDetector := NewBalancerPoolDetector(er.client, er.logger, er.exchanges[math.ExchangeBalancer])
|
||||
balancerLiquidityFetcher := NewBalancerLiquidityFetcher(er.client, er.logger, er.exchanges[math.ExchangeBalancer], engine)
|
||||
balancerRouter := NewBalancerSwapRouter(er.client, er.logger, er.exchanges[math.ExchangeBalancer], engine)
|
||||
er.RegisterExchangeComponents(math.ExchangeBalancer, balancerPoolDetector, balancerLiquidityFetcher, balancerRouter)
|
||||
|
||||
// Initialize PancakeSwap components
|
||||
pancakePoolDetector := NewPancakeSwapPoolDetector(er.client, er.logger, er.exchanges[math.ExchangeSushiSwap])
|
||||
pancakeLiquidityFetcher := NewPancakeSwapLiquidityFetcher(er.client, er.logger, er.exchanges[math.ExchangeSushiSwap], engine)
|
||||
pancakeRouter := NewPancakeSwapSwapRouter(er.client, er.logger, er.exchanges[math.ExchangeSushiSwap], engine)
|
||||
er.RegisterExchangeComponents(math.ExchangeSushiSwap, pancakePoolDetector, pancakeLiquidityFetcher, pancakeRouter)
|
||||
|
||||
// Initialize Kyber components
|
||||
kyberPoolDetector := NewKyberPoolDetector(er.client, er.logger, er.exchanges[math.ExchangeKyber])
|
||||
kyberLiquidityFetcher := NewKyberLiquidityFetcher(er.client, er.logger, er.exchanges[math.ExchangeKyber], engine)
|
||||
kyberRouter := NewKyberSwapRouter(er.client, er.logger, er.exchanges[math.ExchangeKyber], engine)
|
||||
er.RegisterExchangeComponents(math.ExchangeKyber, kyberPoolDetector, kyberLiquidityFetcher, kyberRouter)
|
||||
|
||||
// Initialize Uniswap V4 components
|
||||
uniswapV4PoolDetector := NewUniswapV4PoolDetector(er.client, er.logger, er.exchanges[math.ExchangeUniswapV4])
|
||||
uniswapV4LiquidityFetcher := NewUniswapV4LiquidityFetcher(er.client, er.logger, er.exchanges[math.ExchangeUniswapV4], engine)
|
||||
uniswapV4Router := NewUniswapV4SwapRouter(er.client, er.logger, er.exchanges[math.ExchangeUniswapV4], engine)
|
||||
er.RegisterExchangeComponents(math.ExchangeUniswapV4, uniswapV4PoolDetector, uniswapV4LiquidityFetcher, uniswapV4Router)
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user