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::{
    eips::BlockId,
    network::Ethereum,
    primitives::{aliases::I24, Address, U256},
    providers::DynProvider,
};
use anyhow::Result;
use tracing::{debug, instrument};

// Use full paths for Algebra interfaces
use crate::{
    common::{calculate_position_token_amounts, calculate_tokens_owed_unrealized},
    impl_pool_base, impl_position_predictor, impl_position_viewer, impl_position_viewer_helpers,
    impl_quickswap_pool_state, impl_quickswap_pool_viewer,
    pool_viewers::v3::utils::calculate_price_from_sqrt_price_x96,
    types::{
        position_metadata::PositionMetadata, position_values::PositionValues,
        quickswap_pool_key::QuickswapPoolKey,
    },
};

#[derive(Clone, Debug)]
pub struct QuickswapPositionViewer {
    pub position_manager_address: Address,

    pub pool_address: Address,
    pub pool_key: QuickswapPoolKey,

    pub metadata: PositionMetadata,
    pub provider: DynProvider<Ethereum>,
}

impl QuickswapPositionViewer {
    #[instrument(skip(pool_key), fields(pool_key = ?pool_key, token_id = ?metadata.token_id, position_manager = ?position_manager_address, tick_lower = ?metadata.tick_lower, tick_upper = ?metadata.tick_upper))]
    pub fn new(
        position_manager_address: Address,
        pool_address: Address,
        pool_key: QuickswapPoolKey,
        metadata: PositionMetadata,
        provider: DynProvider<Ethereum>,
    ) -> Self {
        debug!(
            position_manager = ?position_manager_address,
            token_id = ?metadata.token_id,
            pool_address = ?pool_address,
            tick_lower = ?metadata.tick_lower,
            tick_upper = ?metadata.tick_upper,
            "Creating QuickswapPositionViewer"
        );
        Self { position_manager_address, pool_address, pool_key, metadata, provider }
    }

    pub async fn with_position_id(
        provider: DynProvider<Ethereum>,
        position_manager_address: Address,
        pool_address: Address,
        pool_key: QuickswapPoolKey,
        token_id: U256,
        block_id: Option<BlockId>,
    ) -> Result<Self> {
        let position_manager = waterpump_evm_algebra_client::interfaces::INonfungiblePositionManager::INonfungiblePositionManagerInstance::new(
            position_manager_address,
            provider.clone(),
        );
        let mut call_builder = position_manager.positions(token_id);
        if let Some(block_id) = block_id {
            call_builder = call_builder.block(block_id);
        }
        let position_data = call_builder.call().await?;

        debug!(
            liquidity = ?position_data.liquidity,
            tick_lower = ?position_data.tickLower,
            tick_upper = ?position_data.tickUpper,
            "Position data retrieved"
        );

        Ok(Self {
            position_manager_address,
            pool_address,
            pool_key,
            metadata: PositionMetadata {
                token_id,
                liquidity: position_data.liquidity,
                tick_lower: position_data.tickLower,
                tick_upper: position_data.tickUpper,
            },
            provider,
        })
    }
}

impl QuickswapPositionViewer {
    pub fn pool_key(&self) -> &QuickswapPoolKey { &self.pool_key }

    pub fn pool_address(&self) -> Address { self.pool_address }

    pub fn token_id(&self) -> U256 { self.metadata.token_id }

    pub fn liquidity(&self) -> u128 { self.metadata.liquidity }

    pub fn tick_lower(&self) -> I24 { self.metadata.tick_lower }

    pub fn tick_upper(&self) -> I24 { self.metadata.tick_upper }

    pub fn tick_range(&self) -> (I24, I24) { (self.metadata.tick_lower, self.metadata.tick_upper) }

    pub fn position_metadata_internal(&self) -> PositionMetadata { self.metadata.clone() }

    /// Get sqrt price from pool state (required by impl_quickswap_pool_viewer
    /// macro)
    pub async fn sqrt_price_x96(
        &self,
        block_id: Option<BlockId>,
    ) -> Result<alloy::primitives::aliases::U160> {
        let pool_contract =
            waterpump_evm_algebra_client::interfaces::IAlgebraPoolState::IAlgebraPoolStateInstance::new(
                self.pool_address,
                self.provider.clone(),
            );
        let block_id = block_id.unwrap_or(BlockId::latest());
        let global_state = pool_contract.globalState().block(block_id).call().await?;
        // Algebra returns price (sqrtPrice) directly, which is U160 (same as
        // sqrtPriceX96)
        Ok(global_state.price)
    }
}

impl QuickswapPositionViewer {
    #[instrument(skip(self))]
    pub async fn position_values_internal(
        &self,
        block_id: Option<BlockId>,
    ) -> Result<PositionValues> {
        let block_id = block_id.unwrap_or(BlockId::latest());
        let pool_contract =
            waterpump_evm_algebra_client::interfaces::IAlgebraPoolState::IAlgebraPoolStateInstance::new(
                self.pool_address,
                self.provider.clone(),
            );

        let npm_contract = waterpump_evm_algebra_client::interfaces::INonfungiblePositionManager::INonfungiblePositionManagerInstance::new(
            self.position_manager_address,
            self.provider.clone(),
        );

        // Algebra uses globalState() instead of slot0()
        // Algebra uses totalFeeGrowth0Token() and totalFeeGrowth1Token() instead of
        // feeGrowthGlobal0X128() and feeGrowthGlobal1X128()
        let multicall = alloy::providers::Provider::multicall(&self.provider)
            .add(npm_contract.positions(self.metadata.token_id))
            .add(pool_contract.globalState())
            .add(pool_contract.totalFeeGrowth0Token())
            .add(pool_contract.totalFeeGrowth1Token())
            .add(pool_contract.ticks(self.metadata.tick_lower))
            .add(pool_contract.ticks(self.metadata.tick_upper))
            .block(block_id);

        let (
            position_data,
            global_state,
            fee_growth_global_0,
            fee_growth_global_1,
            tick_lower,
            tick_upper,
        ) = multicall.aggregate().await?;

        // Algebra uses outerFeeGrowth0Token and outerFeeGrowth1Token instead of
        // feeGrowthOutside0X128 and feeGrowthOutside1X128
        let (tokens_owed0_unrealized, tokens_owed1_unrealized) = calculate_tokens_owed_unrealized(
            global_state.tick,
            position_data.tickLower,
            position_data.tickUpper,
            fee_growth_global_0,
            fee_growth_global_1,
            tick_lower.outerFeeGrowth0Token,
            tick_lower.outerFeeGrowth1Token,
            tick_upper.outerFeeGrowth0Token,
            tick_upper.outerFeeGrowth1Token,
            position_data.feeGrowthInside0LastX128,
            position_data.feeGrowthInside1LastX128,
            position_data.liquidity,
        )?;

        let amount_owed0 = tokens_owed0_unrealized + U256::from(position_data.tokensOwed0);
        let amount_owed1 = tokens_owed1_unrealized + U256::from(position_data.tokensOwed1);

        let (amount_owed0, amount_owed1) = self.to_currency_amounts(amount_owed0, amount_owed1)?;

        // Algebra uses price (sqrtPrice) from globalState() instead of sqrtPriceX96
        // from slot0()
        let (amount0, amount1) = calculate_position_token_amounts(
            position_data.liquidity,
            position_data.tickLower,
            position_data.tickUpper,
            global_state.tick,
            global_state.price,
        )?;
        let (amount0, amount1) = self.to_currency_amounts(amount0, amount1)?;

        let price = calculate_price_from_sqrt_price_x96(
            global_state.price,
            self.pool_key().token_a.clone(),
            self.pool_key().token_b.clone(),
        );

        Ok(PositionValues { amount0, amount1, amount_owed0, amount_owed1, price })
    }
}

impl_pool_base!(QuickswapPositionViewer);
impl_position_viewer_helpers!(QuickswapPositionViewer);

impl_position_viewer!(QuickswapPositionViewer);
impl_position_predictor!(QuickswapPositionViewer);

impl_quickswap_pool_state!(QuickswapPositionViewer);
impl_quickswap_pool_viewer!(QuickswapPositionViewer);