Claiming Fees
Claim the swap fees from an existing liquidity position.
Contract ID: SaucerSwapV2NonfungiblePositionManager
See Get user positions for details how to retrieve all positions of a user including fees earned if any.
Function name: collect
⛽ Recommended gas: 300,000 gwei (~ $0.026 USD)
Solidity Interface & Function Body
//INonfungiblePositionManager.sol
struct CollectParams {
uint256 tokenSN;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
/// @notice Collects up to a maximum amount of fees owed to a specific position to the recipient
/// @param params tokenSN The serial number of the NFT for which tokens are being collected,
/// recipient The account that should receive the tokens,
/// amount0Max The maximum amount of token0 to collect,
/// amount1Max The maximum amount of token1 to collect
/// @return amount0 The amount of fees collected in token0
/// @return amount1 The amount of fees collected in token1
function collect(CollectParams calldata params) external payable
returns (uint256 amount0, uint256 amount1);
//NonfungiblePositionManager.sol
/// @inheritdoc INonfungiblePositionManager
function collect(CollectParams calldata params)
external
payable
override
isAuthorizedForToken(params.tokenSN)
returns (uint256 amount0, uint256 amount1)
{
require(params.amount0Max > 0 || params.amount1Max > 0);
// allow collecting to the nft position manager address with address 0
address recipient = params.recipient == address(0) ? address(this) : params.recipient;
Position storage position = _positions[params.tokenSN];
PoolAddress.PoolKey memory poolKey = _poolIdToPoolKey[position.poolId];
IUniswapV3Pool pool = IUniswapV3Pool(PoolAddress.computeAddress(factory, poolKey));
(uint128 tokensOwed0, uint128 tokensOwed1) = (position.tokensOwed0, position.tokensOwed1);
// trigger an update of the position fees owed and fee growth snapshots if it has any liquidity
if (position.liquidity > 0) {
pool.burn(position.tickLower, position.tickUpper, 0);
(, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, , ) = pool.positions(
PositionKey.compute(address(this), position.tickLower, position.tickUpper)
);
tokensOwed0 += uint128(
FullMath.mulDiv(
feeGrowthInside0LastX128 - position.feeGrowthInside0LastX128,
position.liquidity,
FixedPoint128.Q128
)
);
tokensOwed1 += uint128(
FullMath.mulDiv(
feeGrowthInside1LastX128 - position.feeGrowthInside1LastX128,
position.liquidity,
FixedPoint128.Q128
)
);
position.feeGrowthInside0LastX128 = feeGrowthInside0LastX128;
position.feeGrowthInside1LastX128 = feeGrowthInside1LastX128;
}
// compute the arguments to give to the pool#collect method
(uint128 amount0Collect, uint128 amount1Collect) = (
params.amount0Max > tokensOwed0 ? tokensOwed0 : params.amount0Max,
params.amount1Max > tokensOwed1 ? tokensOwed1 : params.amount1Max
);
// the actual amounts collected are returned
(amount0, amount1) = pool.collect(
recipient,
position.tickLower,
position.tickUpper,
amount0Collect,
amount1Collect
);
// sometimes there will be a few less wei than expected due to rounding down in core, but we just subtract the full amount expected
// instead of the actual amount so we can burn the token
(position.tokensOwed0, position.tokensOwed1) = (tokensOwed0 - amount0Collect, tokensOwed1 - amount1Collect);
emit Collect(params.tokenSN, recipient, amount0Collect, amount1Collect);
}
Code Overview
The following code demonstrates how to claim all swap fees from a pool.
When claiming fees from a pool that involves HBAR, include unwrapWHBAR in your multicall to convert the Wrapped HBAR (WHBAR) output token back into the native HBAR cryptocurrency.
Resources:
import * as ethers from 'ethers'; //V6
import { Pool, Position, nearestUsableTick, priceToClosestTick } from '@uniswap/v3-sdk';
import { Fraction, Percent, Token, Price } from '@uniswap/sdk-core';
import { ContractExecuteTransaction, .. } from '@hashgraph/sdk';
//Set one of Hedera's JSON RPC Relay as the provider
const provider = new ethers.JsonRpcProvider(hederaJsonRelayUrl, '', {
batchMaxCount: 1, //workaround for V6
});
//Load the ABI data for NonfungiblePositionManager
const nftManagerInterfaces = new ethers.Interface(nftManagerAbi);
//get max possible value for amount0Max and amount1Max
const MAX_UINT128 = new BigNumber(2).pow(128).minus(1).toFixed(0);
//CollectParams struct
const params = {
tokenSN: tokenSN,
recipient: recipientAddress,
amount0Max: MAX_UINT128, //collect max fees
amount1Max: MAX_UINT128, //collect max fees
};
//Construct encoded data for each function
const collectEncoded = nftManagerInterfaces.encodeFunctionData('collect', [params]);
//Not needed if HBAR isn't include in the pool
const unwrapWHBAREncoded = nftManagerInterfaces.encodeFunctionData('unwrapWHBAR', [0, recipientAddress]);
//Build encoded data for the multicall
const encodedData = nftManagerInterfaces.encodeFunctionData('multicall',
[[collectEncoded, unwrapWHBAREncoded]]);
const encodedDataAsUint8Array = hexToUint8Array(encodedData.substring(2));
//Execute the paid contract call
const response = await new ContractExecuteTransaction()
.setContractId(nftManagerContractId)
.setGas(gasGwei)
.setFunctionParameters(encodedDataAsUint8Array)
.execute(client);
//Fetch the result
const record = await response.getRecord(client);
const result = record.contractFunctionResult!;
const results = nftManagerInterfaces.decodeFunctionResult('multicall', result.bytes)[0];
const collectResult = nftManagerInterfaces.decodeFunctionResult('collect', results[0]);
//Retrieve the collected amounts for informative purposes
const amount0 = BigNumber(collectResult.amount0);
const amount1 = BigNumber(collectResult.amount1);
Last updated