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 uniswap_lens::bindings::{
    iuniswapv3nonfungiblepositionmanager::IUniswapV3NonfungiblePositionManager::IUniswapV3NonfungiblePositionManagerInstance,
    iuniswapv3pool::IUniswapV3Pool::IUniswapV3PoolInstance,
};

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_v3_pool_state, impl_v3_pool_viewer,
    pool_viewers::v3::utils::calculate_price_from_sqrt_price_x96,
    types::{
        position_metadata::PositionMetadata, position_values::PositionValues,
        v3_pool_key::V3PoolKey,
    },
};

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

    pub pool_address: Address,
    pub pool_key: V3PoolKey,

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

impl V3MultiPositionViewer {
    /// Create a new multiple position viewer from a vector of position viewers
    pub fn new(
        position_manager_address: Address,
        pool_address: Address,
        pool_key: V3PoolKey,
        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: V3PoolKey,
        token_ids: Vec<U256>,
        block_id: Option<BlockId>,
    ) -> Result<Self> {
        let position_manager = IUniswapV3NonfungiblePositionManagerInstance::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) -> &V3PoolKey { &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()
    }
}

impl V3MultiPositionViewer {
    /// Get position data for fee calculation
    #[instrument(skip(self))]
    pub async fn get_positions(
        &self,
        block_id: Option<BlockId>,
    ) -> Result<Vec<uniswap_lens::bindings::iuniswapv3nonfungiblepositionmanager::IUniswapV3NonfungiblePositionManager::positionsReturn>>{
        let position_manager = IUniswapV3NonfungiblePositionManagerInstance::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 = IUniswapV3PoolInstance::new(self.pool_address, self.provider.clone());

        let npm_contract = IUniswapV3NonfungiblePositionManagerInstance::new(
            self.position_manager_address,
            self.provider.clone(),
        );

        let multicall_global = alloy::providers::Provider::multicall(&self.provider)
            .add(pool_contract.slot0())
            .add(pool_contract.feeGrowthGlobal0X128())
            .add(pool_contract.feeGrowthGlobal1X128())
            .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 (slot0, fee_growth_global_0_x128, fee_growth_global_1_x128) =
            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<(
                uniswap_lens::bindings::iuniswapv3pool::IUniswapV3Pool::ticksReturn,
                uniswap_lens::bindings::iuniswapv3pool::IUniswapV3Pool::ticksReturn,
            )>>();
        let mut position_values = Vec::with_capacity(position_data.len());
        for (position, (tick_lower, tick_upper)) in position_data.iter().zip(ticks.iter()) {
            let (tokens_owed0_unrealized, tokens_owed1_unrealized) =
                calculate_tokens_owed_unrealized(
                    slot0.tick,
                    position.tickLower,
                    position.tickUpper,
                    fee_growth_global_0_x128,
                    fee_growth_global_1_x128,
                    tick_lower.feeGrowthOutside0X128,
                    tick_lower.feeGrowthOutside1X128,
                    tick_upper.feeGrowthOutside0X128,
                    tick_upper.feeGrowthOutside1X128,
                    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)?;

            let (amount0, amount1) = calculate_position_token_amounts(
                position.liquidity,
                position.tickLower,
                position.tickUpper,
                slot0.tick,
                slot0.sqrtPriceX96,
            )?;
            let (amount0, amount1) = self.to_currency_amounts(amount0, amount1)?;

            let price = calculate_price_from_sqrt_price_x96(
                slot0.sqrtPriceX96,
                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!(V3MultiPositionViewer);
impl_multi_position_viewer!(V3MultiPositionViewer);

impl_position_viewer_helpers!(V3MultiPositionViewer);
impl_multi_position_predictor!(V3MultiPositionViewer);

impl_v3_pool_state!(V3MultiPositionViewer);
impl_v3_pool_viewer!(V3MultiPositionViewer);