提供流动性 (V3)
V3 允许 LP 将流动性集中在特定价格范围内,以获得更高的资本效率。每个头寸都是一个具有唯一参数的 NFT。
创建头寸
1. 选择池参数
- 代币对:两种代币
- 手续费等级:0.01%、0.05%、0.3% 或 1%(参阅 手续费等级)
- 价格范围:
[tickLower, tickUpper](参阅 Ticks 与范围)
2. 获取当前池状态
const V3_FACTORY = '0x0dfb1Bb755d872EA1fa4d95E4ad0c2E6317Ce9B9'; // 主网
const V3_POSITION_MANAGER = '0x343b244bEDF133D57C61b241557bF29AA32ea4F9';
const FACTORY_ABI = ['function getPool(address,address,uint24) view returns (address)'];
const POOL_ABI = [
'function slot0() view returns (uint160, int24, uint16, uint16, uint16, uint8, bool)',
'function tickSpacing() view returns (int24)'
];
const factory = new ethers.Contract(V3_FACTORY, FACTORY_ABI, provider);
const poolAddress = await factory.getPool(token0, token1, 3000);
const pool = new ethers.Contract(poolAddress, POOL_ABI, provider);
const [sqrtPriceX96, currentTick] = await pool.slot0();
const tickSpacing = await pool.tickSpacing();
3. 计算 Tick 范围
将你期望的价格范围对齐到池的 tick 间距:
function nearestUsableTick(tick, tickSpacing) {
return Math.round(tick / tickSpacing) * tickSpacing;
}
// 示例:当前价格上下浮动 ±10%
const tickLower = nearestUsableTick(currentTick - 2000, tickSpacing);
const tickUpper = nearestUsableTick(currentTick + 2000, tickSpacing);
4. 授权代币
await token0Contract.approve(V3_POSITION_MANAGER, amount0Desired);
await token1Contract.approve(V3_POSITION_MANAGER, amount1Desired);
5. 铸造头寸
const PM_ABI = [
'function mint((address,address,uint24,int24,int24,uint256,uint256,uint256,uint256,address,uint256)) payable returns (uint256,uint128,uint256,uint256)'
];
const pm = new ethers.Contract(V3_POSITION_MANAGER, PM_ABI, signer);
const deadline = Math.floor(Date.now() / 1000) + 1200;
const tx = await pm.mint({
token0,
token1,
fee: 3000,
tickLower,
tickUpper,
amount0Desired,
amount1Desired,
amount0Min: amount0Desired * 99n / 100n,
amount1Min: amount1Desired * 99n / 100n,
recipient: userAddress,
deadline
});
const receipt = await tx.wait();
// 从交易回执事件中解析 tokenId
token0 的地址必须小于 token1。调用前请先排序。
使用原生 KAS
如果其中一个代币是 KAS,将其作为 value 发送,而不是授权 WKAS:
const tx = await pm.mint(params, { value: kasAmount });
增加流动性
向现有头寸(相同 tick 范围)追加更多代币:
const tx = await pm.increaseLiquidity({
tokenId,
amount0Desired,
amount1Desired,
amount0Min: 0,
amount1Min: 0,
deadline
});
领取手续费
领取累积的交易手续费:
const tx = await pm.collect({
tokenId,
recipient: userAddress,
amount0Max: ethers.MaxUint128,
amount1Max: ethers.MaxUint128
});
移除流动性
1. 减少流动性
const tx = await pm.decreaseLiquidity({
tokenId,
liquidity: liquidityToRemove, // 完全移除时使用全部流动性
amount0Min: 0,
amount1Min: 0,
deadline
});
2. 提取代币
减少流动性后,代币由 Position Manager 持有。调用 collect() 来提取:
const tx = await pm.collect({
tokenId,
recipient: userAddress,
amount0Max: ethers.MaxUint128,
amount1Max: ethers.MaxUint128
});
3. 销毁 NFT(可选)
移除所有流动性并领取所有代币/手续费后:
const tx = await pm.burn(tokenId);
单边头寸
你可以创建完全在当前价格之上或之下的头寸:
| 范围 | 存入 | 行为 |
|---|
高于当前价格(tickLower > currentTick) | 仅 Token0 | 相当于限价卖出订单 |
低于当前价格(tickUpper < currentTick) | 仅 Token1 | 相当于限价买入订单 |
// 限价卖出:仅提供 token0,范围在当前价格之上
const tx = await pm.mint({
token0,
token1,
fee: 3000,
tickLower: currentTick + 60, // 高于当前价格
tickUpper: currentTick + 600,
amount0Desired: sellAmount,
amount1Desired: 0, // 不需要 token1
amount0Min: 0,
amount1Min: 0,
recipient: userAddress,
deadline
});
- 每个头寸都是一个 NFT — 跟踪
tokenId 以进行后续操作
- 手续费必须主动领取(不会自动复利)
- 超出范围的头寸不会赚取手续费,直到价格重新进入范围
- 范围越窄 = 资本效率越高,但需要更积极的管理
- 使用 Position Manager 上的
multicall() 可以在一笔交易中批量执行减少流动性 + 领取