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
Copy
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
Copy
// 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