package pools import ( "context" "fmt" "math/big" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/fraktal/mev-beta/pkg/bindings/algebra" "github.com/fraktal/mev-beta/pkg/bindings/algebraintegral" "github.com/fraktal/mev-beta/pkg/bindings/uniswapv2" "github.com/fraktal/mev-beta/pkg/bindings/uniswapv3" "github.com/fraktal/mev-beta/pkg/dex" ) // PoolBinding is a unified interface for all pool bindings type PoolBinding interface { Token0() (common.Address, error) Token1() (common.Address, error) GetReserves() (*big.Int, *big.Int, error) GetFee() (*big.Int, error) GetLiquidity() (*big.Int, error) Address() common.Address Type() string } // UniswapV2Binding wraps the UniswapV2 pair binding type UniswapV2Binding struct { pool *uniswapv2.UniswapV2Pair address common.Address } func (b *UniswapV2Binding) Token0() (common.Address, error) { return b.pool.Token0(&bind.CallOpts{}) } func (b *UniswapV2Binding) Token1() (common.Address, error) { return b.pool.Token1(&bind.CallOpts{}) } func (b *UniswapV2Binding) GetReserves() (*big.Int, *big.Int, error) { reserves, err := b.pool.GetReserves(&bind.CallOpts{}) if err != nil { return nil, nil, err } return reserves.Reserve0, reserves.Reserve1, nil } func (b *UniswapV2Binding) GetFee() (*big.Int, error) { // UniswapV2 has fixed 0.3% fee (3/1000) return big.NewInt(3), nil } func (b *UniswapV2Binding) GetLiquidity() (*big.Int, error) { reserve0, reserve1, err := b.GetReserves() if err != nil { return nil, err } // Return sum of reserves as liquidity indicator total := new(big.Int).Add(reserve0, reserve1) return total, nil } func (b *UniswapV2Binding) Address() common.Address { return b.address } func (b *UniswapV2Binding) Type() string { return "UniswapV2" } // UniswapV3Binding wraps the UniswapV3 pool binding type UniswapV3Binding struct { pool *uniswapv3.UniswapV3Pool address common.Address } func (b *UniswapV3Binding) Token0() (common.Address, error) { return b.pool.Token0(&bind.CallOpts{}) } func (b *UniswapV3Binding) Token1() (common.Address, error) { return b.pool.Token1(&bind.CallOpts{}) } func (b *UniswapV3Binding) GetReserves() (*big.Int, *big.Int, error) { // V3 doesn't have simple reserves, use liquidity liquidity, err := b.pool.Liquidity(&bind.CallOpts{}) if err != nil { return nil, nil, err } // Return liquidity as both reserves for compatibility return liquidity, liquidity, nil } func (b *UniswapV3Binding) GetFee() (*big.Int, error) { fee, err := b.pool.Fee(&bind.CallOpts{}) if err != nil { return nil, err } // Fee is already *big.Int from the binding return fee, nil } func (b *UniswapV3Binding) GetLiquidity() (*big.Int, error) { liquidity, err := b.pool.Liquidity(&bind.CallOpts{}) if err != nil { return nil, err } // liquidity is already *big.Int from the binding return liquidity, nil } func (b *UniswapV3Binding) Address() common.Address { return b.address } func (b *UniswapV3Binding) Type() string { return "UniswapV3" } // AlgebraBinding wraps the Algebra pool binding type AlgebraBinding struct { pool *algebra.AlgebraPool address common.Address } func (b *AlgebraBinding) Token0() (common.Address, error) { return b.pool.Token0(&bind.CallOpts{}) } func (b *AlgebraBinding) Token1() (common.Address, error) { return b.pool.Token1(&bind.CallOpts{}) } func (b *AlgebraBinding) GetReserves() (*big.Int, *big.Int, error) { // Algebra doesn't have simple reserves, use liquidity liquidity, err := b.pool.Liquidity(&bind.CallOpts{}) if err != nil { return nil, nil, err } return liquidity, liquidity, nil } func (b *AlgebraBinding) GetFee() (*big.Int, error) { // Algebra has dynamic fees, get from globalState state, err := b.pool.GlobalState(&bind.CallOpts{}) if err != nil { return nil, err } return big.NewInt(int64(state.Fee)), nil } func (b *AlgebraBinding) GetLiquidity() (*big.Int, error) { return b.pool.Liquidity(&bind.CallOpts{}) } func (b *AlgebraBinding) Address() common.Address { return b.address } func (b *AlgebraBinding) Type() string { return "Algebra" } // AlgebraIntegralBinding wraps the Algebra Integral pool binding type AlgebraIntegralBinding struct { pool *algebraintegral.AlgebraIntegralPool address common.Address } func (b *AlgebraIntegralBinding) Token0() (common.Address, error) { return b.pool.Token0(&bind.CallOpts{}) } func (b *AlgebraIntegralBinding) Token1() (common.Address, error) { return b.pool.Token1(&bind.CallOpts{}) } func (b *AlgebraIntegralBinding) GetReserves() (*big.Int, *big.Int, error) { // Algebra Integral doesn't have simple reserves, use liquidity liquidity, err := b.pool.Liquidity(&bind.CallOpts{}) if err != nil { return nil, nil, err } return liquidity, liquidity, nil } func (b *AlgebraIntegralBinding) GetFee() (*big.Int, error) { // Algebra Integral has dynamic fees, get from globalState state, err := b.pool.GlobalState(&bind.CallOpts{}) if err != nil { return nil, err } // Extract fee from the state struct return big.NewInt(int64(state.LastFee)), nil } func (b *AlgebraIntegralBinding) GetLiquidity() (*big.Int, error) { return b.pool.Liquidity(&bind.CallOpts{}) } func (b *AlgebraIntegralBinding) Address() common.Address { return b.address } func (b *AlgebraIntegralBinding) Type() string { return "AlgebraIntegral" } // PoolFactory creates pool bindings based on detected type type PoolFactory struct { client *ethclient.Client detector *dex.PoolDetector } // NewPoolFactory creates a new pool factory func NewPoolFactory(client *ethclient.Client) *PoolFactory { return &PoolFactory{ client: client, detector: dex.NewPoolDetector(client), } } // CreateBinding creates the appropriate binding for a pool address func (f *PoolFactory) CreateBinding(ctx context.Context, poolAddress common.Address) (PoolBinding, error) { // First detect the pool type poolInfo, err := f.detector.DetectPoolType(ctx, poolAddress) if err != nil { return nil, fmt.Errorf("failed to detect pool type: %w", err) } // Create the appropriate binding based on type poolType := string(poolInfo.Type) switch poolType { case "uniswap_v2", "sushiswap_v2", "camelot_v2", "traderjoe_v2", "pancake_v2": pool, err := uniswapv2.NewUniswapV2Pair(poolAddress, f.client) if err != nil { return nil, fmt.Errorf("failed to create UniswapV2 binding: %w", err) } return &UniswapV2Binding{ pool: pool, address: poolAddress, }, nil case "uniswap_v3": pool, err := uniswapv3.NewUniswapV3Pool(poolAddress, f.client) if err != nil { return nil, fmt.Errorf("failed to create UniswapV3 binding: %w", err) } return &UniswapV3Binding{ pool: pool, address: poolAddress, }, nil case "algebra_v1", "quickswap_v3", "camelot_v3": pool, err := algebra.NewAlgebraPool(poolAddress, f.client) if err != nil { return nil, fmt.Errorf("failed to create Algebra binding: %w", err) } return &AlgebraBinding{ pool: pool, address: poolAddress, }, nil case "algebra_integral": pool, err := algebraintegral.NewAlgebraIntegralPool(poolAddress, f.client) if err != nil { return nil, fmt.Errorf("failed to create AlgebraIntegral binding: %w", err) } return &AlgebraIntegralBinding{ pool: pool, address: poolAddress, }, nil default: return nil, fmt.Errorf("unsupported pool type: %s", poolType) } } // GetPoolTokens gets the token addresses for a pool using bindings func (f *PoolFactory) GetPoolTokens(ctx context.Context, poolAddress common.Address) (token0, token1 common.Address, err error) { binding, err := f.CreateBinding(ctx, poolAddress) if err != nil { return common.Address{}, common.Address{}, err } token0, err = binding.Token0() if err != nil { return common.Address{}, common.Address{}, fmt.Errorf("failed to get token0: %w", err) } token1, err = binding.Token1() if err != nil { return common.Address{}, common.Address{}, fmt.Errorf("failed to get token1: %w", err) } return token0, token1, nil } // GetPoolInfo gets comprehensive pool information using bindings func (f *PoolFactory) GetPoolInfo(ctx context.Context, poolAddress common.Address) (map[string]interface{}, error) { binding, err := f.CreateBinding(ctx, poolAddress) if err != nil { return nil, err } token0, err := binding.Token0() if err != nil { return nil, err } token1, err := binding.Token1() if err != nil { return nil, err } fee, err := binding.GetFee() if err != nil { // Non-critical, some pools don't have fees fee = big.NewInt(0) } liquidity, err := binding.GetLiquidity() if err != nil { // Non-critical liquidity = big.NewInt(0) } reserve0, reserve1, err := binding.GetReserves() if err != nil { // Non-critical for V3 pools reserve0 = big.NewInt(0) reserve1 = big.NewInt(0) } return map[string]interface{}{ "address": poolAddress.Hex(), "type": binding.Type(), "token0": token0.Hex(), "token1": token1.Hex(), "fee": fee.String(), "liquidity": liquidity.String(), "reserve0": reserve0.String(), "reserve1": reserve1.String(), }, nil }