package uniswap import ( "context" "fmt" "math/big" "strings" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" "github.com/holiman/uint256" ) // UniswapV3Pool represents a Uniswap V3 pool contract interface type UniswapV3Pool struct { address common.Address client *ethclient.Client abi abi.ABI } // PoolState represents the current state of a Uniswap V3 pool type PoolState struct { SqrtPriceX96 *uint256.Int Tick int Liquidity *uint256.Int Token0 common.Address Token1 common.Address Fee int64 } // Uniswap V3 Pool ABI (only the functions we need) const UniswapV3PoolABI = `[ { "inputs": [], "name": "slot0", "outputs": [ {"internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160"}, {"internalType": "int24", "name": "tick", "type": "int24"}, {"internalType": "uint16", "name": "observationIndex", "type": "uint16"}, {"internalType": "uint16", "name": "observationCardinality", "type": "uint16"}, {"internalType": "uint16", "name": "observationCardinalityNext", "type": "uint16"}, {"internalType": "uint8", "name": "feeProtocol", "type": "uint8"}, {"internalType": "bool", "name": "unlocked", "type": "bool"} ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "liquidity", "outputs": [{"internalType": "uint128", "name": "", "type": "uint128"}], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "token0", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "token1", "outputs": [{"internalType": "address", "name": "", "type": "address"}], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "fee", "outputs": [{"internalType": "uint24", "name": "", "type": "uint24"}], "stateMutability": "view", "type": "function" } ]` // NewUniswapV3Pool creates a new Uniswap V3 pool interface func NewUniswapV3Pool(address common.Address, client *ethclient.Client) *UniswapV3Pool { // Parse the ABI parsedABI, err := abi.JSON(strings.NewReader(UniswapV3PoolABI)) if err != nil { // If ABI parsing fails, continue with empty ABI (fallback mode) parsedABI = abi.ABI{} } return &UniswapV3Pool{ address: address, client: client, abi: parsedABI, } } // GetPoolState fetches the current state of a Uniswap V3 pool func (p *UniswapV3Pool) GetPoolState(ctx context.Context) (*PoolState, error) { // In a production implementation, this would use the actual Uniswap V3 pool ABI // to call the slot0() function and other state functions // For now, we'll implement a simplified version using direct calls // Call slot0() to get sqrtPriceX96, tick, and other slot0 data slot0Data, err := p.callSlot0(ctx) if err != nil { return nil, fmt.Errorf("failed to call slot0: %w", err) } // Call liquidity() to get current liquidity liquidity, err := p.callLiquidity(ctx) if err != nil { return nil, fmt.Errorf("failed to call liquidity: %w", err) } // Call token0() and token1() to get token addresses token0, err := p.callToken0(ctx) if err != nil { return nil, fmt.Errorf("failed to call token0: %w", err) } token1, err := p.callToken1(ctx) if err != nil { return nil, fmt.Errorf("failed to call token1: %w", err) } // Call fee() to get fee tier fee, err := p.callFee(ctx) if err != nil { return nil, fmt.Errorf("failed to call fee: %w", err) } return &PoolState{ SqrtPriceX96: slot0Data.SqrtPriceX96, Tick: slot0Data.Tick, Liquidity: liquidity, Token0: token0, Token1: token1, Fee: fee, }, nil } // Slot0Data represents the data returned by slot0() type Slot0Data struct { SqrtPriceX96 *uint256.Int Tick int ObservationIndex int ObservationCardinality int ObservationCardinalityNext int FeeProtocol int Unlocked bool } // callSlot0 calls the slot0() function on the pool contract func (p *UniswapV3Pool) callSlot0(ctx context.Context) (*Slot0Data, error) { // Pack the function call data, err := p.abi.Pack("slot0") if err != nil { return nil, fmt.Errorf("failed to pack slot0 call: %w", err) } // Make the contract call msg := ethereum.CallMsg{ To: &p.address, Data: data, } result, err := p.client.CallContract(ctx, msg, nil) if err != nil { return nil, fmt.Errorf("failed to call slot0: %w", err) } // Unpack the result var unpacked []interface{} err = p.abi.UnpackIntoInterface(&unpacked, "slot0", result) if err != nil { return nil, fmt.Errorf("failed to unpack slot0 result: %w", err) } // Ensure we have the expected number of return values if len(unpacked) < 7 { return nil, fmt.Errorf("unexpected number of return values from slot0: got %d, expected 7", len(unpacked)) } // Convert the unpacked values sqrtPriceX96, ok := unpacked[0].(*big.Int) if !ok { return nil, fmt.Errorf("failed to convert sqrtPriceX96 to *big.Int") } tick, ok := unpacked[1].(*big.Int) if !ok { return nil, fmt.Errorf("failed to convert tick to *big.Int") } observationIndex, ok := unpacked[2].(uint16) if !ok { return nil, fmt.Errorf("failed to convert observationIndex to uint16") } observationCardinality, ok := unpacked[3].(uint16) if !ok { return nil, fmt.Errorf("failed to convert observationCardinality to uint16") } observationCardinalityNext, ok := unpacked[4].(uint16) if !ok { return nil, fmt.Errorf("failed to convert observationCardinalityNext to uint16") } feeProtocol, ok := unpacked[5].(uint8) if !ok { return nil, fmt.Errorf("failed to convert feeProtocol to uint8") } unlocked, ok := unpacked[6].(bool) if !ok { return nil, fmt.Errorf("failed to convert unlocked to bool") } return &Slot0Data{ SqrtPriceX96: uint256.MustFromBig(sqrtPriceX96), Tick: int(tick.Int64()), ObservationIndex: int(observationIndex), ObservationCardinality: int(observationCardinality), ObservationCardinalityNext: int(observationCardinalityNext), FeeProtocol: int(feeProtocol), Unlocked: unlocked, }, nil } // callLiquidity calls the liquidity() function on the pool contract func (p *UniswapV3Pool) callLiquidity(ctx context.Context) (*uint256.Int, error) { // Pack the function call data, err := p.abi.Pack("liquidity") if err != nil { return nil, fmt.Errorf("failed to pack liquidity call: %w", err) } // Make the contract call msg := ethereum.CallMsg{ To: &p.address, Data: data, } result, err := p.client.CallContract(ctx, msg, nil) if err != nil { return nil, fmt.Errorf("failed to call liquidity: %w", err) } // Unpack the result var liquidity *big.Int err = p.abi.UnpackIntoInterface(&liquidity, "liquidity", result) if err != nil { return nil, fmt.Errorf("failed to unpack liquidity result: %w", err) } return uint256.MustFromBig(liquidity), nil } // callToken0 calls the token0() function on the pool contract func (p *UniswapV3Pool) callToken0(ctx context.Context) (common.Address, error) { // Pack the function call data, err := p.abi.Pack("token0") if err != nil { return common.Address{}, fmt.Errorf("failed to pack token0 call: %w", err) } // Make the contract call msg := ethereum.CallMsg{ To: &p.address, Data: data, } result, err := p.client.CallContract(ctx, msg, nil) if err != nil { return common.Address{}, fmt.Errorf("failed to call token0: %w", err) } // Unpack the result var token0 common.Address err = p.abi.UnpackIntoInterface(&token0, "token0", result) if err != nil { return common.Address{}, fmt.Errorf("failed to unpack token0 result: %w", err) } return token0, nil } // callToken1 calls the token1() function on the pool contract func (p *UniswapV3Pool) callToken1(ctx context.Context) (common.Address, error) { // Pack the function call data, err := p.abi.Pack("token1") if err != nil { return common.Address{}, fmt.Errorf("failed to pack token1 call: %w", err) } // Make the contract call msg := ethereum.CallMsg{ To: &p.address, Data: data, } result, err := p.client.CallContract(ctx, msg, nil) if err != nil { return common.Address{}, fmt.Errorf("failed to call token1: %w", err) } // Unpack the result var token1 common.Address err = p.abi.UnpackIntoInterface(&token1, "token1", result) if err != nil { return common.Address{}, fmt.Errorf("failed to unpack token1 result: %w", err) } return token1, nil } // callFee calls the fee() function on the pool contract func (p *UniswapV3Pool) callFee(ctx context.Context) (int64, error) { // Pack the function call data, err := p.abi.Pack("fee") if err != nil { return 0, fmt.Errorf("failed to pack fee call: %w", err) } // Make the contract call msg := ethereum.CallMsg{ To: &p.address, Data: data, } result, err := p.client.CallContract(ctx, msg, nil) if err != nil { return 0, fmt.Errorf("failed to call fee: %w", err) } // Unpack the result var fee *big.Int err = p.abi.UnpackIntoInterface(&fee, "fee", result) if err != nil { return 0, fmt.Errorf("failed to unpack fee result: %w", err) } return fee.Int64(), nil } // CalculatePoolAddress calculates the deterministic address of a Uniswap V3 pool func CalculatePoolAddress(factory common.Address, token0, token1 common.Address, fee int64) common.Address { // This implements the CREATE2 address calculation for Uniswap V3 pools // The actual implementation would use the correct salt and init code hash // For now, return a placeholder that varies based on inputs hash := crypto.Keccak256( append(append(token0.Bytes(), token1.Bytes()...), big.NewInt(fee).Bytes()...), ) var addr common.Address copy(addr[:], hash[12:]) return addr } // IsValidPool checks if an address is a valid pool by checking for code existence // This is a simplified version to break import cycle - use PoolValidator for comprehensive validation func IsValidPool(ctx context.Context, client *ethclient.Client, address common.Address) bool { // Check if address has code (basic contract existence check) code, err := client.CodeAt(ctx, address, nil) if err != nil { return false } // Must have contract code if len(code) == 0 { return false } // Additional basic check: ensure it's not zero address return address != common.HexToAddress("0x0000000000000000000000000000000000000000") } // ParseABI parses an ABI JSON string and returns the parsed ABI func ParseABI(abiJSON string) (abi.ABI, error) { return abi.JSON(strings.NewReader(abiJSON)) } // UniswapV3Pricing provides Uniswap V3 pricing calculations type UniswapV3Pricing struct { client *ethclient.Client } // NewUniswapV3Pricing creates a new Uniswap V3 pricing calculator func NewUniswapV3Pricing(client *ethclient.Client) *UniswapV3Pricing { return &UniswapV3Pricing{ client: client, } } // GetPrice calculates the price for a token pair (basic implementation) func (p *UniswapV3Pricing) GetPrice(ctx context.Context, token0, token1 common.Address) (*big.Int, error) { // This is a placeholder implementation // In production, this would query actual Uniswap V3 pools return big.NewInt(0), fmt.Errorf("not implemented") } // SqrtPriceX96ToPrice converts sqrtPriceX96 to price func (p *UniswapV3Pricing) SqrtPriceX96ToPrice(sqrtPriceX96 *big.Int) *big.Int { // Simplified conversion - in production this would be more precise // Price = (sqrtPriceX96 / 2^96)^2 return big.NewInt(0) } // CalculateAmountOut calculates output amount using proper Uniswap V3 concentrated liquidity math func (p *UniswapV3Pricing) CalculateAmountOut(amountIn, sqrtPriceX96, liquidity *big.Int) (*big.Int, error) { if amountIn == nil || sqrtPriceX96 == nil || liquidity == nil { return nil, fmt.Errorf("input parameters cannot be nil") } if amountIn.Sign() <= 0 || sqrtPriceX96.Sign() <= 0 || liquidity.Sign() <= 0 { return nil, fmt.Errorf("input parameters must be positive") } // Implement proper Uniswap V3 concentrated liquidity calculation // Based on the formula: Δy = L * (√P₁ - √P₀) where L is liquidity // And the price movement: √P₁ = √P₀ + Δx / L // For token0 -> token1 swap: // 1. Calculate new sqrt price after swap // 2. Calculate output amount based on liquidity and price change // Calculate Δ(sqrt(P)) based on input amount and liquidity // For exact input: Δ(1/√P) = Δx / L // So: 1/√P₁ = 1/√P₀ + Δx / L // Therefore: √P₁ = √P₀ / (1 + Δx * √P₀ / L) // Calculate the new sqrt price after the swap numerator := new(big.Int).Mul(amountIn, sqrtPriceX96) denominator := new(big.Int).Add(liquidity, numerator) // Check for overflow/underflow if denominator.Sign() <= 0 { return nil, fmt.Errorf("invalid calculation: denominator non-positive") } sqrtPriceNext := new(big.Int).Div(new(big.Int).Mul(liquidity, sqrtPriceX96), denominator) // Calculate the output amount: Δy = L * (√P₀ - √P₁) priceDiff := new(big.Int).Sub(sqrtPriceX96, sqrtPriceNext) amountOut := new(big.Int).Mul(liquidity, priceDiff) // Adjust for Q96 scaling: divide by 2^96 q96 := new(big.Int).Lsh(big.NewInt(1), 96) amountOut.Div(amountOut, q96) // Apply trading fee (typically 0.3% = 3000 basis points for most pools) // Fee is taken from input, so output is calculated on (amountIn - fee) fee := big.NewInt(3000) // 0.3% in basis points feeAmount := new(big.Int).Mul(amountOut, fee) feeAmount.Div(feeAmount, big.NewInt(1000000)) // Divide by 1M to get basis points amountOut.Sub(amountOut, feeAmount) // Additional slippage protection for large trades // If trade is > 1% of liquidity, apply additional slippage tradeSize := new(big.Int).Mul(amountIn, big.NewInt(100)) if tradeSize.Cmp(liquidity) > 0 { // Large trade - apply additional slippage of 0.1% per 1% of liquidity liquidityRatio := new(big.Int).Div(tradeSize, liquidity) additionalSlippage := new(big.Int).Mul(amountOut, liquidityRatio) additionalSlippage.Div(additionalSlippage, big.NewInt(10000)) // 0.01% base slippage amountOut.Sub(amountOut, additionalSlippage) } // Ensure result is not negative if amountOut.Sign() < 0 { return big.NewInt(0), nil } return amountOut, nil }