How to get all tick ranges with non-zero liquidity to finally calculate Total Value Locked Uniswap V3?
Asked Answered
K

3

1

The aim is to calculate the uniswap v3 pool's total value locked (TVL).

import json
from web3 import Web3
from collections import namedtuple

infura_url = 'https://mainnet.infura.io/v3/******'
web3 = Web3(Web3.HTTPProvider(infura_url))

def read_json_file(directory:str, file_name: str):
    try:
        file_path = directory + file_name
        f_ = open(file_path, 'r')
    except Exception as e:
        print(f"Unable to open the {file_path} file")
        raise e
    else:
        json_data = json.loads(f_.read())
    return json_data

# uniswap_ETH_USDT.v3
abi = read_json_file('./', 'abis/uniswapV3Pool.json')
address = '0x4e68Ccd3E89f51C3074ca5072bbAC773960dFa36'
exchange_contract = web3.eth.contract(address=Web3.toChecksumAddress(address), abi=abi)

Tick = namedtuple("Tick", "liquidityGross liquidityNet feeGrowthOutside0X128 feeGrowthOutside1X128 tickCumulativeOutside secondsPerLiquidityOutsideX128 secondsOutside initialized")

amounts0 = 0
amounts1 = 0
liquidity = 0
slot0 = exchange_contract.functions.slot0().call()
sqrtPriceCurrent = slot0[0] / (1 << 96)
MIN_TICK = -887272
MAX_TICK = 887272
TICK_SPACING = exchange_contract.functions.tickSpacing().call()


def calculate_token0_amount(liquidity, sp, sa, sb):
    sp = max(min(sp, sb), sa)
    return liquidity * (sb - sp) / (sp * sb)

def calculate_token1_amount(liquidity, sp, sa, sb):
    sp = max(min(sp, sb), sa)
    return liquidity * (sp - sa)

for tick in range(MIN_TICK, MAX_TICK, TICK_SPACING):
  tickRange = Tick(*exchange_contract.functions.ticks(tick).call())
  liquidity += tickRange.liquidityNet
  sqrtPriceLow = 1.0001 ** (tick // 2)
  sqrtPriceHigh = 1.0001 ** ((tick + TICK_SPACING) // 2)
  amounts0 += calculate_token0_amount(liquidity, sqrtPriceCurrent, sqrtPriceLow, sqrtPriceHigh)
  amounts1 += calculate_token1_amount(liquidity, sqrtPriceCurrent, sqrtPriceLow, sqrtPriceHigh)

  print(amounts0, amounts1, tick) # for better output, should correct for the amount of decimals before printing

This does print liquidity in MIN_TICK and MAX_TICK but takes a lot of time and waste web3 calls as it is iterating on zero liquidity ticks also. Right now these are hardcoded, here I want to know what can be the value of min-max so that range does not contain any zero liquidity tick.

Kino answered 13/4, 2022 at 6:46 Comment(2)
Are you using python or javascript?Sollows
I am using python hereKino
H
3
  • Getting pair token balance of contracts

web3.eth.contract(address=token_address,abi=abi).functions.balanceOf(contract_address).call()

  • and then get current price of each token / USDT by calling function slot0 in pool tokenA/USDT & tokenB/USDT

slot0 = contract.functions.slot0().call()

sqrtPriceCurrent = slot0[0] / (1 << 96)

priceCurrent = sqrtPriceCurrent ** 2

decimal_diff = USDT_decimal - TOKEN_A_decimal

token_price = 10**(-decimal_diff)/( priceCurrent) if token0_address == USDT_address else priceCurrent/(10**decimal_diff)

  • Finally, TVL = sum(token_balance * token_price)

** Remember: check price from big pool

Highhat answered 17/10, 2022 at 16:10 Comment(0)
M
0

This can be done a few ways the uniswap interface grabs the ticks using the graph, but depending on what you need the liquidity depths for you may want to have the most recent information from the RPC.

I will show you how to grab this from the RPC

Note: I have only tested this code using chunks of 5000 due to its original use case. So you may run into query limitations if requesting the full range from the RPC, if thats the case you just need to break the calls into smaller chunks

The usage looks like so:

import { abi as MulticallABI } from '@uniswap/v3-periphery/artifacts/contracts/lens/UniswapInterfaceMulticall.sol/UniswapInterfaceMulticall.json'

// Hardcoded uniswaps multiicall address:
const multicallAddress = '0x1F98415757620B543A52E61c46B32eB19261F984'
const multicallContract = getMulticallContract(multicallAddress, MulticallABI, provider);

const lowerTick =  TickMath.MIN_TICK;
const upperTick = TickMath.MAX_TICK;
const tickRangeResponse = await getTickRangeResponses(tickRange.lowerTick, tickRange.upperTick, pool, multicallContract);

I've written a few methods to help out with this, there is too much code to dump in a single answer but the majority of the code was translated from:

Multicall usages in the uniswap interface

  1. Add the following packages to your project:

    "@uniswap/sdk-core": "^3.1.0", "@uniswap/v3-sdk": "^3.9.0",

  2. Add typechain and to the project:

  3. Add a script in package.json to compile the solidity code to ethers interfaces:

    "scripts": { "contracts:compile:v3": "typechain --target ethers-v5 --out-dir types/v3 "./node_modules/@uniswap//artifacts/contracts//*[!dbg].json"" },

  4. Run the generation and you should now have new Interfaces in your project

This is the translated Multicall Method:

import type { JsonRpcProvider, JsonRpcSigner } from '@ethersproject/providers'
import { IUniswapV3PoolState, IUniswapV3PoolStateInterface } from "../types/v3/v3-core/artifacts/contracts/interfaces/pool/IUniswapV3PoolState";
import { getAddress } from "@ethersproject/address";

// Note: Multicall will throw an error if the contract call exceeds the expected provisioned gas required
// since we are only using static methods we jack the number up insanely high to assure execution
const DEFAULT_STATIC_CALL_GAS_REQUIRED = 1_000_000_000_000;

const POOL_STATE_INTERFACE = new Interface(IUniswapV3PoolStateABI) as IUniswapV3PoolStateInterface    

export function singleContractMultipleValue<T>(
  multicallContract: UniswapInterfaceMulticall,
  address: string,
  contractInterface: Interface,
  methodName: string,
  callInputs: OptionalMethodInputs[] = [undefined]
) : Promise<MappedCallResponse<T>> {

  //Use Ethers to get the fragmented function
  const fragment = contractInterface.getFunction(methodName);
  if(!fragment) {
      throw new Error('Invalid Fragment: ' + fragment)
  }

  // This will validate we are passing valid method arguments
  const callDatas = callInputs.map(i => {

    if(!isValidMethodArgs(i)) {
      throw new Error('Invalid Method Args:' + i)
    }
    return contractInterface.encodeFunctionData(fragment, i);
  });

  // We construct the call data for our example we are using the same call data for eac
  const calls = callDatas.map((callData, i) => { return { target: address, callData, gasLimit: BigNumber.from(DEFAULT_STATIC_CALL_GAS_REQUIRED) }});
  const result = multicallContract.callStatic.multicall(calls).then(response => {
      if(!(response instanceof Object)) {
          return response
      }

      //Adapt to the proper return type
      const clone: any = Object.assign({}, response);
      clone.returnData = response.returnData.map(v => {
          const vClone: any = Object.assign({}, v);
          vClone.returnData = contractInterface.decodeFunctionResult(methodName, v.returnData);
          return vClone;
      });

      return clone;
  })

  return result as Promise<MappedCallResponse<T>>;
}

This Method will take care of retrieving the responses:

export async function getTickRangeResponses(poolAddress: string, lowerBound: number, upperBound: number, tickSpacing: number,
    multicallContract: UniswapInterfaceMulticall) {
    
    const parameters = [];
    for(let i = lowerBound; i <= upperBound; i+= tickSpacing) {
        parameters.push([i]);
    }

    return singleContractMultipleValue<TickResponse>(
            multicallContract, poolAddress, POOL_STATE_INTERFACE, 'ticks', parameters)
        .catch(err => console.log('Mapped Call Responose error:' + err)) as Promise<MappedCallResponse<TickResponse>>
}
 

A few other things required for the call:

export type MappedCallResponse<T> = [
    BigNumber,
    ([boolean, BigNumber, string] & {
      success: boolean;
      gasUsed: BigNumber;
      returnData: string;
    })[]
  ] & {
    blockNumber: BigNumber;
    returnData: ([boolean, BigNumber, string] & {
      success: boolean;
      gasUsed: BigNumber;
      returnData: T;
    })[];
  }

export type CallResponse = MappedCallResponse<string>

export type MethodArg = string | number | BigNumber;
export type MethodArgs = Array<MethodArg | MethodArg[]>;
type OptionalMethodInputs =
  | Array<MethodArg | MethodArg[] | undefined>
  | undefined;

  function isMethodArg(x: unknown): x is MethodArg {
    return (
      BigNumber.isBigNumber(x) || ['string', 'number'].indexOf(typeof x) !== -1
    );
  }
  
  function isValidMethodArgs(x: unknown): x is MethodArgs | undefined {
    return (
      x === undefined ||
      (Array.isArray(x) &&
        x.every(
          (xi) => isMethodArg(xi) || (Array.isArray(xi) && xi.every(isMethodArg)),
        ))
    );
  }

  // returns the checksummed address if the address is valid, otherwise returns false
export function isAddress(value: any): string | false {
    try {
      return getAddress(value)
    } catch {
      return false
    }
  }

export function getMulticallContract(address: string, abi: any, provider: JsonRpcProvider): UniswapInterfaceMulticall { 
  return getContract(address, abi, provider, undefined) as UniswapInterfaceMulticall;
}

export function getContract<T extends Contract = Contract>(address: string, ABI: any, provider: JsonRpcProvider, account?: string): Contract {
    if (!isAddress(address) || address === AddressZero) {
      throw Error(`Invalid 'address' parameter '${address}'.`)
    }
  
    return new Contract(address, ABI, getProviderOrSigner(provider, account) as any) as T 
  }

// account is not optional
function getSigner(provider: JsonRpcProvider, account: string): JsonRpcSigner {
  return provider.getSigner(account).connectUnchecked()
}

// account is optional
function getProviderOrSigner(provider: JsonRpcProvider, account?: string): JsonRpcProvider | JsonRpcSigner {
  return account ? getSigner(provider, account) : provider
}
Medin answered 10/6, 2023 at 14:52 Comment(0)
F
-4

No offense but you are following a hard way, which needs to use TickBitmap to get the next initialized tick (Remember not all ticks are initialized unless necessary.)

Alternatively the easy way to get a pool's TVL is to query Uniswap V3's subgraph: like

{
  pool(id: "0x4e68ccd3e89f51c3074ca5072bbac773960dfa36") {
    id
    token0 {symbol}
    totalValueLockedToken0
    token1 {symbol}
    totalValueLockedToken1
  }
}

(for some reason it doesn't show result if you put checksum address)

or

{
  pools(first: 5) {
    id
    token0 {symbol}
    totalValueLockedToken0
    token1 {symbol}
    totalValueLockedToken1
  }
}
Fabrice answered 3/6, 2022 at 21:43 Comment(1)
I wanted a solution based on blockchain and not using subgraph apiKino

© 2022 - 2024 — McMap. All rights reserved.