1

I tried to analysis pool information and I want to make a result like below.

pools-uniswap liquidity information from uniswap

I wrote the code below by referring to the uniswap sdk documentation.

import JSBI from "jsbi";
import axios from "axios";
import IUniswapV3PoolABI from "@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json";
import { ethers } from "ethers";
import { ChainId, Token, CurrencyAmount, WETH9 } from "@uniswap/sdk-core";
import { Tick, Pool, TickMath, FeeAmount, tickToPrice } from "@uniswap/v3-sdk";

// -----------------------config------------------------------------


// Currencies and Tokens

export const WETH_TOKEN = new Token(
  ChainId.MAINNET,
  "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
  18,
  "WETH",
  "Wrapped Ether"
);

export const WBTC_TOKEN = new Token(
  ChainId.MAINNET,
  "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
  8,
  "WBTC",
  "Wrapped BTC"
);

export const USDC_TOKEN = new Token(
  ChainId.MAINNET,
  "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
  6,
  "USDC",
  "USD//C"
);

// Sets if the example should run locally or on chain
export enum Environment {
  MAINNET,
}

// Inputs that configure this example to run
export interface ExampleConfig {
  env: Environment;
  rpc: {
    mainnet: string;
  };
  pool: {
    token0: Token;
    token1: Token;
    fee: FeeAmount;
  };
  chart: {
    numSurroundingTicks: number;
  };
}

// Example Configuration

export const CurrentConfig: ExampleConfig = {
  env: Environment.MAINNET,
  rpc: {
    mainnet: "https://mainnet.infura.io/v3/d34caea371204e09a0accdc2834033ee",
  },
  pool: {
    token0: WBTC_TOKEN,
    // token1: WETH_TOKEN,

    token1: WETH_TOKEN,
    // fee: FeeAmount.LOW,
    fee: FeeAmount.MEDIUM,
  },
  chart: {
    numSurroundingTicks: 3,
  },
};

// -----------------------config------------------------------------

enum Direction {
  ASC,
  DESC,
}

interface GraphTick {
  tickIdx: string;
  liquidityGross: string;
  liquidityNet: string;
}
interface TickProcessed {
  tickIdx: number;
  liquidityActive: JSBI;
  liquidityNet: JSBI;
  price0: number;
  price1: number;
  isCurrent: boolean;
}

export interface BarChartTick {
  tickIdx: number;
  liquidityActive: number;
  liquidityLockedToken0: number;
  liquidityLockedToken1: number;
  price0: number;
  price1: number;
  isCurrent: boolean;
}

const MAX_INT128 = JSBI.subtract(
  JSBI.exponentiate(JSBI.BigInt(2), JSBI.BigInt(128)),
  JSBI.BigInt(1)
);

export async function getFullPool(): Promise<{
  pool: Pool;
  ticks: BarChartTick[];
}> {
  // const poolAddress = "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640";
  const poolAddress = "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD";
  const ethersProvider = new ethers.providers.JsonRpcProvider(
    "https://mainnet.infura.io/v3/{api-key}"
  );
  const poolContract = new ethers.Contract(
    poolAddress,
    IUniswapV3PoolABI.abi,
    ethersProvider
  );
  const [slot0, liquidity, graphTicks] = await Promise.all([
    poolContract.slot0(),
    poolContract.liquidity(),
    getFullTickData(poolAddress),
  ]);

  const sdkTicks = graphTicks.map((graphTick: GraphTick) => {
    return new Tick({
      index: +graphTick.tickIdx,
      liquidityGross: graphTick.liquidityGross,
      liquidityNet: graphTick.liquidityNet,
    });
  });

  const fullPool = new Pool(
    CurrentConfig.pool.token0,
    CurrentConfig.pool.token1,
    CurrentConfig.pool.fee,
    slot0.sqrtPriceX96,
    liquidity,
    slot0.tick,
    sdkTicks
  );

  const tickSpacing = fullPool.tickSpacing;
  const activeTickIdx = (
    await fullPool.tickDataProvider.nextInitializedTickWithinOneWord(
      fullPool.tickCurrent,
      fullPool.tickCurrent === TickMath.MIN_TICK ? false : true,
      tickSpacing
    )
  )[0];

  const barChartTicks = await createBarChartTicks(
    activeTickIdx,
    fullPool.liquidity,
    tickSpacing,
    fullPool.token0,
    fullPool.token1,
    3,
    fullPool.fee,
    graphTicks
  );

  return {
    pool: fullPool,
    ticks: barChartTicks,
  };
}

async function getFullTickData(poolAddress: string): Promise<GraphTick[]> {
  let allTicks: GraphTick[] = [];
  let skip = 0;
  let loadingTicks = true;
  while (loadingTicks) {
    const ticks = await getTickDataFromSubgraph(poolAddress, skip);
    allTicks = allTicks.concat(ticks);
    if (ticks.length < 1000) {
      loadingTicks = false;
    } else {
      skip += 1000;
    }
  }

  return allTicks;
}

async function getTickDataFromSubgraph(
  poolAddress: string,
  skip: number
): Promise<GraphTick[]> {
  const ticksQuery = JSON.stringify({
    query: `{ ticks(
            where: {poolAddress:  "${poolAddress.toLowerCase()}", liquidityNet_not: "0"}
            first: 1000,
            orderBy: tickIdx,
            orderDirection: asc,
            skip: ${skip}
          ) {
            tickIdx
            liquidityGross
            liquidityNet
          }
        }
      `,
  });

  const response = await axios.post(
    "https://gateway.thegraph.com/api/{api-key}/subgraphs/id/5zvR82QoaXYFyDEKLZ9t6v9adgnptxYpKpSbxtgVENFV",
    ticksQuery,
    {
      headers: {
        "Content-Type": "application/json",
      },
    }
  );

  return response.data.data.ticks;
}

export async function createBarChartTicks(
  tickCurrent: number,
  poolLiquidity: JSBI,
  tickSpacing: number,
  token0: Token,
  token1: Token,
  numSurroundingTicks: number,
  feeTier: FeeAmount,
  graphTicks: GraphTick[]
): Promise<BarChartTick[]> {
  const processedTicks = processTicks(
    tickCurrent,
    poolLiquidity,
    tickSpacing,
    token0,
    token1,
    numSurroundingTicks,
    graphTicks
  );

  const barTicks = await Promise.all(
    processedTicks.map(async (tick: TickProcessed) => {
      return calculateLockedLiqudity(
        tick,
        token0,
        token1,
        tickSpacing,
        feeTier
      );
    })
  );
  barTicks.map((entry, i) => {
    if (i > 0) {
      barTicks[i - 1].liquidityLockedToken0 = entry.liquidityLockedToken0;
      barTicks[i - 1].liquidityLockedToken1 = entry.liquidityLockedToken1;
    }
  });
  return barTicks;
}

function processTicks(
  tickCurrent: number,
  poolLiquidity: JSBI,
  tickSpacing: number,
  token0: Token,
  token1: Token,
  numSurroundingTicks: number,
  graphTicks: GraphTick[]
): TickProcessed[] {
  const tickIdxToTickDictionary: Record<string, GraphTick> = Object.fromEntries(
    graphTicks.map((graphTick) => [graphTick.tickIdx, graphTick])
  );

  const liquidity = poolLiquidity;

  let activeTickIdx = Math.floor(tickCurrent / tickSpacing) * tickSpacing;

  if (activeTickIdx <= TickMath.MIN_TICK) {
    activeTickIdx = TickMath.MAX_TICK;
  }

  const activeTickProcessed: TickProcessed = {
    tickIdx: activeTickIdx,
    liquidityActive: liquidity,
    liquidityNet: JSBI.BigInt(0),
    price0: parseFloat(
      tickToPrice(token0, token1, activeTickIdx).toSignificant(6)
    ),
    price1: parseFloat(
      tickToPrice(token1, token0, activeTickIdx).toSignificant(6)
    ),
    isCurrent: true,
  };

  const activeTick = tickIdxToTickDictionary[activeTickIdx];
  if (activeTick) {
    activeTickProcessed.liquidityNet = JSBI.BigInt(activeTick.liquidityNet);
  }

  const subsequentTicks: TickProcessed[] = computeInitializedTicks(
    activeTickProcessed,
    numSurroundingTicks,
    tickSpacing,
    Direction.ASC,
    token0,
    token1,
    tickIdxToTickDictionary
  );

  const previousTicks: TickProcessed[] = computeInitializedTicks(
    activeTickProcessed,
    numSurroundingTicks,
    tickSpacing,
    Direction.DESC,
    token0,
    token1,
    tickIdxToTickDictionary
  );

  const TickProcesseds = previousTicks
    .concat(activeTickProcessed)
    .concat(subsequentTicks);

  return TickProcesseds;
}

function computeInitializedTicks(
  activeTickProcessed: TickProcessed,
  numSurroundingTicks: number,
  tickSpacing: number,
  direction: Direction,
  token0: Token,
  token1: Token,
  tickIdxToTickDictionary: Record<string, GraphTick>
): TickProcessed[] {
  let previousTickProcessed: TickProcessed = {
    ...activeTickProcessed,
  };

  let ticksProcessed: TickProcessed[] = [];
  for (let i = 0; i < numSurroundingTicks; i++) {
    const currentTickIdx =
      direction === Direction.ASC
        ? previousTickProcessed.tickIdx + tickSpacing
        : previousTickProcessed.tickIdx - tickSpacing;

    if (
      currentTickIdx < TickMath.MIN_TICK ||
      currentTickIdx > TickMath.MAX_TICK
    ) {
      break;
    }

    const currentTickProcessed: TickProcessed = {
      tickIdx: currentTickIdx,
      liquidityActive: previousTickProcessed.liquidityActive,
      liquidityNet: JSBI.BigInt(0),
      price0: parseFloat(
        tickToPrice(token0, token1, currentTickIdx).toSignificant(6)
      ),
      price1: parseFloat(
        tickToPrice(token1, token0, currentTickIdx).toSignificant(6)
      ),
      isCurrent: false,
    };

    const currentInitializedTick =
      tickIdxToTickDictionary[currentTickIdx.toString()];
    if (currentInitializedTick) {
      currentTickProcessed.liquidityNet = JSBI.BigInt(
        currentInitializedTick.liquidityNet
      );
    }

    if (direction == Direction.ASC && currentInitializedTick) {
      currentTickProcessed.liquidityActive = JSBI.add(
        previousTickProcessed.liquidityActive,
        JSBI.BigInt(currentInitializedTick.liquidityNet)
      );
    } else if (
      direction == Direction.DESC &&
      JSBI.notEqual(previousTickProcessed.liquidityNet, JSBI.BigInt(0))
    ) {
      // We are iterating descending, so look at the previous tick and apply any net liquidity.
      currentTickProcessed.liquidityActive = JSBI.subtract(
        previousTickProcessed.liquidityActive,
        previousTickProcessed.liquidityNet
      );
    }

    ticksProcessed.push(currentTickProcessed);
    previousTickProcessed = currentTickProcessed;
  }

  if (direction == Direction.DESC) {
    ticksProcessed = ticksProcessed.reverse();
  }

  return ticksProcessed;
}

async function calculateLockedLiqudity(
  tick: TickProcessed,
  token0: Token,
  token1: Token,
  tickSpacing: number,
  feeTier: FeeAmount
): Promise<BarChartTick> {
  const sqrtPriceX96 = TickMath.getSqrtRatioAtTick(tick.tickIdx);
  const liqGross = JSBI.greaterThan(tick.liquidityNet, JSBI.BigInt(0))
    ? tick.liquidityNet
    : JSBI.multiply(tick.liquidityNet, JSBI.BigInt("-1"));
  const mockTicks = [
    {
      index: tick.tickIdx - tickSpacing,
      liquidityGross: liqGross,
      liquidityNet: JSBI.multiply(tick.liquidityNet, JSBI.BigInt("-1")),
    },
    {
      index: tick.tickIdx,
      liquidityGross: liqGross,
      liquidityNet: tick.liquidityNet,
    },
    // {
    //   index: tick.tickIdx + tickSpacing,
    //   liquidityGross: liqGross,
    //   liquidityNet: JSBI.multiply(tick.liquidityNet, JSBI.BigInt("-1")),
    // },
  ];

  const pool =
    token0 && token1 && feeTier
      ? new Pool(
          token0,
          token1,
          feeTier,
          // 0,
          // 0,
          sqrtPriceX96,
          tick.liquidityActive,
          tick.tickIdx,
          mockTicks
        )
      : undefined;
  const prevSqrtX96 = TickMath.getSqrtRatioAtTick(tick.tickIdx - tickSpacing);
  const maxAmountToken0 = token0
    ? CurrencyAmount.fromRawAmount(token0, MAX_INT128.toString())
    : undefined;
  const outputRes0 =
    pool && maxAmountToken0
      ? await pool.getOutputAmount(maxAmountToken0, prevSqrtX96)
      : undefined;

  const token1Amount = outputRes0?.[0] as CurrencyAmount<Token> | undefined;

  const amount0 = token1Amount
    ? parseFloat(token1Amount.toExact()) * tick.price1
    : 0;
  const amount1 = token1Amount ? parseFloat(token1Amount.toExact()) : 0;

  return {
    tickIdx: tick.tickIdx,
    liquidityActive: parseFloat(tick.liquidityActive.toString()),
    liquidityLockedToken0: amount0,
    liquidityLockedToken1: amount1,
    price0: tick.price0,
    price1: tick.price1,
    isCurrent: tick.isCurrent,
  };
}

(async () => {
  await getFullPool();
})();


What I wanted was to calculate the data of the image directly.

But my final result in variables barChartTicks is different with capture

for example :

what I calculated in tick 1WBTC = 23.0771WETH is

calculated liquidity

WBTC liquidity : 14.55630885..

WETH liquidity : 337.93881....

those are different with pool information in uniswap webpage(the first capture )

WBTC liquidity : 13.49

WETH liquidity : 313.23

Liquidity status of other ticks are different too.

I can find ticks data is correct. tick info from etherscan calculated tick info

So I think I need to change mockTicks in calculateLockedLiqudity, but I couldn't get it to run properly because the modification didn't work well.

How can I get the exact liquidity token amount for the tick?

0

Your Answer

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

Browse other questions tagged or ask your own question.