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::{Address, U256},
    providers::DynProvider,
};
use anyhow::Result;
use tracing::{instrument, trace};

// Use full paths for Algebra interfaces
use crate::{
    common::{calculate_position_token_amounts, calculate_tokens_owed_unrealized},
    impl_multi_position_predictor, impl_multi_position_viewer, impl_pool_base,
    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,
    },
};

/// Viewer for multiple Algebra positions
#[derive(Clone, Debug)]
pub struct QuickswapMultiPositionViewer {
    pub position_manager_address: Address,

    pub pool_address: Address,
    pub pool_key: QuickswapPoolKey,

    pub position_metadatas: Vec<PositionMetadata>,
    pub provider: DynProvider<Ethereum>,
}

impl QuickswapMultiPositionViewer {
    /// Create a new multiple position viewer from a vector of position viewers
    pub fn new(
        position_manager_address: Address,
        pool_address: Address,
        pool_key: QuickswapPoolKey,
        position_metadatas: Vec<PositionMetadata>,
        provider: DynProvider<Ethereum>,
    ) -> Self {
        Self { position_manager_address, pool_address, pool_key, position_metadatas, provider }
    }

    pub async fn with_position_ids(
        provider: DynProvider<Ethereum>,
        position_manager_address: Address,
        pool_address: Address,
        pool_key: QuickswapPoolKey,
        token_ids: Vec<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 multicall = alloy::providers::Provider::multicall(&provider).dynamic();
        for token_id in &token_ids {
            multicall = multicall.add_dynamic(position_manager.positions(*token_id));
        }
        let block_id = block_id.unwrap_or(BlockId::latest());
        let position_data = multicall.block(block_id).aggregate().await?;

        let position_metadatas = position_data
            .iter()
            .zip(token_ids)
            .map(|(position, token_id)| PositionMetadata {
                token_id,
                liquidity: position.liquidity,
                tick_lower: position.tickLower,
                tick_upper: position.tickUpper,
            })
            .collect::<Vec<PositionMetadata>>();

        Ok(Self { position_manager_address, pool_address, pool_key, position_metadatas, provider })
    }

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

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

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

    /// Get the number of positions
    pub fn len(&self) -> usize { self.position_metadatas.len() }

    /// Check if there are no positions
    pub fn is_empty(&self) -> bool { self.position_metadatas.is_empty() }

    /// Get a reference to the position metadatas
    pub fn position_metadatas_internal(&self) -> Vec<PositionMetadata> {
        self.position_metadatas.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 amm_state =
    // pool_contract.safelyGetStateOfAMM().block(block_id).call().await?;     //
    // Algebra returns sqrtPrice directly, which is U160 (same as sqrtPriceX96)
    //     Ok(amm_state.sqrtPrice)
    // }
}

impl QuickswapMultiPositionViewer {
    /// Get position data for fee calculation
    #[instrument(skip(self))]
    pub async fn get_positions(
        &self,
        block_id: Option<BlockId>,
    ) -> Result<
        Vec<waterpump_evm_algebra_client::interfaces::INonfungiblePositionManager::positionsReturn>,
    > {
        let position_manager = waterpump_evm_algebra_client::interfaces::INonfungiblePositionManager::INonfungiblePositionManagerInstance::new(
            self.position_manager_address,
            self.provider.clone(),
        );
        let block_id = block_id.unwrap_or(BlockId::latest());
        let mut multicall = alloy::providers::Provider::multicall(&self.provider).dynamic();
        for metadata in &self.position_metadatas {
            multicall = multicall.add_dynamic(position_manager.positions(metadata.token_id));
        }
        let position_data = multicall.block(block_id).aggregate().await?;
        trace!("Calling positions() on position manager for fees");
        Ok(position_data)
    }

    #[instrument(skip(self))]
    pub async fn position_values_internal(
        &self,
        block_id: Option<BlockId>,
    ) -> Result<Vec<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(),
        );

        println!("pool_contract: {:?}", pool_contract);
        println!("npm_contract: {:?}", npm_contract);

        // Algebra uses globalState() instead of slot0()
        // Algebra uses totalFeeGrowth0Token() and totalFeeGrowth1Token() instead of
        // feeGrowthGlobal0X128() and feeGrowthGlobal1X128()
        let multicall_global = alloy::providers::Provider::multicall(&self.provider)
            .add(pool_contract.globalState())
            .add(pool_contract.totalFeeGrowth0Token())
            .add(pool_contract.totalFeeGrowth1Token())
            .block(block_id);

        let mut multicall_for_tick_upper =
            alloy::providers::Provider::multicall(&self.provider).dynamic();
        let mut multicall_for_tick_lower =
            alloy::providers::Provider::multicall(&self.provider).dynamic();
        let mut multicall_for_position =
            alloy::providers::Provider::multicall(&self.provider).dynamic();
        for metadata in &self.position_metadatas {
            multicall_for_position =
                multicall_for_position.add_dynamic(npm_contract.positions(metadata.token_id));
            multicall_for_tick_lower =
                multicall_for_tick_lower.add_dynamic(pool_contract.ticks(metadata.tick_lower));
            multicall_for_tick_upper =
                multicall_for_tick_upper.add_dynamic(pool_contract.ticks(metadata.tick_upper));
        }
        let position_data = multicall_for_position.block(block_id).aggregate().await?;
        let (global_state, fee_growth_global_0, fee_growth_global_1) =
            multicall_global.block(block_id).aggregate().await?;
        let ticks_lower = multicall_for_tick_lower.block(block_id).aggregate().await?;
        let ticks_upper = multicall_for_tick_upper.block(block_id).aggregate().await?;
        let ticks = ticks_lower
            .iter()
            .zip(ticks_upper.iter())
            .map(|(tick_lower, tick_upper)| (tick_lower.clone(), tick_upper.clone()))
            .collect::<Vec<(
                waterpump_evm_algebra_client::interfaces::IAlgebraPoolState::ticksReturn,
                waterpump_evm_algebra_client::interfaces::IAlgebraPoolState::ticksReturn,
            )>>();
        let mut position_values = Vec::with_capacity(position_data.len());
        for (position, (tick_lower, tick_upper)) in position_data.iter().zip(ticks.iter()) {
            // 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.tickLower,
                    position.tickUpper,
                    fee_growth_global_0,
                    fee_growth_global_1,
                    tick_lower.outerFeeGrowth0Token,
                    tick_lower.outerFeeGrowth1Token,
                    tick_upper.outerFeeGrowth0Token,
                    tick_upper.outerFeeGrowth1Token,
                    position.feeGrowthInside0LastX128,
                    position.feeGrowthInside1LastX128,
                    position.liquidity,
                )?;

            let amount_owed0 = tokens_owed0_unrealized + U256::from(position.tokensOwed0);
            let amount_owed1 = tokens_owed1_unrealized + U256::from(position.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.liquidity,
                position.tickLower,
                position.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(),
            );

            position_values.push(PositionValues {
                amount0,
                amount1,
                amount_owed0,
                amount_owed1,
                price,
            });
        }

        Ok(position_values)
    }
}

impl_pool_base!(QuickswapMultiPositionViewer);
impl_multi_position_viewer!(QuickswapMultiPositionViewer);

impl_position_viewer_helpers!(QuickswapMultiPositionViewer);
impl_multi_position_predictor!(QuickswapMultiPositionViewer);

impl_quickswap_pool_state!(QuickswapMultiPositionViewer);
impl_quickswap_pool_viewer!(QuickswapMultiPositionViewer);