// src/swap_service.rs
use anyhow::{Result, Context};
use log::{info, warn, error, debug};
use saros_dlmm::amms::amm::SarosDlmm;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
pubkey::Pubkey,
signature::{Keypair, Signature},
transaction::Transaction,
commitment_config::CommitmentConfig,
};
use num_bigint::BigInt;
use std::time::{Duration, Instant};
/// Production-grade DLMM swap service with advanced features
pub struct DlmmSwapService {
dlmm: SarosDlmm,
client: RpcClient,
config: SwapConfig,
}
#[derive(Debug, Clone)]
pub struct SwapConfig {
pub pool_address: Pubkey,
pub program_id: Pubkey,
pub max_slippage: f64,
pub retry_attempts: u8,
pub timeout_ms: u64,
}
#[derive(Debug, Clone)]
pub struct SwapRequest {
pub amount_in: BigInt,
pub token_mint_in: Pubkey,
pub token_mint_out: Pubkey,
pub decimals_in: u8,
pub decimals_out: u8,
pub slippage_tolerance: f64,
pub max_price_impact: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct SwapResult {
pub signature: Signature,
pub amount_in: BigInt,
pub amount_out: BigInt,
pub price_impact: f64,
pub fees_paid: BigInt,
pub execution_time_ms: u64,
pub effective_price: f64,
}
impl DlmmSwapService {
/// Create new swap service with optimized configuration
pub fn new(client: RpcClient, config: SwapConfig) -> Self {
let dlmm = SarosDlmm::new(config.pool_address, config.program_id);
info!("π― DLMM Swap Service initialized");
info!("π Pool: {}", config.pool_address);
info!("βοΈ Max slippage: {}%", config.max_slippage * 100.0);
Self { dlmm, client, config }
}
/// Execute swap with professional-grade error handling and retries
pub async fn execute_swap(
&self,
request: SwapRequest,
wallet: &Keypair,
) -> Result<SwapResult> {
let start_time = Instant::now();
info!("π Executing DLMM swap...");
debug!("π Request: {:?}", request);
// 1. Validate request
self.validate_swap_request(&request)?;
// 2. Get quote with retries
let quote = self.get_quote_with_retries(&request).await?;
// 3. Validate quote meets requirements
self.validate_quote("e, &request)?;
// 4. Execute swap with retries
let swap_result = self.execute_swap_with_retries(&request, "e, wallet).await?;
let execution_time = start_time.elapsed().as_millis() as u64;
let result = SwapResult {
signature: swap_result.signature,
amount_in: request.amount_in.clone(),
amount_out: BigInt::from(quote.amount_out),
price_impact: quote.price_impact.unwrap_or(0.0),
fees_paid: BigInt::from(quote.fees.unwrap_or(0)),
execution_time_ms: execution_time,
effective_price: quote.amount_out as f64 / request.amount_in.to_string().parse::<f64>().unwrap_or(1.0),
};
info!("β
Swap completed successfully!");
info!("β‘ Execution time: {}ms", execution_time);
info!("π° Amount out: {} tokens", result.amount_out);
info!("π Price impact: {:.3}%", result.price_impact);
Ok(result)
}
/// Validate swap request parameters
fn validate_swap_request(&self, request: &SwapRequest) -> Result<()> {
if request.amount_in <= BigInt::from(0) {
anyhow::bail!("Amount must be positive");
}
if request.slippage_tolerance > self.config.max_slippage {
anyhow::bail!("Slippage tolerance exceeds maximum allowed");
}
if request.slippage_tolerance <= 0.0 {
anyhow::bail!("Slippage tolerance must be positive");
}
debug!("β
Swap request validation passed");
Ok(())
}
/// Get quote with automatic retries and fallback RPC endpoints
async fn get_quote_with_retries(&self, request: &SwapRequest) -> Result<QuoteResult> {
let mut last_error = None;
for attempt in 1..=self.config.retry_attempts {
debug!("π Quote attempt {}/{}", attempt, self.config.retry_attempts);
match self.get_quote_single_attempt(request).await {
Ok(quote) => {
info!("β
Quote retrieved on attempt {}", attempt);
return Ok(quote);
}
Err(e) => {
warn!("β οΈ Quote attempt {} failed: {}", attempt, e);
last_error = Some(e);
if attempt < self.config.retry_attempts {
let delay = Duration::from_millis(100 * attempt as u64);
tokio::time::sleep(delay).await;
}
}
}
}
error!("β All quote attempts failed");
Err(last_error.unwrap_or_else(|| anyhow::anyhow!("Quote failed after all retries")))
}
async fn get_quote_single_attempt(&self, request: &SwapRequest) -> Result<QuoteResult> {
let quote = self.dlmm.get_quote(
request.amount_in.clone(),
true, // exact_input
request.token_mint_in == request.token_mint_in, // swap direction
self.config.pool_address,
request.token_mint_in,
request.token_mint_out,
request.decimals_in,
request.decimals_out,
request.slippage_tolerance,
).await
.context("Failed to get DLMM quote")?;
Ok(quote)
}
fn validate_quote(&self, quote: &QuoteResult, request: &SwapRequest) -> Result<()> {
// Check price impact limits
if let Some(max_impact) = request.max_price_impact {
let actual_impact = quote.price_impact.unwrap_or(0.0);
if actual_impact > max_impact {
anyhow::bail!(
"Price impact {:.3}% exceeds maximum {:.3}%",
actual_impact, max_impact
);
}
}
// Check minimum output (slippage protection)
let min_out = request.amount_in.to_string().parse::<f64>().unwrap_or(0.0)
* (1.0 - request.slippage_tolerance);
let actual_out = quote.amount_out as f64;
if actual_out < min_out {
anyhow::bail!("Output {} below minimum expected {}", actual_out, min_out);
}
debug!("β
Quote validation passed");
Ok(())
}
async fn execute_swap_with_retries(
&self,
request: &SwapRequest,
quote: &QuoteResult,
wallet: &Keypair,
) -> Result<ExecutionResult> {
let mut last_error = None;
for attempt in 1..=self.config.retry_attempts {
debug!("π Swap execution attempt {}/{}", attempt, self.config.retry_attempts);
match self.execute_swap_single_attempt(request, quote, wallet).await {
Ok(result) => {
info!("β
Swap executed on attempt {}", attempt);
return Ok(result);
}
Err(e) => {
warn!("β οΈ Swap attempt {} failed: {}", attempt, e);
last_error = Some(e);
if attempt < self.config.retry_attempts {
let delay = Duration::from_millis(200 * attempt as u64);
tokio::time::sleep(delay).await;
}
}
}
}
error!("β All swap attempts failed");
Err(last_error.unwrap_or_else(|| anyhow::anyhow!("Swap failed after all retries")))
}
async fn execute_swap_single_attempt(
&self,
request: &SwapRequest,
quote: &QuoteResult,
wallet: &Keypair,
) -> Result<ExecutionResult> {
let swap_result = self.dlmm.swap(
quote.amount_in.clone(),
request.token_mint_in,
request.token_mint_out,
quote.other_amount_offset,
None, // No custom hooks for now
true, // exact_input
true, // swap_for_y direction
self.config.pool_address,
wallet.pubkey(),
).await
.context("DLMM swap execution failed")?;
// Wait for confirmation
self.client
.confirm_transaction_with_spinner(&swap_result.signature, &self.client.commitment())
.context("Transaction confirmation failed")?;
Ok(swap_result)
}
}
// Type aliases for the demo (real types would come from the SDK)
type QuoteResult = QuoteData;
type ExecutionResult = SwapExecutionResult;
#[derive(Debug, Clone)]
pub struct QuoteData {
pub amount_in: BigInt,
pub amount_out: u64,
pub price_impact: Option<f64>,
pub fees: Option<u64>,
pub other_amount_offset: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct SwapExecutionResult {
pub signature: Signature,
pub amount_in: BigInt,
pub amount_out: u64,
}