waterpump-evm-pool-sdk 0.1.0

EVM pool SDK — viewers, infusers, harvesters, swappers for Uniswap V3/V4, PancakeSwap, Slipstream, Shadow, Algebra
Documentation
use alloy::primitives::{
    aliases::{I24, U160},
    U256,
};
use anyhow::{Context, Result};
use uniswap_sdk_core::{
    entities::FractionBase,
    prelude::{Currency, Percent, Price},
    utils::FromBig,
};
use uniswap_v3_sdk::prelude::max_liquidity_for_amounts;
use waterpump_evm_amm_math::tick_math::get_sqrt_ratio_at_tick;

use crate::common::{
    mint_amounts_utils::calculate_mint_amounts, slippage_utils::ratios_after_slippage,
};

/// Convert I24 tick to i32 for evm-amm-math functions.
fn i24_to_i32(tick: I24) -> i32 {
    let bytes = tick.to_le_bytes::<3>();
    let sign_byte = if bytes[2] & 0x80 != 0 { 0xFF } else { 0x00 };
    i32::from_le_bytes([bytes[0], bytes[1], bytes[2], sign_byte])
}

/// Encode sqrt ratio from Price, returning U160 for backward compatibility.
fn encode_sqrt_ratio_x96_from_price(price: &Price<Currency, Currency>) -> U160 {
    use waterpump_evm_amm_math::encode_sqrt_ratio::encode_sqrt_ratio_x96;
    let fraction = price.as_fraction();
    let num = U256::from_big_int(fraction.numerator);
    let den = U256::from_big_int(fraction.denominator);
    let result = encode_sqrt_ratio_x96(num, den).expect("encode_sqrt_ratio_x96 failed");
    U160::from(result)
}

/// Calculated values for slippage protection
pub struct SlippageContext {
    pub sqrt_ratio_x96_current: U160,
    pub sqrt_ratio_x96_lower: U160,
    pub sqrt_ratio_x96_upper: U160,
}

/// Calculate slippage context from price and slippage tolerance
pub fn calculate_slippage_context(
    token0_price: &Price<Currency, Currency>,
    slippage_tolerance: &Percent,
) -> Result<SlippageContext> {
    let sqrt_ratio_x96_current = encode_sqrt_ratio_x96_from_price(token0_price);

    let (sqrt_ratio_x96_lower, sqrt_ratio_x96_upper) =
        ratios_after_slippage(token0_price, slippage_tolerance)
            .context("Failed to calculate ratios after slippage")?;

    Ok(SlippageContext { sqrt_ratio_x96_current, sqrt_ratio_x96_lower, sqrt_ratio_x96_upper })
}

/// Calculated amounts for adding liquidity
pub struct AddLiquidityAmounts {
    pub amount0_desired: U256,
    pub amount1_desired: U256,
    pub amount0_min: U256,
    pub amount1_min: U256,
    pub liquidity: u128,
}

/// Calculate min amounts with slippage for a single position
pub fn calculate_position_amounts(
    tick_lower: I24,
    tick_upper: I24,
    amount0_desired: U256,
    amount1_desired: U256,
    slippage_ctx: &SlippageContext,
) -> Result<AddLiquidityAmounts> {
    // Get sqrt ratios for tick bounds via evm-amm-math, then convert to U160
    let sqrt_ratio_x96_tick_lower = U160::from(
        get_sqrt_ratio_at_tick(i24_to_i32(tick_lower))
            .context("Failed to get sqrt ratio at tick_lower")?,
    );
    let sqrt_ratio_x96_tick_upper = U160::from(
        get_sqrt_ratio_at_tick(i24_to_i32(tick_upper))
            .context("Failed to get sqrt ratio at tick_upper")?,
    );

    // Calculate liquidity from desired amounts at current price
    let liquidity_big = max_liquidity_for_amounts(
        slippage_ctx.sqrt_ratio_x96_current,
        sqrt_ratio_x96_tick_lower,
        sqrt_ratio_x96_tick_upper,
        amount0_desired,
        amount1_desired,
        false, // use_full_precision = false (router precision)
    );

    let liquidity_u256 = U256::from_big_uint(liquidity_big);
    if liquidity_u256 > U256::from(u128::MAX) {
        return Err(anyhow::anyhow!("Liquidity overflow: value too large for u128"));
    }
    let liquidity = liquidity_u256.to::<u128>();

    // Calculate mint amounts at worst-case prices:
    // - amount0 at upper price (price goes up, needs more amount0)
    // - amount1 at lower price (price goes down, needs more amount1)
    let mint_amounts_upper = calculate_mint_amounts(
        tick_lower,
        tick_upper,
        liquidity,
        slippage_ctx.sqrt_ratio_x96_upper,
    )
    .context("Failed to calculate mint amounts at upper price")?;

    let mint_amounts_lower = calculate_mint_amounts(
        tick_lower,
        tick_upper,
        liquidity,
        slippage_ctx.sqrt_ratio_x96_lower,
    )
    .context("Failed to calculate mint amounts at lower price")?;

    Ok(AddLiquidityAmounts {
        amount0_desired,
        amount1_desired,
        amount0_min: mint_amounts_upper.amount0,
        amount1_min: mint_amounts_lower.amount1,
        liquidity,
    })
}