Claiming Fees

Claim the swap fees from an existing liquidity position.

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)

Struct Parameter NameDescription

uint256 tokenSN

The serial number of the token for which liquidity is being increased

address recipient

EVM address to receive the claimed swap fees

uint256 amount0Max

The maximum amount for the first token in its smallest unit

uint256 amount1Max

The maximum amount for the second token in its smallest unit

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