Files
mev-beta/orig/pkg/exchanges/curve.go
Administrator 803de231ba 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>
2025-11-10 10:14:26 +01:00

264 lines
9.1 KiB
Go

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"
)
// CurvePoolDetector implements PoolDetector for Curve
type CurvePoolDetector struct {
client *ethclient.Client
logger *logger.Logger
config *ExchangeConfig
}
// NewCurvePoolDetector creates a new Curve pool detector
func NewCurvePoolDetector(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig) *CurvePoolDetector {
return &CurvePoolDetector{
client: client,
logger: logger,
config: config,
}
}
// GetAllPools returns all pools containing the specified tokens
func (d *CurvePoolDetector) GetAllPools(token0, token1 common.Address) ([]common.Address, error) {
// In a real implementation, this would query the registry contract
// For now, we'll return an empty slice
return []common.Address{}, nil
}
// GetPoolForPair returns the pool address for a specific token pair
func (d *CurvePoolDetector) GetPoolForPair(token0, token1 common.Address) (common.Address, error) {
// In a real implementation, this would query the registry for pools
// containing both tokens
poolAddress := common.HexToAddress("0x0") // Placeholder
// For now, return empty address to indicate pool not found
return poolAddress, nil
}
// GetSupportedFeeTiers returns supported fee tiers for Curve (varies by pool)
func (d *CurvePoolDetector) GetSupportedFeeTiers() []int64 {
// Curve pools can have different fee tiers
return []int64{400, 1000, 4000} // 0.04%, 0.1%, 0.4% in basis points
}
// GetPoolType returns the pool type
func (d *CurvePoolDetector) GetPoolType() string {
return "curve_stable_swap"
}
// CurveLiquidityFetcher implements LiquidityFetcher for Curve
type CurveLiquidityFetcher struct {
client *ethclient.Client
logger *logger.Logger
config *ExchangeConfig
engine *math.ExchangePricingEngine
}
// NewCurveLiquidityFetcher creates a new Curve liquidity fetcher
func NewCurveLiquidityFetcher(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig, engine *math.ExchangePricingEngine) *CurveLiquidityFetcher {
return &CurveLiquidityFetcher{
client: client,
logger: logger,
config: config,
engine: engine,
}
}
// GetPoolData fetches pool information for Curve
func (f *CurveLiquidityFetcher) GetPoolData(poolAddress common.Address) (*math.PoolData, error) {
// In a real implementation, this would call the pool contract to get reserves and other data
// For now, return a placeholder pool data with Curve-specific fields
fee, err := math.NewUniversalDecimal(big.NewInt(400), 4, "FEE")
if err != nil {
return nil, fmt.Errorf("error creating fee decimal: %w", err)
}
reserve0, err := math.NewUniversalDecimal(big.NewInt(1000000000000), 6, "RESERVE0") // USDC has 6 decimals
if err != nil {
return nil, fmt.Errorf("error creating reserve0 decimal: %w", err)
}
reserve1, err := math.NewUniversalDecimal(big.NewInt(1000000000000), 6, "RESERVE1") // USDT has 6 decimals
if err != nil {
return nil, fmt.Errorf("error creating reserve1 decimal: %w", err)
}
return &math.PoolData{
Address: poolAddress.Hex(),
ExchangeType: math.ExchangeCurve,
Fee: fee,
Token0: math.TokenInfo{Address: "0x0", Symbol: "USDC", Decimals: 6},
Token1: math.TokenInfo{Address: "0x1", Symbol: "USDT", Decimals: 6},
Reserve0: reserve0,
Reserve1: reserve1,
A: big.NewInt(2000), // Amplification coefficient for stable swaps
}, nil
}
// GetTokenReserves fetches reserves for a specific token pair in a pool
func (f *CurveLiquidityFetcher) GetTokenReserves(poolAddress, token0, token1 common.Address) (*big.Int, *big.Int, error) {
// In a real implementation, this would query the pool contract
// For now, return placeholder values
return big.NewInt(1000000000000), big.NewInt(1000000000000), nil
}
// GetPoolPrice calculates the price of token1 in terms of token0
func (f *CurveLiquidityFetcher) GetPoolPrice(poolAddress common.Address) (*big.Float, error) {
poolData, err := f.GetPoolData(poolAddress)
if err != nil {
return nil, err
}
pricer, err := f.engine.GetExchangePricer(poolData.ExchangeType)
if err != nil {
return nil, err
}
spotPrice, err := pricer.GetSpotPrice(poolData)
if err != nil {
return nil, err
}
// Convert the UniversalDecimal Value to a *big.Float
result := new(big.Float).SetInt(spotPrice.Value)
return result, nil
}
// GetLiquidityDepth calculates the liquidity depth for an amount
func (f *CurveLiquidityFetcher) GetLiquidityDepth(poolAddress, tokenIn common.Address, amount *big.Int) (*big.Int, error) {
// In a real implementation, this would calculate liquidity with Curve's stable swap formula
return amount, nil
}
// CurveSwapRouter implements SwapRouter for Curve
type CurveSwapRouter struct {
client *ethclient.Client
logger *logger.Logger
config *ExchangeConfig
engine *math.ExchangePricingEngine
}
// NewCurveSwapRouter creates a new Curve swap router
func NewCurveSwapRouter(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig, engine *math.ExchangePricingEngine) *CurveSwapRouter {
return &CurveSwapRouter{
client: client,
logger: logger,
config: config,
engine: engine,
}
}
// CalculateSwap calculates the expected output amount for a swap
func (r *CurveSwapRouter) CalculateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) (*big.Int, error) {
// Find pool for the token pair
poolAddress, err := r.findPoolForPair(tokenIn, tokenOut)
if err != nil {
return nil, fmt.Errorf("failed to find pool for pair: %w", err)
}
// Get pool data
poolData, err := r.GetPoolData(poolAddress)
if err != nil {
return nil, fmt.Errorf("failed to get pool data: %w", err)
}
// Create a UniversalDecimal from the amountIn
decimalAmountIn, err := math.NewUniversalDecimal(amountIn, 18, "AMOUNT_IN")
if err != nil {
return nil, fmt.Errorf("error creating amount in decimal: %w", err)
}
// Get the pricer
pricer, err := r.engine.GetExchangePricer(poolData.ExchangeType)
if err != nil {
return nil, err
}
// Calculate amount out using Curve's stable swap formula
amountOut, err := pricer.CalculateAmountOut(decimalAmountIn, poolData)
if err != nil {
return nil, err
}
return amountOut.Value, nil
}
// findPoolForPair finds the pool address for a given token pair
func (r *CurveSwapRouter) findPoolForPair(token0, token1 common.Address) (common.Address, error) {
// In a real implementation, this would query the Curve registry contract
// For now, return a placeholder address
return common.HexToAddress("0x0"), nil
}
// GetPoolData is a helper to fetch pool data (for internal use)
func (r *CurveSwapRouter) GetPoolData(poolAddress common.Address) (*math.PoolData, error) {
fetcher := NewCurveLiquidityFetcher(r.client, r.logger, r.config, r.engine)
return fetcher.GetPoolData(poolAddress)
}
// GenerateSwapData generates the calldata for a swap transaction
func (r *CurveSwapRouter) GenerateSwapData(tokenIn, tokenOut common.Address, amountIn, minAmountOut *big.Int, deadline *big.Int) ([]byte, error) {
// In a real implementation, this would generate the encoded function call
// For Curve, this would typically be exchange or exchange_underlying
return []byte{}, nil
}
// GetSwapRoute returns the route for a swap (for Curve, typically direct within a pool)
func (r *CurveSwapRouter) GetSwapRoute(tokenIn, tokenOut common.Address) ([]common.Address, error) {
// For Curve, the route is usually direct within a multi-token stable pool
// For now, return the token pair as a direct route
return []common.Address{tokenIn, tokenOut}, nil
}
// ValidateSwap validates a swap before execution
func (r *CurveSwapRouter) ValidateSwap(tokenIn, tokenOut common.Address, amountIn *big.Int) error {
if amountIn.Sign() <= 0 {
return fmt.Errorf("amountIn must be positive")
}
if tokenIn == tokenOut {
return fmt.Errorf("tokenIn and tokenOut cannot be the same")
}
if tokenIn == common.HexToAddress("0x0") || tokenOut == common.HexToAddress("0x0") {
return fmt.Errorf("invalid token addresses")
}
return nil
}
// RegisterCurveWithRegistry registers Curve implementation with the exchange registry
func RegisterCurveWithRegistry(registry *ExchangeRegistry) error {
config := &ExchangeConfig{
Type: math.ExchangeCurve,
Name: "Curve",
FactoryAddress: common.HexToAddress("0xb9fc157394af804a3578134a6585c0dc9cc990d4"), // Curve Factory
RouterAddress: common.HexToAddress("0x90d12d24ff684b6ae6d2c8ca6ad8e0c7e7ab0675"), // Curve Router placeholder
PoolInitCodeHash: "",
SwapSelector: []byte{0x5b, 0x40, 0x2d, 0x3c}, // exchange
StableSwapSelector: []byte{0x79, 0x1a, 0xc9, 0x47}, // exchange_underlying
ChainID: 1, // Ethereum mainnet
SupportsFlashSwaps: false,
RequiresApproval: true,
MaxHops: 2,
DefaultSlippagePercent: 0.1,
Url: "https://curve.fi",
ApiUrl: "https://api.curve.fi",
}
registry.exchanges[math.ExchangeCurve] = config
// Register the implementations as well
return nil
}