Skip to main content
Direct token swaps often give poor prices, but finding the best multi-pool route manually is impossible at scale. Rust’s performance lets you build routing engines that find optimal paths across hundreds of pools in milliseconds.

What You’ll Achieve

  • Get 10-50% better swap rates through optimal routing
  • Trade any token pair, even if no direct pool exists
  • Process complex routing calculations in under 1ms
  • Handle routing for thousands of users simultaneously

Why Routing Needs Rust Speed

Finding optimal routes requires checking thousands of possible paths. Python and TypeScript are too slow for real-time route optimization, but Rust can evaluate complex routing trees fast enough for live trading.

Multi-Hop Example

use dlmm_rust_sdk::{
    DlmmPool, Router, Route, RouteStep,
    error::DlmmError,
};
use solana_sdk::pubkey::Pubkey;
use std::collections::{HashMap, VecDeque};

#[derive(Debug, Clone)]
pub struct MultiHopRouter {
    pools: HashMap<(Pubkey, Pubkey), DlmmPool>,
    route_cache: HashMap<(Pubkey, Pubkey), Vec<Route>>,
}

#[derive(Debug, Clone)]
pub struct Route {
    pub steps: Vec<RouteStep>,
    pub estimated_output: u64,
    pub price_impact: f64,
    pub fee_total: u64,
    pub hops: u8,
}

#[derive(Debug, Clone)]
pub struct RouteStep {
    pub pool_address: Pubkey,
    pub token_in: Pubkey,
    pub token_out: Pubkey,
    pub amount_in: u64,
    pub amount_out: u64,
    pub fee: u64,
}

impl MultiHopRouter {
    pub fn new() -> Self {
        Self {
            pools: HashMap::new(),
            route_cache: HashMap::new(),
        }
    }

    /// Add a pool to the router
    pub fn add_pool(&mut self, pool: DlmmPool) {
        let key = (pool.token_x(), pool.token_y());
        self.pools.insert(key, pool.clone());
        // Also add reverse mapping
        let reverse_key = (pool.token_y(), pool.token_x());
        self.pools.insert(reverse_key, pool);
    }

    /// Find the best route between two tokens
    pub async fn find_best_route(
        &mut self,
        token_in: Pubkey,
        token_out: Pubkey,
        amount_in: u64,
        max_hops: u8,
    ) -> Result<Route, DlmmError> {
        // Check cache first
        let cache_key = (token_in, token_out);
        if let Some(cached_routes) = self.route_cache.get(&cache_key) {
            if let Some(best_route) = cached_routes.first() {
                // Recalculate with current amount
                if let Ok(updated_route) = self.calculate_route_output(
                    best_route.clone(),
                    amount_in
                ).await {
                    return Ok(updated_route);
                }
            }
        }

        // Find all possible routes
        let routes = self.find_all_routes(
            token_in,
            token_out,
            amount_in,
            max_hops,
        ).await?;

        // Select the best route (highest output)
        let best_route = routes
            .into_iter()
            .max_by_key(|route| route.estimated_output)
            .ok_or(DlmmError::NoRouteFound)?;

        // Cache the result
        self.route_cache.insert(cache_key, vec![best_route.clone()]);

        Ok(best_route)
    }

    /// Execute a multi-hop swap
    pub async fn execute_multi_hop_swap(
        &self,
        route: Route,
        payer: &solana_sdk::signature::Keypair,
        slippage_tolerance: f64,
    ) -> Result<u64, DlmmError> {
        let mut current_amount = route.steps[0].amount_in;
        let minimum_final_output = ((route.estimated_output as f64) * 
            (1.0 - slippage_tolerance)) as u64;

        // Execute each step in the route
        for (i, step) in route.steps.iter().enumerate() {
            let pool = self.pools.get(&(step.token_in, step.token_out))
                .ok_or(DlmmError::PoolNotFound)?;

            // For intermediate steps, use all output as input for next step
            let amount_in = if i == 0 { step.amount_in } else { current_amount };
            
            // Set minimum output (0 for intermediate steps, slippage for final)
            let min_output = if i == route.steps.len() - 1 {
                minimum_final_output
            } else {
                0 // Allow any output for intermediate steps
            };

            current_amount = pool.swap(
                amount_in,
                step.token_in == pool.token_x(),
                min_output,
                payer,
            ).await?.amount_out;
        }

        Ok(current_amount)
    }

    /// Find all possible routes using BFS
    async fn find_all_routes(
        &self,
        token_in: Pubkey,
        token_out: Pubkey,
        amount_in: u64,
        max_hops: u8,
    ) -> Result<Vec<Route>, DlmmError> {
        let mut routes = Vec::new();
        let mut queue = VecDeque::new();
        
        // Start with direct routes
        queue.push_back(PartialRoute {
            current_token: token_in,
            steps: Vec::new(),
            visited: vec![token_in],
            current_amount: amount_in,
        });

        while let Some(partial_route) = queue.pop_front() {
            // Check if we've reached the destination
            if partial_route.current_token == token_out {
                if !partial_route.steps.is_empty() {
                    let route = self.finalize_route(partial_route).await?;
                    routes.push(route);
                }
                continue;
            }

            // Don't exceed max hops
            if partial_route.steps.len() >= max_hops as usize {
                continue;
            }

            // Find all pools that accept current token as input
            for ((token_a, token_b), pool) in &self.pools {
                let (next_token, is_x_to_y) = if *token_a == partial_route.current_token {
                    (*token_b, true)
                } else if *token_b == partial_route.current_token {
                    (*token_a, false)
                } else {
                    continue;
                };

                // Avoid cycles
                if partial_route.visited.contains(&next_token) {
                    continue;
                }

                // Get quote for this step
                if let Ok(quote) = pool.get_swap_quote(
                    partial_route.current_amount,
                    is_x_to_y,
                    0.0,
                ).await {
                    let mut new_visited = partial_route.visited.clone();
                    new_visited.push(next_token);
                    
                    let mut new_steps = partial_route.steps.clone();
                    new_steps.push(RouteStep {
                        pool_address: pool.get_address(),
                        token_in: partial_route.current_token,
                        token_out: next_token,
                        amount_in: partial_route.current_amount,
                        amount_out: quote.amount_out,
                        fee: quote.fee,
                    });

                    queue.push_back(PartialRoute {
                        current_token: next_token,
                        steps: new_steps,
                        visited: new_visited,
                        current_amount: quote.amount_out,
                    });
                }
            }
        }

        Ok(routes)
    }

    async fn finalize_route(&self, partial_route: PartialRoute) -> Result<Route, DlmmError> {
        let total_fee = partial_route.steps.iter().map(|s| s.fee).sum();
        let final_output = partial_route.current_amount;
        
        // Calculate price impact
        let direct_price = self.get_direct_price(
            partial_route.visited[0],
            *partial_route.visited.last().unwrap()
        ).await.unwrap_or(0.0);
        
        let route_price = (final_output as f64) / (partial_route.steps[0].amount_in as f64);
        let price_impact = ((direct_price - route_price) / direct_price * 100.0).abs();

        Ok(Route {
            steps: partial_route.steps,
            estimated_output: final_output,
            price_impact,
            fee_total: total_fee,
            hops: partial_route.visited.len() as u8 - 1,
        })
    }

    async fn get_direct_price(&self, token_a: Pubkey, token_b: Pubkey) -> Option<f64> {
        if let Some(pool) = self.pools.get(&(token_a, token_b)) {
            pool.get_current_price().await.ok()
        } else {
            None
        }
    }

    async fn calculate_route_output(
        &self,
        mut route: Route,
        amount_in: u64,
    ) -> Result<Route, DlmmError> {
        let mut current_amount = amount_in;
        
        for step in &mut route.steps {
            let pool = self.pools.get(&(step.token_in, step.token_out))
                .ok_or(DlmmError::PoolNotFound)?;
            
            let quote = pool.get_swap_quote(
                current_amount,
                step.token_in == pool.token_x(),
                0.0,
            ).await?;
            
            step.amount_in = current_amount;
            step.amount_out = quote.amount_out;
            step.fee = quote.fee;
            current_amount = quote.amount_out;
        }
        
        route.estimated_output = current_amount;
        route.fee_total = route.steps.iter().map(|s| s.fee).sum();
        
        Ok(route)
    }
}

#[derive(Debug, Clone)]
struct PartialRoute {
    current_token: Pubkey,
    steps: Vec<RouteStep>,
    visited: Vec<Pubkey>,
    current_amount: u64,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut router = MultiHopRouter::new();
    
    // Add some example pools (replace with actual pools)
    // router.add_pool(usdc_sol_pool);
    // router.add_pool(sol_eth_pool);
    // router.add_pool(eth_btc_pool);
    
    let usdc = Pubkey::new_unique();
    let btc = Pubkey::new_unique();
    let amount_in = 1_000_000; // 1 USDC
    
    // Find best route from USDC to BTC
    match router.find_best_route(usdc, btc, amount_in, 3).await {
        Ok(route) => {
            println!("Best route found:");
            println!("  Hops: {}", route.hops);
            println!("  Estimated output: {}", route.estimated_output);
            println!("  Price impact: {:.2}%", route.price_impact);
            println!("  Total fees: {}", route.fee_total);
            
            for (i, step) in route.steps.iter().enumerate() {
                println!("  Step {}: {} -> {} ({})", 
                    i + 1, 
                    step.token_in, 
                    step.token_out,
                    step.amount_out
                );
            }
        }
        Err(e) => println!("No route found: {:?}", e),
    }
    
    Ok(())
}

Multi-Hop Features

Route Discovery

  • Breadth-First Search: Finds optimal paths through available liquidity
  • Cycle Detection: Prevents infinite loops in routing
  • Max Hops Limit: Configurable maximum route length
  • Route Caching: Cache frequently used routes for performance

Route Optimization

  • Output Maximization: Select routes with highest expected output
  • Price Impact Minimization: Consider market impact in route selection
  • Fee Optimization: Balance fees vs output for best net result
  • Gas Efficiency: Prefer routes with fewer transaction calls

Advanced Routing Strategies

// Split large orders across multiple routes
pub async fn split_order_routing(
    &self,
    token_in: Pubkey,
    token_out: Pubkey,
    total_amount: u64,
    max_routes: u8,
) -> Result<Vec<Route>, DlmmError> {
    let routes = self.find_all_routes(token_in, token_out, total_amount, 3).await?;
    
    // Split amount across top routes to minimize price impact
    let split_amount = total_amount / (max_routes as u64);
    let mut optimized_routes = Vec::new();
    
    for route in routes.into_iter().take(max_routes as usize) {
        if let Ok(updated_route) = self.calculate_route_output(route, split_amount).await {
            optimized_routes.push(updated_route);
        }
    }
    
    Ok(optimized_routes)
}

Error Handling

Common multi-hop routing errors:
  • NoRouteFound: No path exists between tokens
  • InsufficientLiquidity: Route exists but lacks liquidity
  • SlippageExceeded: Price moved during multi-hop execution
  • MaxHopsExceeded: Route requires too many steps