23

I am trying to compute a Uniswap V3 pool token price in Solidity.

I can get the square root price of Q64.96 number as uint160 (e.g., 1234217676608908277512433764 - value of DAI/ETH pool (price at that time around 1 ETH for 4090 DAI)).

This can be retrieved via IUniswapV3PoolState.slot0()

As the function docstring says: "sqrtPriceX96 The current price of the pool as a sqrt(token1/token0) Q64.96 value"

How can I convert this number to uint256?

The calculated price should suggest the value of 1 ETH for 4090 DAI

Is there any other way of getting the Uniswap V3 pair price?

The answer would preferably be in solidity, but other answers are also acceptable.

Unsuccessful attempt

I tried squaring the number 1234217676608908277512433764 and then shifting it by 96, but the result was 1.922666416729829e+25 which doesn't seem too be correct.

(1234217676608908277512433764^2) >> 96 = 1.922666416729829e+25

1

8 Answers 8

23

The relationship between sqrtPriceX96 and price is metioned in the official doc:https://docs.uniswap.org/sdk/guides/fetching-prices

    sqrtPriceX96 = sqrt(price) * 2 ** 96
    # divide both sides by 2 ** 96
    sqrtPriceX96 / (2 ** 96) = sqrt(price)
    # square both sides
    (sqrtPriceX96 / (2 ** 96)) ** 2 = price
    # expand the squared fraction
    (sqrtPriceX96 ** 2) / ((2 ** 96) ** 2)  = price
    # multiply the exponents in the denominator to get the final expression
    sqrtRatioX96 ** 2 / 2 ** 192 = price
4
  • 1
    Where did sqrtRatioX96 at the bottom come from?
    – Barry G
    Commented Nov 22, 2022 at 22:31
  • 1
    sqrtRatioX96 and sqrtPriceX96 are interchangeable terms according to the official docs linked in this answer
    – Voy
    Commented Dec 6, 2022 at 15:27
  • thanks for this, good mathematical breakdown
    – gosuto
    Commented Mar 22, 2023 at 19:30
  • 1
    Just a note that ABOVE link is 404 now and needs to be replaced
    – Blissful
    Commented Mar 31 at 22:02
19
function getPrice(address tokenIn, address tokenOut)
    external
    view
    returns (uint256 price)
{
    IUniswapV3Pool pool = IUniswapV3Pool(factory.getPool(tokenIn, tokenOut, FEE);
    (uint160 sqrtPriceX96,,,,,,) =  pool.slot0();
    return uint(sqrtPriceX96).mul(uint(sqrtPriceX96)).mul(1e18) >> (96 * 2);
}

Returns spot price with 1e18 precision. Be careful to use spot price because it is a subject for a flash loan attacks. Either use TWAP price or check that price hasn't move much before using spot price. Also, I haven't tested this code for precision loss and upper/lower boundaries. Because Uniswap v3 use Q64.96 notion for decimals but it doesn't seem that practical. The output of the function is a number with e18 precision. It depends which decimal lib you use in your project.

1
  • 1
    This does not seem to generalize well. How would this work on a pair like usdt/wbtc which is of decimals 8 and 6 respectively?
    – Barry G
    Commented Dec 25, 2022 at 13:49
6

TLDR: This is something that seems to work with every token (regardless of how many decimals it has) and it takes into consideration math overflow issues. Read explanation below:

import '@uniswap/v3-core/contracts/libraries/FullMath.sol';

function sqrtPriceX96ToUint(uint160 sqrtPriceX96, uint8 decimalsToken0)
    internal
    pure
    returns (uint256)
{
    uint256 numerator1 = uint256(sqrtPriceX96) * uint256(sqrtPriceX96);
    uint256 numerator2 = 10**decimalsToken0;
    return FullMath.mulDiv(numerator1, numerator2, 1 << 192);
}

Regarding decimals: If you are calculating the price on a token with 18 decimal places, multiplying sqrtPriceX96 by 1e18 before the division should work just fine, but it will break for tokens with other decimals (USDC anyone). In order to obtain the correct results you should pass ERC20(token0).decimals() to the above function.

Regarding math overflow: Depending on the price you convert the uint256 will overflow and you will end up with a wrong price. The safest way to do this is with the MathFull.sol library from Uniswap v3 which accounts for overflowing.

0
2

Another way to calculate the price is by taking advantage of the relationship between liquidity, sqrtPricex96 and token amounts.

import '@openzeppelin/contracts/token/ERC20/ERC20.sol';
import '@uniswap/v3-core/contracts/libraries/FixedPoint96.sol';
import '@uniswap/v3-core/contracts/libraries/FullMath.sol';
import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol';
import '@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol';

function calculatePriceFromLiquidity(
  address token0,
  address token1,
  uint24 fee,
  address factory
) public view returns (uint256) {
  IUniswapV3Pool pool = IUniswapV3Pool(IUniswapV3Factory(factory).getPool(token0, token1, fee));
  (uint160 sqrtPriceX96, , , , , , ) = pool.slot0();

  uint256 amount0 = FullMath.mulDiv(pool.liquidity(), FixedPoint96.Q96, sqrtPriceX96);

  uint256 amount1 = FullMath.mulDiv(pool.liquidity(), sqrtPriceX96, FixedPoint96.Q96);

  return (amount1 * 10**ERC20(token0).decimals()) / amount0;
}
1
  • This is wrong/misleading, if the active liquidity in a pool is 0 then your function would attempt to divide by 0. Liquidity is irrelevant to calculating price, the only reason your function returns the correct price(provided liquidity != 0) is because your function can be reduced to the same formula as many of the other answers listed here: (sqrtPriceX96 * sqrtPriceX96 * 1e18)/(2^96 * 2 ^ 96) = (sqrtPriceX96*sqrtPriceX96 * 1e18)/(2^192)
    – MShakeG
    Commented Dec 2, 2023 at 14:07
1

We can adapt code from the actual Uniswap v3-periphery library function OracleLibrary.getQuoteAtTick. Instead of having the function accept the tick and then convert the tick to sqrtRatioX96 using TickMath.getSqrtRatioAtTick(tick), we modify it to directly accept the sqrtRatioX96 as shown below:

/**
 * @dev Calculates the amount of quote token received for a given amount of base token
 * based on the square root of the price ratio (sqrtRatioX96).
 *
 * @param sqrtRatioX96 The square root of the price ratio(in terms of token1/token0) between two tokens, encoded as a Q64.96 value.
 * @param baseAmount The amount of the base token for which the quote is to be calculated. Specify 1e18 for a price(quoteAmount) with 18 decimals of precision.
 * @param inverse Specifies the direction of the price quote. If true then baseAmount must be the amount of token1, if false then baseAmount must be the amount for token0
 *
 * @return quoteAmount The calculated amount of the quote token for the specified baseAmount
 */
function getQuoteFromSqrtRatioX96(
    uint160 sqrtRatioX96,
    uint128 baseAmount,
    bool inverse
) internal pure returns (uint256 quoteAmount) {
    // Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself
    if (sqrtRatioX96 <= type(uint128).max) {
        uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96;
        quoteAmount = !inverse
            ? FullMath.mulDiv(ratioX192, baseAmount, 1 << 192)
            : FullMath.mulDiv(1 << 192, baseAmount, ratioX192);
    } else {
        uint256 ratioX128 = FullMath.mulDiv(
            sqrtRatioX96,
            sqrtRatioX96,
            1 << 64
        );
        quoteAmount = !inverse
            ? FullMath.mulDiv(ratioX128, baseAmount, 1 << 128)
            : FullMath.mulDiv(1 << 128, baseAmount, ratioX128);
    }
}

To further simplify getting the price we can define the following function.

/**
 * @dev Returns the fixed-point price for a specified sqrtRatioX96, denoted in terms of token1/token0.
 * This function simplifies the process of obtaining the price from sqrtRatioX96 by using a standard
 * base amount (1e18) and setting the price as non-inverted (token1/token0).
 *
 * @param sqrtRatioX96 The square root of the price ratio between two tokens, encoded as a Q64.96 value.
 * 
 * @return priceU18 The calculated price in terms of token1/token0, with 18 decimal places of precision.
 */
function getPriceFromSqrtRatioX96(uint160 sqrtRatioX96) internal pure returns (uint256 priceU18) {
    // if we specified inverted = true, then we'd get the price in terms of token0/token1
    // whereas the UniswapV3Pool sqrtRatioX96 is in terms of token1/token0
    return getQuoteFromSqrtRatioX96(sqrtRatioX96, 1e18, false);
}
0

I created a rust lib that simply returns the price and abstracts the calculations away. Feel free to use it https://github.com/FredCoen/uniswap-utilities

0

Had to write code for this exact purpose but with both decimal numbers variable, this did the trick for me:

 function getPrice(uint160 sqrtRatioX96, uint dec0, uint dec1)
    external
    pure
    returns (uint256 price)
{

uint256 dec = dec1<=dec0 ? (18-dec1)+dec0 :dec0;
uint256 numerator1 =uint256(sqrtRatioX96) *uint256(sqrtRatioX96);  
uint256 numerator2 =10**dec; 
price = FullMath.mulDiv(numerator1, numerator2, 1 << 192);
}
0

From https://blog.uniswap.org/uniswap-v3-math-primer

// Get the two token prices of the pool
// PoolInfo is a dictionary object containing the 4 variables needed
// {"SqrtX96" : slot0.sqrtPriceX96.toString(), "Pair": pairName, "Decimal0": Decimal0, "Decimal1": Decimal1}
// to get slot0 call factory contract with tokens and fee, 
// then call the pool slot0, sqrtPriceX96 is returned as first dictionary variable
// var FactoryContract =  new ethers.Contract(factory, IUniswapV3FactoryABI, provider);
// var V3pool = await FactoryContract.getPool(token0, token1, fee);
// var poolContract =  new ethers.Contract(V3pool, IUniswapV3PoolABI, provider);
// var slot0 = await poolContract.slot0();

function GetPrice(PoolInfo){
    let sqrtPriceX96 = PoolInfo.SqrtX96;
    let Decimal0 = PoolInfo.Decimal0;
    let Decimal1 = PoolInfo.Decimal1;

    const buyOneOfToken0 = ((sqrtPriceX96 / 2**96)**2) / (10**Decimal1 / 10**Decimal0).toFixed(Decimal1);

    const buyOneOfToken1 = (1 / buyOneOfToken0).toFixed(Decimal0);
    console.log("price of token0 in value of token1 : " + buyOneOfToken0.toString());
    console.log("price of token1 in value of token0 : " + buyOneOfToken1.toString());
    console.log("");
        // Convert to wei
    const buyOneOfToken0Wei =(Math.floor(buyOneOfToken0 * (10**Decimal1))).toLocaleString('fullwide', {useGrouping:false});
    const buyOneOfToken1Wei =(Math.floor(buyOneOfToken1 * (10**Decimal0))).toLocaleString('fullwide', {useGrouping:false});
    console.log("price of token0 in value of token1 in lowest decimal : " + buyOneOfToken0Wei);
    console.log("price of token1 in value of token1 in lowest decimal : " + buyOneOfToken1Wei);
    console.log("");
}

        // WETH / USDC pool 0.05%    →(1% == 10000, 0.3% == 3000, 0.05% == 500, 0.01 == 100)
("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 500)
// Output
price of token0 in value of token1 : 1539.296453
price of token1 in value of token0 : 0.000649647439939888
price of token0 in value of token1 in lowest decimal : 1539296453
price of token1 in value of token1 in lowest decimal : 649647439939888

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.