Uniswap学习笔记
简介
v1版本,基于恒定乘积做市商模型( x × y = k x\times y = k x×y=k)
v2版本,包含Core和Periphery
- Factory 管生(创建池子)
- Pair 管养(存钱、算价、交易)
- Router 管用(让用户方便地操作)
其支持预言机机制,TWAP(Time-Weighted Average Price)时间加权平均价格
v3版本
- 增加集中流动性
- 费率分层:支持0.05%/0.30%/1.00%
- 非同质化流动性
- 高级预言机:优化了 TWAP 的计算方式,支持更高效的价格查询
增强关系式
L = x y P = y x L = Δ y Δ P \begin{align*}L &= \sqrt {x y} \\ \sqrt{P}&= \sqrt {\frac{y}{x}}\\ L&= \frac{\Delta y}{\Delta \sqrt{P}} \end{align*} LPL=xy=xy=ΔPΔy
v4 版本
单例类PoolManager管理所有的代币对池,支持Hooks
V2
Core核心
主要有
- UniswapV2Factory
- UniswapV2Pair
UniswapV2Pair
比较使用 k b e f o r e ≤ k a f t e r k_{before} \le k_{after} kbefore≤kafter
v2交易对,其主要成员有reserve0/reserve1:表示交易对的两种代币的储备金量totalSupply:表示所有用户的LP代币总数量factory:表示创建交易对的工厂合约token0/token1:表示交易对的两种代币合约地址price0CumulativeLast/price1CumulativeLast:表示TWAP预言机的核心变量,是对“边际价格 × 持续时间”进行连续积分,其计算公式为 p r i c e 0 C u m u l a t i v e L a s t = ∑ r e s e r v e 1 ∗ 2 112 r e s e r v e 0 ∗ Δ t price0CumulativeLast=\sum{\frac{reserve1 * 2^{112}} {reserve0}} * \Delta t price0CumulativeLast=∑reserve0reserve1∗2112∗Δt
p r i c e 1 C u m u l a t i v e L a s t = ∑ r e s e r v e 0 ∗ 2 112 r e s e r v e 1 ∗ Δ t price1CumulativeLast =\sum{\frac{reserve0 * 2^{112}}{reserve1}} * \Delta t price1CumulativeLast=∑reserve1reserve0∗2112∗Δt
其中 Δ t = b l o c k T i m e s t a m p L a s t − b l o c k T i m e s t a m p L a s t l a s t \Delta t = blockTimestampLast - blockTimestampLast_{last} Δt=blockTimestampLast−blockTimestampLastlastblockTimestampLast:表示上次计算连续积分时的区块时间戳对 2 32 2^{32} 232的模
关键操作
| 名称 | 说明 |
|---|---|
mint |
增加流动性 |
burn |
减少流动性 |
swap |
实现资产交换 |
mint:对于初次增加流动性,其流动性计算公式为 a m o u n t 0 ∗ a m o u n t 1 − 10 3 \sqrt{amount0 * amount1} - 10^3 amount0∗amount1−103追加情况时计算公式为 min { a m o u n t 0 r e s e r v e 0 ∗ t o t a l S u p p l y , a m o u n t 1 r e s e r v e 1 ∗ t o t a l S u p p l y } \min \{ \frac{amount0}{reserve0} *totalSupply, \frac{amount1}{reserve1} * totalSupply \} min{reserve0amount0∗totalSupply,reserve1amount1∗totalSupply} 其中amount0/amount1为存入的交易对代币值
如果设置了要收协议费,即factory的feeTo()接收地址不为0,协议费计算公式为 k − k l a s t 5 k + k l a s t ∗ t o t a l S u p p l y \frac{k - k_{last}}{5k + k_{last}} * totalSupply 5k+klastk−klast∗totalSupply 其中 k = r e s e r v e 0 ∗ r e s e r v e 1 k=\sqrt{reserve0 * reserve1} k=reserve0∗reserve1, k l a s t k_{last} klast表示上一次计算的 k k kswap:由Router来调用,其传的参数amount0Out和amount1Out其中一个必定为0,采用了“乐观转账(Optimistic Transfer)”模式,主要承担三件职责
- 资产交割:根据输入的参数,将代币从流动性池(Pair)转移至目标地址(to)
- 数学验证:在转账后,通过余额反推输入金额,并验证扣除 0.3% 手续费后的乘积是否不小于交易前的乘积
- 状态同步:更新储备金余额和时间戳,为 TWAP(时间加权平均价格)预言机提供数据。
交换时需要满足 x × y ≥ k x \times y \ge k x×y≥k。闪电贷是通过swap来实现的,uniswapV2Call 中实现偿还贷款及费用
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
(uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');
uint balance0;
uint balance1;
{ // scope for _token{0,1}, avoids stack too deep errors
address _token0 = token0;
address _token1 = token1;
require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
balance0 = IERC20(_token0).balanceOf(address(this));
balance1 = IERC20(_token1).balanceOf(address(this));
}
uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
{ // scope for reserve{0,1}Adjusted, avoids stack too deep errors
uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
}
_update(balance0, balance1, _reserve0, _reserve1);
emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}
Periphery外围
主要包含
- UniswapV2Migrator
- UniswapV2Router01(已废弃)
- UniswapV2Router02
UniswapV2Router02
通过amountIn计算amoutOut公式为 r e s e r v e 0 ⋅ r e s e r v e 1 = ( r e s e r v e 0 + 0.997 ⋅ a m o u n t I n ) ⋅ ( r e s e r v e 1 − a m o u n t O u t ) reserve0 \cdot reserve1 = (reserve0 + 0.997 \cdot amountIn) \cdot (reserve1 - amountOut) reserve0⋅reserve1=(reserve0+0.997⋅amountIn)⋅(reserve1−amountOut)
即 a m o u t O u t = 0.997 ⋅ r e s e r v e 1 ⋅ a m o u n t I n r e s e r v e 0 + 0.997 ⋅ a m o u n t I n amoutOut = \frac{0.997 \cdot reserve1 \cdot amountIn}{reserve0 + 0.997 \cdot amountIn} amoutOut=reserve0+0.997⋅amountIn0.997⋅reserve1⋅amountIn
| 函数 | 说明 |
|---|---|
swapExactTokensForTokens |
path中的第一个为输入token,即根据一个amoutIn,批量计算amoutOut |
swapTokensForExactTokens |
path中的最后一个为输出token,即根据一个amoutOut批量计算amoutIn |
UniswapV2Library
| 函数 | 说明 |
|---|---|
getAmountOut |
根据amountIn计算amountOut |
getAmountIn |
根据amoutOut计算amoutIn |
getAmountsOut |
批量计算amountOut |
getAmountsIn |
批量计算amoutIn |
getReserves |
返回代币对的储备量 |
quote |
计算LP代币的价值 |
V3
core核心
主要有
- UniswapV3Factory
- UniswapV3Pool
P ( i ) = 1.0001 i × 2 96 \sqrt{P(i)} = \sqrt{1.0001^i} \times 2^{96} P(i)=1.0001i×296,其中 i i i表示tick的索引UniswapV3Pool的主要成员有
| 成员 | 说明 |
|---|---|
slot0 |
池子的“仪表盘” |
ticks |
记录每个 Tick 边界上流动性变化的账本 |
tickBitmap |
这是一张位图(Bitmap),用于快速寻找“下一个有流动性的 Tick” ,以256为一组 |
positions |
非同质化的仓位账本,是以地址+下界+上界作为key。position即头寸,即一个区间相关的信息 |
struct Slot0 {
// the current price
uint160 sqrtPriceX96;
// the current tick
int24 tick;
// the most-recently updated index of the observations array
uint16 observationIndex;
// the current maximum number of observations that are being stored
uint16 observationCardinality;
// the next maximum number of observations to store, triggered in observations.write
uint16 observationCardinalityNext;
// the current protocol fee as a percentage of the swap fee taken on withdrawal
// represented as an integer denominator (1/x)%
uint8 feeProtocol;
// whether the pool is locked
bool unlocked;
}
mapping(int24 => Tick.Info) public override ticks;
mapping(int16 => uint256) public override tickBitmap;
mapping(bytes32 => Position.Info) public override positions;
//Tick.Info
struct Info {
// the total position liquidity that references this tick
uint128 liquidityGross;
// amount of net liquidity added (subtracted) when tick is crossed from left to right (right to left),
int128 liquidityNet;
// fee growth per unit of liquidity on the _other_ side of this tick (relative to the current tick)
// only has relative meaning, not absolute — the value depends on when the tick is initialized
uint256 feeGrowthOutside0X128;
uint256 feeGrowthOutside1X128;
// the cumulative tick value on the other side of the tick
int56 tickCumulativeOutside;
// the seconds per unit of liquidity on the _other_ side of this tick (relative to the current tick)
// only has relative meaning, not absolute — the value depends on when the tick is initialized
uint160 secondsPerLiquidityOutsideX128;
// the seconds spent on the other side of the tick (relative to the current tick)
// only has relative meaning, not absolute — the value depends on when the tick is initialized
uint32 secondsOutside;
// true iff the tick is initialized, i.e. the value is exactly equivalent to the expression liquidityGross != 0
// these 8 bits are set to prevent fresh sstores when crossing newly initialized ticks
bool initialized;
}
//Position.Info
struct Info {
// the amount of liquidity owned by this position
uint128 liquidity;
// fee growth per unit of liquidity as of the last update to liquidity or fees owed
uint256 feeGrowthInside0LastX128;
uint256 feeGrowthInside1LastX128;
// the fees owed to the position owner in token0/token1
uint128 tokensOwed0;
uint128 tokensOwed1;
}
Tick.Info中计算单位增长费率的参数
feeGrowthOutside0X128:代币0在该tick相对池中当前tick的单位增长费率,即不包含当前tick的那一方feeGrowthOutside01X128:代币1在该tick相对池中当前tick的单位增长费率,即不包含当前tick的那一方
tick,position,pool 的feeGrowth的关系
- tick中fee的修改是在更新(即tick小于池中当前tick时,会初始经为池中的全局值)以及跨tick时(翻转即全局值减去tick记录的外部值)
- position中fee的修改是在更新即增加区间流动性时
- pool 中fee的修改是在资金交易时
主要方法有
| 方法 | 说明 |
|---|---|
mint |
流动性提供者(LP)向特定价格区间注入资金 |
burn |
移除流动性,实际没有把钱转给用户,只是销毁账本上的流动性记录 |
collect |
把钱转给用户 |
swap |
zeroForOne为true表示用 token0 买 token1即抛售token0,买入token1会导致价值下降,为false时表示用token1买token0即抛售token1买入token0会导致价值上升amountSpecified正数表示知道需要花多少输入代币,负数表示知道需要收到多少输出代币 |
mint主要步骤为
- 修改position信息(
_modifyPosition)- 检查
tickLower和tickUpper的有效性,要求tickLower小于tickUpper,tickLower不能小于TickMath.MIN_TICK,tickUpper不能大于TickMath.MAX_TICK - 更新
positions,ticks和tickBitmap信息(_updatePosition),在ticks[tickLower]处增加流动性(liquidityNet)同时增加liquidityGross,在ticks[tickUpper]处减少流动性,更新 positions映射,主要包含以下数据liquidity:头寸拥有的流动性数量feeGrowthInside0LastX128 :上一次你与该头寸交互时,token0 在该价格区间内的每单位流动性累积手续费快照feeGrowthInside1LastX128:上一次你与该头寸交互时,token1在该价格区间内的每单位流动性累积手续费快照tokensOwed0:已经结算但尚未提取的 token0 手续费tokensOwed1:已经结算但尚未提取的 token1 手续费
- 如果
slot0中的当前tick正好在这个区间内,会增加池子的流动性liquidity - 如果当前
tick在区间内,你需要提供两种代币,如果tick在区间下方,只提供token0,如果tick在区间上方,只提供token1
- 检查
- 回调索取资金
uniswapV3MintCallback,乐观记账,事后索偿
swap的步骤为
- 找下一tick(
nextInitializedTickWithinOneWord),当zeroForOne为真时,即价格下跌时,取小于tick的一端,当zeroForOne为假时,即价格上升时,取大于tick的一端 - 算该步可以换多少(
computeSwapStep) - 处理穿越(
cross),更新tick时总是取下界 - 更新全局状态,即
slot0中的sqrtPriceX96和tick以及池中的流动性liquidity - 池子帐转为用户帐,即作转帐
safeTransfer - 执行回调
uniswapV3SwapCallback
periphery外围
主要是两个合约
| 合约 | 说明 |
|---|---|
NonfungiblePositionManager |
用于管理流动性提供者行为 |
SwapRouter |
用于管理交易者的行为 |
sdk
v2-sdk/v3-sdk基于ethers.js或者wagmi得到链上连接,主要是获取、构造交易数据
- 读链上状态
- 链下 quote
- 生成调 Periphery Router 的 calldata
实体类(Entities)有
Pool:流动性池Position:流动性仓位Route:交易路由,用于跨池交易Tick:价格刻度Trade:交易
其中交易构造器(Trade & Router)负责生成最终发送给链上 Router 合约(如 SwapRouter)的调用数据(Calldata),包括处理单跳、多跳交换以及路径编码SwapQuoter:封装了与 Quoter(报价器)合约的交互,用于获取预估价格
uniswap v3-sdk发起交易时如何调用
- 初始化核心对象与 Router 合约
import { Token, CurrencyAmount, TradeType, Percent } from '@uniswap/sdk-core';
import { Pool, Route, Trade, Position } from '@uniswap/v3-sdk';
import { ethers } from 'ethers';
import ISwapRouter from '@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json';
// 1. 定义代币 (以 ETH 和 USDC 为例)
const WETH = new Token(1, '0xC02aa...', 18, 'WETH', 'Wrapped Ether');
const USDC = new Token(1, '0xA0b86...', 6, 'USDC', 'USD Coin');
// 2. 连接钱包和 Router 合约
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const routerAddress = '0xE592427A0AEce92De3Edee1F18E0157C05861564'; // 主网 SwapRouter 地址
const routerContract = new ethers.Contract(routerAddress, ISwapRouter.abi, signer);
- 获取链上池子数据并构建路由
SDK 需要在链下复刻池子的状态来计算最优路径。你需要从链上读取当前池子的 sqrtPriceX96 和 liquidity 等数据
// 3. 获取特定费率(如 0.3%)的池子数据
const poolAddress = Pool.getAddress(WETH, USDC, 3000);
// 这里需要通过 Multicall 或 RPC 读取链上 Pool 合约的 slot0 获取 sqrtPriceX96 和当前 tick
const pool = new Pool(WETH, USDC, 3000, sqrtPriceX96, liquidity, tickCurrent);
// 4. 构建路由 (这里演示单跳,多跳可以传入多个 Pool)
const route = new Route([pool], WETH, USDC);
- 构造交易并计算滑点保护
使用 SDK 的 Trade 类来构建交易对象,它会自动帮你计算输入输出金额,并设置滑点容忍度
// 5. 设定交易金额 (例如卖出 1 个 WETH)
const amountIn = CurrencyAmount.fromRawAmount(WETH, ethers.utils.parseUnits('1', 18).toString());
// 6. 使用 SDK 构造交易
const trade = await Trade.fromRoute(route, amountIn, TradeType.EXACT_INPUT);
// 7. 设置滑点容忍度 (例如 0.5%)
const slippageTolerance = new Percent(50, 10000); // 0.5%
const amountOutMinimum = trade.minimumAmountOut(slippageTolerance).quotient.toString();
const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20分钟后过期
- 调用 Router 合约发起链上交易
最后,将 SDK 计算好的参数填入 SwapRouter 的 exactInputSingle 方法中,通过钱包发起交易
// 8. 组装链上调用参数
const params = {
tokenIn: WETH.address,
tokenOut: USDC.address,
fee: 3000,
recipient: await signer.getAddress(), // 接收代币的地址
deadline: deadline,
amountIn: amountIn.quotient.toString(),
amountOutMinimum: amountOutMinimum, // 考虑滑点后的最小输出
sqrtPriceLimitX96: 0 // 设为 0 表示不限制价格
};
// 9. 发起交易 (需确保用户已授权 Router 花费 WETH)
const tx = await routerContract.exactInputSingle(params);
console.log('交易已发送,哈希:', tx.hash);
// 10. 等待交易上链确认
await tx.wait();
console.log('交易成功!');
资料
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)