Python SDK
Cross-chain route discovery, simulation, and analysis in Python.
Installation
Requires Python 3.10+.
1git clone https://github.com/MNMX-labs/mnmx.git2cd mnmx/sdk/python3pip install -e .This installs the mnmx package in development mode, so changes to the source are immediately reflected. Dependencies include httpx for async HTTP, pydanticfor data validation, and numpy for Monte Carlo simulations.
Verify Installation
1# Verify the installation2python -c "import mnmx; print(mnmx.__version__)"3# 0.4.24
5# Run the test suite6cd mnmx/sdk/python7python -m pytest tests/ -vConfiguration
Set your RPC endpoints via environment variables or pass them directly to the router:
1# Environment variables2export ETH_RPC_URL="https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"3export SOL_RPC_URL="https://mainnet.helius-rpc.com/?api-key=YOUR_KEY"4export ARB_RPC_URL="https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY"1# Or configure programmatically2from mnmx import MnmxRouter3
4router = MnmxRouter(5 strategy="minimax",6 chains={7 "ethereum": {"rpc": "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"},8 "solana": {"rpc": "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY"},9 "arbitrum": {"rpc": "https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY"},10 },11)Basic Usage
1from mnmx import MnmxRouter2
3router = MnmxRouter(strategy="minimax")4
5route = router.find_route(6 from_chain="ethereum",7 from_token="ETH",8 amount="1.0",9 to_chain="solana",10 to_token="SOL",11)12
13print(f"Path: {' → '.join(route.path)}")14print(f"Expected output: {route.expected_output} SOL")15print(f"Guaranteed minimum: {route.guaranteed_minimum} SOL")16print(f"Total fees: {route.total_fees}")17print(f"Estimated time: {route.estimated_time}s")18print(f"Minimax score: {route.minimax_score:.4f}")19print(f"Strategy: {route.strategy}")20
21# Access individual hops22for i, hop in enumerate(route.hops):23 print(f" Hop {i+1}: {hop.provider} ({hop.type})")24 print(f" {hop.from_chain}:{hop.from_token} → {hop.to_chain}:{hop.to_token}")25 print(f" Fee: {hop.fee} | Time: {hop.estimated_time}s")Compare All Routes
1routes = router.find_all_routes(2 from_chain="ethereum",3 from_token="ETH",4 amount="1.0",5 to_chain="solana",6 to_token="SOL",7)8
9print(f"Found {len(routes)} viable routes\n")10
11for route in routes:12 print(f"{' → '.join(route.path)}")13 print(f" Expected: {route.expected_output} SOL")14 print(f" Minimum: {route.guaranteed_minimum} SOL")15 print(f" Fees: ${route.total_fees_usd}")16 print(f" Time: {route.estimated_time}s")17 print(f" Score: {route.minimax_score:.4f}")18 print()19
20# Sort by different criteria21cheapest = sorted(routes, key=lambda r: float(r.total_fees_usd))[0]22fastest = sorted(routes, key=lambda r: r.estimated_time)[0]23safest = sorted(routes, key=lambda r: float(r.guaranteed_minimum), reverse=True)[0]24
25print(f"Cheapest route: {' → '.join(cheapest.path)} (${cheapest.total_fees_usd})")26print(f"Fastest route: {' → '.join(fastest.path)} ({fastest.estimated_time}s)")27print(f"Safest route: {' → '.join(safest.path)} (min: {safest.guaranteed_minimum} SOL)")Route Options
Fine-tune route discovery with additional parameters:
1# Exclude specific bridges2route = router.find_route(3 from_chain="ethereum",4 from_token="ETH",5 amount="1.0",6 to_chain="solana",7 to_token="SOL",8 exclude_bridges=["allbridge"],9)10
11# Set maximum transfer time12route = router.find_route(13 from_chain="ethereum",14 from_token="USDC",15 amount="5000",16 to_chain="solana",17 to_token="USDC",18 deadline=300, # Max 5 minutes19)20
21# Set minimum output threshold22route = router.find_route(23 from_chain="ethereum",24 from_token="ETH",25 amount="1.0",26 to_chain="solana",27 to_token="SOL",28 min_output="13.5", # Reject routes with guaranteed minimum below 13.5 SOL29)30
31# Prefer a specific bridge (still evaluated by minimax)32route = router.find_route(33 from_chain="ethereum",34 from_token="USDC",35 amount="10000",36 to_chain="arbitrum",37 to_token="USDC",38 preferred_bridge="debridge",39)Strategy Comparison
Compare how different strategies evaluate the same transfer:
1from mnmx import MnmxRouter2
3strategies = ["minimax", "cheapest", "fastest", "safest"]4
5for strategy in strategies:6 router = MnmxRouter(strategy=strategy)7 route = router.find_route(8 from_chain="ethereum",9 from_token="ETH",10 amount="5.0",11 to_chain="solana",12 to_token="SOL",13 )14 print(f"Strategy: {strategy}")15 print(f" Route: {' → '.join(route.path)}")16 print(f" Expected: {route.expected_output} SOL")17 print(f" Guaranteed: {route.guaranteed_minimum} SOL")18 print(f" Fees: ${route.total_fees_usd}")19 print(f" Time: {route.estimated_time}s")20 print()21
22# Example output:23# Strategy: minimax24# Route: uniswap-v3 → debridge → jupiter25# Expected: 71.42 SOL26# Guaranteed: 69.25 SOL27# Fees: $18.5028# Time: 110s29#30# Strategy: cheapest31# Route: uniswap-v3 → allbridge → jupiter32# Expected: 71.28 SOL33# Guaranteed: 68.10 SOL34# Fees: $12.8035# Time: 300s36#37# Strategy: fastest38# Route: debridge-dln → jupiter39# Expected: 71.15 SOL40# Guaranteed: 68.50 SOL41# Fees: $22.0042# Time: 65s43#44# Strategy: safest45# Route: uniswap-v3 → wormhole → jupiter46# Expected: 70.95 SOL47# Guaranteed: 69.80 SOL48# Fees: $25.0049# Time: 780sRoute Simulation
Simulate routes under various market conditions without executing:
1from mnmx import RouteSimulator2
3sim = RouteSimulator()4
5# Simulate with default adversarial conditions6result = sim.simulate(route)7print(f"Default simulation: {result.output} SOL")8print(f"Fees: {result.total_fees}")9print(f"Time: {result.total_time}s")10
11# Simulate with custom adversarial conditions12result = sim.simulate(route, conditions={13 "slippage_multiplier": 2.0, # 2x quoted slippage14 "gas_multiplier": 1.5, # 1.5x current gas15 "bridge_delay_multiplier": 3.0, # 3x median delay16 "mev_extraction": 0.003, # 0.3% MEV17 "price_movement": 0.005, # 0.5% adverse price change18})19print(f"Adversarial simulation: {result.output} SOL")20
21# Simulate extreme conditions22result = sim.simulate(route, conditions={23 "slippage_multiplier": 5.0, # 5x slippage (extreme)24 "gas_multiplier": 3.0, # 3x gas (network congestion event)25 "bridge_delay_multiplier": 10.0,# 10x delay (bridge degradation)26 "mev_extraction": 0.01, # 1% MEV (sandwich attack)27 "price_movement": 0.02, # 2% price move (volatile market)28})29print(f"Extreme simulation: {result.output} SOL")30
31# Compare simulation output against quoted values32print(f"\nQuoted output: {route.expected_output} SOL")33print(f"Guaranteed minimum: {route.guaranteed_minimum} SOL")34print(f"Default sim: {result_default.output} SOL")35print(f"Adversarial sim: {result_adversarial.output} SOL")36print(f"Extreme sim: {result_extreme.output} SOL")Scenario-Based Simulation
1# Simulate specific real-world scenarios2scenarios = {3 "normal": {4 "slippage_multiplier": 1.0,5 "gas_multiplier": 1.0,6 "bridge_delay_multiplier": 1.0,7 "mev_extraction": 0.001,8 "price_movement": 0.001,9 },10 "nft_mint_gas_spike": {11 "slippage_multiplier": 1.2,12 "gas_multiplier": 4.0, # Gas spikes during popular mints13 "bridge_delay_multiplier": 1.5,14 "mev_extraction": 0.002,15 "price_movement": 0.003,16 },17 "market_crash": {18 "slippage_multiplier": 3.0, # Liquidity dries up19 "gas_multiplier": 2.5,20 "bridge_delay_multiplier": 2.0,21 "mev_extraction": 0.005,22 "price_movement": 0.05, # 5% adverse price movement23 },24 "bridge_congestion": {25 "slippage_multiplier": 1.5,26 "gas_multiplier": 1.2,27 "bridge_delay_multiplier": 8.0, # Severe bridge delays28 "mev_extraction": 0.002,29 "price_movement": 0.01,30 },31}32
33for name, conditions in scenarios.items():34 result = sim.simulate(route, conditions=conditions)35 print(f"{name:25s} → {result.output:>8s} SOL (time: {result.total_time:>5}s)")Monte Carlo Analysis
Run thousands of simulations with randomized conditions to understand the outcome distribution:
1mc = sim.monte_carlo(2 route=route,3 iterations=10_000,4 seed=42,5)6
7print(f"Mean output: {mc.mean_output:.4f} SOL")8print(f"Median output: {mc.median_output:.4f} SOL")9print(f"Std deviation: {mc.std_output:.4f} SOL")10print(f"5th percentile: {mc.percentile_5:.4f} SOL")11print(f"25th percentile: {mc.percentile_25:.4f} SOL")12print(f"75th percentile: {mc.percentile_75:.4f} SOL")13print(f"95th percentile: {mc.percentile_95:.4f} SOL")14print(f"Worst observed: {mc.min_output:.4f} SOL")15print(f"Best observed: {mc.max_output:.4f} SOL")16print(f"Guaranteed min: {route.guaranteed_minimum} SOL")17print(f"Below guarantee: {mc.below_guarantee_pct:.2f}%")18
19# Distribution histogram (text-based)20mc.print_histogram(bins=20)21
22# Example output:23# 11.20 - 11.50 | ## (0.3%)24# 11.50 - 11.80 | ### (0.5%)25# 11.80 - 12.10 | ##### (1.2%)26# 12.10 - 12.40 | ######## (2.1%)27# 12.40 - 12.70 | ########### (3.8%)28# 12.70 - 13.00 | ################ (5.5%)29# 13.00 - 13.30 | ##################### (7.2%)30# 13.30 - 13.60 | ############################ (9.8%)31# 13.60 - 13.90 | ################################(12.1%) ← guaranteed minimum32# 13.90 - 14.20 | ################################(14.5%)33# 14.20 - 14.50 | ################################(15.2%)34# 14.50 - 14.80 | ############################ (11.8%)35# 14.80 - 15.10 | ##################### (8.2%)36# ...Monte Carlo Comparison of Routes
1# Compare multiple routes using Monte Carlo2routes = router.find_all_routes(3 from_chain="ethereum",4 from_token="ETH",5 amount="5.0",6 to_chain="solana",7 to_token="SOL",8)9
10print(f"{'Route':<40} {'Mean':>8} {'P5':>8} {'P95':>8} {'Min':>8} {'Guarantee':>10}")11print("-" * 90)12
13for route in routes[:5]: # Top 5 routes14 mc = sim.monte_carlo(route=route, iterations=5_000, seed=42)15 path = ' → '.join(route.path)[:38]16 print(17 f"{path:<40} "18 f"{mc.mean_output:>8.2f} "19 f"{mc.percentile_5:>8.2f} "20 f"{mc.percentile_95:>8.2f} "21 f"{mc.min_output:>8.2f} "22 f"{route.guaranteed_minimum:>10}"23 )24
25# Example output:26# Route Mean P5 P95 Min Guarantee27# ------------------------------------------------------------------------------------------28# uniswap-v3 → debridge → jupiter 71.28 69.15 72.80 67.50 69.2529# wormhole → jupiter 71.05 67.80 73.20 64.20 68.1030# uniswap-v3 → allbridge → jupiter 70.95 68.50 72.40 66.80 68.5031# layerzero → jupiter 70.80 67.20 73.10 63.50 67.8032# uniswap-v3 → wormhole → orca 70.60 68.90 72.10 67.20 69.80Batch Route Analysis
Compare strategies across multiple token pairs:
1from mnmx import BatchAnalyzer2
3analyzer = BatchAnalyzer(router)4
5pairs = [6 ("ethereum", "ETH", "solana", "SOL", "1.0"),7 ("ethereum", "USDC", "solana", "USDC", "5000"),8 ("ethereum", "USDC", "arbitrum", "USDC", "10000"),9 ("solana", "SOL", "base", "ETH", "10.0"),10 ("arbitrum", "ETH", "polygon", "MATIC","0.5"),11 ("ethereum", "WBTC", "avalanche","AVAX", "0.1"),12]13
14report = analyzer.compare_strategies(15 pairs=pairs,16 strategies=["minimax", "cheapest", "fastest", "safest"],17)18
19# Print summary table20print(report.summary())21
22# Detailed per-pair analysis23for pair_result in report.results:24 print(f"\n{pair_result.from_chain}:{pair_result.from_token} → "25 f"{pair_result.to_chain}:{pair_result.to_token}")26 for strategy, route in pair_result.routes.items():27 print(f" {strategy:10s}: {route.expected_output:>10s} "28 f"(min: {route.guaranteed_minimum:>10s}, "29 f"fees: ${route.total_fees_usd:>6s}, "30 f"time: {route.estimated_time:>4}s)")31
32# Export results33report.to_csv("route_comparison.csv")34report.to_json("route_comparison.json")Historical Route Analysis
1from mnmx import HistoricalAnalyzer2
3analyzer = HistoricalAnalyzer(router)4
5# Analyze how routes would have performed over the past 24 hours6# by replaying historical state snapshots7analysis = analyzer.backtest(8 from_chain="ethereum",9 from_token="ETH",10 amount="1.0",11 to_chain="solana",12 to_token="SOL",13 period="24h",14 interval="1h", # Sample every hour15 strategies=["minimax", "cheapest"],16)17
18print(f"Samples: {analysis.sample_count}")19print(f"\nMinimax strategy:")20print(f" Avg output: {analysis.minimax.avg_output:.4f} SOL")21print(f" Worst output: {analysis.minimax.min_output:.4f} SOL")22print(f" Best output: {analysis.minimax.max_output:.4f} SOL")23print(f" Avg fees: ${analysis.minimax.avg_fees:.2f}")24print(f"\nCheapest strategy:")25print(f" Avg output: {analysis.cheapest.avg_output:.4f} SOL")26print(f" Worst output: {analysis.cheapest.min_output:.4f} SOL")27print(f" Best output: {analysis.cheapest.max_output:.4f} SOL")28print(f" Avg fees: ${analysis.cheapest.avg_fees:.2f}")29
30# Minimax advantage: how often does minimax produce a better31# worst-case outcome than cheapest?32print(f"\nMinimax had better worst-case: "33 f"{analysis.minimax_advantage_pct:.1f}% of samples")Bridge Health Monitoring
1from mnmx import BridgeMonitor2
3monitor = BridgeMonitor(router)4
5# Get current health status of all bridges6health = monitor.get_all_health()7
8for bridge, status in health.items():9 print(f"{bridge}:")10 print(f" Online: {status.online}")11 print(f" Congestion: {status.congestion}")12 print(f" Success rate: {status.success_rate:.1%}")13 print(f" Median time: {status.median_confirm_time}s")14 print(f" Pending: {status.pending_transfers}")15 print()16
17# Get liquidity info for a specific bridge and token18liquidity = monitor.get_liquidity("allbridge", "solana", "USDC")19print(f"Allbridge USDC on Solana:")20print(f" Available: ${liquidity.available}")21print(f" Total: ${liquidity.total}")22print(f" Util: {liquidity.utilization:.1f}%")Async Usage
The Python SDK supports async operations for non-blocking integration:
1import asyncio2from mnmx import AsyncMnmxRouter3
4async def main():5 router = AsyncMnmxRouter(strategy="minimax")6
7 # Find routes for multiple pairs concurrently8 tasks = [9 router.find_route(10 from_chain="ethereum", from_token="ETH", amount="1.0",11 to_chain="solana", to_token="SOL",12 ),13 router.find_route(14 from_chain="ethereum", from_token="USDC", amount="5000",15 to_chain="arbitrum", to_token="USDC",16 ),17 router.find_route(18 from_chain="solana", from_token="SOL", amount="10.0",19 to_chain="base", to_token="ETH",20 ),21 ]22
23 routes = await asyncio.gather(*tasks)24
25 for route in routes:26 print(f"{' → '.join(route.path)}: {route.expected_output}")27
28asyncio.run(main())CLI
The Python SDK includes a command-line interface for quick lookups and scripting:
1# Find optimal route2mnmx route --from ethereum:ETH:1.0 --to solana:SOL3
4# Compare all routes5mnmx route --from ethereum:ETH:1.0 --to solana:SOL --all6
7# Use a specific strategy8mnmx route --from ethereum:ETH:1.0 --to solana:SOL --strategy cheapest9
10# Monte Carlo simulation11mnmx simulate --from ethereum:ETH:1.0 --to solana:SOL --iterations 1000012
13# Compare strategies14mnmx compare --from ethereum:ETH:1.0 --to solana:SOL15
16# Check bridge health17mnmx health18mnmx health --bridge wormhole19
20# Check liquidity21mnmx liquidity --bridge allbridge --chain solana --token USDC22
23# Output as JSON (for scripting)24mnmx route --from ethereum:ETH:1.0 --to solana:SOL --json25
26# Verbose output with debug logging27mnmx route --from ethereum:ETH:1.0 --to solana:SOL -vCLI Output Examples
1$ mnmx route --from ethereum:ETH:1.0 --to solana:SOL2
3MNMX Route Finder4═══════════════════════════════════════════════════5
6Input: 1.0 ETH on Ethereum7Output: SOL on Solana8Strategy: minimax9
10Optimal Route:11 Step 1: Swap ETH → USDC on Uniswap V3 (Ethereum)12 Step 2: Bridge USDC via deBridge DLN (Ethereum → Solana)13 Step 3: Swap USDC → SOL on Jupiter (Solana)14
15Expected output: 14.32 SOL16Guaranteed minimum: 13.85 SOL17Total fees: $6.0918Estimated time: ~2 minutes19Minimax score: 0.847020
21$ mnmx route --from ethereum:ETH:1.0 --to solana:SOL --all22
23Found 8 viable routes:24
25# Route Expected Minimum Fees Time Score26─────────────────────────────────────────────────────────────────────────────────────271 uniswap → debridge → jupiter 14.32 13.85 $6.09 110s 0.847282 uniswap → wormhole → jupiter 14.28 13.71 $8.50 300s 0.821293 debridge-dln → jupiter 14.25 13.52 $9.20 65s 0.815304 uniswap → allbridge → jupiter 14.18 13.48 $7.80 240s 0.809315 wormhole → jupiter 14.15 13.22 $13.2 780s 0.792326 layerzero → jupiter 14.10 13.10 $5.50 420s 0.788337 uniswap → wormhole → arb → debridge 14.05 12.85 $15.0 600s 0.772348 allbridge → orca 13.95 12.90 $8.20 360s 0.764Error Handling
| Exception | When | Recovery |
|---|---|---|
MnmxError | Base class for all SDK errors | Catch-all for unexpected errors |
NoRouteFoundError | No viable path exists | Try different token pair, increase max_hops, or check bridge health |
InsufficientLiquidityError | Bridge liquidity below transfer amount | Reduce amount or try a different bridge |
SimulationError | Invalid simulation parameters | Check condition values are positive numbers |
TimeoutError | Route discovery exceeded time limit | Increase timeout or reduce max_hops |
RpcConnectionError | Cannot connect to RPC endpoint | Check RPC URL and network connectivity |
ConfigurationError | Invalid configuration parameters | Check parameter types and ranges |
1from mnmx.exceptions import (2 MnmxError,3 NoRouteFoundError,4 InsufficientLiquidityError,5 TimeoutError,6 RpcConnectionError,7)8
9try:10 route = router.find_route(11 from_chain="ethereum", from_token="ETH", amount="1.0",12 to_chain="solana", to_token="SOL",13 )14except NoRouteFoundError as e:15 print(f"No route available: {e.reason}")16 print(f"Tried bridges: {e.bridges_checked}")17 print(f"Suggestion: {e.suggestion}")18
19except InsufficientLiquidityError as e:20 print(f"Liquidity too low on {e.bridge}")21 print(f"Available: {e.available}, Required: {e.required}")22
23except TimeoutError as e:24 print(f"Route discovery timed out after {e.elapsed}ms")25 print(f"Try: router = MnmxRouter(timeout=60000)")26
27except RpcConnectionError as e:28 print(f"Cannot connect to {e.chain} RPC: {e.url}")29 print(f"Error: {e.message}")30
31except MnmxError as e:32 print(f"Unexpected error: {e}")Data Types Reference
1from dataclasses import dataclass2from typing import Optional3
4@dataclass5class Route:6 path: list[str] # Provider names in order7 hops: list[RouteHop] # Detailed hop information8 expected_output: str # Best-case output9 guaranteed_minimum: str # Minimax worst-case output10 total_fees: str # Total fees in source token11 total_fees_usd: str # Total fees in USD12 estimated_time: int # Seconds13 minimax_score: float # 0.0 - 1.014 strategy: str # Strategy used15 quoted_at: int # Timestamp (ms)16 expires_at: int # Timestamp (ms)17
18@dataclass19class RouteHop:20 type: str # 'swap' | 'bridge'21 from_chain: str22 from_token: str23 from_amount: str24 to_chain: str25 to_token: str26 to_amount: str27 provider: str # DEX or bridge name28 fee: str29 fee_usd: str30 estimated_time: int # Seconds31 price_impact: float # 0.01 = 1%32
33@dataclass34class MonteCarloResult:35 iterations: int36 mean_output: float37 median_output: float38 std_output: float39 min_output: float40 max_output: float41 percentile_5: float42 percentile_25: float43 percentile_75: float44 percentile_95: float45 below_guarantee_pct: float # % of iterations below guaranteed min46 distribution: list[float] # Raw output values47
48 def print_histogram(self, bins: int = 20) -> None: ...49 def to_csv(self, path: str) -> None: ...50 def to_json(self, path: str) -> None: ...51
52@dataclass53class SimulationResult:54 output: str # Simulated output amount55 total_fees: str # Simulated total fees56 total_time: int # Simulated total time (seconds)57 per_hop: list[HopSimResult] # Per-hop breakdown58 conditions_applied: dict # Conditions used