如何获得非零流动性的所有tick范围以最终计算总价值锁定的UNISWAP V3?

发布于 2025-01-20 23:58:32 字数 2106 浏览 7 评论 0原文

目的是计算 uniswap v3 池的总锁定价值 (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

这确实会打印 MIN_TICK 和 MAX_TICK 中的流动性,但会花费大量时间并浪费 web3 调用,因为它也在零流动性刻度上进行迭代。现在这些都是硬编码的,在这里我想知道最小-最大的值是多少,以便该范围不包含任何零流动性刻度。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

你爱我像她 2025-01-27 23:58:32
  • 获得合同的对代币余额

web3.eth.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()。呼叫()

sqrtpricecurrent = slot0 [0] /(1&lt;&lt; 96)< / p>

priceCurrent = sqrtpricecurrent ** 2

decimal_diff = usdt_decimal- token_a_decimal

token_price = 10 **( - demimal_diff)/(pricecurrent)如果token0_address == usdt_address else priceCurrent/(10 ** decimal_diff)

  • Finally, TVL = sum(token_balance * token_price)

** Remember: check price from big pool

  • 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

寂寞花火° 2025-01-27 23:58:32

这可以做到几种方法,即使用图形界面来抓取tick的几种方法,但根据您需要的流动性深度,您可能希望从RPC中获取最新信息。

我将向您展示如何从RPC

注:我仅使用5000块的原始用例来测试此代码。因此,如果请求RPC的完整范围,您可能会遇到查询限制,如果就是这样,您只需要将呼叫分解为较小的块,

如下:

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);

则使用方式 代码太多,无法在一个答案中转储,但是大多数代码是从:

多校法用法

  1. 中您的项目的以下软件包:

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

  2. 添加typechain并在项目中:

  3. package.json中添加脚本以将固体代码编译到Ethers Interfaces:

    “脚本”:{
    “合同:compile:v3”:“ TypeChain -target Ethers-v5 --ot-out-dir types/v3” ./node_modules/@uniswap/< strong/pintrong>/artifacts/contracts/contracts/contracts/contracts/contracts/contracks/controng> /////// dhim [bgg] .json“”
    },

  4. 运行一代,您现在应该在项目中具有新的接口

这是翻译的多键方法:

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>>;
}

此方法将负责检索响应:

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>>
}
 

调用所需的其他几件事:

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
}

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
}
我不是你的备胎 2025-01-27 23:58:32

没有犯罪,但是您正在遵循一种艰难的方式,它需要使用tickbitmap获取下一个初始化的tick(请记住,除非必要,否则所有tick都会初始化

。查询UNISWAP v3的

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

?如果您放置校验和地址)

{
  pools(first: 5) {
    id
    token0 {symbol}
    totalValueLockedToken0
    token1 {symbol}
    totalValueLockedToken1
  }
}

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
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文