# Get Uniswap V3 position amounts & unclaimed fees from NFT ID

Hello guys! This tutorial will explain how to get a snapshot of a liquidity position (amounts and unclaimed fee) from a Uniswap V3 NFT ID. That could come in handy for an airdrop, to reward liquidity providers. Let's get started!

**Dependencies we'll use**:

* web3@1.2.9
    
* ethers@5.6.1
    
* @uniswap/sdk@3.0.3
    

Github repo for the full code: [https://github.com/BenAzlay/UniswapV3LiquiditySnapshotMaker](https://github.com/BenAzlay/UniswapV3LiquiditySnapshotMaker)

# Get liquidity position amounts

First of all, let's instance Uniswap's [NonfungiblePositionManager](https://docs.uniswap.org/contracts/v3/reference/periphery/NonfungiblePositionManager) contract to get on-chain position data (the ABI can be found [on Etherscan](https://etherscan.io/address/0xC36442b4a4522E871399CD717aBDD847Ab11FE88#code)). We'll use Ethers for this, and not Web3.js, because Ethers enables us to make static call, which will be useful later to fetch the unclaimed fees ;)

```javascript
const provider = new ethers.providers.StaticJsonRpcProvider("<your-mainnet-rpc>", 1);
const positionManagerContract = new ethers.Contract(
      "0xC36442b4a4522E871399CD717aBDD847Ab11FE88",
      NFTPositionManagerABI,
      provider,
);
```

Now we can make calls to the *positions* method to get data on the liquidity position corresponding to the *nftId* (of your choice). Here and in the rest of this tutorial, *defaultBlock* will be the block number at which we are fetching the on-chain data. You can set it at "latest" to get the latest block's data.

```javascript
const {
      token0: token0Address,
      token1: token1Address,
      fee,
      tickLower,
      tickUpper,
      liquidity,
} = await positionManagerContract.functions.positions(nftId, { blockTag: defaultBlock });
```

This call does not get us all the data we'd want, though. For example, we don't have the address of the owner of that NFT. Here's how to get it:

```javascript
const [nftOwner] = await positionManagerContract.functions.ownerOf(nftId, { blockTag: defaultBlock });
```

Now we need to fetch data from the pool itself. To do this, we first have to fetch the pool address from the [UniswapV3Factory](https://docs.uniswap.org/contracts/v3/reference/core/UniswapV3Factory) contract, then instanciate the pool's contract using that address. Once we have the pool's contract, we can fetch *sqrtPriceX96*, which we'll use to calculate the amounts. We also need to get the token decimals so we can convert the amounts in wei to the actual human-readable amounts.

```javascript
// Get pool address from token addresses and fee
const uniswapV3FactoryContract = new web3.eth.Contract(UniswapV3FactoryAbi, '0x1F98431c8aD98523631AE4a59f267346ea31F984');
const poolAddress = await uniswapV3FactoryContract.methods.getPool(token0Address, token1Address, fee).call({}, defaultBlock);
    
// Get sqrtPriceX96 from pool contract
const poolContract = new web3.eth.Contract(UniswapPoolContractAbi, poolAddress);
const { sqrtPriceX96 } = await poolContract.methods.slot0().call({}, defaultBlock);
    
// Get token decimals
const token0Contract = new web3.eth.Contract(Erc20Abi, token0Address);
const decimals0 = await token0Contract.methods.decimals().call({}, defaultBlock);

const token1Contract = new web3.eth.Contract(Erc20Abi, token1Address);
const decimals1 = await token1Contract.methods.decimals().call({}, defaultBlock);
```

And now, we get into the weeds of it: Uniswap maths! Those maths are very complicated but you can read about them [here](https://blog.uniswap.org/uniswap-v3-math-primer). For the following code, huge thanks to [Crypto\_Rachel on Ethereum Stack Exchange](https://ethereum.stackexchange.com/users/100615/crypto-rachel) for [her brilliant answer](https://ethereum.stackexchange.com/a/138711/83865).

```javascript
import { JSBI } from "@uniswap/sdk";
import { BigNumber } from "@ethersproject/bignumber";

const Q96 = JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(96));
const MAX_UINT128 = BigNumber.from(2).pow(128).sub(1);

function getTickAtSqrtRatio(sqrtPriceX96){
  let tick = Math.floor(Math.log((sqrtPriceX96/Q96)**2)/Math.log(1.0001));
  return tick;
}

// Calculate amounts
let sqrtRatioA = Math.sqrt(1.0001**tickLower).toFixed(18);
let sqrtRatioB = Math.sqrt(1.0001**tickUpper).toFixed(18);
let currentTick = getTickAtSqrtRatio(sqrtPriceX96);
let currentRatio = Math.sqrt(1.0001**currentTick).toFixed(18);
let amount0wei = 0;
let amount1wei = 0;
if(currentTick <= tickLower){
      amount0wei = Math.floor(liquidity*((sqrtRatioB-sqrtRatioA)/(sqrtRatioA*sqrtRatioB)));
}
if(currentTick > tickUpper){
        amount1wei = Math.floor(liquidity*(sqrtRatioB-sqrtRatioA));
}
if(currentTick >= tickLower && currentTick < tickUpper){ 
        amount0wei = Math.floor(liquidity*((sqrtRatioB-currentRatio)/(currentRatio*sqrtRatioB)));
        amount1wei = Math.floor(liquidity*(currentRatio-sqrtRatioA));
}
const amount0 = amount0wei / (10**decimals0);
const amount1 = amount1wei / (10**decimals1);
```

# Get unclaimed fees

For the unclaimed fees, this is where static calls are useful, because we need to call the method *collect* from the NonFungiblePositionManager contract, BUT that method is a write method, not a read method. We don't want to execute it, but we want to get its return values, so we need to perform a static call.

```javascript
// Get unclaimed fees
const {
      amount0: unclaimedFee0Wei,
      amount1: unclaimedFee1Wei,
} = await positionManagerContract.callStatic.collect({
      tokenId: nftId,
      recipient: nftOwner,
      amount0Max: MAX_UINT128,
      amount1Max: MAX_UINT128,
}, {from: nftOwner, blockTag: defaultBlock});
const unclaimedFee0 = unclaimedFee0Wei / (10**decimals0);
const unclaimedFee1 = unclaimedFee1Wei / (10**decimals1);
```

And voilà! If this tutorial helped you, you can send me a little tip at **benjaminazoulay.eth**
