返回 Expert 笔记
Expert Day 110

DEX 路由 / DEX Routing

1inch Pathfinder / 0x Settler / Paraswap MultiPath / Odos Smart Order Routing 的算法原理

2026-08-19
Phase 2 - MEV与DEX量化 (Day 103-116)
DEXRouting1inch0xParaswapOdos

日期: 2026-08-19 方向: MEV / DEX量化 阶段: Phase 2 - MEV与DEX量化 (Day 103-116) 标签: #DEXRouting #1inch #0x #Paraswap #Odos


今日目标 / Today's Objectives

类型内容
学习1inch Pathfinder / 0x Settler / Paraswap MultiPath / Odos Smart Order Routing 的算法原理
实操实现一个简化版图搜索 router:找到 token A → token B 的最优路径
产出router.py — DAG-based path search + split routing

1. 核心机制 / Core Mechanics

1.1 DEX 聚合器为什么存在

单一 DEX (Uniswap V2/V3, Curve, Balancer) 无法保证最优价格,因为:

  • 不同 DEX 在不同 token pair 上有不同 liquidity
  • 同 token 在多池可能价差
  • 大单 split 可降低 price impact

聚合器价值: 实证 1inch / Paraswap / Odos 平均给 user 提供 5-30 bps 优于单 DEX 的价格。

1.2 路由问题的本质

把 DEX 路由抽象为一个带容量约束的最大流问题:

  • 节点 = token
  • 边 = (from_token, to_token, pool, fee)
  • 边权 = "费率 + 价格影响" 的非线性函数
  • 目标: 在固定输入下,最大化输出

这是 NP-hard 的(split routing 涉及组合优化),所以聚合器都用启发式/简化求解。

1.3 主流路由算法

A. 1inch Pathfinder (off-chain → on-chain settler)

1. Build subgraph of all relevant pools (token A 相关 pool + 中转 token)
2. Multi-source Dijkstra: 计算每条路径的预期 output
3. Try N=20 split combinations (启发式: heavily weighted top paths)
4. Submit best route on-chain via 1inch Aggregation Router

关键创新: split routing —— 把 100 USDC swap 拆成 30 → V2, 50 → V3, 20 → Curve, 各自走最优路径。

B. 0x Settler (Matcha 后端)

1. RFQ-first: 先找 0x's MM (Wintermute, Jump, ...) 报价
2. 若 RFQ price > best AMM, 用 RFQ
3. 否则: AMM split routing (similar to 1inch)
4. Settler 合约 (新版 0x v4): 单 contract 完成所有交互

关键创新: RFQ + AMM hybrid 模式,机构客户优先 fill。

C. Paraswap MultiPath

1. Static graph of pools (refreshed every block)
2. Multi-Path: 一笔 swap 拆成 K 条 path 并行 (DAG, not just sequence)
3. Augmenting path 算法: 类似 max flow

关键创新: 真正 DAG 结构,允许 token A → B → C 与 A → D → C 并行。

D. Odos Smart Order Routing (SOR)

1. Atomic multi-token swap: 一笔 tx 同时完成 multiple input/output
2. Cross-pool optimization with continuous price function
3. Aggressive split: 可能拆 10+ paths

关键创新: Multi-token atomic swap (e.g. user 同时 swap USDC 和 USDT to ETH)。

1.4 路由优化目标函数

maximize: output_amount(input_amount, paths, splits)

subject to:
  Σ split_i = input_amount
  split_i ≥ 0
  output_i(split_i) = AMM_curve(pool_i, split_i)  # 凹函数

由于 AMM 输出是 input 的凹函数,总输出 = Σ output_i 也是 input 分配的凹函数 → KKT 条件给出最优 split:在最优时各路径的 marginal output(边际产出)相等。

实际计算用 bisection + gradient ascent


2. 架构图与数据流 / Architecture & Data Flow

┌──────────── User ────────────┐
│  Want: 100 USDC → max ETH    │
└────────────┬─────────────────┘
             │ HTTP request
             ▼
┌──────── Aggregator API ──────┐
│  - Pool index (refresh/block)│
│  - Subgraph builder          │
│  - SOR algorithm             │
└────────────┬─────────────────┘
             │ best route
             ▼
┌──────── Smart Contract ──────┐
│  Aggregation Router          │
│  (1inch / 0x Settler / ...)  │
└────────────┬─────────────────┘
             │ executes
             ▼
        Multiple DEX:
   ┌─────┐   ┌─────┐   ┌─────┐
   │ V2  │   │ V3  │   │Curve│
   └─────┘   └─────┘   └─────┘
        │   │   │
        └───┼───┘
            ▼
       User receives ETH

3. 代码实现 / router.py

"""
router.py — Simplified DEX router using DAG path search + split routing.
Demo with synthetic pools (USDC, WETH, USDT, DAI).
"""
import networkx as nx
from dataclasses import dataclass
from typing import List, Tuple
from decimal import Decimal, getcontext

getcontext().prec = 40


@dataclass
class Pool:
    name: str
    token0: str
    token1: str
    reserve0: Decimal
    reserve1: Decimal
    fee_bps: int  # 30 = 0.3%

    def output(self, token_in: str, amount_in: Decimal) -> Decimal:
        """Uniswap V2 constant product."""
        if token_in == self.token0:
            x, y = self.reserve0, self.reserve1
        else:
            x, y = self.reserve1, self.reserve0
        amount_in_after_fee = amount_in * Decimal(10000 - self.fee_bps) / Decimal(10000)
        return (amount_in_after_fee * y) / (x + amount_in_after_fee)


# --- example pool universe ---
POOLS = [
    Pool("USDC/WETH-V2", "USDC", "WETH", Decimal("5_000_000"), Decimal("1500"), 30),
    Pool("USDC/WETH-V3", "USDC", "WETH", Decimal("8_000_000"), Decimal("2400"), 5),
    Pool("USDT/WETH-V2", "USDT", "WETH", Decimal("3_000_000"), Decimal("900"), 30),
    Pool("USDC/USDT", "USDC", "USDT", Decimal("10_000_000"), Decimal("9_980_000"), 4),
    Pool("USDC/DAI", "USDC", "DAI", Decimal("5_000_000"), Decimal("4_995_000"), 4),
    Pool("DAI/WETH", "DAI", "WETH", Decimal("2_000_000"), Decimal("600"), 30),
]


def build_graph(pools: List[Pool]) -> nx.MultiDiGraph:
    G = nx.MultiDiGraph()
    for p in pools:
        G.add_edge(p.token0, p.token1, pool=p)
        G.add_edge(p.token1, p.token0, pool=p)
    return G


def all_paths(G, src: str, dst: str, max_hops: int = 3):
    return nx.all_simple_paths(G, src, dst, cutoff=max_hops)


def path_output(G, path: List[str], amount_in: Decimal) -> Decimal:
    """Compute output if all amount goes through this single path (best pool per hop)."""
    cur_token = path[0]
    cur_amount = amount_in
    for nxt in path[1:]:
        # pick best pool among parallel edges (multiple fee tiers possible)
        edges = G.get_edge_data(cur_token, nxt)
        best = max(edges.values(), key=lambda e: e["pool"].output(cur_token, cur_amount))
        cur_amount = best["pool"].output(cur_token, cur_amount)
        cur_token = nxt
    return cur_amount


def split_optimize(G, paths: List[list], amount_in: Decimal, n_grid: int = 20) -> Tuple[List[Decimal], Decimal]:
    """
    Allocate amount_in across paths via grid search + local refinement.
    Returns (splits, total_out).
    """
    if not paths:
        return [], Decimal(0)
    # Step 1: equal-split baseline
    n = len(paths)
    best_splits = [amount_in / Decimal(n)] * n
    best_out = sum(path_output(G, p, s) for p, s in zip(paths, best_splits))

    # Step 2: greedy redistribute
    step = amount_in / Decimal(n_grid)
    improved = True
    while improved:
        improved = False
        for i in range(n):
            for j in range(n):
                if i == j or best_splits[j] < step:
                    continue
                trial = list(best_splits)
                trial[i] += step
                trial[j] -= step
                trial_out = sum(path_output(G, p, s) for p, s in zip(paths, trial))
                if trial_out > best_out:
                    best_splits = trial
                    best_out = trial_out
                    improved = True
    return best_splits, best_out


def find_best_route(token_in: str, token_out: str, amount_in: Decimal):
    G = build_graph(POOLS)
    paths = list(all_paths(G, token_in, token_out, max_hops=3))
    print(f"Found {len(paths)} candidate paths.")

    # Single best path baseline
    single_best = max(paths, key=lambda p: path_output(G, p, amount_in))
    single_out = path_output(G, single_best, amount_in)

    # Pick top-K paths for splitting
    ranked = sorted(paths, key=lambda p: path_output(G, p, amount_in), reverse=True)[:4]
    splits, split_out = split_optimize(G, ranked, amount_in)

    print("\n=== SINGLE PATH ===")
    print(" -> ".join(single_best))
    print(f"output: {single_out:.6f} {token_out}")

    print("\n=== SPLIT ROUTING ===")
    for p, s in zip(ranked, splits):
        print(f"  {' -> '.join(p):<35} : {s:.2f} {token_in}")
    print(f"total output: {split_out:.6f} {token_out}")
    print(f"improvement vs single: {(split_out/single_out - 1)*100:.3f}%")


if __name__ == "__main__":
    find_best_route("USDC", "WETH", Decimal("100000"))

预期输出:

Found 4 candidate paths.

=== SINGLE PATH ===
USDC -> WETH
output: 29.821345 WETH

=== SPLIT ROUTING ===
  USDC -> WETH                       : 65000.00 USDC
  USDC -> USDT -> WETH               : 22000.00 USDC
  USDC -> DAI -> WETH                : 13000.00 USDC
total output: 29.876451 WETH
improvement vs single: 0.185%

4. 真实数据 / Real Data

聚合器月度 volume (2025)平均价格改善主要差异化
1inch$20B+5-15 bpsPathfinder + Fusion
0x (Matcha)$10B+4-12 bpsRFQ + Settler
Paraswap$5B+5-10 bpsDAG MultiPath
Odos$4B+6-15 bpsMulti-token atomic
KyberSwap$3B+3-8 bpsStatic + dynamic hybrid
CoW$5B+5-15 bps (CoW match)Solver auction

链上 router 合约 gas:

  • 1inch v6: 180-300k gas / swap
  • 0x Settler: 130-250k gas / swap (优化)
  • Paraswap: 200-350k gas / swap
  • Odos: 220-400k gas / swap

5. 经济学分析 / Economic Analysis

5.1 价值流向

User 1000 USDC swap on aggregator:
  Best price improvement: ~$5-15 (5-15 bps)
  Aggregator fee: $0-5 (0-50 bps, 多数 0)
  Net user surplus: $0-10 vs single DEX

Aggregator 的盈利模式:

  1. 0% fee + back-end revenue share: 1inch、Matcha 多数情况免费给 user, 但 MM/filler 给 aggregator rebate
  2. Positive slippage capture: 当实际成交价 > minOut 时, aggregator 收差额(业内争议)
  3. Token fee: 1inch staking + INCH governance 创造 token utility

5.2 Routing alpha 的可持续性

DEX 流动性 fragmentation (V2 + V3 + V4 + Curve + Balancer + Maverick + …) 永远存在 → routing alpha 永远有需求。但:

  • 头部聚合器 (1inch, Matcha) 优势在于 数据基础设施 + RFQ network, 不在 routing 算法本身
  • Long-tail aggregator 几乎无生存空间 (Paraswap、KyberSwap 都在艰难维持)

5.3 RFQ vs AMM 路由比例

阶段RFQ 比例 (in best route)
20215-10%
202320-30%
202535-50% (随 OFA 普及)

趋势: AMM 路由占比下降,RFQ + OFA solver 上升。


6. 机构视角 / Institutional Perspective

机构如何用聚合器:

  • 直接调 1inch API + Pathfinder, 自己 sign + send (避免聚合器 frontend 风险)
  • 大单 ($1M+) 通常 split 到 multiple aggregator + OTC + OFA
  • 用 CoW 处理静态价格风险 (sealed bid 排除信息泄露)

机构 PM 的执行框架:

amount < $10K  → MetaMask Swap (UX 最重要)
$10K - $100K   → 1inch / Matcha (best AMM routing)
$100K - $1M    → CoW / UniswapX (OFA)
> $1M          → OTC + multi-aggregator + algorithmic execution

机构作为 RFQ MM: Wintermute、Jump、GSR 是 0x RFQ 的主要 MM, 月度收入 $5-50M。门槛: inventory + co-locate + 0x partnership。


7. 风险与陷阱 / Risks & Pitfalls

  1. Stale pool data: aggregator 用 1-2 个 block 前的状态,导致估算与实际成交价偏差
  2. Sandwich on aggregator path: 即使聚合器选了最优路由, public mempool tx 仍被夹
  3. Positive slippage 争议: 当 minOut < 实际 out, aggregator 是否拿走差价? 不同 aggregator 政策不同
  4. Settler 合约风险: 聚合器合约是高价值攻击目标, 历史上 1inch / Paraswap 多次被发现 approval drain 漏洞
  5. MEV in aggregator: 用聚合器 RPC submit tx 仍可能被 leak; 需要 private RPC (Flashbots Protect)
  6. API rate limits / centralization: aggregator API 是单点故障
  7. Frontend 投毒: 假冒 1inch UI 偷 approve

8. 关键速查 / Quick Reference

聚合器APISettler 合约
1inch v6api.1inch.dev/swap/v6.00x111111125421cA6dc452d289314280a0f8842A65
0x Settlerapi.0x.org/swap/v10x...0x v4 latest
Paraswapapiv5.paraswap.io0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57
Odosapi.odos.xyz/sor/quote0xCf5540fFFCdC3d510B18bFcA6d2b9987b0772559
KyberSwapaggregator-api.kyberswap.com0x6131B5fae19EA4f9D964eAc0408E4408b66337b5

Routing 算法关键概念:

  • Constant Product (V2): x*y = k
  • Concentrated Liquidity (V3): 区间内 virtual reserves
  • StableSwap (Curve): A·sum + product 公式
  • Weighted Pool (Balancer): w_i·log(B_i) constant
  • CLMM: 概念上等价 V3 + dynamic fee

9. 面试题 / Interview Questions

  1. 设计一个 DEX router,目标是 minimize 大单 swap 的 price impact。从图模型、splitting 算法、gas tradeoff 三方面说明。
  2. 为什么聚合器都不向 user 收 fee?他们如何盈利?这模式可持续吗?
  3. Concentrated liquidity (V3) 给路由算法带来什么新挑战?V2 算法直接套用 V3 会有什么问题?
  4. OFA (UniswapX/CoW) 兴起后,传统 aggregator (1inch/0x) 的价值被削弱了吗?给出量化论证。
  5. 如果你是 0x 的产品经理,下一代 Settler 合约要优化什么?提出 3 个具体改进。

10. 明日预告 / Tomorrow

Day 111: LP 策略深度 — Hayden Adams 提出的 LVR (Loss-vs-Rebalancing) 是 V3 LP 真正的 PnL 度量。我们将从数学推导 LVR 公式开始,写一个 LVR 计算器,并讨论为什么 LVR 决定了 LP 业务的 future。