首个 Uniswap 集成实战:从零开始写、测、部署 Swap 智能合约

·

想在主网之外安全玩透 Uniswap V3?这篇指南把从环境到代码、再到单元测试的完整流程拆解透彻,开发者只需一步步跟着做,就能快速拥有可复用的 Uniswap 开发环境,并亲手完成第一个链上代币兑换合约。

目录


环境准备:一键启动本地 Hardhat + 主网分叉

1. 安装工具链

Uniswap 官方并没有限定框架,但日常开发最顺手的组合是:

2. 创建 Alchemy 账号并获取 API Key

  1. 打开 https://www.alchemy.com/ 注册账号(免费)。
  2. 点击「Create App」→ 选择 Ethereum → Mainnet → 生成 API Key。
    把这个 Key 保存好,下一步就要用到。

3. 启动主网分叉节点

# 把 {YOUR_API_KEY} 换成上一步生成的字符串
npx hardhat node --fork https://eth-mainnet.alchemyapi.io/v2/{YOUR_API_KEY}

出现 Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/ 你就成功了:
此刻本地 8545 端口跑的是一条完全复制主网状态的测试链,所有 Uniswap Pool、Pair、Factory 地址俱全,gas 还是假的,随便玩。


克隆官方样板项目

不想从零搭脚手架?直接切到官方预先配好的仓库即可:

git clone https://github.com/Uniswap/uniswap-first-contract-example.git
cd uniswap-first-contract-example
npm install        # 安装依赖

仓库已内置


编写 SimpleSwap 合约

步骤 1:初始化文件

打开 contracts/SimpleSwap.sol,骨架已给出。先把关键常量补全:

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';

contract SimpleSwap {
    ISwapRouter public immutable swapRouter;

    address public constant DAI   = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    uint24  public constant FEE_TIER = 3000;  // 0.3%

    constructor(ISwapRouter _swapRouter) {
        swapRouter = _swapRouter;
    }
}

步骤 2:实现 swapWETHForDAI

新增对外调用函数,实现精确输入模式(exact input):

function swapWETHForDAI(uint256 amountIn) external returns (uint256 amountOut) {
    // 1. 把用户的 WETH 拉到合约
    TransferHelper.safeTransferFrom(
        WETH9, msg.sender, address(this), amountIn
    );

    // 2. 授权 SwapRouter 花这笔 WETH
    TransferHelper.safeApprove(
        WETH9, address(swapRouter), amountIn
    );

    // 3. 组装参数并执行兑换
    ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
        tokenIn: WETH9,
        tokenOut: DAI,
        fee: FEE_TIER,
        recipient: msg.sender,
        deadline: block.timestamp,
        amountIn: amountIn,
        amountOutMinimum: 0,
        sqrtPriceLimitX96: 0
    });

    amountOut = swapRouter.exactInputSingle(params);
    return amountOut;
}

至此,合约逻辑写完:拉起金额 → 授权 → 交易,一行多余代码都没有。


单元测试:在“分叉主网”跑通交易

Test 文件位置

test/SimpleSwap.test.js 已搭好骨架,只需填充。

Test 流程步骤

  1. 部署 SimpleSwap,注入主网 SwapRouter 地址。
  2. 取 Hardhat 自带账户,把 10 ETH wrap 出 10 WETH。
  3. 授权合约可划 1 WETH。
  4. 调用 swapWETHForDAI(0.1 WETH)
  5. 断言 DAI 余额增加。

关键代码片段:

const DAIBalanceBefore = Number(
  ethers.utils.formatUnits(await DAI.balanceOf(signer.address), 18)
);

await WETH.connect(signer).approve(simpleSwap.address, ethers.utils.parseEther('1'));
await simpleSwap.connect(signer).swapWETHForDAI(ethers.utils.parseEther('0.1'));

const DAIBalanceAfter = Number(
  ethers.utils.formatUnits(await DAI.balanceOf(signer.address), 18)
);

expect(DAIBalanceAfter).to.be.greaterThan(DAIBalanceBefore);

运行测试

确保此前用 npx hardhat node --fork ... 启动的节点仍在后台,然后:

npx hardhat test --network localhost

看到绿色 ✔ Should provide a caller with more DAI than they started with after a swap 代表流程一步到位,主网 fork 环境已可反复利用。


常见问题 FAQ

Q1:如果我在本地测试没问题,如何部署到测试网?

A:在 hardhat.config.js 里新增 goerli 网络配置,把 INFURA_KEY 或 Alchemy Goerli Key 放进去;然后在 Goerli 领取测试 ETH + 新版 WETH 与 DAI(可直接用 Uniswap 的测试币“水龙头楼”),最后 npx hardhat run scripts/deploy.js --network goerli 即可。

Q2:mainnet fork 遇到块高延迟怎么办?

A:在 node 启动命令加 --fork-block-number 17100000 固定块高,既能提速又能避免同名 Token 迁移带来的干扰。

Q3:amountOutMinimum 设 0 会不会被“三明治”攻击?

A:会。示例仅用于学习。实际生产建议:链下把期望最小输出量计算好再传进来,或用 Quoter 合约实时采样。

Q4:可以把 swapWETHForDAI 做成任意 ERC-20 之间的互换吗?

A:当然可以。改造方向:通过 ExactInputParams 构造多跳路径;再用 Path 库拼接 token → pool → token 字符串即可。


进阶路线图

做完最基础的 WETH→DAI 一对一交换后,可以挑战以下“作业”,每一个都能深度学习 Uniswap 深层设计:

若你准备把“纸上谈兵”变成活生生的产品,👉 立即查看如何安全部署到主网还能监控实时价格。


结语
一次部署、多次复用:Hardhat + 主网分叉 + Uniswap V3 正成为链上开发者最丝滑的“三件套”。
把这份 SimpleSwap 作为起点,再加上你的创意,无论做 DEX 聚合器、收益策略还是游戏内兑换,都将拥有世界第一深度流动性做后盾。你的下一个 DeFi 爆款,也许就从这一行 exactInputSingle 开始。