waterpump-evm-pool-sdk 0.1.0

EVM pool SDK — viewers, infusers, harvesters, swappers for Uniswap V3/V4, PancakeSwap, Slipstream, Shadow, Algebra
Documentation
use std::time::Duration;

use alloy::{
    network::Ethereum,
    primitives::{Address, TxHash},
    providers::{DynProvider, Provider},
    rpc::types::TransactionRequest,
};
use anyhow::Result;
use async_trait::async_trait;
use tracing::{debug, error, info, instrument};
use uniswap_sdk_core::entities::BaseCurrency;
use waterpump_evm_uniswap_v3_client;

use super::{build_swap_call_parameters, decode_swap_event_to_result, handle_pancake_v3_error};
use crate::{
    pool_swappers::{
        common::{build_transaction_with_gas_prices, find_event, send_and_wait_for_transaction},
        v3::{build_quote_call_parameters, decoder::decode_quote_amount},
    },
    traits::pool_swapper::PoolSwapper,
    types::{
        swap_params::{GasPriceOptions, QuoteOptions, SwapOptions, SwapParams},
        swap_results::V3SwapResult,
        v3_pool_key::V3PoolKey,
    },
};

// Helper functions to convert between evm-pool-lib types and uniswap-v3-lib
// types
fn convert_swap_params(params: SwapParams) -> waterpump_evm_uniswap_v3_client::SwapParams {
    waterpump_evm_uniswap_v3_client::SwapParams {
        amount: params.amount,
        trade_type: params.trade_type,
        is_to_b: params.is_to_b,
    }
}

fn convert_quote_options(options: QuoteOptions) -> waterpump_evm_uniswap_v3_client::QuoteOptions {
    waterpump_evm_uniswap_v3_client::QuoteOptions {
        sqrt_price_limit_x96: options.sqrt_price_limit_x96,
        use_quoter_v2: options.use_quoter_v2,
    }
}

#[derive(Clone, Debug)]
pub struct PancakeV3OneHopSwapper {
    pub pool_address: Address,
    pub pool_key: V3PoolKey,

    pub router_address: Address,
    pub quoter_address: Address,
    pub sender_address: Address,
    pub provider: DynProvider<Ethereum>,
}

impl PancakeV3OneHopSwapper {
    #[instrument(skip(pool_key), fields(pool_address = ?pool_address, token_a = ?pool_key.token_a.address(), token_b = ?pool_key.token_b.address(), fee = ?pool_key.fee, router_address = ?router_address, quoter_address = ?quoter_address, sender_address = ?sender_address))]
    pub fn new(
        pool_address: Address,
        pool_key: V3PoolKey,
        router_address: Address,
        quoter_address: Address,
        sender_address: Address,
        provider: DynProvider<Ethereum>,
    ) -> Self {
        debug!(pool_address = ?pool_address, "Using provided pool address");
        Self { pool_address, pool_key, router_address, quoter_address, sender_address, provider }
    }

    /// Get the sender address
    pub fn sender_address(&self) -> Address { self.sender_address }

    /// Get the router address
    pub fn router_address(&self) -> Address { self.router_address }

    /// Get the quoter address
    pub fn quoter_address(&self) -> Address { self.quoter_address }

    /// Get the pool address
    pub fn pool_address(&self) -> Address { self.pool_address }

    /// Get the pool key
    pub fn pool_key(&self) -> &V3PoolKey { &self.pool_key }
}

use crate::impl_token_helper;

impl_token_helper!(PancakeV3OneHopSwapper);

// Import macro from common module for PoolViewer implementation
use crate::{impl_pool_base, impl_v3_pool_state, impl_v3_pool_viewer};

impl_pool_base!(PancakeV3OneHopSwapper);
impl_v3_pool_viewer!(PancakeV3OneHopSwapper);
impl_v3_pool_state!(PancakeV3OneHopSwapper);

#[async_trait]
impl PoolSwapper for PancakeV3OneHopSwapper {
    #[instrument(skip(self, swap_params, swap_options, gas_price_options), fields(pool_address = ?self.pool_address, router_address = ?self.router_address(), is_to_b = ?swap_params.is_to_b, trade_type = ?swap_params.trade_type, amount = ?swap_params.amount))]
    async fn swap(
        &self,
        swap_params: SwapParams,
        swap_options: SwapOptions,
        gas_price_options: Option<GasPriceOptions>,
    ) -> Result<V3SwapResult> {
        let is_to_b = swap_params.is_to_b;

        let swap_call_parameters =
            build_swap_call_parameters(&self.pool_key, swap_params, swap_options.clone())?;
        debug!(
            calldata_len = swap_call_parameters.calldata.len(),
            value = ?swap_call_parameters.value,
            "Swap call parameters built"
        );

        let tx = build_transaction_with_gas_prices(
            &self.provider,
            self.sender_address(),
            self.router_address(),
            swap_call_parameters,
            gas_price_options,
        )
        .await?;

        let receipt = send_and_wait_for_transaction(
            &self.provider,
            tx,
            Some(Duration::from_secs(30)),
            Some(|e| handle_pancake_v3_error(format!("{}", e))),
        )
        .await?;

        let tx_hash = receipt.transaction_hash;

        info!(
            tx_hash = ?receipt.transaction_hash,
            block_number = ?receipt.block_number,
            gas_used = ?receipt.gas_used,
            status = ?receipt.status(),
            "Transaction confirmed"
        );

        if !receipt.status() {
            error!(
                tx_hash = ?tx_hash,
                block_number = ?receipt.block_number,
                "Transaction failed"
            );
            return Err(anyhow::anyhow!("Transaction failed"));
        }

        let currency0 = self.pool_key.token_a.clone();
        let currency1 = self.pool_key.token_b.clone();
        let is_to_b_captured = is_to_b;
        find_event(
            &receipt,
            move |log: &alloy::rpc::types::Log, tx_hash: TxHash| -> anyhow::Result<V3SwapResult> {
                decode_swap_event_to_result(
                    log,
                    tx_hash,
                    is_to_b_captured,
                    currency0.clone(),
                    currency1.clone(),
                )
            },
        )
    }

    #[instrument(skip(self, swap_params, swap_options), fields(pool_address = ?self.pool_address, router_address = ?self.router_address(), is_to_b = ?swap_params.is_to_b, trade_type = ?swap_params.trade_type, amount = ?swap_params.amount))]
    async fn simulate_swap(
        &self,
        swap_params: SwapParams,
        swap_options: SwapOptions,
    ) -> Result<()> {
        let swap_call_parameters =
            build_swap_call_parameters(&self.pool_key, swap_params, swap_options.clone())?;
        debug!(
            calldata_len = swap_call_parameters.calldata.len(),
            value = ?swap_call_parameters.value,
            "Simulation call parameters built"
        );
        let tx = TransactionRequest::default()
            .from(self.sender_address())
            .to(self.router_address())
            .input(swap_call_parameters.calldata.into())
            .value(swap_call_parameters.value);

        let res = Provider::call(&self.provider, tx).await?;
        info!(result_len = res.len(), "Simulation completed successfully");
        debug!(simulation_result = ?res, "Simulation result details");

        Ok(())
    }

    #[instrument(skip(self, swap_params, options), fields(pool_address = ?self.pool_address, quoter_address = ?self.quoter_address, is_to_b = ?swap_params.is_to_b, trade_type = ?swap_params.trade_type, amount = ?swap_params.amount, use_quoter_v2 = ?options.use_quoter_v2))]
    async fn quote(
        &self,
        swap_params: SwapParams,
        options: QuoteOptions,
    ) -> Result<alloy::primitives::U256> {
        let use_quoter_v2 = options.use_quoter_v2;
        let swap_call_parameters = build_quote_call_parameters(
            &self.pool_key,
            convert_swap_params(swap_params.clone()),
            convert_quote_options(options),
        )?;
        debug!(
            calldata_len = swap_call_parameters.calldata.len(),
            value = ?swap_call_parameters.value,
            "Quote call parameters built"
        );
        let tx = TransactionRequest::default()
            .from(self.sender_address())
            .to(self.quoter_address)
            .input(swap_call_parameters.calldata.into())
            .value(swap_call_parameters.value);
        let res = Provider::call(&self.provider, tx).await?;
        let amount = decode_quote_amount(res, &swap_params.trade_type, use_quoter_v2)?;
        info!(
            quoted_amount = ?amount,
            trade_type = ?swap_params.trade_type,
            "Quote completed successfully"
        );
        Ok(amount)
    }
}