想要 close 一个 uniswap v3 的 lp, 我该怎么做?使用 ethers。

2024-04-25by@tim&@ChatGPT.Model: GPT-4
tim

想要 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"}]
Avatar
Avatar

要关闭一个 Uniswap V3 的流动性提供者(LP)位置,你需要执行以下步骤:

  1. 减少流动性(Decrease Liquidity) - 首先从你的 LP 位置中移除全部或部分流动性。
  2. 收集费用(Collect Fees) - 收集因提供流动性而累积的交易费用。
  3. 销毁 NFT(Burn the NFT) - 如果你已经完全移除了流动性,并且收集了所有费用,你可以选择销毁代表该位置的 NFT。

以下是使用 ethers.js 和 Uniswap V3 的 NonfungiblePositionManager 合约的代码示例,展示了如何完成这些操作:

步骤 1: 设置环境

首先,你需要确保安装了 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);

步骤 2: 减少流动性

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');

步骤 3: 收集费用

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');

步骤 4: 销毁 NFT

async function burnToken(tokenId) {
    const tx = await positionManager.burn(tokenId);
    await tx.wait();
    console.log('Token burned');
}

burnToken('你的tokenId');

确保替换 '你的Infura项目ID''你的私钥''你的tokenId' 为实际的值。这些函数需要按顺序调用,确保在尝试收集费用或销毁 NFT 之前已经减少了流动性。

此外,务必处理可能发生的异常和错误,确保交易不会因意外情况而失败。在生产环境中使用时,还应检查合约函数调用的返回值和事件,以确保操作的正确执行。

tim
liquidity: ethers.BigNumber.from('你的流动性量'),

可以直接全部提取吗

Avatar
Avatar

在 Uniswap V3 中,你可以通过查询你的 NFT 代表的位置来获取当前的流动性量,然后使用这个值来设定提取的流动性量,从而实现全部提取。

首先,你需要使用 positions 函数查询特定 tokenId 的流动性信息。然后,使用这个流动性值在 decreaseLiquidity 函数中减少流动性。

这里是如何查询和使用全部流动性的示例代码:

步骤 1: 查询流动性

async function getLiquidity(tokenId) {
    const position = await positionManager.positions(tokenId);
    return position.liquidity;  // 返回流动性量
}

// 示例用法
const tokenId = '你的tokenId';
getLiquidity(tokenId).then((liquidity) => {
    console.log(`当前流动性量: ${liquidity.toString()}`);
});

步骤 2: 使用查询到的流动性减少全部流动性

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 参数,从而实现完全提取流动性。这确保了你能够准确地移除所有已提供的流动性。

请注意,在实际操作中,你可能还需要考虑如交易费用、滑点容忍度等因素,这些都可能影响到你的交易执行。

tim

如果 liquidity 设置超过最大值会怎么样

Avatar
Avatar

在 Uniswap V3 中,如果你在 decreaseLiquidity 函数中设置的 liquidity 参数超过了你实际持有的流动性量,交易将会失败。这是因为智能合约会检查提供的流动性是否有效,即是否小于或等于当前位置的总流动性。

发生的具体错误可能包括:

  1. Revert(回退):智能合约通常会在条件不满足时回退交易,并可能提供错误消息,如 "insufficient liquidity"(流动性不足)。
  2. Gas 消耗:尽管交易最终失败,但在执行检查过程中仍然消耗了 Gas。这意味着你会损失用于处理交易的 Gas 费用,即使交易没有成功。

避免此类错误的建议:

  • 准确查询流动性:在执行减少流动性操作前,先查询并确认你的 NFT 代表的流动性位置确切的流动性量,确保使用正确的数值。
  • 错误处理:在你的代码中添加错误处理逻辑,优雅地处理合约调用失败的情况,例如使用 try-catch 语句捕捉和处理异常。

这里是一个添加了错误处理的代码示例,展示了如何安全地调用 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);

通过这种方式,你可以确保即使在流动性参数设置不当时,用户和系统的其他部分也不会受到不利影响,并且能够得到清晰的错误反馈。

tim

调用 decreaseFullLiquidity 之后,资金会去哪里

Avatar
Avatar

在调用 decreaseLiquidity 函数减少 Uniswap V3 流动性提供位置的流动性之后,资金(即代币)并不会立即转移至你的钱包。实际上,这些代币会暂存于 Uniswap V3 的合约中,直到你显式地调用 collect 函数来领取这些代币。

这里是整个流程的详细说明:

1. 减少流动性

当你调用 decreaseLiquidity 函数时,你指定了要从池中移除多少流动性。这个操作会根据当前的价格和你的流动性位置,计算你能够取回的代币数量(token0token1)。然而,这些代币在操作完成时并不会自动发送到你的钱包。它们仍留在合约中,等待被收集。

2. 收集代币

要实际接收到这些代币,你需要调用 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');
}

3. 资金的安全

在你调用 collect 函数之前,你的代币安全地存储在 Uniswap V3 合约中。只有你(或者你授权的地址)可以调用 collect 函数来移动这些代币。

总结

因此,完成 decreaseLiquidity 后,资金仍然在 Uniswap V3 合约中。你需要显式地调用 collect 来将资金移动到你的钱包或其他指定地址。这种设计允许用户在不同时间收集费用,提供了更大的灵活性和控制。