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::{Ok, Result};
use tracing::{debug, instrument};

use crate::{
    common::{calculate_position_token_amounts, calculate_tokens_owed_unrealized},
    impl_pancake_v3_pool_state, impl_pancake_v3_pool_viewer, impl_pool_base,
    impl_position_predictor, impl_position_viewer, impl_position_viewer_helpers,
    pool_viewers::v3::utils::calculate_price_from_sqrt_price_x96,
    types::{
        position_metadata::PositionMetadata, position_values::PositionValues,
        v3_pool_key::V3PoolKey,
    },
};

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

    pub pool_address: Address,
    pub pool_key: V3PoolKey,

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

impl PancakeV3PositionViewer {
    #[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: V3PoolKey,
        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 PancakeV3PositionViewer"
        );
        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: V3PoolKey,
        token_id: U256,
        block_id: Option<BlockId>,
    ) -> Result<Self> {
        let position_manager = waterpump_evm_pancakeswap_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 PancakeV3PositionViewer {
    pub fn pool_key(&self) -> &V3PoolKey { &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() }
}

impl PancakeV3PositionViewer {
    #[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_pancakeswap_client::interfaces::IPancakeV3PoolState::IPancakeV3PoolStateInstance::new(
                self.pool_address,
                self.provider.clone(),
            );

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

        let multicall = alloy::providers::Provider::multicall(&self.provider)
            .add(npm_contract.positions(self.metadata.token_id))
            .add(pool_contract.slot0())
            .add(pool_contract.feeGrowthGlobal0X128())
            .add(pool_contract.feeGrowthGlobal1X128())
            .add(pool_contract.ticks(self.metadata.tick_lower))
            .add(pool_contract.ticks(self.metadata.tick_upper))
            .block(block_id);

        let (
            position_data,
            slot0,
            fee_growth_global_0_x128,
            fee_growth_global_1_x128,
            tick_lower,
            tick_upper,
        ) = multicall.aggregate().await?;

        let (tokens_owed0_unrealized, tokens_owed1_unrealized) = calculate_tokens_owed_unrealized(
            slot0.tick,
            position_data.tickLower,
            position_data.tickUpper,
            fee_growth_global_0_x128,
            fee_growth_global_1_x128,
            tick_lower.feeGrowthOutside0X128,
            tick_lower.feeGrowthOutside1X128,
            tick_upper.feeGrowthOutside0X128,
            tick_upper.feeGrowthOutside1X128,
            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)?;

        let (amount0, amount1) = calculate_position_token_amounts(
            position_data.liquidity,
            position_data.tickLower,
            position_data.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(),
        );

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

impl_pool_base!(PancakeV3PositionViewer);
impl_position_viewer_helpers!(PancakeV3PositionViewer);

impl_position_viewer!(PancakeV3PositionViewer);
impl_position_predictor!(PancakeV3PositionViewer);

impl_pancake_v3_pool_state!(PancakeV3PositionViewer);
impl_pancake_v3_pool_viewer!(PancakeV3PositionViewer);