UniV3Prices
A Node.js library to calculate Uniswap V3 ratios (prices) and liquidity (reserves).
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:
- Calculate price based on the sqrtPrice Value.
- Calculate price based on the current tick Value.
- Calculate amounts of tokens (reserves) for the current tick of a pool.
- Tick Math functions for converting tick to sqrt and vice versa.
- Provide utility functions to work with Uniswap V3.
- Various constants to work with Uniswap V3.
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:tickStep
{number=} Optionally, set how many tick steps of liquidity range should be calculated (default: 0).token0Opts
{Object=} Calculation and formatting options for the token0 reserves, see available options on the crypto-utils package..token1Opts
{Object=} Calculation and formatting options for the token1 reserves, see available options on the crypto-utils package..
-
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 thesqrtPriceX96
.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 of96
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 at32
.Q96
:: Power of 2 at96
.Q192
:: Power of 2 at192
.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
- Update the changelog bellow ("Release History").
- Ensure you are on master and your repository is clean.
- 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
- JSBI 3.2.1 also had an issue when used on the browser environment so had to upgrade to
^3.2.2
to ensure compatibility.
- JSBI 3.2.1 also had an issue when used on the browser environment so had to upgrade to
- 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.
- Updated all dependencies to latest and especially jsbi to
- 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()
andtickPrice()
, 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()
andtoFixed()
functions). - Replaced internal fraction and formatting functions with crypto-utils package.
- Fixed bug with
priceRange()
utility that affected the liquidity calculation when thetickSpacing
argument was used, thank you @sydneyhenrard.
- v2.0.1, 06 Aug 2021
- Fixed order of price calculation for
sqrtPrice()
.
- Fixed order of price calculation for
- v2.0.0, 06 Aug 2021
- Implemented the liquidity calculation functions
getAmountsForCurrentLiquidity()
andgetAmountsForLiquidityRange()
. - Implemented Tick Math functions at
tickMath.getSqrtRatioAtTick()
andtickMath.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()
andsqrt()
) into theutils
namespace. - Breaking Moved constants (
Rounding
,Q96
andQ192
) into theconstants
namespace. - Breaking Renamed output function
.toScalar()
to.toFraction()
. - Internal change of
toFixed()
andtoSignificant()
to accept a tuple Array instead of an Object.
- Implemented the liquidity calculation functions
- 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.
- Added
- v1.0.0, 19 Jul 2021
- Big Bang
License
Copyright Š Thanos Polychronakis and Authors, Licensed under ISC.