Skip to content

🔄 去中心化交易所(DEX)与自动化做市商(AMM)详解

1 ⛓️ DEX 与 AMM 概述

与传统中心化交易所(CEX)不同,DEX 允许用户直接从自己的钱包中进行点对点的资产兑换,无需注册、KYC 或将资产托管给第三方。其核心优势在于:

  • 自我托管:用户始终掌握资产私钥,从根本上消除了交易所被盗或跑路的风险。
  • 无许可上币:任何项目都可以无需审核地创建交易对,提供了极大的开放性。
  • 抗审查性:任何人都无法单方面阻止某个地址进行交易。

而 AMM 是当今 DEX 的核心技术引擎。它用智能合约和资金池取代了传统的订单簿。用户不再与交易对手匹配,而是与池中的流动性进行交互。

2 🔄 完整的兑换流程

一次典型的 DEX 兑换涉及用户、前端界面和多个智能合约的复杂交互。其核心流程和资金流向如下图所示:

swap

2.1 用户交互与查询

  1. 连接钱包:用户首先通过 Web3 钱包(如 MetaMask)连接至 DEX 前端界面。
  2. 选择交易对和数量:用户选择想要卖出的代币(Input)和想要买入的代币(Output),并输入卖出数量。
  3. 前端查询报价:前端界面会向智能合约查询这笔交易预计可得到的输出代币数量。这个过程是只读的,不消耗 Gas。
    • 前端会调用路由合约的 getAmountsOut 函数,传入路径和输入数量,计算出输出数量。
    • 同时,前端会从链上或自己的数据库中获取当前交易的滑点容忍度(Slippage Tolerance)和交易截止时间(Deadline)的默认值。

2.2 交易执行与结算

  1. 用户确认:用户确认报价,并可以调整滑点容忍度和 Gas 费。较低的滑点容忍度可以防止大幅的价格波动,但也可能导致交易失败。
  2. 交易签名:用户点击“Swap”,钱包会弹出请求签名的提示。用户签名后,交易被广播到区块链网络。
  3. 智能合约执行
    • 路由合约(Router):这是处理复杂交易的核心。它负责找到最佳兑换路径(例如,直接 USDC->ETH,或通过中间池 USDC->USDT->ETH)。
    • 资金池合约(Pair):路由合约最终会调用目标资金池的 swap 函数。该函数会执行以下操作:
      • 验证:检查交易是否满足预设条件(如未超过截止时间、输出代币数量大于用户设置的最小值)。
      • 计算:根据恒定乘积公式(x * y = k) 计算用户应收到的代币数量,并扣除手续费。
      • 更新储备金:减少输出代币的储备量,增加输入代币的储备量。
      • 转账:将输出代币发送到用户地址。
  4. 交易完成:区块确认后,用户的钱包中会收到输出代币,前端界面会更新显示交易成功的状态。

3 💰 收益与佣金分配机制

在 AMM 模型中,收益主要来自于交易手续费,并由流动性提供者和协议平台共享。

3.1 用户的收益(流动性提供者 - LPs)

普通交易用户没有直接收益,反而需要支付手续费。收益主要来自于为资金池提供流动性的用户,即流动性提供者(LPs)

  • 收益来源交易手续费。每笔交易都会收取一定比例的费用(常见为 0.3%),这部分费用会直接添加到资金池的储备金中。
  • 收益计算:LP 的收益是其按份额占有的那部分手续费
    • 当你注入流动性时,你会获得LP 代币(如 UNI-V2),代表你在该资金池中的份额。
    • 你的份额 = (你提供的流动性数量) / (池中总流动性数量)
    • 你的收益 = 总手续费收入 * 你的份额
  • 无常损失(Impermanent Loss):这是提供流动性时的主要风险。当两种代币的市场价格比率发生变化时,与简单持有代币相比,LP 可能会遭受损失。手续费收益就是对冲这一风险和激励 LP 提供流动性的补偿。

3.2 平台的收益

协议的收益(佣金)是通过从总手续费中抽成来实现的。

  • 手续费抽成(Fee Take):这是最主流的方式。协议不会额外收费,而是从 LP 应得的手续费中抽取一部分。例如:
    • 总交易手续费率为:0.3%
    • 其中,0.25% 分配给所有 LP。
    • 另外 0.05% 则转入协议的国库地址(Treasury) 作为平台收入。
  • 其他模式
    • 原生代币回购与销毁:部分协议(如 PancakeSwap)会使用部分平台收入在公开市场上回购并销毁其原生代币(如 CAKE),创造通缩压力。
    • 治理与激励:平台收入可以用于资助生态开发、市场推广,或作为激励分配给质押了平台治理代币的用户。

3.3 佣金分配案例

假设一个 USDC/ETH 池:

  • 总流动性:$1,000,000
  • 每日交易量:$10,000,000
  • 总手续费率:0.3%
  • 协议抽成率:0.05% (即总手续费的 1/6)
  • LP 手续费率:0.25%

每日手续费总收入: $10,000,000 * 0.3% = $30,000

平台每日佣金收入: $10,000,000 _ 0.05% = $5,000 (或 $30,000 _ (1/6) = $5,000)

LP 每日总收益: $10,000,000 _ 0.25% = $25,000 (或 $30,000 _ (5/6) = $25,000)

如果一个 LP 提供了该池总流动性 1%的资产(价值$10,000),那么他每日可以分得: $25,000 * 1% = $25 的手续费收益。

4 ⚙️ 技术实现:前后端与智能合约

4.1 智能合约开发

DEX 的核心是经过审计的、gas 优化的智能合约。通常采用 Uniswap V2 的架构作为基础。

4.1.1 核心合约结构

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

// 核心配对合约(代表一个交易对,如USDC/ETH)
contract UniswapV2Pair {
    address public factory;
    address public token0;
    address public token1;

    uint112 private reserve0;           // 代币0的储备量
    uint112 private reserve1;           // 代币1的储备量
    uint32  private blockTimestampLast; // 最后更新时间戳

    uint public totalSupply; // LP代币总供应量
    mapping(address => uint) public balanceOf; // LP代币余额

    // 恒定乘积公式计算
    function getAmountOut(uint amountIn, address tokenIn) public view returns (uint amountOut) {
        uint amountInWithFee = amountIn * 997; // 扣除0.3%手续费
        uint numerator = amountInWithFee * (tokenIn == token0 ? reserve1 : reserve0);
        uint denominator = (tokenIn == token0 ? reserve0 : reserve1) * 1000 + amountInWithFee;
        amountOut = numerator / denominator;
    }

    // 兑换函数(外部由路由合约调用)
    function swap(uint amount0Out, uint amount1Out, address to) external {
        // 1. 安全检查...
        // 2. 从合约中转出输出代币给用户 `to`
        // 3. 更新储备金
        _update(balance0, balance1);
    }

    // 更新储备金
    function _update(uint balance0, uint balance1) private {
        reserve0 = uint112(balance0);
        reserve1 = uint112(balance1);
        blockTimestampLast = uint32(block.timestamp);
        emit Sync(reserve0, reserve1); // 触发同步事件
    }
}

// 工厂合约(创建和管理所有交易对)
contract UniswapV2Factory {
    mapping(address => mapping(address => address)) public getPair;
    address[] public allPairs;
    address public feeTo; // 协议收费地址

    function createPair(address tokenA, address tokenB) external returns (address pair) {
        // 创建新的Pair合约
        bytes memory bytecode = type(UniswapV2Pair).creationCode;
        bytes32 salt = keccak256(abi.encodePacked(tokenA, tokenB));
        assembly {
            pair := create2(0, add(bytecode, 32), mload(bytecode), salt)
        }
        UniswapV2Pair(pair).initialize(tokenA, tokenB);
        getPair[tokenA][tokenB] = pair;
        getPair[tokenB][tokenA] = pair;
        allPairs.push(pair);
    }
}

// 路由合约(处理复杂交易和路由)
contract UniswapV2Router {
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin, // 最小输出数量(防滑点)
        address[] calldata path, // 兑换路径
        address to,
        uint deadline
    ) external ensure(deadline) returns (uint[] memory amounts) {
        // 1. 计算路径中每一步的预期输出量
        amounts = getAmountsOut(amountIn, path);
        require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');

        // 2. 将用户的输入代币转入第一个Pair合约
        TransferHelper.safeTransferFrom(
            path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
        );

        // 3. 循环调用路径上的每一个Pair合约进行兑换
        _swap(amounts, path, to);
    }

    function _swap(uint[] memory amounts, address[] memory path, address _to) internal {
        for (uint i; i < path.length - 1; i++) {
            (address input, address output) = (path[i], path[i + 1]);
            (address token0,) = UniswapV2Library.sortTokens(input, output);
            uint amountOut = amounts[i + 1];
            (uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
            address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
            IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
                amount0Out, amount1Out, to, new bytes(0)
            );
        }
    }
}

4.1.2 安全与优化考量

  • 重入攻击防护:在关键函数中使用nonReentrant修饰符(如来自 OpenZeppelin 的ReentrancyGuard)。
  • 精度处理:妥善处理不同代币的decimals差异,使用足够的精度进行数学运算(通常乘以1e18)。
  • 闪电贷:AMM 本身支持闪电贷。swap函数不需要特殊的闪电贷代码,但需确保在所有外部调用之前完成状态更新。
  • Gas 优化:使用create2部署 Pair 合约、使用uint112存储储备金以优化存储布局。

4.2 后端开发

后端主要负责提供链下数据索引和 API,以增强前端体验。

  • 技术栈:Node.js + Express/NestJS, PostgreSQL, Redis。
  • 核心任务
    • 事件监听与索引:使用Ethers.js监听链上事件(Swap, Mint, Burn, Sync),将交易、池子、用户余额等数据存入数据库。
    • 计算聚合数据:计算并缓存全局数据,如总锁仓量(TVL)、24 小时交易量、热门交易对、代币价格等。
    • 提供 API
      • GET /pairs: 返回所有交易对列表及其数据。
      • GET /pairs/:address: 返回特定交易对的详细数据。
      • GET /pairs/:address/transactions: 返回交易对的最近交易记录。
    • 获取代币元数据:从链上或第三方服务获取代币的图标、名称、符号等信息。

4.3 前端开发

前端是用户直接交互的界面,需要直观、响应快且安全。

  • 技术栈:React + Vite, Ethers.js, Web3Modal (用于钱包连接), Chart.js (用于显示价格图表)。
  • 核心功能
    • 钱包连接:集成多种钱包(MetaMask, WalletConnect, Coinbase Wallet)。
    • 数据展示:从后端 API 和链上合约获取数据,展示代币价格、池子流动性、24 小时成交量、历史图表等。
    • 兑换界面
      • 实时获取和显示报价。
      • 允许用户设置滑点容忍度和交易截止时间。
      • 显示预估的 Gas 费用。
    • 流动性管理界面:允许用户添加/移除流动性,并直观地显示他们的头寸和累计费用。
    • 交易状态跟踪:显示交易状态,并提供区块链浏览器的链接。