想要 close 一个 uniswap v3 的 lp, 我该怎么做?使用 ethers。ABI:
[{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"positions","outputs":[{"internalType":"uint96","name":"nonce","type":"uint96"},{"internalType":"address","name":"operator","type":"address"},{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"feeGrowthInside0LastX128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthInside1LastX128","type":"uint256"},{"internalType":"uint128","name":"tokensOwed0","type":"uint128"},{"internalType":"uint128","name":"tokensOwed1","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct INonfungiblePositionManager.DecreaseLiquidityParams","name":"params","type":"tuple"}],"name":"decreaseLiquidity","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0Desired","type":"uint256"},{"internalType":"uint256","name":"amount1Desired","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct INonfungiblePositionManager.IncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"increaseLiquidity","outputs":[{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount0Owed","type":"uint256"},{"internalType":"uint256","name":"amount1Owed","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"uniswapV3MintCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"}],"name":"createAndInitializePoolIfNecessary","outputs":[{"internalType":"address","name":"pool","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"refundETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0Desired","type":"uint256"},{"internalType":"uint256","name":"amount1Desired","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"internalType":"struct INonfungiblePositionManager.MintParams","name":"params","type":"tuple"}],"name":"mint","outputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowedIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint128","name":"amount0Max","type":"uint128"},{"internalType":"uint128","name":"amount1Max","type":"uint128"}],"internalType":"struct INonfungiblePositionManager.CollectParams","name":"params","type":"tuple"}],"name":"collect","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]
要关闭一个 Uniswap V3 的流动性提供者(LP)位置,你需要执行以下步骤:
以下是使用 ethers.js 和 Uniswap V3 的 NonfungiblePositionManager 合约的代码示例,展示了如何完成这些操作:
首先,你需要确保安装了 ethers
库,并正确连接到以太坊网络。
const { ethers } = require("ethers");
// 连接到以太坊网络,这里以Infura为例
const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/你的Infura项目ID');
// 连接到钱包
const signer = new ethers.Wallet('你的私钥', provider);
// Uniswap V3 的 NonfungiblePositionManager 地址
const positionManagerAddress = "0xC36442b4a4522E871399CD717aBDD847Ab11FE88";
// 创建合约实例
const positionManager = new ethers.Contract(positionManagerAddress, abi, signer);
async function decreaseLiquidity(tokenId) {
const tx = await positionManager.decreaseLiquidity({
tokenId: tokenId,
liquidity: ethers.BigNumber.from('你的流动性量'),
amount0Min: 0,
amount1Min: 0,
deadline: Math.floor(Date.now() / 1000) + 3600, // 1小时后超时
});
await tx.wait();
console.log('Liquidity decreased');
}
decreaseLiquidity('你的tokenId');
async function collectFees(tokenId) {
const tx = await positionManager.collect({
tokenId: tokenId,
recipient: signer.address, // 收款地址
amount0Max: ethers.constants.MaxUint256,
amount1Max: ethers.constants.MaxUint256,
});
await tx.wait();
console.log('Fees collected');
}
collectFees('你的tokenId');
async function burnToken(tokenId) {
const tx = await positionManager.burn(tokenId);
await tx.wait();
console.log('Token burned');
}
burnToken('你的tokenId');
确保替换 '你的Infura项目ID'
、'你的私钥'
和 '你的tokenId'
为实际的值。这些函数需要按顺序调用,确保在尝试收集费用或销毁 NFT 之前已经减少了流动性。
此外,务必处理可能发生的异常和错误,确保交易不会因意外情况而失败。在生产环境中使用时,还应检查合约函数调用的返回值和事件,以确保操作的正确执行。
liquidity: ethers.BigNumber.from('你的流动性量'),
可以直接全部提取吗
在 Uniswap V3 中,你可以通过查询你的 NFT 代表的位置来获取当前的流动性量,然后使用这个值来设定提取的流动性量,从而实现全部提取。
首先,你需要使用 positions
函数查询特定 tokenId 的流动性信息。然后,使用这个流动性值在 decreaseLiquidity
函数中减少流动性。
这里是如何查询和使用全部流动性的示例代码:
async function getLiquidity(tokenId) {
const position = await positionManager.positions(tokenId);
return position.liquidity; // 返回流动性量
}
// 示例用法
const tokenId = '你的tokenId';
getLiquidity(tokenId).then((liquidity) => {
console.log(`当前流动性量: ${liquidity.toString()}`);
});
async function decreaseFullLiquidity(tokenId) {
const liquidity = await getLiquidity(tokenId);
const tx = await positionManager.decreaseLiquidity({
tokenId: tokenId,
liquidity: liquidity,
amount0Min: 0,
amount1Min: 0,
deadline: Math.floor(Date.now() / 1000) + 3600, // 1小时后超时
});
await tx.wait();
console.log('全部流动性已减少');
}
// 示例用法
decreaseFullLiquidity(tokenId);
这段代码首先查询指定 tokenId 的当前流动性,然后使用这个值作为 decreaseLiquidity
函数中的 liquidity
参数,从而实现完全提取流动性。这确保了你能够准确地移除所有已提供的流动性。
请注意,在实际操作中,你可能还需要考虑如交易费用、滑点容忍度等因素,这些都可能影响到你的交易执行。
如果 liquidity 设置超过最大值会怎么样
在 Uniswap V3 中,如果你在 decreaseLiquidity
函数中设置的 liquidity
参数超过了你实际持有的流动性量,交易将会失败。这是因为智能合约会检查提供的流动性是否有效,即是否小于或等于当前位置的总流动性。
这里是一个添加了错误处理的代码示例,展示了如何安全地调用 decreaseLiquidity
函数:
async function decreaseFullLiquidity(tokenId) {
try {
const liquidity = await getLiquidity(tokenId);
const tx = await positionManager.decreaseLiquidity({
tokenId: tokenId,
liquidity: liquidity,
amount0Min: 0,
amount1Min: 0,
deadline: Math.floor(Date.now() / 1000) + 3600, // 1小时后超时
});
await tx.wait();
console.log('全部流动性已减少');
} catch (error) {
console.error('减少流动性失败:', error);
}
}
// 示例用法
decreaseFullLiquidity(tokenId);
通过这种方式,你可以确保即使在流动性参数设置不当时,用户和系统的其他部分也不会受到不利影响,并且能够得到清晰的错误反馈。
调用 decreaseFullLiquidity 之后,资金会去哪里
在调用 decreaseLiquidity
函数减少 Uniswap V3 流动性提供位置的流动性之后,资金(即代币)并不会立即转移至你的钱包。实际上,这些代币会暂存于 Uniswap V3 的合约中,直到你显式地调用 collect
函数来领取这些代币。
这里是整个流程的详细说明:
当你调用 decreaseLiquidity
函数时,你指定了要从池中移除多少流动性。这个操作会根据当前的价格和你的流动性位置,计算你能够取回的代币数量(token0
和 token1
)。然而,这些代币在操作完成时并不会自动发送到你的钱包。它们仍留在合约中,等待被收集。
要实际接收到这些代币,你需要调用 collect
函数,指定你想要从合约中取出多少代币到你的地址。你可以选择收集全部或部分代币。
这是一个调用 collect
函数的示例代码:
async function collectFees(tokenId) {
const tx = await positionManager.collect({
tokenId: tokenId,
recipient: signer.address, // 收款地址
amount0Max: ethers.constants.MaxUint256, // 最大数量,表示收集所有可用的 token0
amount1Max: ethers.constants.MaxUint256, // 最大数量,表示收集所有可用的 token1
});
await tx.wait();
console.log('Fees and tokens collected');
}
在你调用 collect
函数之前,你的代币安全地存储在 Uniswap V3 合约中。只有你(或者你授权的地址)可以调用 collect
函数来移动这些代币。
因此,完成 decreaseLiquidity
后,资金仍然在 Uniswap V3 合约中。你需要显式地调用 collect
来将资金移动到你的钱包或其他指定地址。这种设计允许用户在不同时间收集费用,提供了更大的灵活性和控制。