Docs
Docs/Routing Engine

Routing Engine

How MNMX discovers, evaluates, and executes cross-chain routes.

Overview

The routing engine is the central component of MNMX. It takes a user's intent (source asset, destination asset, amount) and produces an optimal execution plan through five stages: path discovery, state collection, minimax evaluation, route selection, and execution. Each stage is designed to be independently testable and cacheable.

text
1Engine Pipeline:
2
3 User Intent Optimal Route
4 │ ▲
5 ▼ │
6 ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
7 │ Path │→ │ State │→ │ Minimax │→ │ Route │→ │ Route │
8 │ Discovery│ │Collection│ │Evaluator │ │ Selector │ │ Executor │
9 │ │ │ │ │ │ │ │ │ │
10 │ 15-50 │ │ Parallel │ │ Alpha- │ │ Apply │ │ Multi- │
11 │ candidate│ │ RPC + API│ │ beta │ │ strategy │ │ step TX │
12 │ paths │ │ calls │ │ pruning │ │ weights │ │ signing │
13 └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
14 ~100ms 500ms-2s ~50ms ~5ms 30s-20min

Path Discovery

Given a source asset on chain A and a destination asset on chain B, the engine builds a tree of all feasible routes:

text
1findRoutes(1 ETH on Ethereum → SOL on Solana):
2
3Direct bridges:
4 ├── Wormhole: ETH(Ethereum) → ETH(Solana) → Jupiter → SOL
5 ├── deBridge: ETH(Ethereum) → ETH(Solana) → Jupiter → SOL
6 └── Allbridge: ETH(Ethereum) → ETH(Solana) → Jupiter → SOL
7
8Multi-hop via stablecoins:
9 ├── Uniswap(ETH→USDC) → Wormhole(USDC) → Jupiter(USDC→SOL)
10 ├── Uniswap(ETH→USDC) → deBridge(USDC) → Jupiter(USDC→SOL)
11 └── Uniswap(ETH→USDT) → Allbridge(USDT) → Jupiter(USDT→SOL)
12
13Multi-hop via intermediate chains:
14 ├── Wormhole(ETH→Arbitrum) → Swap → deBridge(Arbitrum→Solana)
15 └── LayerZero(ETH→Base) → Swap → Wormhole(Base→Solana)
16
17Total candidate paths: 23

Graph Construction

The path discovery module maintains a directed graph where nodes are (chain, token) pairs and edges represent either bridge transfers or DEX swaps. The graph is reconstructed every 30 seconds from bridge adapter health checks.

typescript
1class RouteGraph {
2 private nodes: Map<string, ChainToken>; // "ethereum:ETH" → node
3 private edges: Map<string, RouteEdge[]>; // adjacency list
4
5 // Build graph from registered adapters
6 buildGraph(bridges: BridgeAdapter[], dexes: DexAdapter[]): void {
7 // Add cross-chain edges from bridges
8 for (const bridge of bridges) {
9 for (const pair of bridge.getSupportedPairs()) {
10 this.addEdge({
11 from: `${pair.fromChain}:${pair.fromToken}`,
12 to: `${pair.toChain}:${pair.toToken}`,
13 type: 'bridge',
14 provider: bridge.name,
15 baseLatency: bridge.getBaseLatency(pair),
16 baseFee: bridge.getBaseFee(pair),
17 });
18 }
19 }
20
21 // Add intra-chain edges from DEXes
22 for (const dex of dexes) {
23 for (const pair of dex.getSupportedPairs()) {
24 this.addEdge({
25 from: `${dex.chain}:${pair.tokenA}`,
26 to: `${dex.chain}:${pair.tokenB}`,
27 type: 'swap',
28 provider: dex.name,
29 baseLatency: 15, // ~1 block on most EVM chains
30 baseFee: dex.getBaseFee(pair),
31 });
32 }
33 }
34 }
35
36 // Bounded DFS to find all paths up to maxHops
37 findAllPaths(
38 from: string,
39 to: string,
40 maxHops: number
41 ): CandidatePath[] {
42 const paths: CandidatePath[] = [];
43 const visited = new Set<string>();
44
45 const dfs = (current: string, path: RouteEdge[], hops: number) => {
46 if (current === to) {
47 paths.push({ edges: [...path], hopCount: hops });
48 return;
49 }
50 if (hops >= maxHops) return;
51
52 visited.add(current);
53 for (const edge of this.edges.get(current) || []) {
54 if (!visited.has(edge.to)) {
55 path.push(edge);
56 dfs(edge.to, path, hops + (edge.type === 'bridge' ? 1 : 0));
57 path.pop();
58 }
59 }
60 visited.delete(current);
61 };
62
63 dfs(from, [], 0);
64 return paths;
65 }
66}

Token Resolution

When the source and destination tokens differ, the engine must find intermediate swap points. The token resolver identifies all viable swap paths on each chain:

typescript
1class TokenResolver {
2 // Find all ways to convert tokenA to tokenB on a given chain
3 resolveSwapPath(
4 chain: string,
5 tokenA: string,
6 tokenB: string
7 ): SwapPath[] {
8 const paths: SwapPath[] = [];
9
10 // Direct pair (e.g., ETH/USDC on Uniswap)
11 if (this.hasPair(chain, tokenA, tokenB)) {
12 paths.push({ hops: [{ from: tokenA, to: tokenB }] });
13 }
14
15 // Via common intermediaries (WETH, USDC, USDT)
16 for (const mid of ['WETH', 'USDC', 'USDT']) {
17 if (mid === tokenA || mid === tokenB) continue;
18 if (this.hasPair(chain, tokenA, mid) &&
19 this.hasPair(chain, mid, tokenB)) {
20 paths.push({
21 hops: [
22 { from: tokenA, to: mid },
23 { from: mid, to: tokenB },
24 ],
25 });
26 }
27 }
28
29 return paths;
30 }
31}

Early Pruning

Not all paths are worth evaluating. The engine prunes before the minimax search begins:

  • Bridge offline — Skip paths through bridges that are down or congested
  • Insufficient liquidity — Skip paths where bridge liquidity is below transfer amount
  • Excessive hops — Cap at 4 hops to limit gas accumulation and failure probability
  • Dominated paths — Remove paths that are strictly worse than another on all dimensions
  • Token blacklist — Exclude paths through tokens flagged as honeypots or low-liquidity traps
  • Stale quotes — Exclude paths with bridge quotes older than the configured TTL
typescript
1class PathPruner {
2 prune(paths: CandidatePath[], state: PathState[]): CandidatePath[] {
3 let viable = paths;
4
5 // Stage 1: Hard constraints (binary pass/fail)
6 viable = viable.filter((path, i) => {
7 const s = state[i];
8 if (!s.bridgeHealth.every(h => h.online)) return false; // Bridge offline
9 if (s.bridgeQuotes.some(q => q.liquidityDepth < path.amount)) // No liquidity
10 return false;
11 if (path.hopCount > this.config.maxHops) return false; // Too many hops
12 if (s.timestamp + this.config.quoteTTL < Date.now()) return false; // Stale data
13 return true;
14 });
15
16 // Stage 2: Dominance pruning
17 viable = this.removeDominated(viable, state);
18
19 // Stage 3: Sort by estimated quality for alpha-beta efficiency
20 viable.sort((a, b) => this.estimateQuality(b) - this.estimateQuality(a));
21
22 return viable;
23 }
24
25 // Path A dominates Path B if A is >= B on ALL dimensions
26 private removeDominated(
27 paths: CandidatePath[],
28 state: PathState[]
29 ): CandidatePath[] {
30 return paths.filter((pathA, i) => {
31 return !paths.some((pathB, j) => {
32 if (i === j) return false;
33 const sA = state[i], sB = state[j];
34 return (
35 sB.totalFees <= sA.totalFees &&
36 sB.totalSlippage <= sA.totalSlippage &&
37 sB.estimatedTime <= sA.estimatedTime &&
38 sB.riskScore <= sA.riskScore &&
39 sB.mevExposure <= sA.mevExposure &&
40 // At least one strict inequality
41 (sB.totalFees < sA.totalFees ||
42 sB.totalSlippage < sA.totalSlippage ||
43 sB.estimatedTime < sA.estimatedTime)
44 );
45 });
46 });
47 }
48}

Typically reduces 20-50 raw paths to 8-15 viable candidates for minimax evaluation. The dominance pruning step alone usually eliminates 30-40% of remaining paths.

State Collection

For each viable path, the StateCollector fetches real-time conditions in parallel:

typescript
1// Parallel state collection across all chains and bridges
2const states = await Promise.all(
3 candidatePaths.map(path => collectPathState(path))
4);
5
6interface PathState {
7 bridgeQuotes: BridgeQuote[]; // Fee, time, liquidity for each bridge hop
8 dexQuotes: DexQuote[]; // Swap output, slippage for each DEX hop
9 gasEstimates: GasEstimate[]; // Gas cost per chain per transaction
10 bridgeHealth: BridgeHealth[]; // Uptime, congestion, recent failures
11 timestamp: number; // State freshness
12}
13
14interface DexQuote {
15 dex: string;
16 chain: string;
17 inputToken: string;
18 outputToken: string;
19 inputAmount: string;
20 outputAmount: string;
21 priceImpact: number; // Percentage (0.01 = 1%)
22 route: string[]; // Token path through pools
23 poolReserves: PoolReserve[]; // Current reserves for slippage calc
24}
25
26interface GasEstimate {
27 chain: string;
28 gasPrice: bigint; // Current gas price in wei
29 gasLimit: bigint; // Estimated gas units for this tx
30 costUSD: number; // Gas cost in USD
31 priorityFee: bigint; // EIP-1559 priority fee
32 baseFee: bigint; // EIP-1559 base fee
33}

RPC Connection Management

Each chain has a pool of RPC endpoints with automatic failover. The pool tracks response times and error rates to route requests to the fastest healthy endpoint:

typescript
1class RpcConnectionPool {
2 private endpoints: Map<string, RpcEndpoint[]>;
3 private metrics: Map<string, EndpointMetrics>;
4
5 async call(chain: string, method: string, params: unknown[]): Promise<unknown> {
6 const endpoints = this.getHealthyEndpoints(chain);
7
8 for (const endpoint of endpoints) {
9 try {
10 const start = performance.now();
11 const result = await endpoint.call(method, params);
12 const latency = performance.now() - start;
13
14 this.metrics.get(endpoint.url)!.recordSuccess(latency);
15 return result;
16 } catch (error) {
17 this.metrics.get(endpoint.url)!.recordFailure(error);
18 continue; // Try next endpoint
19 }
20 }
21
22 throw new AllEndpointsFailedError(chain);
23 }
24
25 private getHealthyEndpoints(chain: string): RpcEndpoint[] {
26 return this.endpoints.get(chain)!
27 .filter(ep => this.metrics.get(ep.url)!.errorRate < 0.1)
28 .sort((a, b) =>
29 this.metrics.get(a.url)!.p50Latency -
30 this.metrics.get(b.url)!.p50Latency
31 );
32 }
33}

Worst-Case Modeling

For each path, the engine models adversarial conditions. These multipliers are calibrated from historical data across millions of bridge transfers:

Adversarial FactorWorst-Case ModelRationale
Slippage2x quoted slippageLiquidity can drain between quote and execution; large trades can trigger cascading pool imbalances
Gas spikes1.5x current gas priceCongestion from MEV bots, NFT mints, or airdrop claims can spike gas within seconds
Bridge delay3x median confirmation timeGuardian/validator set can be slow during network congestion or consensus issues
MEV extraction0.3% of transfer value on vulnerable hopsSandwich attacks on DEX swaps; front-running on bridge redemption transactions
Price movement0.5% adverse price change during bridge transitAsset prices can move against you during the minutes a bridge transfer takes
typescript
1class WorstCaseModel {
2 private config: AdversarialConfig;
3
4 evaluateWorstCase(path: CandidatePath, state: PathState): WorstCaseResult {
5 let outputUSD = state.quotedOutputUSD;
6 const penalties: Penalty[] = [];
7
8 // 1. Slippage amplification
9 for (const swap of state.dexQuotes) {
10 const worstSlippage = swap.priceImpact * this.config.slippageMultiplier;
11 const slippageCost = swap.outputAmountUSD * worstSlippage;
12 outputUSD -= slippageCost;
13 penalties.push({
14 type: 'slippage',
15 hop: swap.dex,
16 amount: slippageCost,
17 });
18 }
19
20 // 2. Gas surge
21 for (const gas of state.gasEstimates) {
22 const worstGas = gas.costUSD * this.config.gasMultiplier;
23 const gasSurge = worstGas - gas.costUSD;
24 outputUSD -= gasSurge;
25 penalties.push({
26 type: 'gas_surge',
27 hop: gas.chain,
28 amount: gasSurge,
29 });
30 }
31
32 // 3. MEV extraction (only on unprotected swaps)
33 for (const swap of state.dexQuotes) {
34 if (!swap.mevProtected) {
35 const mevCost = swap.outputAmountUSD * this.config.mevExtraction;
36 outputUSD -= mevCost;
37 penalties.push({
38 type: 'mev',
39 hop: swap.dex,
40 amount: mevCost,
41 });
42 }
43 }
44
45 // 4. Adverse price movement during bridge transit
46 for (const bridge of state.bridgeQuotes) {
47 const transitTime = bridge.estimatedTime * this.config.bridgeDelayMultiplier;
48 const priceRisk = bridge.outputAmountUSD * this.config.priceMovement;
49 outputUSD -= priceRisk;
50 penalties.push({
51 type: 'price_movement',
52 hop: bridge.bridge,
53 amount: priceRisk,
54 });
55 }
56
57 // 5. Bridge-specific risk penalty
58 for (const health of state.bridgeHealth) {
59 const riskPenalty = this.calculateBridgeRisk(health) * state.quotedOutputUSD;
60 outputUSD -= riskPenalty;
61 penalties.push({
62 type: 'bridge_risk',
63 hop: health.bridge,
64 amount: riskPenalty,
65 });
66 }
67
68 return {
69 worstCaseOutputUSD: outputUSD,
70 penalties,
71 totalPenalty: state.quotedOutputUSD - outputUSD,
72 penaltyPercent: ((state.quotedOutputUSD - outputUSD) / state.quotedOutputUSD) * 100,
73 };
74 }
75
76 private calculateBridgeRisk(health: BridgeHealth): number {
77 let risk = 0;
78 if (health.recentSuccessRate < 0.99) risk += (1 - health.recentSuccessRate) * 0.1;
79 if (health.congestion === 'high') risk += 0.005;
80 if (health.congestion === 'medium') risk += 0.002;
81 return risk;
82 }
83}

The worst-case output for each path is: quoted output minus all worst-case cost adjustments. The minimax score is this worst-case output — the engine picks the path with the highest floor.

Route Execution

Once the optimal route is selected, the RouteExecutorhandles multi-step execution with monitoring:

text
1Executing: ETH → Uniswap(ETH→USDC) → Wormhole(USDC) → Jupiter(USDC→SOL)
2
3Step 1/3: Swap ETH → USDC on Uniswap
4 ├── Build transaction
5 ├── Simulate (verify output within tolerance)
6 ├── Submit and confirm
7 └── Received 3,247.82 USDC
8
9Step 2/3: Bridge USDC via Wormhole (Ethereum → Solana)
10 ├── Initiate bridge transfer
11 ├── Monitor VAA confirmation
12 ├── Redeem on Solana
13 └── Received 3,247.82 USDC on Solana (2m 14s)
14
15Step 3/3: Swap USDC → SOL on Jupiter
16 ├── Fetch quote
17 ├── Submit and confirm
18 └── Received 14.12 SOL
19
20Result: 1.0 ETH → 14.12 SOL (guaranteed minimum was 13.8 SOL)

Execution State Machine

Each execution follows a strict state machine that ensures funds are tracked at every point:

text
1State Machine per Hop:
2
3 ┌──────────┐ ┌───────────┐ ┌───────────┐
4 │ PENDING │ ──→ │ SIMULATED │ ──→ │ SUBMITTED │
5 └──────────┘ └───────────┘ └─────┬─────┘
6
7 ┌────────────────┤
8 ▼ ▼
9 ┌───────────┐ ┌───────────┐
10 │ CONFIRMED │ │ FAILED │
11 └─────┬─────┘ └─────┬─────┘
12 │ │
13 ▼ ▼
14 ┌───────────┐ ┌───────────┐
15 │ COMPLETED │ │ RETRYING │
16 └───────────┘ └───────────┘
17
18State transitions are logged with:
19 - Timestamp (ms precision)
20 - Transaction hash (when available)
21 - Input/output amounts
22 - Gas used
23 - Chain + block number
typescript
1class ExecutionStateMachine {
2 private hops: Map<string, HopState>;
3 private listeners: ExecutionListener[];
4
5 transition(hopId: string, newState: HopStatus, data?: TransitionData): void {
6 const hop = this.hops.get(hopId)!;
7 const oldState = hop.status;
8
9 // Validate transition is allowed
10 if (!this.isValidTransition(oldState, newState)) {
11 throw new InvalidTransitionError(hopId, oldState, newState);
12 }
13
14 hop.status = newState;
15 hop.lastUpdated = Date.now();
16
17 if (data?.txHash) hop.txHash = data.txHash;
18 if (data?.output) hop.actualOutput = data.output;
19 if (data?.error) hop.error = data.error;
20
21 // Notify listeners (progress callbacks, logging, telemetry)
22 for (const listener of this.listeners) {
23 listener.onTransition(hopId, oldState, newState, data);
24 }
25 }
26
27 // Track exactly where funds are at any point
28 getFundsLocation(): FundsLocation {
29 for (const [hopId, hop] of this.hops) {
30 if (hop.status === 'completed') continue;
31 if (hop.status === 'pending') {
32 // Funds still at previous hop's destination (or original wallet)
33 return this.getPreviousLocation(hopId);
34 }
35 if (hop.status === 'submitted' || hop.status === 'confirmed') {
36 return {
37 chain: hop.edge.type === 'bridge' ? 'in_transit' : hop.edge.from.chain,
38 token: hop.edge.from.token,
39 amount: hop.inputAmount,
40 status: 'locked',
41 };
42 }
43 }
44 return this.getFinalLocation();
45 }
46}

Pre-Execution Simulation

Before submitting any on-chain transaction, the executor simulates it using eth_call (EVM) or simulateTransaction(Solana) to verify the expected output:

typescript
1class TransactionSimulator {
2 async simulate(tx: PreparedTransaction): Promise<SimulationResult> {
3 if (tx.chain.type === 'evm') {
4 // EVM simulation via eth_call
5 const result = await this.rpc.call(tx.chain.id, 'eth_call', [{
6 from: tx.from,
7 to: tx.to,
8 data: tx.data,
9 value: tx.value,
10 gas: tx.gasLimit,
11 }, 'latest']);
12
13 const decoded = this.decodeOutput(tx.abi, result);
14 return {
15 success: true,
16 expectedOutput: decoded.amountOut,
17 gasUsed: decoded.gasUsed,
18 };
19 }
20
21 if (tx.chain.type === 'solana') {
22 // Solana simulation
23 const sim = await this.connection.simulateTransaction(tx.transaction);
24 return {
25 success: sim.value.err === null,
26 expectedOutput: this.extractSolanaOutput(sim),
27 computeUnits: sim.value.unitsConsumed,
28 };
29 }
30
31 throw new UnsupportedChainError(tx.chain);
32 }
33}

Failure Handling

If any step fails or conditions degrade beyond tolerance:

  1. Pre-bridge failure — Revert source chain transaction. No funds at risk. The executor re-runs path discovery excluding the failed provider and selects the next best route.
  2. Bridge stall — Monitor until completion or timeout. The executor polls bridge status every 15 seconds. After 3x the expected confirmation time, it alerts the user with the transaction hash and bridge-specific recovery instructions.
  3. Post-bridge failure — Assets are on destination chain. Re-route the final swap through alternative DEX. If all DEXes fail, the user retains the bridged tokens.
  4. Partial execution — If execution is interrupted mid-pipeline, the executor records the exact state and provides a recovery plan that can be resumed.
typescript
1class FailureHandler {
2 async handleFailure(
3 execution: Execution,
4 failedHop: RouteHop,
5 error: Error,
6 opts: ExecOpts
7 ): Promise<ExecResult> {
8 const fundsLocation = execution.getFundsLocation();
9
10 // Case 1: Funds still in user's wallet (pre-bridge)
11 if (fundsLocation.status === 'wallet') {
12 if (opts.autoRetry) {
13 // Re-discover routes excluding the failed provider
14 const newRoute = await this.router.findRoute({
15 ...execution.originalParams,
16 options: {
17 excludeProviders: [failedHop.provider],
18 },
19 });
20 return this.executor.execute(newRoute, opts);
21 }
22 return execution.finalize('failed', { error, fundsLocation });
23 }
24
25 // Case 2: Funds in transit (bridge in progress)
26 if (fundsLocation.status === 'in_transit') {
27 // Wait for bridge to complete (or timeout)
28 const bridgeResult = await this.waitForBridge(
29 failedHop,
30 execution.config.bridgeTimeout
31 );
32 if (bridgeResult.completed) {
33 // Continue execution from the next hop
34 return this.executor.resumeFrom(execution, failedHop);
35 }
36 return execution.finalize('partial', {
37 error,
38 fundsLocation,
39 recoveryInstructions: this.getRecoveryInstructions(failedHop),
40 });
41 }
42
43 // Case 3: Funds on destination chain (swap failed)
44 if (fundsLocation.chain === execution.destinationChain) {
45 // Try alternative DEX
46 const altDexes = this.dexRegistry.getAlternatives(
47 fundsLocation.chain,
48 fundsLocation.token,
49 execution.destinationToken
50 );
51 for (const dex of altDexes) {
52 try {
53 return await this.executor.executeSwap(dex, fundsLocation, opts);
54 } catch { continue; }
55 }
56 // All DEXes failed — user keeps the bridged token
57 return execution.finalize('partial', {
58 fundsLocation,
59 message: `Bridged ${fundsLocation.amount} ${fundsLocation.token} to ${fundsLocation.chain}. Final swap failed — tokens available in wallet.`,
60 });
61 }
62
63 return execution.finalize('failed', { error, fundsLocation });
64 }
65}
Safety guarantee: At no point are user funds split across chains without monitoring. The executor tracks the full lifecycle and provides status updates at each step. The getFundsLocation()method always returns the exact chain, token, and amount where user funds currently reside.

Gas Optimization

The executor applies several gas optimization techniques to minimize on-chain costs:

TechniqueSavingsDescription
EIP-1559 dynamic fees10-30%Uses maxFeePerGas and maxPriorityFeePerGas instead of legacy gasPrice
Gas limit estimationPrevents overpaySimulates transaction to get exact gas used, adds 15% buffer
Batch approvals~21,000 gas per avoided txUses permit2 or infinite approvals where safe to avoid redundant approve calls
Calldata optimization5-15%Encodes function calls with minimal calldata where possible
Nonce managementPrevents stuck txsTracks pending nonces to avoid transaction replacement conflicts

Monitoring and Telemetry

Every execution emits structured telemetry data that is used to improve routing decisions over time:

typescript
1interface ExecutionTelemetry {
2 execId: string;
3 route: {
4 hops: number;
5 bridges: string[];
6 dexes: string[];
7 chains: string[];
8 };
9 timing: {
10 quoteTime: number; // Time to generate quote (ms)
11 executionTime: number; // Total execution time (ms)
12 perHop: { hop: string; time: number }[];
13 };
14 accuracy: {
15 quotedOutput: string; // What we quoted
16 actualOutput: string; // What the user received
17 deviation: number; // Percentage deviation
18 worstCaseMet: boolean; // Did actual >= guaranteed minimum?
19 };
20 costs: {
21 totalGas: string; // Total gas cost in USD
22 totalBridgeFees: string; // Total bridge fees in USD
23 totalSwapFees: string; // Total swap fees in USD
24 mevExtracted: string; // Detected MEV extraction in USD
25 };
26}

This telemetry feeds back into the worst-case model calibration. If the model consistently over- or under-estimates adversarial conditions for a specific bridge or DEX, the multipliers are adjusted automatically on a weekly basis.

Performance

OperationTypical TimeP99 TimeBottleneck
Path discovery<100ms250msGraph traversal depth
State collection (cached)50-200ms500msCache hit rate
State collection (cold)500ms-2s4sSlowest RPC endpoint
Minimax evaluation (15 paths)<50ms120msPath count after pruning
Total quote time (warm)200-500ms1sState collection
Total quote time (cold)1-3s5sState collection
Execution timeDepends on bridgeBridge confirmation time (30s-20min)

Edge Cases

ScenarioBehavior
No viable route existsThrows NoRouteFoundError with reason (unsupported chain pair, all bridges offline, etc.)
All routes have negative minimax scoreReturns the least-negative route with a warning that fees exceed output
Transfer amount exceeds all bridge liquiditySuggests splitting into multiple smaller transfers
Bridge goes offline mid-executionWaits for bridge recovery up to timeout, then provides recovery instructions
Gas price spikes above 2x estimate during executionPauses execution, re-evaluates cost, continues if within tolerance
Same token on same chain (no-op)Returns identity route with zero fees and zero time
Dust amounts below bridge minimumThrows AmountBelowMinimumError with the bridge-specific minimum