351 lines
9.0 KiB
Go
351 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
|
|
}
|