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::{
    network::Ethereum,
    primitives::{Address, U256},
    providers::Provider,
    rpc::types::TransactionRequest,
    sol,
};
use alloy_sol_types::SolCall;
use anyhow::{Context, Result};
use tracing::{debug, info};
use uniswap_sdk_core::{
    entities::{BaseCurrency, BaseCurrencyCore, FractionBase},
    prelude::{Currency, CurrencyAmount, WETH9},
    utils::FromBig,
};
use waterpump_evm_core::weth_ext::Weth9Ext;

sol! {
    #[sol(rpc)]
    interface ERC20 {
        function approve(address spender, uint256 amount) returns (bool);
        function allowance(address owner, address spender) returns (uint256);
    }
}

/// Approve the swap router to spend tokens if needed
/// Returns true if approval was needed and executed, false if already approved
/// or native token
#[tracing::instrument(skip(provider, token, amount), fields(sender_address = ?sender_address, spender_address = ?spender_address, token_address = ?token.address(), token_is_native = token.is_native(), amount = ?amount))]
pub async fn approve_if_needed<P: Provider<Ethereum>>(
    provider: &P,
    sender_address: Address,
    spender_address: Address,
    token: &Currency,
    amount: CurrencyAmount<Currency>,
) -> Result<bool> {
    // If token is native (ETH), approve WETH instead
    if token.is_native() {
        info!("Token is native (ETH), approving WETH instead");

        // Get chain ID from provider
        let chain_id_raw =
            provider.get_chain_id().await.context("Failed to get chain ID from provider")?;
        let chain_id: u64 = chain_id_raw;

        let weth_token = WETH9::on_chain_with_custom(chain_id)?;
        // Approve with max uint256 for simplicity (common practice)
        let max_approval = U256::MAX;
        info!("Insufficient WETH allowance, approving router");
        debug!(approving_amount = ?max_approval, "Approving max amount");

        let approve_calldata =
            ERC20::approveCall { spender: spender_address, amount: max_approval }.abi_encode();

        let tx = TransactionRequest::default()
            .from(sender_address)
            .to(weth_token.address())
            .input(approve_calldata.into());

        let pending_tx = provider
            .send_transaction(tx)
            .await
            .context("Failed to send WETH approval transaction")?;

        let tx_hash = pending_tx.watch().await?;
        info!(tx_hash = ?tx_hash, "WETH approval transaction sent");

        let receipt = provider
            .get_transaction_receipt(tx_hash)
            .await
            .context("Failed to get WETH approval transaction receipt")?
            .unwrap();

        info!(
            tx_hash = ?tx_hash,
            block_number = ?receipt.block_number,
            gas_used = ?receipt.gas_used,
            "WETH approval confirmed"
        );

        return Ok(true);
    }

    let token_address = token.address();
    debug!(token_address = ?token_address, "Processing ERC20 token approval");
    let erc20 = ERC20::new(token_address, provider);

    // Check current allowance
    let current_allowance = erc20
        .allowance(sender_address, spender_address)
        .call()
        .await
        .context("Failed to check token allowance")?;

    let required_amount = U256::from_big_int(amount.quotient());

    debug!(current_allowance = ?current_allowance, required_amount = ?required_amount, "Token allowance check");

    // Check if approval is needed
    if current_allowance >= required_amount {
        info!("Sufficient allowance, no approval needed");
        return Ok(false);
    }

    // Approve with max uint256 for simplicity (common practice)
    let max_approval = U256::MAX;
    info!("Insufficient allowance, approving router");
    debug!(approving_amount = ?max_approval, "Approving max amount");

    let approve_calldata =
        ERC20::approveCall { spender: spender_address, amount: max_approval }.abi_encode();

    let tx = TransactionRequest::default()
        .from(sender_address)
        .to(token_address)
        .input(approve_calldata.into());

    let pending_tx =
        provider.send_transaction(tx).await.context("Failed to send approval transaction")?;

    let tx_hash = pending_tx.watch().await?;
    info!(tx_hash = ?tx_hash, "Approval transaction sent");

    let _receipt = provider
        .get_transaction_receipt(tx_hash)
        .await
        .context("Failed to get approval transaction receipt")?
        .unwrap();

    info!(
        tx_hash = ?tx_hash,
        "Approval confirmed"
    );

    Ok(true)
}