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::{
    primitives::{aliases::U128, Address, TxHash},
    rpc::types::Log,
    sol,
};
use anyhow::Result;
use tracing::{debug, error, info, instrument};
use uniswap_sdk_core::prelude::{Currency, CurrencyAmount, ToBig};

use crate::{
    pool_swappers::common::SwapEventData,
    types::swap_results::{V3SwapResult, V3SwapWithIntermediateResult},
};

sol! {
    /// @notice Emitted by the pool for any swaps between token0 and token1
    /// @param sender The address that initiated the swap call, and that received the callback
    /// @param recipient The address that received the output of the swap
    /// @param amount0 The delta of the token0 balance of the pool
    /// @param amount1 The delta of the token1 balance of the pool
    /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96
    /// @param liquidity The liquidity of the pool after the swap
    /// @param tick The log base 1.0001 of price of the pool after the swap
    /// @param protocolFeesToken0 The protocol fee of token0 in the swap
    /// @param protocolFeesToken1 The protocol fee of token1 in the swap
    ///
    /// Note: This is the PancakeSwap V3 Swap event which includes protocol fees
    /// Reference: https://github.com/pancakeswap/pancake-v3-contracts/blob/main/projects/v3-core/contracts/interfaces/pool/IPancakeV3PoolEvents.sol
    event Swap(
        address indexed sender,
        address indexed recipient,
        int256 amount0,
        int256 amount1,
        uint160 sqrtPriceX96,
        uint128 liquidity,
        int24 tick,
        uint128 protocolFeesToken0,
        uint128 protocolFeesToken1
    );
}

/// Decode Swap event from a log entry
///
/// # Arguments
/// * `log` - The log entry from a transaction receipt
/// * `pool_address` - The address of the PancakeSwap V3 pool (optional, for
///   validation)
///
/// # Returns
/// Returns the decoded Swap event log, or an error if the log doesn't match the
/// Swap event signature
///
/// # Example
/// ```no_run
/// use waterpump_evm_pool_sdk::pool_swappers::pancake_v3::decoder::decode_swap_event;
/// use alloy::rpc::types::Log;
///
/// // Assuming you have a log from a transaction receipt
/// let swap_log = decode_swap_event(&log, Some(pool_address))?;
/// // Access event data: swap_log.inner.sender, swap_log.inner.amount0, etc.
/// ```
#[instrument(skip(log), fields(log_address = ?log.address(), pool_address = ?pool_address))]
pub fn decode_swap_event(log: &Log, pool_address: Option<Address>) -> Result<Log<Swap>> {
    // Validate that this log is from the expected pool address if provided
    if let Some(expected_address) = pool_address {
        if log.address() != expected_address {
            error!(
                log_address = ?log.address(),
                expected_address = ?expected_address,
                "Log address does not match expected pool address"
            );
            return Err(anyhow::anyhow!(
                "Log address {:?} does not match expected pool address {:?}",
                log.address(),
                expected_address
            ));
        }
    }

    // Decode the Swap event from the log
    let result = log.log_decode::<Swap>().map_err(|e| {
        debug!(error = ?e, "Failed to decode PancakeSwap V3 Swap event");
        anyhow::anyhow!("Failed to decode PancakeSwap V3 Swap event: {:?}", e)
    });
    if result.is_ok() {
        debug!("Successfully decoded PancakeSwap V3 swap event");
    }
    result
}

/// Decode all Swap events from transaction receipt logs
///
/// # Arguments
/// * `logs` - The logs from a transaction receipt
/// * `pool_address` - The address of the PancakeSwap V3 pool (optional, for
///   filtering)
///
/// # Returns
/// Returns a vector of decoded Swap event logs
///
/// # Example
/// ```no_run
/// use waterpump_evm_pool_sdk::pool_swappers::pancake_v3::decoder::decode_swap_events_from_logs;
///
/// // Assuming you have logs from a transaction receipt
/// let swap_events = decode_swap_events_from_logs(&receipt.logs, Some(pool_address))?;
/// for swap_log in swap_events {
///     println!("Swap: amount0={:?}, amount1={:?}",
///              swap_log.inner.amount0,
///              swap_log.inner.amount1);
/// }
/// ```
#[instrument(skip(logs), fields(log_count = logs.len(), pool_address = ?pool_address))]
pub fn decode_swap_events_from_logs(
    logs: &[Log],
    pool_address: Option<Address>,
) -> Result<Vec<Log<Swap>>> {
    let mut swap_events = Vec::new();

    for log in logs {
        // Try to decode as Swap event
        if let Ok(swap_event) = decode_swap_event(log, pool_address) {
            swap_events.push(swap_event);
        }
    }

    info!(
        total_logs = logs.len(),
        decoded_swap_events = swap_events.len(),
        "Finished decoding PancakeSwap V3 swap events"
    );
    Ok(swap_events)
}

/// Decode swap event from log and convert to V3SwapResult
///
/// This function:
/// 1. Decodes a log as a PancakeSwap V3 Swap event
/// 2. Extracts swap event data
/// 3. Calculates amount_in and amount_out based on swap direction
/// 4. Returns a V3SwapResult
///
/// # Arguments
///
/// * `log` - The log entry from a transaction receipt
/// * `tx_hash` - The transaction hash
/// * `is_to_b` - If true, swap is from token A to token B; if false, from token
///   B to token A
///
/// # Returns
///
/// Returns a `V3SwapResult` if the log is a valid Swap event, or an error
/// otherwise.
#[instrument(skip(log), fields(
    log_address = ?log.address(),
    tx_hash = ?tx_hash,
    is_to_b = is_to_b
))]
pub fn decode_swap_event_to_result(
    log: &Log,
    tx_hash: TxHash,
    is_to_b: bool,
    currency0: Currency,
    currency1: Currency,
) -> Result<V3SwapResult> {
    let decoded = log.log_decode::<Swap>()?;
    let swap_data = SwapEventData {
        sender: decoded.inner.sender,
        recipient: decoded.inner.recipient,
        amount0: decoded.inner.amount0,
        amount1: decoded.inner.amount1,
        sqrt_price_x96: decoded.inner.sqrtPriceX96,
        liquidity: decoded.inner.liquidity,
        tick: decoded.inner.tick,
    };

    let (amount_in, amount_out, currency_in, currency_out) = if is_to_b {
        (swap_data.amount0.unsigned_abs(), swap_data.amount1.unsigned_abs(), currency0, currency1)
    } else {
        (swap_data.amount1.unsigned_abs(), swap_data.amount0.unsigned_abs(), currency1, currency0)
    };

    debug!(
        protocol_fees_token0 = ?decoded.inner.protocolFeesToken0,
        protocol_fees_token1 = ?decoded.inner.protocolFeesToken1,
        "PancakeSwap V3 protocol fees decoded"
    );

    Ok(V3SwapResult {
        tx_hash,
        sender: swap_data.sender,
        recipient: swap_data.recipient,
        amount_in: CurrencyAmount::from_raw_amount(currency_in, amount_in.to_big_int())?,
        amount_out: CurrencyAmount::from_raw_amount(currency_out, amount_out.to_big_int())?,
        sqrt_price_x96: swap_data.sqrt_price_x96,
        liquidity: U128::from(swap_data.liquidity),
        tick: swap_data.tick,
    })
}

/// Decode swap event from log and convert to V3SwapWithIntermediateResult
///
/// This function:
/// 1. Decodes a log as a PancakeSwap V3 Swap event
/// 2. Extracts swap event data
/// 3. Calculates amount_in and amount_out based on swap direction
/// 4. Returns a V3SwapWithIntermediateResult with is_to_intermediate flag
///
/// # Arguments
///
/// * `log` - The log entry from a transaction receipt
/// * `tx_hash` - The transaction hash
/// * `is_to_b` - If true, swap is from token A to token B; if false, from token
///   B to token A
/// * `is_to_intermediate` - If true, this swap is going towards the
///   intermediate token (in a two-hop swap context)
///
/// # Returns
///
/// Returns a `V3SwapWithIntermediateResult` if the log is a valid Swap event,
/// or an error otherwise.
#[instrument(skip(log), fields(
    log_address = ?log.address(),
    tx_hash = ?tx_hash,
    is_to_b = is_to_b,
    is_to_intermediate = is_to_intermediate
))]
pub fn decode_swap_event_to_result_with_intermediate(
    log: &Log,
    tx_hash: TxHash,
    is_to_b: bool,
    is_to_intermediate: bool,
    currency0: Currency,
    currency1: Currency,
) -> Result<V3SwapWithIntermediateResult> {
    let decoded = log.log_decode::<Swap>()?;
    let swap_data = SwapEventData {
        sender: decoded.inner.sender,
        recipient: decoded.inner.recipient,
        amount0: decoded.inner.amount0,
        amount1: decoded.inner.amount1,
        sqrt_price_x96: decoded.inner.sqrtPriceX96,
        liquidity: decoded.inner.liquidity,
        tick: decoded.inner.tick,
    };

    let (amount_in, amount_out, currency_in, currency_out) = if is_to_b {
        (swap_data.amount0.unsigned_abs(), swap_data.amount1.unsigned_abs(), currency0, currency1)
    } else {
        (swap_data.amount1.unsigned_abs(), swap_data.amount0.unsigned_abs(), currency1, currency0)
    };

    debug!(
        protocol_fees_token0 = ?decoded.inner.protocolFeesToken0,
        protocol_fees_token1 = ?decoded.inner.protocolFeesToken1,
        "PancakeSwap V3 protocol fees decoded"
    );

    Ok(V3SwapWithIntermediateResult {
        tx_hash,
        sender: swap_data.sender,
        recipient: swap_data.recipient,
        amount_in: CurrencyAmount::from_raw_amount(currency_in, amount_in.to_big_int())?,
        amount_out: CurrencyAmount::from_raw_amount(currency_out, amount_out.to_big_int())?,
        sqrt_price_x96: swap_data.sqrt_price_x96,
        liquidity: U128::from(swap_data.liquidity),
        tick: swap_data.tick,
        is_to_intermediate,
    })
}