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 super::{
build_quote_call_parameters, build_swap_call_parameters, decode_quote_amount,
decode_swap_event_to_result, handle_uniswap_v3_error,
};
use crate::{
pool_swappers::common::{
build_transaction_with_gas_prices, find_event, send_and_wait_for_transaction,
},
traits::pool_swapper::PoolSwapper,
types::{
swap_params::{GasPriceOptions, QuoteOptions, SwapOptions, SwapParams},
swap_results::V3SwapResult,
v3_pool_key::V3PoolKey,
},
};
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_swap_options(options: SwapOptions) -> waterpump_evm_uniswap_v3_client::SwapOptions {
waterpump_evm_uniswap_v3_client::SwapOptions {
amount_in_maximum: options.amount_in_maximum,
amount_out_minimum: options.amount_out_minimum,
recipient: options.recipient,
input_token_permit: options.input_token_permit,
sqrt_price_limit_x96: options.sqrt_price_limit_x96,
fee: options.fee,
}
}
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 V3OneHopSwapper {
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 V3OneHopSwapper {
#[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 }
}
pub fn sender_address(&self) -> Address { self.sender_address }
pub fn router_address(&self) -> Address { self.router_address }
pub fn quoter_address(&self) -> Address { self.quoter_address }
pub fn pool_address(&self) -> Address { self.pool_address }
pub fn pool_key(&self) -> &V3PoolKey { &self.pool_key }
}
use crate::impl_token_helper;
impl_token_helper!(V3OneHopSwapper);
use crate::{impl_pool_base, impl_v3_pool_state, impl_v3_pool_viewer};
impl_pool_base!(V3OneHopSwapper);
impl_v3_pool_viewer!(V3OneHopSwapper);
impl_v3_pool_state!(V3OneHopSwapper);
#[async_trait]
impl PoolSwapper for V3OneHopSwapper {
#[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,
convert_swap_params(swap_params),
convert_swap_options(swap_options.clone()),
)?;
debug!(
calldata_len = swap_call_parameters.calldata.len(),
value = ?swap_call_parameters.value,
"Swap call parameters built"
);
let method_params = crate::pool_swappers::common::MethodParameters {
calldata: swap_call_parameters.calldata,
value: swap_call_parameters.value,
};
let tx = build_transaction_with_gas_prices(
&self.provider,
self.sender_address(),
self.router_address(),
method_params,
gas_price_options,
)
.await?;
let receipt = send_and_wait_for_transaction(
&self.provider,
tx,
Some(Duration::from_secs(30)),
Some(|e| handle_uniswap_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,
convert_swap_params(swap_params),
convert_swap_options(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 = self.provider.call(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 = self.provider.call(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)
}
}