waterpump-evm-pool-sdk 0.1.0

EVM pool SDK — viewers, infusers, harvesters, swappers for Uniswap V3/V4, PancakeSwap, Slipstream, Shadow, Algebra
Documentation
//! Utilities for working with pool contracts

use alloy::{network::Network, primitives::Address, providers::Provider};
use anyhow::{Context, Result};
use tracing::{debug, instrument};
use uniswap_lens::bindings::ierc20::IERC20::IERC20Instance;
use uniswap_sdk_core::{
    prelude::{Currency, Token},
    token,
};
use uniswap_v3_sdk::prelude::FeeAmount;
#[cfg(feature = "slipstream")]
use waterpump_evm_core::pool_key::SlipstreamPoolKey;
use waterpump_evm_core::pool_key::V3PoolKey;

#[cfg(feature = "algebra")]
use crate::types::quickswap_pool_key::QuickswapPoolKey;

/// Get the underlying currencies (token0 and token1) from a V3-style pool
/// contract
///
/// This function queries the pool contract for token0 and token1 addresses,
/// then fetches their decimals and symbols to create Currency objects.
///
/// # Arguments
/// * `provider` - The provider to use for RPC calls
/// * `pool_address` - The address of the pool contract
/// * `chain_id` - The chain ID for creating Currency objects
///
/// # Returns
/// A tuple of (token0, token1) as Currency objects
///
/// # Example
/// ```no_run
/// use waterpump_evm_pool_sdk::common::pool_utils;
/// use alloy::providers::Provider;
///
/// # async fn example<P: Provider<alloy::network::Ethereum>>(
/// #     provider: &P,
/// #     pool_address: alloy::primitives::Address,
/// #     chain_id: u64,
/// # ) -> anyhow::Result<()> {
/// let (token0, token1) = pool_utils::get_pool_underlying_currencies_v3(
///     provider,
///     pool_address,
///     chain_id,
/// )
/// .await?;
/// println!("Token0: {:?}", token0);
/// println!("Token1: {:?}", token1);
/// # Ok(())
/// # }
/// ```
#[instrument(skip(provider), fields(pool_address = ?pool_address, chain_id = chain_id))]
pub async fn get_pool_underlying_currencies_v3<P: Provider<N>, N: Network>(
    provider: &P,
    pool_address: Address,
    chain_id: u64,
) -> Result<(Currency, Currency)> {
    use uniswap_lens::bindings::iuniswapv3pool::IUniswapV3Pool::IUniswapV3PoolInstance;

    let pool_contract = IUniswapV3PoolInstance::new(pool_address, provider);

    let multicall = provider.multicall().add(pool_contract.token0()).add(pool_contract.token1());

    let (token0_address, token1_address) =
        multicall.aggregate().await.context("Failed to call token0()")?;

    debug!(
        token0_address = ?token0_address,
        token1_address = ?token1_address,
        "Retrieved token addresses from pool"
    );

    // Fetch decimals and symbols for both tokens
    let token0_contract = IERC20Instance::new(token0_address, provider);
    let token1_contract = IERC20Instance::new(token1_address, provider);

    let multicall = provider
        .multicall()
        .add(token0_contract.decimals())
        .add(token0_contract.symbol())
        .add(token1_contract.decimals())
        .add(token1_contract.symbol());

    let (token0_decimals, token0_symbol, token1_decimals, token1_symbol) =
        multicall.aggregate().await.context("Failed to call token0 decimals()")?;

    debug!(
        token0_decimals = token0_decimals,
        token0_symbol = %token0_symbol,
        token1_decimals = token1_decimals,
        token1_symbol = %token1_symbol,
        "Retrieved token metadata"
    );

    // Create Currency objects using token! macro
    let token0: Currency = token!(
        chain_id,
        &format!("{:x}", token0_address),
        token0_decimals,
        &token0_symbol,
        &token0_symbol
    )
    .into();
    let token1: Currency = token!(
        chain_id,
        &format!("{:x}", token1_address),
        token1_decimals,
        &token1_symbol,
        &token1_symbol
    )
    .into();

    Ok((token0, token1))
}

/// Get the pool key (token0, token1, fee) from a V3 pool contract
///
/// This function queries the pool contract to get token0, token1, and fee,
/// then constructs a V3PoolKey. The fee is read from the pool's storage slot.
///
/// # Arguments
/// * `provider` - The provider to use for RPC calls
/// * `pool_address` - The address of the pool contract
/// * `chain_id` - The chain ID for creating Currency objects
///
/// # Returns
/// A `V3PoolKey` containing token_a, token_b, and fee
///
/// # Example
/// ```no_run
/// use waterpump_evm_pool_sdk::common::pool_utils;
/// use alloy::providers::Provider;
///
/// # async fn example<P: Provider<alloy::network::Ethereum>>(
/// #     provider: &P,
/// #     pool_address: alloy::primitives::Address,
/// #     chain_id: u64,
/// # ) -> anyhow::Result<()> {
/// let pool_key = pool_utils::get_pool_key_v3(provider, pool_address, chain_id).await?;
/// println!("Token A: {:?}", pool_key.token_a.address());
/// println!("Token B: {:?}", pool_key.token_b.address());
/// println!("Fee: {:?}", pool_key.fee);
/// # Ok(())
/// # }
/// ```
#[instrument(skip(provider), fields(pool_address = ?pool_address, chain_id = chain_id))]
pub async fn get_pool_key_v3<P: Provider<N>, N: Network>(
    provider: &P,
    pool_address: Address,
    chain_id: u64,
) -> Result<V3PoolKey> {
    use uniswap_lens::bindings::iuniswapv3pool::IUniswapV3Pool::IUniswapV3PoolInstance;
    // Get token0 and token1
    let (token0, token1) =
        get_pool_underlying_currencies_v3(provider, pool_address, chain_id).await?;

    let contract = IUniswapV3PoolInstance::new(pool_address, provider);

    let fee = contract.fee().call().await?;

    let fee_amount: FeeAmount = fee.into();

    Ok(V3PoolKey { token_a: token0, token_b: token1, fee: fee_amount })
}

#[cfg(feature = "slipstream")]
/// Get the pool key (token0, token1, tick_spacing) from a Slipstream pool
/// contract
///
/// This function queries the Slipstream pool contract to get token0, token1,
/// and tickSpacing, then constructs a SlipstreamPoolKey.
///
/// # Arguments
/// * `provider` - The provider to use for RPC calls
/// * `pool_address` - The address of the pool contract
/// * `chain_id` - The chain ID for creating Currency objects
///
/// # Returns
/// A `SlipstreamPoolKey` containing token_a, token_b, and tick_spacing
///
/// # Example
/// ```no_run
/// use waterpump_evm_pool_sdk::common::pool_utils;
/// use alloy::providers::Provider;
///
/// # async fn example<P: Provider<alloy::network::Ethereum>>(
/// #     provider: &P,
/// #     pool_address: alloy::primitives::Address,
/// #     chain_id: u64,
/// # ) -> anyhow::Result<()> {
/// let pool_key = pool_utils::get_pool_key_slipstream(provider, pool_address, chain_id).await?;
/// println!("Token A: {:?}", pool_key.token_a.address());
/// println!("Token B: {:?}", pool_key.token_b.address());
/// println!("Tick Spacing: {}", pool_key.tick_spacing);
/// # Ok(())
/// # }
/// ```
#[instrument(skip(provider), fields(pool_address = ?pool_address, chain_id = chain_id))]
pub async fn get_pool_key_slipstream<P: Provider<N>, N: Network>(
    provider: &P,
    pool_address: Address,
    chain_id: u64,
) -> Result<SlipstreamPoolKey> {
    use waterpump_evm_slipstream_client::interfaces::ICLPool::ICLPoolInstance;

    let (token0, token1) =
        get_pool_underlying_currencies_v3(provider, pool_address, chain_id).await?;

    let pool_contract = ICLPoolInstance::new(pool_address, provider);

    let tick_spacing = pool_contract.tickSpacing().call().await?;

    Ok(SlipstreamPoolKey { token_a: token0, token_b: token1, tick_spacing })
}

#[cfg(feature = "algebra")]
/// Get the pool key (token0, token1, deployer) from an Quickswap pool contract
///
/// This function queries the Quickswap pool contract to get token0 and token1,
/// then constructs an QuickswapPoolKey with the deployer address from Quickswap
/// deployment addresses.
///
/// # Arguments
/// * `provider` - The provider to use for RPC calls
/// * `pool_address` - The address of the pool contract
/// * `chain_id` - The chain ID for creating Currency objects and getting
///   deployer address
///
/// # Returns
/// A `QuickswapPoolKey` containing token_a, token_b, and deployer
///
/// # Example
/// ```no_run
/// use waterpump_evm_pool_sdk::common::pool_utils;
/// use alloy::providers::Provider;
///
/// # async fn example<P: Provider<alloy::network::Ethereum>>(
/// #     provider: &P,
/// #     pool_address: alloy::primitives::Address,
/// #     chain_id: u64,
/// # ) -> anyhow::Result<()> {
/// let pool_key = pool_utils::get_pool_key_algebra(provider, pool_address, chain_id).await?;
/// println!("Token A: {:?}", pool_key.token_a.address());
/// println!("Token B: {:?}", pool_key.token_b.address());
/// println!("Deployer: {:?}", pool_key.deployer);
/// # Ok(())
/// # }
/// ```
#[instrument(skip(provider), fields(pool_address = ?pool_address, chain_id = chain_id))]
pub async fn get_pool_key_quickswap<P: Provider<N>, N: Network>(
    provider: &P,
    pool_address: Address,
    chain_id: u64,
) -> Result<QuickswapPoolKey> {
    // Get token0 and token1 (Quickswap pools use the same token0/token1 interface
    // as V3)
    let (token0, token1) =
        get_pool_underlying_currencies_v3(provider, pool_address, chain_id).await?;

    // Get deployer address from Quickswap deployment addresses
    let deployer = waterpump_evm_algebra_client::addresses::get_deployment_addresses(chain_id)
        .with_context(|| format!("Quickswap addresses not found for chain_id: {}", chain_id))?
        .pool_deployer;

    Ok(QuickswapPoolKey { token_a: token0, token_b: token1, deployer })
}