//! Cross-Chain DEX Liquidity Aggregator //! //! This contract aggregates liquidity from multiple sources to provide: //! - Best execution price across all DEXs //! - Cross-chain swaps via IBC //! - Split routing for large orders //! - MEV protection via private mempool //! //! # Zero-Capital Strategy //! //! ```text //! ┌─────────────────────────────────────────────────────────────┐ //! │ SYNOR LIQUIDITY AGGREGATOR │ //! │ (No Capital Required) │ //! ├─────────────────────────────────────────────────────────────┤ //! │ │ //! │ USER REQUEST: Swap 10 ETH for USDC (best price) │ //! │ │ │ //! │ ▼ │ //! │ ┌─────────────────────────────────────────────┐ │ //! │ │ QUOTE AGGREGATION │ │ //! │ │ │ │ //! │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ //! │ │ │ Synor │ │ Osmosis │ │ dYdX │ │ │ //! │ │ │ DEX │ │ (IBC) │ │ (IBC) │ │ │ //! │ │ │ $25,010 │ │ $25,015 │ │ $25,020 │ │ │ //! │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ //! │ │ │ │ │ │ │ //! │ │ └─────────────┼─────────────┘ │ │ //! │ │ │ │ │ //! │ │ BEST PRICE: $25,020 (dYdX) │ │ //! │ └─────────────────────┬────────────────────┘ │ //! │ │ │ //! │ ▼ │ //! │ ┌─────────────────────────────────────────────┐ │ //! │ │ ROUTE EXECUTION │ │ //! │ │ │ │ //! │ │ Option A: Single route (small order) │ │ //! │ │ ETH ────────────────────────────► USDC │ │ //! │ │ via dYdX IBC bridge │ │ //! │ │ │ │ //! │ │ Option B: Split route (large order) │ │ //! │ │ ETH ──┬── 60% via dYdX ──────────► USDC │ │ //! │ │ ├── 25% via Osmosis ────────► │ │ //! │ │ └── 15% via Synor DEX ──────► │ │ //! │ └─────────────────────────────────────────────┘ │ //! │ │ //! │ REVENUE SOURCES: │ //! │ • Aggregation fee: 0.05% of swap volume │ //! │ • Positive slippage capture │ //! │ • Bridge fee share (partner programs) │ //! └─────────────────────────────────────────────────────────────┘ //! ``` #![no_std] extern crate alloc; use alloc::string::String; use alloc::vec::Vec; use borsh::{BorshDeserialize, BorshSerialize}; use synor_sdk::prelude::*; use synor_sdk::{require, require_auth}; // ============================================================================= // CONSTANTS // ============================================================================= /// Price precision (6 decimals) pub const PRICE_PRECISION: u64 = 1_000_000; /// Amount precision (8 decimals) pub const AMOUNT_PRECISION: u64 = 100_000_000; /// Basis points denominator pub const BPS_DENOMINATOR: u64 = 10000; /// Aggregation fee (5 bps = 0.05%) pub const AGGREGATION_FEE_BPS: u64 = 5; /// Maximum slippage (5% = 500 bps) pub const MAX_SLIPPAGE_BPS: u64 = 500; /// Minimum split percentage (10%) pub const MIN_SPLIT_PERCENT: u64 = 10; /// Maximum routes per swap pub const MAX_ROUTES: usize = 5; /// IBC timeout (10 minutes) pub const IBC_TIMEOUT_SECONDS: u64 = 10 * 60; // ============================================================================= // EVENT TOPICS // ============================================================================= fn event_topic(name: &[u8]) -> [u8; 32] { use synor_sdk::crypto::blake3_hash; blake3_hash(name).0 } // ============================================================================= // STORAGE KEYS // ============================================================================= mod keys { pub const OWNER: &[u8] = b"agg:owner"; pub const INITIALIZED: &[u8] = b"agg:initialized"; pub const PAUSED: &[u8] = b"agg:paused"; // Liquidity sources pub const SOURCES: &[u8] = b"agg:sources"; pub const SOURCE_COUNT: &[u8] = b"agg:source_count"; // Supported tokens pub const TOKENS: &[u8] = b"agg:tokens"; pub const TOKEN_COUNT: &[u8] = b"agg:token_count"; // Pending IBC swaps pub const PENDING_SWAPS: &[u8] = b"agg:pending"; pub const SWAP_COUNT: &[u8] = b"agg:swap_count"; // Fee collector pub const FEE_COLLECTOR: &[u8] = b"agg:fee_collector"; pub const COLLECTED_FEES: &[u8] = b"agg:fees"; // Statistics pub const TOTAL_VOLUME: &[u8] = b"agg:volume"; } // ============================================================================= // DATA STRUCTURES // ============================================================================= /// Type of liquidity source #[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub enum SourceType { /// Local AMM (Synor DEX) LocalAmm, /// IBC-connected DEX (Osmosis, dYdX, etc.) IbcDex, /// Centralized exchange API (for quotes only) CexApi, /// Order book OrderBook, } /// Liquidity source status #[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub enum SourceStatus { Active, Paused, Deprecated, } /// A liquidity source (DEX or bridge) #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct LiquiditySource { /// Source ID pub id: u32, /// Human-readable name pub name: String, /// Type of source pub source_type: SourceType, /// Contract address (for local sources) pub contract: Option
, /// IBC channel (for IBC sources) pub ibc_channel: Option, /// Chain ID (for IBC sources) pub chain_id: Option, /// Status pub status: SourceStatus, /// Base fee (bps) pub base_fee_bps: u64, /// Priority (higher = preferred) pub priority: u32, /// Total volume routed pub total_volume: u64, /// Success rate (bps, e.g., 9900 = 99%) pub success_rate_bps: u64, /// Average latency (milliseconds) pub avg_latency_ms: u64, } /// Token information #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct TokenInfo { /// Token symbol (e.g., "ETH") pub symbol: String, /// Token address (local) pub local_address: Address, /// Decimals pub decimals: u8, /// IBC denom mapping (chain_id -> denom) pub ibc_denoms: Vec<(String, String)>, /// Is native asset pub is_native: bool, } /// Quote from a liquidity source #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct Quote { /// Source ID pub source_id: u32, /// Source name pub source_name: String, /// Input amount pub amount_in: u64, /// Output amount (expected) pub amount_out: u64, /// Price impact (bps) pub price_impact_bps: u64, /// Fee (in output token) pub fee: u64, /// Is available pub available: bool, /// Estimated execution time (seconds) pub estimated_time: u64, } /// Route for executing a swap #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct Route { /// Source ID pub source_id: u32, /// Percentage of total amount (0-100) pub percentage: u64, /// Expected output pub expected_output: u64, /// Minimum output (with slippage) pub min_output: u64, } /// Swap execution plan #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct SwapPlan { /// Input token pub token_in: String, /// Output token pub token_out: String, /// Total input amount pub amount_in: u64, /// Routes to execute pub routes: Vec, /// Total expected output pub total_expected_output: u64, /// Minimum output (user's slippage tolerance) pub min_output: u64, /// Aggregation fee pub aggregation_fee: u64, /// Valid until timestamp pub valid_until: u64, } /// Pending IBC swap #[derive(Clone, Debug, BorshSerialize, BorshDeserialize)] pub struct PendingSwap { /// Swap ID pub id: u64, /// User address pub user: Address, /// Plan that was executed pub plan: SwapPlan, /// Current status pub status: SwapStatus, /// Amount received so far pub amount_received: u64, /// Routes completed pub routes_completed: u32, /// Created timestamp pub created_at: u64, /// Timeout timestamp pub timeout_at: u64, } /// Swap execution status #[derive(Clone, Copy, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub enum SwapStatus { /// Swap initiated, waiting for execution Pending, /// Partially filled PartiallyFilled, /// Fully completed Completed, /// Failed Failed, /// Refunded Refunded, /// Timed out TimedOut, } // ============================================================================= // EVENTS // ============================================================================= #[derive(BorshSerialize)] pub struct SourceAdded { pub source_id: u32, pub name: String, pub source_type: SourceType, } #[derive(BorshSerialize)] pub struct SwapExecuted { pub swap_id: u64, pub user: Address, pub token_in: String, pub token_out: String, pub amount_in: u64, pub amount_out: u64, pub fee: u64, pub routes_used: u32, } #[derive(BorshSerialize)] pub struct SwapPartialFill { pub swap_id: u64, pub source_id: u32, pub amount_filled: u64, } #[derive(BorshSerialize)] pub struct SwapFailed { pub swap_id: u64, pub reason: String, } // ============================================================================= // STORAGE HELPERS // ============================================================================= fn get_owner() -> Option
{ storage::get::
(keys::OWNER) } fn is_initialized() -> bool { storage::get::(keys::INITIALIZED).unwrap_or(false) } fn is_paused() -> bool { storage::get::(keys::PAUSED).unwrap_or(false) } fn get_source_count() -> u32 { storage::get::(keys::SOURCE_COUNT).unwrap_or(0) } fn set_source_count(count: u32) { storage::set(keys::SOURCE_COUNT, &count); } fn get_source(id: u32) -> Option { storage::get_with_suffix::(keys::SOURCES, &id.to_le_bytes()) } fn set_source(id: u32, source: &LiquiditySource) { storage::set_with_suffix(keys::SOURCES, &id.to_le_bytes(), source); } fn get_token_count() -> u32 { storage::get::(keys::TOKEN_COUNT).unwrap_or(0) } fn set_token_count(count: u32) { storage::set(keys::TOKEN_COUNT, &count); } fn get_token(symbol: &str) -> Option { storage::get_with_suffix::(keys::TOKENS, symbol.as_bytes()) } fn set_token(symbol: &str, token: &TokenInfo) { storage::set_with_suffix(keys::TOKENS, symbol.as_bytes(), token); } fn get_swap_count() -> u64 { storage::get::(keys::SWAP_COUNT).unwrap_or(0) } fn set_swap_count(count: u64) { storage::set(keys::SWAP_COUNT, &count); } fn get_pending_swap(id: u64) -> Option { storage::get_with_suffix::(keys::PENDING_SWAPS, &id.to_le_bytes()) } fn set_pending_swap(id: u64, swap: &PendingSwap) { storage::set_with_suffix(keys::PENDING_SWAPS, &id.to_le_bytes(), swap); } fn get_total_volume() -> u64 { storage::get::(keys::TOTAL_VOLUME).unwrap_or(0) } fn set_total_volume(volume: u64) { storage::set(keys::TOTAL_VOLUME, &volume); } fn get_collected_fees(token: &str) -> u64 { storage::get_with_suffix::(keys::COLLECTED_FEES, token.as_bytes()).unwrap_or(0) } fn set_collected_fees(token: &str, amount: u64) { storage::set_with_suffix(keys::COLLECTED_FEES, token.as_bytes(), &amount); } // ============================================================================= // CALCULATION HELPERS // ============================================================================= /// Calculate aggregation fee fn calculate_aggregation_fee(amount: u64) -> u64 { (amount as u128 * AGGREGATION_FEE_BPS as u128 / BPS_DENOMINATOR as u128) as u64 } /// Apply slippage tolerance fn apply_slippage(amount: u64, slippage_bps: u64) -> u64 { let slippage = (amount as u128 * slippage_bps as u128 / BPS_DENOMINATOR as u128) as u64; amount.saturating_sub(slippage) } /// Find best quote among sources fn find_best_quote(quotes: &[Quote]) -> Option<&Quote> { quotes.iter() .filter(|q| q.available) .max_by_key(|q| q.amount_out) } /// Create optimal routing plan fn create_routing_plan( quotes: &[Quote], amount_in: u64, slippage_bps: u64, ) -> Vec { let mut routes = Vec::new(); // Filter available quotes and sort by output (best first) let mut available: Vec<_> = quotes.iter() .filter(|q| q.available && q.amount_out > 0) .collect(); available.sort_by(|a, b| b.amount_out.cmp(&a.amount_out)); if available.is_empty() { return routes; } // For simplicity, use single best route for small amounts // Split across multiple routes for large amounts to reduce price impact let total_liquidity: u64 = available.iter().map(|q| q.amount_out).sum(); if available.len() == 1 || amount_in < total_liquidity / 10 { // Single route let best = &available[0]; routes.push(Route { source_id: best.source_id, percentage: 100, expected_output: best.amount_out, min_output: apply_slippage(best.amount_out, slippage_bps), }); } else { // Split routing - distribute based on available liquidity let mut remaining_percent = 100u64; for (i, quote) in available.iter().take(MAX_ROUTES).enumerate() { let percent = if i == available.len().min(MAX_ROUTES) - 1 { remaining_percent // Last route gets remainder } else { // Proportional to output let share = (quote.amount_out as u128 * 100) / total_liquidity as u128; (share as u64).max(MIN_SPLIT_PERCENT).min(remaining_percent - MIN_SPLIT_PERCENT) }; if percent < MIN_SPLIT_PERCENT { continue; } let route_amount_in = (amount_in as u128 * percent as u128 / 100) as u64; let expected_out = (quote.amount_out as u128 * percent as u128 / 100) as u64; routes.push(Route { source_id: quote.source_id, percentage: percent, expected_output: expected_out, min_output: apply_slippage(expected_out, slippage_bps), }); remaining_percent = remaining_percent.saturating_sub(percent); if remaining_percent < MIN_SPLIT_PERCENT { break; } } } routes } // ============================================================================= // ENTRY POINTS // ============================================================================= synor_sdk::entry_point!(init, call); fn init(params: &[u8]) -> Result<()> { require!(!is_initialized(), Error::invalid_args("Already initialized")); #[derive(BorshDeserialize)] struct InitParams { fee_collector: Address, } let params = InitParams::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid init params"))?; storage::set(keys::OWNER, &caller()); storage::set(keys::INITIALIZED, &true); storage::set(keys::PAUSED, &false); storage::set(keys::FEE_COLLECTOR, ¶ms.fee_collector); set_source_count(0); set_token_count(0); set_swap_count(0); set_total_volume(0); Ok(()) } fn call(selector: &[u8], params: &[u8]) -> Result> { // Method selectors let add_source_sel = synor_sdk::method_selector("add_source"); let update_source_sel = synor_sdk::method_selector("update_source"); let add_token_sel = synor_sdk::method_selector("add_token"); let get_quote_sel = synor_sdk::method_selector("get_quote"); let get_quotes_sel = synor_sdk::method_selector("get_quotes"); let swap_sel = synor_sdk::method_selector("swap"); let swap_exact_out_sel = synor_sdk::method_selector("swap_exact_out"); let get_swap_status_sel = synor_sdk::method_selector("get_swap_status"); let complete_ibc_swap_sel = synor_sdk::method_selector("complete_ibc_swap"); let refund_swap_sel = synor_sdk::method_selector("refund_swap"); let get_source_sel = synor_sdk::method_selector("get_source"); let get_token_sel = synor_sdk::method_selector("get_token"); let get_stats_sel = synor_sdk::method_selector("get_stats"); let pause_sel = synor_sdk::method_selector("pause"); let unpause_sel = synor_sdk::method_selector("unpause"); match selector { // ===== Admin Methods ===== s if s == add_source_sel => { let owner = get_owner().ok_or(Error::Unauthorized)?; require_auth!(owner); #[derive(BorshDeserialize)] struct Args { name: String, source_type: SourceType, contract: Option
, ibc_channel: Option, chain_id: Option, base_fee_bps: u64, priority: u32, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid add_source params"))?; let source_id = get_source_count(); let source = LiquiditySource { id: source_id, name: args.name.clone(), source_type: args.source_type, contract: args.contract, ibc_channel: args.ibc_channel, chain_id: args.chain_id, status: SourceStatus::Active, base_fee_bps: args.base_fee_bps, priority: args.priority, total_volume: 0, success_rate_bps: 10000, // Start at 100% avg_latency_ms: 0, }; set_source(source_id, &source); set_source_count(source_id + 1); emit_raw( &[event_topic(b"SourceAdded")], &borsh::to_vec(&SourceAdded { source_id, name: args.name, source_type: args.source_type, }).unwrap(), ); Ok(borsh::to_vec(&source_id).unwrap()) } s if s == update_source_sel => { let owner = get_owner().ok_or(Error::Unauthorized)?; require_auth!(owner); #[derive(BorshDeserialize)] struct Args { source_id: u32, status: SourceStatus, base_fee_bps: u64, priority: u32, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid update_source params"))?; let mut source = get_source(args.source_id) .ok_or_else(|| Error::invalid_args("Source not found"))?; source.status = args.status; source.base_fee_bps = args.base_fee_bps; source.priority = args.priority; set_source(args.source_id, &source); Ok(borsh::to_vec(&true).unwrap()) } s if s == add_token_sel => { let owner = get_owner().ok_or(Error::Unauthorized)?; require_auth!(owner); #[derive(BorshDeserialize)] struct Args { symbol: String, local_address: Address, decimals: u8, ibc_denoms: Vec<(String, String)>, is_native: bool, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid add_token params"))?; let token = TokenInfo { symbol: args.symbol.clone(), local_address: args.local_address, decimals: args.decimals, ibc_denoms: args.ibc_denoms, is_native: args.is_native, }; set_token(&args.symbol, &token); set_token_count(get_token_count() + 1); Ok(borsh::to_vec(&true).unwrap()) } s if s == pause_sel => { let owner = get_owner().ok_or(Error::Unauthorized)?; require_auth!(owner); storage::set(keys::PAUSED, &true); Ok(borsh::to_vec(&true).unwrap()) } s if s == unpause_sel => { let owner = get_owner().ok_or(Error::Unauthorized)?; require_auth!(owner); storage::set(keys::PAUSED, &false); Ok(borsh::to_vec(&true).unwrap()) } // ===== Quote Methods ===== s if s == get_quote_sel => { #[derive(BorshDeserialize)] struct Args { token_in: String, token_out: String, amount_in: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid get_quote params"))?; // Get quotes from all sources and find best let quotes = get_all_quotes(&args.token_in, &args.token_out, args.amount_in)?; let best = find_best_quote("es); Ok(borsh::to_vec(&best).unwrap()) } s if s == get_quotes_sel => { #[derive(BorshDeserialize)] struct Args { token_in: String, token_out: String, amount_in: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid get_quotes params"))?; let quotes = get_all_quotes(&args.token_in, &args.token_out, args.amount_in)?; Ok(borsh::to_vec("es).unwrap()) } // ===== Swap Methods ===== s if s == swap_sel => { require!(!is_paused(), Error::invalid_args("Contract paused")); #[derive(BorshDeserialize)] struct Args { token_in: String, token_out: String, amount_in: u64, min_amount_out: u64, slippage_bps: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid swap params"))?; require!(args.slippage_bps <= MAX_SLIPPAGE_BPS, Error::invalid_args("Slippage too high")); // Get quotes and create routing plan let quotes = get_all_quotes(&args.token_in, &args.token_out, args.amount_in)?; require!(!quotes.is_empty(), Error::invalid_args("No quotes available")); let routes = create_routing_plan("es, args.amount_in, args.slippage_bps); require!(!routes.is_empty(), Error::invalid_args("No routes available")); let total_expected: u64 = routes.iter().map(|r| r.expected_output).sum(); require!(total_expected >= args.min_amount_out, Error::invalid_args("Output below minimum")); // Calculate aggregation fee let fee = calculate_aggregation_fee(args.amount_in); // Create swap plan let swap_id = get_swap_count(); let now = timestamp(); let plan = SwapPlan { token_in: args.token_in.clone(), token_out: args.token_out.clone(), amount_in: args.amount_in, routes: routes.clone(), total_expected_output: total_expected, min_output: args.min_amount_out, aggregation_fee: fee, valid_until: now + IBC_TIMEOUT_SECONDS, }; // For local-only routes, execute immediately let is_local_only = routes.iter().all(|r| { get_source(r.source_id) .map(|s| s.source_type == SourceType::LocalAmm) .unwrap_or(false) }); if is_local_only { // Execute local swap (simplified - would call DEX contract) let amount_out = execute_local_swap(&plan)?; // Update statistics set_total_volume(get_total_volume() + args.amount_in); let fees = get_collected_fees(&args.token_in); set_collected_fees(&args.token_in, fees + fee); emit_raw( &[event_topic(b"SwapExecuted")], &borsh::to_vec(&SwapExecuted { swap_id, user: caller(), token_in: args.token_in, token_out: args.token_out, amount_in: args.amount_in, amount_out, fee, routes_used: routes.len() as u32, }).unwrap(), ); set_swap_count(swap_id + 1); return Ok(borsh::to_vec(&amount_out).unwrap()); } // For IBC routes, create pending swap let pending = PendingSwap { id: swap_id, user: caller(), plan, status: SwapStatus::Pending, amount_received: 0, routes_completed: 0, created_at: now, timeout_at: now + IBC_TIMEOUT_SECONDS, }; set_pending_swap(swap_id, &pending); set_swap_count(swap_id + 1); // Initiate IBC transfers (would call IBC module) // In production, this triggers actual IBC packet sending Ok(borsh::to_vec(&swap_id).unwrap()) } s if s == complete_ibc_swap_sel => { // Called by relayer when IBC swap completes #[derive(BorshDeserialize)] struct Args { swap_id: u64, source_id: u32, amount_received: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid complete_ibc_swap params"))?; let mut swap = get_pending_swap(args.swap_id) .ok_or_else(|| Error::invalid_args("Swap not found"))?; require!(swap.status == SwapStatus::Pending || swap.status == SwapStatus::PartiallyFilled, Error::invalid_args("Swap not in progress")); swap.amount_received += args.amount_received; swap.routes_completed += 1; emit_raw( &[event_topic(b"SwapPartialFill")], &borsh::to_vec(&SwapPartialFill { swap_id: args.swap_id, source_id: args.source_id, amount_filled: args.amount_received, }).unwrap(), ); // Check if all routes completed if swap.routes_completed as usize >= swap.plan.routes.len() { swap.status = SwapStatus::Completed; // Update statistics set_total_volume(get_total_volume() + swap.plan.amount_in); let fees = get_collected_fees(&swap.plan.token_in); set_collected_fees(&swap.plan.token_in, fees + swap.plan.aggregation_fee); // Update source stats for route in &swap.plan.routes { if let Some(mut source) = get_source(route.source_id) { source.total_volume += (swap.plan.amount_in as u128 * route.percentage as u128 / 100) as u64; set_source(route.source_id, &source); } } emit_raw( &[event_topic(b"SwapExecuted")], &borsh::to_vec(&SwapExecuted { swap_id: args.swap_id, user: swap.user, token_in: swap.plan.token_in.clone(), token_out: swap.plan.token_out.clone(), amount_in: swap.plan.amount_in, amount_out: swap.amount_received, fee: swap.plan.aggregation_fee, routes_used: swap.routes_completed, }).unwrap(), ); } else { swap.status = SwapStatus::PartiallyFilled; } set_pending_swap(args.swap_id, &swap); Ok(borsh::to_vec(&swap.amount_received).unwrap()) } s if s == refund_swap_sel => { #[derive(BorshDeserialize)] struct Args { swap_id: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Invalid refund_swap params"))?; let mut swap = get_pending_swap(args.swap_id) .ok_or_else(|| Error::invalid_args("Swap not found"))?; // Can only refund if timed out or failed let now = timestamp(); require!( now > swap.timeout_at || swap.status == SwapStatus::Failed, Error::invalid_args("Swap not refundable yet") ); swap.status = SwapStatus::Refunded; set_pending_swap(args.swap_id, &swap); emit_raw( &[event_topic(b"SwapFailed")], &borsh::to_vec(&SwapFailed { swap_id: args.swap_id, reason: String::from("Timed out"), }).unwrap(), ); // In production, would transfer refund back to user Ok(borsh::to_vec(&swap.plan.amount_in).unwrap()) } // ===== Query Methods ===== s if s == get_swap_status_sel => { #[derive(BorshDeserialize)] struct Args { swap_id: u64, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Expected (swap_id: u64)"))?; let swap = get_pending_swap(args.swap_id); Ok(borsh::to_vec(&swap).unwrap()) } s if s == get_source_sel => { #[derive(BorshDeserialize)] struct Args { source_id: u32, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Expected (source_id: u32)"))?; let source = get_source(args.source_id); Ok(borsh::to_vec(&source).unwrap()) } s if s == get_token_sel => { #[derive(BorshDeserialize)] struct Args { symbol: String, } let args = Args::try_from_slice(params) .map_err(|_| Error::invalid_args("Expected (symbol: String)"))?; let token = get_token(&args.symbol); Ok(borsh::to_vec(&token).unwrap()) } s if s == get_stats_sel => { #[derive(BorshSerialize)] struct Stats { total_volume: u64, source_count: u32, token_count: u32, swap_count: u64, } let stats = Stats { total_volume: get_total_volume(), source_count: get_source_count(), token_count: get_token_count(), swap_count: get_swap_count(), }; Ok(borsh::to_vec(&stats).unwrap()) } _ => Err(Error::InvalidMethod), } } // ============================================================================= // QUOTE AND EXECUTION LOGIC // ============================================================================= /// Get quotes from all active sources fn get_all_quotes(token_in: &str, token_out: &str, amount_in: u64) -> Result> { let source_count = get_source_count(); let mut quotes = Vec::new(); for i in 0..source_count { if let Some(source) = get_source(i) { if source.status != SourceStatus::Active { continue; } // Get quote from source (simplified - would call actual source) let quote = get_source_quote(&source, token_in, token_out, amount_in); quotes.push(quote); } } // Sort by output amount (best first) quotes.sort_by(|a, b| b.amount_out.cmp(&a.amount_out)); Ok(quotes) } /// Get quote from a specific source fn get_source_quote(source: &LiquiditySource, _token_in: &str, _token_out: &str, amount_in: u64) -> Quote { // In production, this would: // - For LocalAmm: call the DEX contract's quote function // - For IbcDex: query cached prices or send IBC query // - For CexApi: use off-chain oracle data // Simplified: estimate output based on source fee let fee = (amount_in as u128 * source.base_fee_bps as u128 / BPS_DENOMINATOR as u128) as u64; let amount_out = amount_in.saturating_sub(fee); // Add some variation based on source type for demonstration let adjusted_out = match source.source_type { SourceType::LocalAmm => amount_out, SourceType::IbcDex => amount_out + (amount_out / 1000), // IBC might have better prices SourceType::CexApi => amount_out + (amount_out / 500), // CEX often has best prices SourceType::OrderBook => amount_out, }; let estimated_time = match source.source_type { SourceType::LocalAmm => 2, SourceType::IbcDex => 30, SourceType::CexApi => 5, SourceType::OrderBook => 3, }; Quote { source_id: source.id, source_name: source.name.clone(), amount_in, amount_out: adjusted_out, price_impact_bps: 10, // Simplified fee, available: true, estimated_time, } } /// Execute local swap (calls DEX contract) fn execute_local_swap(plan: &SwapPlan) -> Result { // In production, this would: // 1. Call each source's swap function with the allocated amount // 2. Aggregate the outputs // 3. Handle any failures // Simplified: return expected output Ok(plan.total_expected_output) }