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:
272
orig/pkg/exchanges/pancakeswap.go
Normal file
272
orig/pkg/exchanges/pancakeswap.go
Normal file
@@ -0,0 +1,272 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// PancakeSwapPoolDetector implements PoolDetector for PancakeSwap
|
||||
type PancakeSwapPoolDetector struct {
|
||||
client *ethclient.Client
|
||||
logger *logger.Logger
|
||||
config *ExchangeConfig
|
||||
}
|
||||
|
||||
// NewPancakeSwapPoolDetector creates a new PancakeSwap pool detector
|
||||
func NewPancakeSwapPoolDetector(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig) *PancakeSwapPoolDetector {
|
||||
return &PancakeSwapPoolDetector{
|
||||
client: client,
|
||||
logger: logger,
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAllPools returns all pools containing the specified tokens
|
||||
func (d *PancakeSwapPoolDetector) GetAllPools(token0, token1 common.Address) ([]common.Address, error) {
|
||||
// In a real implementation, this would query the factory 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 *PancakeSwapPoolDetector) GetPoolForPair(token0, token1 common.Address) (common.Address, error) {
|
||||
// Calculate pool address using PancakeSwap factory formula (same as Uniswap V2)
|
||||
// In a real implementation, this would call the factory's getPair function
|
||||
poolAddress := common.HexToAddress("0x0") // Placeholder
|
||||
|
||||
// For now, return empty address to indicate pool not found
|
||||
return poolAddress, nil
|
||||
}
|
||||
|
||||
// GetSupportedFeeTiers returns supported fee tiers for PancakeSwap V2 (standard 0.25%)
|
||||
func (d *PancakeSwapPoolDetector) GetSupportedFeeTiers() []int64 {
|
||||
return []int64{2500} // 0.25% in basis points
|
||||
}
|
||||
|
||||
// GetPoolType returns the pool type
|
||||
func (d *PancakeSwapPoolDetector) GetPoolType() string {
|
||||
return "pancakeswap_v2_constant_product"
|
||||
}
|
||||
|
||||
// PancakeSwapLiquidityFetcher implements LiquidityFetcher for PancakeSwap
|
||||
type PancakeSwapLiquidityFetcher struct {
|
||||
client *ethclient.Client
|
||||
logger *logger.Logger
|
||||
config *ExchangeConfig
|
||||
engine *math.ExchangePricingEngine
|
||||
}
|
||||
|
||||
// NewPancakeSwapLiquidityFetcher creates a new PancakeSwap liquidity fetcher
|
||||
func NewPancakeSwapLiquidityFetcher(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig, engine *math.ExchangePricingEngine) *PancakeSwapLiquidityFetcher {
|
||||
return &PancakeSwapLiquidityFetcher{
|
||||
client: client,
|
||||
logger: logger,
|
||||
config: config,
|
||||
engine: engine,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPoolData fetches pool information for PancakeSwap
|
||||
func (f *PancakeSwapLiquidityFetcher) GetPoolData(poolAddress common.Address) (*math.PoolData, error) {
|
||||
// In a real implementation, this would call the pool contract to get reserves
|
||||
// For now, return a placeholder pool data
|
||||
|
||||
fee, err := math.NewUniversalDecimal(big.NewInt(250), 4, "FEE")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating fee decimal: %w", err)
|
||||
}
|
||||
|
||||
reserve0Value := new(big.Int)
|
||||
reserve0Value.SetString("1000000000000000000000", 10) // WBNB
|
||||
reserve0, err := math.NewUniversalDecimal(reserve0Value, 18, "RESERVE0")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating reserve0 decimal: %w", err)
|
||||
}
|
||||
|
||||
reserve1Value := new(big.Int)
|
||||
reserve1Value.SetString("1000000000000000000000000", 10) // CAKE
|
||||
reserve1, err := math.NewUniversalDecimal(reserve1Value, 18, "RESERVE1")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating reserve1 decimal: %w", err)
|
||||
}
|
||||
|
||||
return &math.PoolData{
|
||||
Address: poolAddress.Hex(),
|
||||
ExchangeType: math.ExchangeUniswapV2, // Using UniswapV2 logic since PancakeSwap is forked from it
|
||||
Fee: fee,
|
||||
Token0: math.TokenInfo{Address: "0x0", Symbol: "WBNB", Decimals: 18},
|
||||
Token1: math.TokenInfo{Address: "0x1", Symbol: "CAKE", Decimals: 18},
|
||||
Reserve0: reserve0,
|
||||
Reserve1: reserve1,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetTokenReserves fetches reserves for a specific token pair in a pool
|
||||
func (f *PancakeSwapLiquidityFetcher) 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
|
||||
reserve0 := new(big.Int)
|
||||
reserve0.SetString("1000000000000000000000", 10) // WBNB
|
||||
reserve1 := new(big.Int)
|
||||
reserve1.SetString("1000000000000000000000000", 10) // CAKE
|
||||
return reserve0, reserve1, nil
|
||||
}
|
||||
|
||||
// GetPoolPrice calculates the price of token1 in terms of token0
|
||||
func (f *PancakeSwapLiquidityFetcher) 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 *PancakeSwapLiquidityFetcher) GetLiquidityDepth(poolAddress, tokenIn common.Address, amount *big.Int) (*big.Int, error) {
|
||||
// In a real implementation, this would calculate how much of the token
|
||||
// can be swapped before the price impact becomes too large
|
||||
return amount, nil
|
||||
}
|
||||
|
||||
// PancakeSwapSwapRouter implements SwapRouter for PancakeSwap
|
||||
type PancakeSwapSwapRouter struct {
|
||||
client *ethclient.Client
|
||||
logger *logger.Logger
|
||||
config *ExchangeConfig
|
||||
engine *math.ExchangePricingEngine
|
||||
}
|
||||
|
||||
// NewPancakeSwapSwapRouter creates a new PancakeSwap swap router
|
||||
func NewPancakeSwapSwapRouter(client *ethclient.Client, logger *logger.Logger, config *ExchangeConfig, engine *math.ExchangePricingEngine) *PancakeSwapSwapRouter {
|
||||
return &PancakeSwapSwapRouter{
|
||||
client: client,
|
||||
logger: logger,
|
||||
config: config,
|
||||
engine: engine,
|
||||
}
|
||||
}
|
||||
|
||||
// CalculateSwap calculates the expected output amount for a swap
|
||||
func (r *PancakeSwapSwapRouter) 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
|
||||
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 *PancakeSwapSwapRouter) findPoolForPair(token0, token1 common.Address) (common.Address, error) {
|
||||
// In a real implementation, this would query the factory 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 *PancakeSwapSwapRouter) GetPoolData(poolAddress common.Address) (*math.PoolData, error) {
|
||||
fetcher := NewPancakeSwapLiquidityFetcher(r.client, r.logger, r.config, r.engine)
|
||||
return fetcher.GetPoolData(poolAddress)
|
||||
}
|
||||
|
||||
// GenerateSwapData generates the calldata for a swap transaction
|
||||
func (r *PancakeSwapSwapRouter) 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 PancakeSwap, this would typically be swapExactTokensForTokens
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
// GetSwapRoute returns the route for a swap (for PancakeSwap, this is typically direct)
|
||||
func (r *PancakeSwapSwapRouter) GetSwapRoute(tokenIn, tokenOut common.Address) ([]common.Address, error) {
|
||||
// PancakeSwap typically requires a direct swap
|
||||
return []common.Address{tokenIn, tokenOut}, nil
|
||||
}
|
||||
|
||||
// ValidateSwap validates a swap before execution
|
||||
func (r *PancakeSwapSwapRouter) 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
|
||||
}
|
||||
|
||||
// RegisterPancakeSwapWithRegistry registers PancakeSwap implementation with the exchange registry
|
||||
func RegisterPancakeSwapWithRegistry(registry *ExchangeRegistry) error {
|
||||
config := &ExchangeConfig{
|
||||
Type: math.ExchangeUniswapV2, // Using UniswapV2 type for PancakeSwap V2 since they're similar
|
||||
Name: "PancakeSwap",
|
||||
FactoryAddress: common.HexToAddress("0xcA143Ce32Fe78f1f7019d7d551a6402fC535aa17"), // PancakeSwap V2 Factory
|
||||
RouterAddress: common.HexToAddress("0x10ED43C718612782E9E672E01bCc53Bb3a3b6B2e"), // PancakeSwap V2 Router
|
||||
PoolInitCodeHash: "0x00fb7f630766e6a796048ea87d01acd3068e8ff67d078148a3fa3f4a84f69bd5",
|
||||
SwapSelector: []byte{0x18, 0x2d, 0x2e, 0xdb}, // swapExactTokensForTokens
|
||||
StableSwapSelector: []byte{},
|
||||
ChainID: 56, // Binance Smart Chain
|
||||
SupportsFlashSwaps: true,
|
||||
RequiresApproval: true,
|
||||
MaxHops: 3,
|
||||
DefaultSlippagePercent: 0.5,
|
||||
Url: "https://pancakeswap.finance",
|
||||
ApiUrl: "https://api.pancakeswap.finance",
|
||||
}
|
||||
|
||||
// Acknowledge unused config variable to avoid compiler error
|
||||
_ = config
|
||||
|
||||
// Note: For a complete implementation, we would need to add PancakeSwap as a separate exchange type
|
||||
// For now, we're using the UniswapV2 type since PancakeSwap V2 is based on Uniswap V2
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user