use alloy::{
eips::BlockId,
network::Ethereum,
primitives::{Address, U256},
providers::DynProvider,
};
use anyhow::Result;
use tracing::{instrument, trace};
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,
},
};
#[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 {
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 }
pub fn len(&self) -> usize { self.position_metadatas.len() }
pub fn is_empty(&self) -> bool { self.position_metadatas.is_empty() }
pub fn position_metadatas_internal(&self) -> Vec<PositionMetadata> {
self.position_metadatas.clone()
}
}
impl QuickswapMultiPositionViewer {
#[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);
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()) {
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)?;
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);