1

I need to get a precise reading of the fees generated by a liquidity providing position I opened on Uniswap V3, and I need to get it on another smart contract (not on a frontend, so no use in considering APIs).

My steps have been:

  1. Created a pair of custom tokens on kovan testnet
  2. Created a liquidity pool using the two tokens on the official testnet Uniswap V3 (address 0xC36442b4a4522E871399CD717aBDD847Ab11FE88)
  3. Created a custom smart contract on which I am depositing my NFT. This smart contract must be able to access through a read function the unclaimed fees generated by the underlying position.

After an extensive research it does not seem to exist an official Uniswap function to read the unclaimed fees onchain, but by digging in their frontend code I realized that they calculate the user unclaimed fees by calling the collect function forcing read-only state through the STATICCALL opcode.

So I tried to replicate it in my smart contract in a view function but the STATICCALL call continues to fail.

Here is my relevant smart contract code:

[...]
IINonfungiblePositionManager NFPosManager = INonfungiblePositionManager(0xC36442b4a4522E871399CD717aBDD847Ab11FE88);
[...]
function getUnclaimedFeeBalanceOfNFT(uint256 objectId) public view returns (bool _success, bytes memory data) {

     (_success, data) = address(NFPosManager).staticcall(abi.encodeWithSelector(INonfungiblePositionManager.collect.selector, INonfungiblePositionManager.CollectParams({
          tokenId: objectId,
          recipient: address(this),
          amount0Max: type(uint128).max,
          amount1Max: type(uint128).max
     })));
       
     return (_success, data);
}

When calling the collect function in this manner (before depositing the NFT on my smart contract) the INonfungiblePositionManager smart contract receives the transaction but returns:

0:
bool: _success false
1:
bytes: data 0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000c4e6f7420617070726f7665640000000000000000000000000000000000000000

The data field, decoded from bytes, is a 'Not approved' error generated by Uniswap NonfungiblePositionManager smart contract.

After having deposited the NFT on my smart contract, the return values changes in:

0:
bool: _success false
1:
bytes: data 0x

So no answer ...

Here are the link of the used Uniswap smart contract repo: https://github.com/Uniswap/v3-periphery/blob/main/contracts/NonfungiblePositionManager.sol

Does anyone encounter the same problem? Am I doing something wrong? Is there another method to read the unclaimed fees onchain?

Thanks for the help!

2 Answers 2

1

For the 1st part, the collect function has the modifier

modifier isAuthorizedForToken(uint256 tokenId) {
        require(_isApprovedOrOwner(msg.sender, tokenId), 'Not approved');
}

with _isApprovedOrOwner being part of the ERC721 implementation (check OpenZeppelin doc).

So for a contract to call this function, even with a non-state changing staticcall, it needs to either be the owner or be approved.

For the 2nd part, it seems STATICCALL prevents modifying the state, and because collect tries to change state it reverts, as per EIP-214.

Afaik there's no way to know the UNI v3 fees from inside a smart contract without collecting them. Not sure why such a design it is indeed inconvenient.

1

I had a similar issue. I was trying to get the value of unclaimed fees within Solidity and found tokensOwed0 and tokensOwed1 were always coming back with 0.

After reading the UniswapV3 Book, I found this section about updating fees: https://uniswapv3book.com/docs/milestone_5/swap-fees/#updating-position-fees-and-token-amounts. This gave me the following idea which ended up working for my use case.

Call collect with a value of 1 wei for each amounts. This triggered an update and then the next call to positions returned the right values for tokensOwed0 and tokensOwed1. Here's some example code to show you what I mean:

// Collect 1 wei worth of tokens so that `tokensOwed0` and `tokensOwed1` are updated
nonfungiblePositionManager.collect(
    INonfungiblePositionManager.CollectParams({
        tokenId: tokenId,
        recipient: address(this),
        amount0Max: 1,
        amount1Max: 1
    })
);

// Get the Uniswap V3 NFT
(, , state.token0, state.token1, , , , , , , state.tokensOwed0, state.tokensOwed1) = nonfungiblePositionManager.positions(tokenId);

Might not be the best since it actually performs a collection, but at least it gets the job done. Hopefully someone will find a better way that doesn't involve doing this tiny collection.

Note: This won't work if there's 0 fees to collect from the pool for either token0 or token1. I tried to collect 0 fees but that seems to revert, which makes sense.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.