Architecture
System design, data flow, and bridge adapter layer behind the MNMX cross-chain routing protocol.
System Overview
MNMX sits between the user and the cross-chain infrastructure. It doesn't replace bridges — it searches across all of them to find the optimal path for each transfer. The system is designed as a layered architecture where each layer has a single responsibility and communicates through well-defined interfaces.
High-Level Architecture
1┌─────────────────────────────────────────────────────────────────┐2│ CLIENT LAYER │3│ TypeScript SDK │ Python SDK │ REST API │ CLI Tool │4└────────────────────────────┬────────────────────────────────────┘5 │6 ▼7┌─────────────────────────────────────────────────────────────────┐8│ ROUTING CORE │9│ ┌──────────────┐ ┌───────────────┐ ┌──────────────────────┐ │10│ │ PathDiscovery│→ │ StateCollector│→ │ MinimaxEvaluator │ │11│ │ │ │ │ │ (alpha-beta pruning) │ │12│ └──────────────┘ └───────────────┘ └──────────────────────┘ │13│ │ │14│ ▼ │15│ ┌──────────────┐ │16│ │RouteExecutor │ │17│ └──────────────┘ │18└────────────────────────────┬────────────────────────────────────┘19 │20 ▼21┌─────────────────────────────────────────────────────────────────┐22│ ADAPTER LAYER │23│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │24│ │ Wormhole │ │ deBridge │ │ LayerZero│ │ Allbridge│ │25│ │ Adapter │ │ Adapter │ │ Adapter │ │ Adapter │ │26│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │27│ │ │ │ │ │28│ ┌────┴─────┐ ┌────┴─────┐ ┌────┴─────┐ ┌────┴─────┐ │29│ │ DEX Pool │ │ DEX Pool │ │ DEX Pool │ │ DEX Pool │ │30│ │ Adapters │ │ Adapters │ │ Adapters │ │ Adapters │ │31│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │32└────────────────────────────┬────────────────────────────────────┘33 │34 ▼35┌─────────────────────────────────────────────────────────────────┐36│ INFRASTRUCTURE │37│ RPC Nodes │ Bridge Contracts │ DEX Contracts │ Indexers │38└─────────────────────────────────────────────────────────────────┘Core Components
PathDiscovery Module
The PathDiscovery module is responsible for enumerating all possible routes between a source and destination. It maintains a graph of supported chains, bridges, and DEXes that is updated every 30 seconds based on health checks and liquidity snapshots.
1class PathDiscovery {2 private chainGraph: ChainGraph;3 private bridgeRegistry: BridgeRegistry;4 private dexRegistry: DexRegistry;5
6 // Build the chain connectivity graph on startup7 constructor(config: DiscoveryConfig) {8 this.chainGraph = new ChainGraph();9
10 // Register all bridge connections as graph edges11 for (const adapter of config.bridges) {12 for (const [chainA, chainB] of adapter.supportedPairs()) {13 this.chainGraph.addEdge(chainA, chainB, {14 bridge: adapter.name,15 weight: adapter.getBaseLatency(chainA, chainB),16 });17 }18 }19
20 // Register DEXes as intra-chain swap nodes21 for (const dex of config.dexes) {22 this.chainGraph.addSwapNode(dex.chain, {23 dex: dex.name,24 pairs: dex.supportedPairs(),25 });26 }27 }28
29 // Enumerate all feasible paths between source and destination30 async findPaths(params: RouteParams): Promise<CandidatePath[]> {31 const paths: CandidatePath[] = [];32
33 // 1. Direct bridge paths34 paths.push(...this.findDirectPaths(params));35
36 // 2. Single-swap + bridge paths (swap on source, bridge, done)37 paths.push(...this.findSwapBridgePaths(params));38
39 // 3. Bridge + swap paths (bridge, swap on destination)40 paths.push(...this.findBridgeSwapPaths(params));41
42 // 4. Swap + bridge + swap paths (full pipeline)43 paths.push(...this.findFullPipelinePaths(params));44
45 // 5. Multi-hop paths through intermediate chains46 if (params.maxHops > 1) {47 paths.push(...this.findMultiHopPaths(params));48 }49
50 return this.deduplicateAndSort(paths);51 }52}The graph traversal uses a bounded depth-first search with a maximum depth equal to maxHops (default: 3). This caps the computational complexity at O(B^H) where B is the average branching factor (typically 4-6 bridges per chain) and H is the max hop count. For typical configurations, this produces 15-50 candidate paths in under 100ms.
StateCollector Module
The StateCollector is the data layer that fetches real-time conditions for each candidate path. It uses parallel RPC calls and maintains a local cache with configurable TTL to avoid redundant network requests.
1class StateCollector {2 private cache: StateCache;3 private rpcPool: RpcConnectionPool;4
5 constructor(config: CollectorConfig) {6 this.cache = new StateCache({7 bridgeQuoteTTL: 10_000, // Bridge quotes expire after 10s8 gasEstimateTTL: 15_000, // Gas estimates valid for 15s9 dexQuoteTTL: 5_000, // DEX quotes expire after 5s10 poolReserveTTL: 30_000, // Pool reserves cached for 30s11 healthCheckTTL: 60_000, // Health data cached for 60s12 });13
14 this.rpcPool = new RpcConnectionPool({15 ethereum: { endpoints: config.ethRpcs, maxConcurrent: 10 },16 solana: { endpoints: config.solRpcs, maxConcurrent: 15 },17 arbitrum: { endpoints: config.arbRpcs, maxConcurrent: 10 },18 });19 }20
21 async collectPathState(path: CandidatePath): Promise<PathState> {22 // Fire all data requests in parallel23 const [bridgeQuotes, dexQuotes, gasEstimates, healthChecks] =24 await Promise.all([25 this.fetchBridgeQuotes(path.bridgeHops),26 this.fetchDexQuotes(path.swapHops),27 this.fetchGasEstimates(path.chains),28 this.fetchHealthChecks(path.bridges),29 ]);30
31 return {32 bridgeQuotes,33 dexQuotes,34 gasEstimates,35 bridgeHealth: healthChecks,36 timestamp: Date.now(),37 staleness: 0,38 };39 }40}Data Flow
1. Path Discovery
The PathDiscovery module enumerates all possible routes between source and destination:
- Direct bridges — Single-hop transfers via Wormhole, deBridge, LayerZero, etc.
- Multi-hop paths — Routes through intermediate chains when direct paths are suboptimal
- DEX combinations — Swap on source chain, bridge, swap on destination chain
For a transfer from ETH on Ethereum to SOL on Solana, this might produce 15-50 candidate paths depending on available bridges and intermediate chains.
2. State Collection
The StateCollector fetches real-time data for each candidate path:
| Data Point | Source | Refresh Rate | Impact |
|---|---|---|---|
| Bridge liquidity depth | On-chain pool queries | Every 30s | Determines max transfer size without slippage |
| Gas costs per chain | RPC eth_gasPrice / fee estimates | Every 15s | Total transaction fee across all hops |
| DEX pool reserves | On-chain getReserves() calls | Every 5s | Swap slippage estimation via constant product formula |
| Bridge latency | Internal telemetry database | Rolling 1hr window | Expected transfer time |
| Bridge status | Guardian/validator health endpoints | Every 60s | Availability and congestion |
| Token prices | On-chain oracle (Pyth, Chainlink) | Every 10s | Cross-chain value normalization |
| Mempool state | RPC pending transaction queries | Every 5s | MEV exposure estimation |
3. Minimax Search
The routing engine constructs a game tree from the candidate paths and evaluates each under adversarial conditions:
1Route Tree:2├── Path A: Wormhole direct (ETH → SOL)3│ ├── Best case: 14.5 SOL │ Worst case: 11.2 SOL │ Minimax: 11.24│5├── Path B: deBridge direct (ETH → SOL)6│ ├── Best case: 14.8 SOL │ Worst case: 12.1 SOL │ Minimax: 12.17│8├── Path C: Wormhole → Arbitrum → Jupiter (ETH → SOL)9│ ├── Best case: 14.2 SOL │ Worst case: 13.8 SOL │ Minimax: 13.8 ← OPTIMAL10│11└── Path D: LayerZero → Base → Solana12 ├── Best case: 13.9 SOL │ Worst case: 12.5 SOL │ Minimax: 12.5Path C has the highest minimax score — it's not the best when everything goes right, but it has the best guaranteed minimum outcome.
4. Route Evaluation
Each path is scored across five weighted dimensions:
| Dimension | Weight | Calculation Method | Description |
|---|---|---|---|
| Total fees | 0.25 | Sum of gas + bridge + swap fees in USD | Gas + bridge fees + swap fees across all hops |
| Slippage | 0.25 | Constant-product AMM model per pool | Price impact from swaps and bridge liquidity |
| Speed | 0.15 | p90 historical confirmation times | Expected total transfer time |
| Bridge risk | 0.20 | Composite: uptime * TVL_score * audit_score | Bridge security score, uptime history, TVL |
| MEV exposure | 0.15 | Mempool analysis + historical extraction rates | Vulnerability to extraction during multi-hop execution |
5. Route Execution
The RouteExecutor handles the multi-step execution with a state machine that tracks each hop through its lifecycle:
1class RouteExecutor {2 private stateMachine: ExecutionStateMachine;3
4 async execute(route: Route, opts: ExecOpts): Promise<ExecResult> {5 const execution = this.stateMachine.create(route);6
7 for (const hop of route.path) {8 execution.transition(hop, 'executing');9
10 try {11 // Re-validate conditions before each hop12 const currentState = await this.stateCollector.collect(hop);13 const drift = this.calculateDrift(hop.quotedState, currentState);14
15 if (drift > opts.maxDrift || 0.02) {16 // Conditions degraded beyond tolerance17 if (hop.type === 'swap' && !execution.hasBridgedFunds) {18 // Pre-bridge: safe to abort and re-route19 execution.transition(hop, 'aborted');20 return this.reRoute(route, execution);21 }22 // Post-bridge: must continue, find best available swap23 hop.provider = await this.findAlternativeDex(hop);24 }25
26 const result = await this.executeHop(hop, opts.signer);27 execution.transition(hop, 'completed', result);28
29 } catch (error) {30 execution.transition(hop, 'failed', { error });31 return this.handleFailure(execution, hop, error, opts);32 }33 }34
35 return execution.finalize();36 }37}- Swap on source chain (if needed)
- Initiate bridge transfer
- Monitor bridge completion via polling (2s intervals for fast bridges, 15s for slow)
- Swap on intermediate/destination chain (if needed)
- Verify final balance against quoted output
Each step includes failure handling — if a bridge stalls or conditions change mid-execution, the engine can reroute through an alternative path.
Bridge Adapter Layer
Bridges are integrated as modular adapters. Each adapter implements a standard interface that normalizes the bridge-specific details into a uniform data model:
1interface BridgeAdapter {2 name: string;3 supportedChains: Chain[];4
5 getQuote(params: QuoteParams): Promise<BridgeQuote>;6 execute(quote: BridgeQuote, signer: Signer): Promise<BridgeResult>;7 getStatus(txHash: string): Promise<BridgeStatus>;8 getHealth(): Promise<BridgeHealth>;9
10 // Adapter-specific methods11 getSupportedPairs(): Promise<ChainPair[]>;12 getLiquidityDepth(chain: string, token: string): Promise<bigint>;13 getHistoricalLatency(fromChain: string, toChain: string): Promise<LatencyStats>;14}| Adapter | Chains | Avg Latency | Implementation | Status |
|---|---|---|---|---|
| Wormhole | Ethereum, Solana, Arbitrum, Base, Polygon, +20 | 2-15 min | VAA-based message passing | Supported |
| deBridge | Ethereum, Solana, Arbitrum, BNB Chain, +10 | 1-5 min | DLN order system | Supported |
| LayerZero | Ethereum, Arbitrum, Base, Optimism, +30 | 2-10 min | DVN + Ultra Light Node | Supported |
| Allbridge | Ethereum, Solana, BNB Chain, +8 | 2-10 min | Liquidity pool model | Supported |
Adding a new bridge requires implementing the BridgeAdapter interface. The routing engine automatically incorporates new adapters into its path discovery — more bridges means more candidate paths means better optimization.
Caching Strategy
The system uses a multi-tier caching strategy to minimize RPC calls while keeping data fresh enough for accurate routing decisions:
| Data Type | Cache TTL | Invalidation | Rationale |
|---|---|---|---|
| Bridge quotes | 10 seconds | TTL-based | Prices move; quotes stale after 10s are unreliable |
| Gas estimates | 15 seconds | TTL-based | Gas prices fluctuate but not drastically block-to-block |
| DEX pool reserves | 5 seconds | Event-driven + TTL | Swap events change reserves; need near-real-time data |
| Bridge health | 60 seconds | TTL-based | Health status changes infrequently |
| Chain graph topology | 5 minutes | Manual + TTL | New bridges/chains added rarely |
| Historical latency | 5 minutes | Rolling window | Smoothed over 1hr window; stable enough for 5min cache |
| Token metadata | 24 hours | Manual | Token addresses and decimals rarely change |
1// Cache configuration example2const cacheConfig: CacheConfig = {3 driver: 'redis', // 'memory' | 'redis'4 redis: {5 host: process.env.REDIS_HOST || 'localhost',6 port: 6379,7 keyPrefix: 'mnmx:state:',8 },9 defaultTTL: 15_000,10 maxMemoryMB: 256, // Memory cache limit11 evictionPolicy: 'lru', // Least recently used12 compressionThreshold: 1024, // Compress entries > 1KB13};Error Handling Architecture
Errors are categorized into three tiers based on recoverability:
1┌─────────────────────────────────────────────────────────┐2│ TIER 1: Recoverable (automatic retry) │3│ - RPC timeout → retry with fallback endpoint │4│ - Stale quote → re-fetch and re-evaluate │5│ - Transaction underpriced → bump gas and resubmit │6├─────────────────────────────────────────────────────────┤7│ TIER 2: Re-routable (find alternative path) │8│ - Bridge offline during execution → reroute │9│ - Slippage exceeded on swap → try alternative DEX │10│ - Insufficient liquidity discovered → exclude bridge │11├─────────────────────────────────────────────────────────┤12│ TIER 3: Terminal (abort with user notification) │13│ - All bridges unavailable for chain pair │14│ - User wallet insufficient balance │15│ - Contract reverted with non-transient error │16└─────────────────────────────────────────────────────────┘Deployment Architecture
MNMX is designed to run as a self-contained service or be embedded directly into applications via the SDK. The deployment model depends on the use case:
| Deployment Mode | Use Case | Latency | Infrastructure |
|---|---|---|---|
| Embedded SDK | Wallet integrations, dApps | Lowest (in-process) | Client-side only |
| Self-hosted service | Trading desks, protocols | Low (local network) | Single node + Redis |
| Distributed cluster | High-volume aggregators | Variable | Multi-node + Redis cluster + load balancer |
1Distributed Cluster Deployment:2
3 ┌──────────────┐4 │ Load Balancer│5 │ (nginx/HAP) │6 └──────┬───────┘7 │8 ┌────────────┼────────────┐9 ▼ ▼ ▼10 ┌───────────┐┌───────────┐┌───────────┐11 │ MNMX ││ MNMX ││ MNMX │12 │ Node 1 ││ Node 2 ││ Node 3 │13 │ (quote) ││ (quote) ││ (execute) │14 └─────┬─────┘└─────┬─────┘└─────┬─────┘15 │ │ │16 └────────────┼────────────┘17 │18 ┌──────┴───────┐19 │ Redis Cluster│20 │ (state cache)│21 └──────┬───────┘22 │23 ┌───────────────┼───────────────┐24 ▼ ▼ ▼25 ┌───────────┐ ┌───────────┐ ┌───────────┐26 │ RPC Pool │ │ RPC Pool │ │ RPC Pool │27 │ Ethereum │ │ Solana │ │ Arbitrum │28 └───────────┘ └───────────┘ └───────────┘Performance Characteristics
| Metric | Value | Conditions |
|---|---|---|
| Path discovery | <100ms | 8 chains, 4 bridges, maxHops=3 |
| State collection (cached) | 50-200ms | Cache hit rate >80% |
| State collection (cold) | 500ms-2s | All data fetched from RPC |
| Minimax evaluation | <50ms | 15 candidate paths, alpha-beta pruning |
| Total quote time (warm) | 200-500ms | Cache populated from recent queries |
| Total quote time (cold) | 1-3s | First query for a chain pair |
| Memory usage (embedded) | ~128MB | In-memory cache, 4 bridge adapters |
| Memory usage (service) | ~512MB | Redis cache, full telemetry |
Design Decisions
Why aggregate instead of building a new bridge?
Bridges are commoditized infrastructure. Building another one adds no value. The value is in choosing the right bridge at the right time — and that's a search problem, not an infrastructure problem. MNMX treats bridges as interchangeable modules and focuses on the optimization layer above them. Each bridge has different liquidity profiles, fee structures, and latency characteristics that change continuously. No single bridge is optimal for all transfers at all times.
Why optimize for worst case instead of expected value?
Expected value optimization assumes you know the probability distribution of outcomes. In cross-chain, you don't — bridge delays are unpredictable, MEV is adversarial, liquidity can drain between quote and execution. Minimax doesn't need probability estimates. It guarantees the best outcome under the worst conditions, which is the rational choice when uncertainty is high. For a 10 ETH transfer, the difference between a route that averages 14.2 SOL but can drop to 11 SOL, versus a route that averages 14.0 SOL but never drops below 13.5 SOL, is significant. The minimax approach captures this.
Why multi-hop routes?
Direct paths are often suboptimal. ETH to SOL direct via Wormhole might cost more than ETH to USDC on Uniswap, USDC bridge via deBridge, USDC to SOL on Jupiter. The extra hops add small fees but can avoid large slippage on illiquid direct pairs. The engine evaluates all combinations and picks the global optimum. In practice, multi-hop routes outperform direct bridges in roughly 40% of transfers above $5,000 in value.
Why parallel state collection?
State collection dominates the quote latency budget. By fetching all bridge quotes, DEX quotes, gas estimates, and health checks in parallel using Promise.all, the total collection time equals the slowest single RPC call rather than the sum of all calls. This typically reduces collection time from 5-10 seconds (sequential) to 500ms-2s (parallel). The caching layer further reduces this to 50-200ms for repeated queries on popular chain pairs.