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