Docs
Docs/Architecture

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.

PathDiscovery
StateCollector
SearchEngine
OptimalRoute
RouteExecutor
RouteScorer
BridgeAdapters
RiskMonitor
DEXAdapters
Bridge LayerRouting Core

High-Level Architecture

text
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.

typescript
1class PathDiscovery {
2 private chainGraph: ChainGraph;
3 private bridgeRegistry: BridgeRegistry;
4 private dexRegistry: DexRegistry;
5
6 // Build the chain connectivity graph on startup
7 constructor(config: DiscoveryConfig) {
8 this.chainGraph = new ChainGraph();
9
10 // Register all bridge connections as graph edges
11 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 nodes
21 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 destination
30 async findPaths(params: RouteParams): Promise<CandidatePath[]> {
31 const paths: CandidatePath[] = [];
32
33 // 1. Direct bridge paths
34 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 chains
46 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.

typescript
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 10s
8 gasEstimateTTL: 15_000, // Gas estimates valid for 15s
9 dexQuoteTTL: 5_000, // DEX quotes expire after 5s
10 poolReserveTTL: 30_000, // Pool reserves cached for 30s
11 healthCheckTTL: 60_000, // Health data cached for 60s
12 });
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 parallel
23 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 PointSourceRefresh RateImpact
Bridge liquidity depthOn-chain pool queriesEvery 30sDetermines max transfer size without slippage
Gas costs per chainRPC eth_gasPrice / fee estimatesEvery 15sTotal transaction fee across all hops
DEX pool reservesOn-chain getReserves() callsEvery 5sSwap slippage estimation via constant product formula
Bridge latencyInternal telemetry databaseRolling 1hr windowExpected transfer time
Bridge statusGuardian/validator health endpointsEvery 60sAvailability and congestion
Token pricesOn-chain oracle (Pyth, Chainlink)Every 10sCross-chain value normalization
Mempool stateRPC pending transaction queriesEvery 5sMEV exposure estimation

The routing engine constructs a game tree from the candidate paths and evaluates each under adversarial conditions:

text
1Route Tree:
2├── Path A: Wormhole direct (ETH → SOL)
3│ ├── Best case: 14.5 SOL │ Worst case: 11.2 SOL │ Minimax: 11.2
4
5├── Path B: deBridge direct (ETH → SOL)
6│ ├── Best case: 14.8 SOL │ Worst case: 12.1 SOL │ Minimax: 12.1
7
8├── Path C: Wormhole → Arbitrum → Jupiter (ETH → SOL)
9│ ├── Best case: 14.2 SOL │ Worst case: 13.8 SOL │ Minimax: 13.8 ← OPTIMAL
10
11└── Path D: LayerZero → Base → Solana
12 ├── Best case: 13.9 SOL │ Worst case: 12.5 SOL │ Minimax: 12.5

Path 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:

DimensionWeightCalculation MethodDescription
Total fees0.25Sum of gas + bridge + swap fees in USDGas + bridge fees + swap fees across all hops
Slippage0.25Constant-product AMM model per poolPrice impact from swaps and bridge liquidity
Speed0.15p90 historical confirmation timesExpected total transfer time
Bridge risk0.20Composite: uptime * TVL_score * audit_scoreBridge security score, uptime history, TVL
MEV exposure0.15Mempool analysis + historical extraction ratesVulnerability 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:

typescript
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 hop
12 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 tolerance
17 if (hop.type === 'swap' && !execution.hasBridgedFunds) {
18 // Pre-bridge: safe to abort and re-route
19 execution.transition(hop, 'aborted');
20 return this.reRoute(route, execution);
21 }
22 // Post-bridge: must continue, find best available swap
23 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}
  1. Swap on source chain (if needed)
  2. Initiate bridge transfer
  3. Monitor bridge completion via polling (2s intervals for fast bridges, 15s for slow)
  4. Swap on intermediate/destination chain (if needed)
  5. 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:

typescript
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 methods
11 getSupportedPairs(): Promise<ChainPair[]>;
12 getLiquidityDepth(chain: string, token: string): Promise<bigint>;
13 getHistoricalLatency(fromChain: string, toChain: string): Promise<LatencyStats>;
14}
AdapterChainsAvg LatencyImplementationStatus
WormholeEthereum, Solana, Arbitrum, Base, Polygon, +202-15 minVAA-based message passingSupported
deBridgeEthereum, Solana, Arbitrum, BNB Chain, +101-5 minDLN order systemSupported
LayerZeroEthereum, Arbitrum, Base, Optimism, +302-10 minDVN + Ultra Light NodeSupported
AllbridgeEthereum, Solana, BNB Chain, +82-10 minLiquidity pool modelSupported

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 TypeCache TTLInvalidationRationale
Bridge quotes10 secondsTTL-basedPrices move; quotes stale after 10s are unreliable
Gas estimates15 secondsTTL-basedGas prices fluctuate but not drastically block-to-block
DEX pool reserves5 secondsEvent-driven + TTLSwap events change reserves; need near-real-time data
Bridge health60 secondsTTL-basedHealth status changes infrequently
Chain graph topology5 minutesManual + TTLNew bridges/chains added rarely
Historical latency5 minutesRolling windowSmoothed over 1hr window; stable enough for 5min cache
Token metadata24 hoursManualToken addresses and decimals rarely change
typescript
1// Cache configuration example
2const 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 limit
11 evictionPolicy: 'lru', // Least recently used
12 compressionThreshold: 1024, // Compress entries > 1KB
13};

Error Handling Architecture

Errors are categorized into three tiers based on recoverability:

text
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 ModeUse CaseLatencyInfrastructure
Embedded SDKWallet integrations, dAppsLowest (in-process)Client-side only
Self-hosted serviceTrading desks, protocolsLow (local network)Single node + Redis
Distributed clusterHigh-volume aggregatorsVariableMulti-node + Redis cluster + load balancer
text
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

MetricValueConditions
Path discovery<100ms8 chains, 4 bridges, maxHops=3
State collection (cached)50-200msCache hit rate >80%
State collection (cold)500ms-2sAll data fetched from RPC
Minimax evaluation<50ms15 candidate paths, alpha-beta pruning
Total quote time (warm)200-500msCache populated from recent queries
Total quote time (cold)1-3sFirst query for a chain pair
Memory usage (embedded)~128MBIn-memory cache, 4 bridge adapters
Memory usage (service)~512MBRedis 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.

Extensibility principle: Every component in the system is designed to be replaced or extended independently. Bridge adapters can be added without touching the routing core. Scoring weights can be adjusted without modifying the minimax evaluator. Cache backends can be swapped without changing the state collector. This modularity ensures the system can evolve as the cross-chain landscape changes.