/univ3prices

A Node.js library to calculate Uniswap V3 ratios (prices) from token pairs.

Primary LanguageJavaScriptISC LicenseISC

UniV3Prices

A Node.js library to calculate Uniswap V3 ratios (prices) and liquidity (reserves).

NPM Version CircleCI codecov Discord Twitter Follow



Check out the other Uniswap and crypto libraries, that depend on this library and this library depends on:

☎️ @thanpolas/uniswap-chain-queries for fetching on-chain data for ERC20 tokens, Uniswap V2 (and clones) and Uniswap V3.
🔧 @thanpolas/crypto-utils for calculating and formatting tokens and fractions.



Features

This library will allow you to:

Install

Install the module using NPM:

npm install @thanpolas/univ3prices --save

Quick Start

const univ3prices = require('@thanpolas/univ3prices');

const price = univ3prices([USDC.decimals, USDT.decimals], sqrtPrice).toAuto();

console.log(price);
// "1.00212"

sqrtPrice(tokenDecimals, sqrtRatioX96)

The default function; calculates Uniswap V3 Liquidity Pool (LP) ratios (prices) for Token Pairs.

  • tokenDecimals {Array<number|string>} An Array tuple with 2 elements, the token0 and token1 decimal place values.
  • sqrtPrice {string} The Square Root price value of the LP.

ℹ️ :: This is the default function available by directly requiring the library (const uniV3Prices = require('univ3prices')) or as a property (const { sqrtPrice } = require('univ3prices')).

ℹ️ :: See the How to Get the sqrtPrice and Tick values for a guide on how to get those values.

The sqrtPrice() returns an object that contains four functions depending on the output type you wish to have. The calculation functions are from the crypto-utils, find a brief description below.

toAuto(optOptions)

Does automatic decimal calculation and applies appropriate function. If result is above 1 then toFixed() is applied, if under 1 then toSignificant() is applied. View toAuto() documentation on crypto-utils.

  • optOptions {Object=} Calculation and Formatting options from the crypto-utils package.
  • Returns {string} an optimally formatted value.
// prepare a sqrtPrice value, by emulating a 10 / 7 division.
const sqrtPrice = encodeSqrtRatioX96(10e18, 7e18);

univ3Price([18, 18], sqrtPrice).toAuto();
// '0.7'

univ3Price([18, 18], sqrtPrice).toAuto({ reverse: true });
// '1.42857'

univ3Price([18, 18], sqrtPrice).toSignificant({
    reverse: true,
    decimalPlaces: 3,
});
// '1.439'

toSignificant(optOptions)

calculates the value to significant digits. View the toSignificant() documentation on crypto-utils

  • optOptions {Object=} Calculation and Formatting options from the crypto-utils package.
  • Returns string, the last significant decimals, default 5.

toSignificant Examples - Defaults

// prepare a sqrtPrice value, by emulating a 7 / 10 division.
const sqrtPrice = encodeSqrtRatioX96(7e18, 10e18);

univ3Price([18, 18], sqrtPrice).toSignificant();
// '1.4286'

univ3Price([18, 18], sqrtPrice).toSignificant({ decimalPlaces: 3 });
// '1.43'

univ3Price([18, 18], sqrtPrice).toSignificant({ decimalPlaces: 2 });
// '1.4'

toFixed(optOptions)

Calculates to fixed decimals. View the toSignificant() documentation on crypto-utils

  • optOptions {Object=} Calculation and Formatting options from the crypto-utils package.
  • Returns string, ration with fixed decimals, default 5.

toFixed Examples

// prepare a sqrtPrice value, by emulating a 7 / 10 division.
const sqrtPrice = encodeSqrtRatioX96(7e18, 10e18);
// and a sqrtPrice value emulating  20000000 / 1  division.
const sqrtPrice_20m = encodeSqrtRatioX96(20000000e18, 1e18);

univ3Price([18, 18], sqrtPrice).toFixed();
// '1.42857'

univ3Price([18, 18], sqrtPrice).toFixed({ decimalPlaces: 3 });
// '1.429'

univ3Price([18, 18], sqrtPrice).toFixed({ decimalPlaces: 2 });
// '1.43'

// This time use the 20m ratio
univ3Price([18, 18], sqrtPrice_20m).toFixed({ decimalPlaces: 2 });
// '20000000.00'

toFraction()

Returns the raw fraction tuple Array; contains the numerator and denominator in BigInt type of the token pairs.

toFraction() Examples

const JSBI = require('jsbi');
// prepare a sqrtPrice value, by emulating a 10 / 7 division.
const sqrtPrice = encodeSqrtRatioX96(10e18, 7e18);

const fraction = univ3Price([18, 18], sqrtPrice).toFraction();

const [numerator, denominator] = fraction;

numerator instanceOf JSBI; // true
denominator instanceOf JSBI; // true

How to get the sqrtPrice and tick Values From Uniswap

In regards to the sqrtPrice and tick values. there are two primary ways to get it:

Using the Liquidity Pool Contract

Query the Liquidity Pool contract of interest and use the slot0() method.

This method will return a collection of properties, the ones you care about is sqrtPriceX96 or tick.

Using the Subgraph

Use the Uniswap V3 Subgraph that is publicly available and fetch the sqrtPrice or tick property from the Pool schema.


univ3prices.tickPrice(tokenDecimals, tick)

calculates Uniswap V3 Liquidity Pool (LP) ratios (prices) for Token Pairs using the current tick value.

  • tokenDecimals {Array<number|string>} An Array tuple with 2 elements, the token0 and token1 decimal place values.
  • tick {string} The current tick value.

ℹ️ :: See the How to Get the sqrtPrice and Tick values for a guide on how to get those values.

The univ3prices.tickPrice() returns an object that contains four functions depending on the output type you wish to have, and has the exact same functions as the default function:


univ3prices.getAmountsForCurrentLiquidity(tokenDecimals, liquidity, sqrtPrice, tickSpacing, optOpts)

Calculates the reserves for the current sqrt price value.

  • tokenDecimals {Array<number|string>} An Array tuple with 2 elements, the token0 and token1 decimal place values.

  • liquidity {string} The liquidity value of the LP.

  • tickSpacing {string} The tick spacing value of the LP.

  • optOptions {Object=} A set of optional options:

  • Returns {Array} A tuple array containing the amount of each token in the defined liquidity range.

ℹ️ :: This function is a wrapper to getAmountsForLiquidityRange(), will automatically calculate the liquidity range expressed as sqrtRatioAX96 and sqrtRatioBX96.

Examples for univ3prices.getAmountsForCurrentLiquidity

Standard example:

const tokenDecimals = [
    '18', // decimals of DAI
    '18', // decimals of WETH
];
// Get the reserves for the DAI/WETH Liquidity Pool.
const [token0Reserves, token1Reserves] = getAmountsForCurrentLiquidity(
    tokenDecimals,
    '2830981547246997099758055', // Current liquidity value of the pool
    '1550724133884968571999296281', // Current sqrt price value of the pool
    '60', // the tickSpacing value from the pool
);
// The total amount of DAI available in this liquidity range
expect(token0Reserves).toEqual('116596.90182');
// The total amount of WETH available in this liquidity range
expect(token1Reserves).toEqual('121.40391');

Widening the liquidity range by having a step of 5:

// Get the reserves for the DAI/WETH Liquidity Pool.
const [token0Reserves, token1Reserves] = getAmountsForCurrentLiquidity(
    tokenDecimals,
    '2830981547246997099758055', // Current liquidity value of the pool
    '1550724133884968571999296281', // Current sqrt price value of the pool
    '60', // the tickSpacing value from the pool
    {tickStep: 5} // Choose 5 steps of tickSpacing (60 * 5) for low and high tick values.
);
// The total amount of DAI available in this liquidity range
expect(token0Reserves).toEqual('2268131.86622');
// The total amount of WETH available in this liquidity range
expect(token1Reserves).toEqual('944.51034');
});

Where to Find The Values for the Liquidity Function

Query directly the Liquidity Pool contract you care about (i.e. this is the DAI/WETH pool) and call the functions:

  • slot0() To get a list of values including the sqrtPriceX96.
  • liquidity() To get the liquidity value.
  • tickSpacing() To get the tick spacing value.

univ3prices.getAmountsForLiquidityRange(sqrtPrice, sqrtPriceA, sqrtPriceB, liquidity)

Calculates the reserves for a range of sqrt price.

  • sqrtPrice {string} The Square Root price value of the LP.
  • sqrtPriceA {string} The Square Root price representing the low tick boundary.
  • sqrtPriceB {string} The Square Root price representing the high tick boundary.
  • liquidity {string} The liquidity value.
  • Returns {Array} A tuple array containing the amount of each token in the defined liquidity range.

Examples for univ3prices.getAmountsForLiquidityRange

const [amount0, amount1] = getAmountsForLiquidityRange(
    encodePriceSqrt(1, 1), // generate sqrt price for range
    encodePriceSqrt(100, 110),
    encodePriceSqrt(110, 100),
    2148,
);

expect(Number(amount0)).toEqual(99); // Amount of token0
expect(Number(amount1)).toEqual(99); // Amount of token1

Tick Math Functions

univ3prices.tickMath.getSqrtRatioAtTick(tick)

Calculates the sqrt ratio at the given tick.

  • tick {string} The tick value to calculate the sqrt ratio for.
  • Returns {string} The sqrt ratio.

univ3prices.tickMath.getTickAtSqrtRatio(sqrtPrice)

Calculates the tick at the given sqrt ratio.

  • sqrtPrice {string} The sqrt price to calculate the tick for.
  • Returns {string} The tick.

Utility Functions

The following utility functions are available in the univ3prices.utils path:

  • encodeSqrtRatioX96(amount0, amount1) Convert a value pair to sqrt price.
  • sqrt(value) Computes the floor(sqrt(value)).
  • tickRange(tick, tickSpacing, optTickStep) Will calculate the low and high tick ranges for a given tick, optionally multiplying the spacing with the step for a wider range.
  • expDecs(decimals) Will return the exponent of the given decimals number.
  • biConv(value) Will safely convert any value to JSBI and not touch values that are of JSBI type.

Constants

The following constants are available in the univ3prices.constants path:

  • RESOLUTION :: Fixed point resolution of 96 as a bigint.
  • NEGATIVE_ONE :: -1 as a bigint.
  • ZERO :: 0 as a bigint.
  • ONE :: 1 as a bigint.
  • TWO :: 2 as a bigint.
  • Q32 :: Power of 2 at 32.
  • Q96 :: Power of 2 at 96.
  • Q192 :: Power of 2 at 192.
  • MaxUint256 :: Maximum signed integer value.
  • MIN_TICK :: Minimum tick value.
  • MAX_TICK :: Maximum tick value.
  • MIN_SQRT_RATIO :: Minimum sqrt price (ratio) value.
  • MAX_SQRT_RATIO :: Maximum sqrt price (ratio) value.
  • Rounding :: The Rounding enumeration from the crypto-utils package.

Acknowledgements & Credits

This library has been a study and break down, to understand how Uniswap V3 works. It acts both as a library for you to use and a way for you to understand, in simpler terms, how price is calculated.

In particular, the Uniswap V3 SDK's Pool Class and the Uniswap SDK Core's Price and Fraction classes were reverse engineered and rewritten in a functional manner. Most of the tests where also ported directly from the excellently tested SDK and Core packages.

Thank you goes to [JNP][jnp] who helped me understand how to work with tick and sqrt ranges.

Thank you goes to Georgios Konstantopoulos who helped me with liquidity calculations and code review.

Finally, thank you goes to Jorropo.eth who has accompanied and helped me in the weeks long journey of discovering how to calculate Uniswap's V3 sqrt ratios, on Uniswap's Discord. He also gave the following excellent explanation as to why the Token Pair reserves are square rooted:

This is so the difference gets exponentially written.

Let's assume ticks were just 100$ in size, so you have one from 0-100, 100-200, ... A token that is price at 250$ would need to do +20% in price to cross a tick. But a token priced 25050$ it's bearly +0.2%.

Having them SQRT makes the ratio constant. So in any cases it's just let's say any 1% of price change, cross a tick.

This spreads them each 1% appart (so fewer and fewer ticks), instead of each 100$ appart.

Maintenance & Development

Update Node Version

When a new node version should be supported, updated the following:

  • /package.json
  • /.nvmrc
  • /.circleci/config.yml

Releasing

  1. Update the changelog bellow ("Release History").
  2. Ensure you are on master and your repository is clean.
  3. Type: npm run release for patch version jump.
    • npm run release:minor for minor version jump.
    • npm run release:major for major major jump.

Release History

  • v3.0.2, 07 Sep 2021
  • v3.0.1, 06 Sep 2021
    • Updated all dependencies to latest and especially jsbi to ^3.2.1 so users won't suffer from the broken 3.2.0 release.
  • v3.0.0, 22 Aug 2021
    • Breaking Changed signature of `getAmountsForCurrentLiquidity()``, it now uses a tuple for the decimals and added formatting options for the result. By default liquidity values will now have 5 decimal places instead of 1
    • Breaking Changed signature of sqrtPrice() and tickPrice(), they now use a tuple for the decimals and reversing the price has been decoupled to formatting.
    • Breaking Decoupled and replaced fraction calculation and formatting functions to crypto-utils package (toSignificant() and toFixed() functions).
    • Replaced internal fraction and formatting functions with crypto-utils package.
    • Fixed bug with priceRange() utility that affected the liquidity calculation when the tickSpacing argument was used, thank you @sydneyhenrard.
  • v2.0.1, 06 Aug 2021
    • Fixed order of price calculation for sqrtPrice().
  • v2.0.0, 06 Aug 2021
    • Implemented the liquidity calculation functions getAmountsForCurrentLiquidity() and getAmountsForLiquidityRange().
    • Implemented Tick Math functions at tickMath.getSqrtRatioAtTick() and tickMath.getTickAtSqrtRatio.
    • Added sqrtPrice function on the API (same as the default export).
    • New constant values added (ZERO, ONE, TWO, MaxUint256, MIN_TICK, MAX_TICK, MIN_SQRT_RATIO, MAX_SQRT_RATIO).
    • New utils functions added (tickRange, expDecs, biConv).
    • Breaking Moved utility functions (encodeSqrtRatioX96() and sqrt()) into the utils namespace.
    • Breaking Moved constants (Rounding, Q96 and Q192) into the constants namespace.
    • Breaking Renamed output function .toScalar() to .toFraction().
    • Internal change of toFixed() and toSignificant() to accept a tuple Array instead of an Object.
  • v1.1.0, 31 Jul 2021
    • Added tickPrice() function to calculate price based on current tick value.
    • Refactored the default price calculation function with better variable names.
    • Fixed a decimal miscalculation issue on pairs with different decimal values.
  • v1.0.0, 19 Jul 2021
    • Big Bang

License

Copyright © Thanos Polychronakis and Authors, Licensed under ISC.