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::I24, Address, TxHash, B256, U256},
    rpc::types::TransactionReceipt,
};
use alloy_sol_types::sol;
use anyhow::{Context, Result};

use crate::pool_swappers::common::find_events;

// Define event structs for V4 IPoolManager events
// Based on: https://github.com/Uniswap/v4-core/blob/main/src/interfaces/IPoolManager.sol
sol! {
    interface IPoolManager {
        /// Emitted when a new pool is initialized
        event Initialize(
            bytes32 indexed id,
            address indexed currency0,
            address indexed currency1,
            uint24 fee,
            int24 tickSpacing,
            address hooks,
            uint160 sqrtPriceX96,
            int24 tick
        );

        /// Emitted when a liquidity position is modified
        /// @param id The abi encoded hash of the pool key struct for the pool that was modified
        /// @param sender The address that modified the pool
        /// @param tickLower The lower tick of the position
        /// @param tickUpper The upper tick of the position
        /// @param liquidityDelta The amount of liquidity that was added or removed
        /// @param salt The extra data to make positions unique
        event ModifyLiquidity(
            bytes32 indexed id,
            address indexed sender,
            int24 tickLower,
            int24 tickUpper,
            int256 liquidityDelta,
            bytes32 salt
        );

        /// Emitted for swaps between currency0 and currency1
        event Swap(
            bytes32 indexed id,
            address indexed sender,
            int128 amount0,
            int128 amount1,
            uint160 sqrtPriceX96,
            uint128 liquidity,
            int24 tick,
            uint24 fee
        );

        /// Emitted for donations
        event Donate(
            bytes32 indexed id,
            address indexed sender,
            uint256 amount0,
            uint256 amount1
        );
    }
}

/// Struct containing liquidity modification details
#[derive(Debug, Clone)]
pub struct LiquidityModification {
    pub pool_id: B256,
    pub token_id: U256,
    pub sender: Address,
    pub tick_lower: I24,
    pub tick_upper: I24,
    pub liquidity_delta: alloy::primitives::I256,
}

/// Decode add liquidity result from transaction receipt
///
/// Decodes the ModifyLiquidity event from IPoolManager to extract liquidity
/// delta and tick information.
#[allow(clippy::unnecessary_fallible_conversions)]
pub fn decode_modify_liquidity_events(
    receipt: &TransactionReceipt,
    pool_manager_address: Address,
) -> Result<(Vec<LiquidityModification>, TxHash)> {
    let tx_hash = receipt.transaction_hash;

    // Find ModifyLiquidity event with positive liquidityDelta
    let modify_events = find_events(receipt, |log, _| {
        // Verify the event is from the PoolManager
        if log.address() != pool_manager_address {
            return Err(anyhow::anyhow!(
                "ModifyLiquidity event from unexpected address: expected {:?}, got {:?}",
                pool_manager_address,
                log.address()
            ));
        }

        let decoded = log.log_decode::<IPoolManager::ModifyLiquidity>()?;

        Ok(LiquidityModification {
            pool_id: B256::try_from(decoded.inner.id).expect("Failed to convert id to pool_id"),
            token_id: U256::try_from(decoded.inner.salt)
                .expect("Failed to convert salt to token_id"),
            sender: decoded.inner.sender,
            tick_lower: decoded.inner.tickLower,
            tick_upper: decoded.inner.tickUpper,
            liquidity_delta: decoded.inner.liquidityDelta,
        })
    })
    .context(
        "Failed to find ModifyLiquidity event with positive liquidityDelta in transaction receipt",
    )?;

    Ok((modify_events, tx_hash))
}

pub fn decode_modify_liquidity_event_with_pool_id(
    receipt: &TransactionReceipt,
    pool_manager_address: Address,
    pool_id: B256,
) -> Result<(LiquidityModification, TxHash)> {
    let (modify_events, tx_hash) = decode_modify_liquidity_events(receipt, pool_manager_address)?;
    for event in modify_events {
        if event.pool_id == pool_id {
            return Ok((event, tx_hash));
        }
    }
    Err(anyhow::anyhow!("ModifyLiquidity event with pool_id not found"))
}