Files
mev-beta/pkg/pools/pool_factory.go

350 lines
9.0 KiB
Go

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
}