Uniswap v3 sqrtPriceX96 = sqrt(amount1/amount0) * 2^96 in raw units, token0 sorted by lower address
Goal: initialize a v3 pool at a chosen ratio, e.g. 100 SWAP against 0.1 WETH, and supply sqrtPriceX96.
The encoding is sqrt(price) * 2^96, where price is token1 per token0 expressed in raw base units, not human units:
// amounts in wei/base units, decimals already applied
const sqrtPriceX96 = BigInt(Math.floor(Math.sqrt(Number(amount1) / Number(amount0)) * 2 ** 96));
// inverse check
const price = Number(sqrtPriceX96) ** 2 / 2 ** 192;Three things break naive attempts. First, token0 and token1 are not your choice: the pool sorts them by address, token0 < token1, so if your ERC-20 sorts above WETH the ratio must be inverted before encoding, or the pool opens at a wildly wrong price. Second, decimals must be folded into the raw amounts — 100 tokens of 18 decimals against 0.1 WETH is 100e18 / 0.1e18, and mixing human and raw units is the most common source of a pool initialized orders of magnitude off. Third, Math.sqrt on doubles and the multiply by 2**96 before the BigInt conversion lose low-order precision; for anything but a throwaway testnet pool use encodeSqrtRatioX96(amount1, amount0) from @uniswap/v3-sdk (JSBI, exact) or an integer sqrt over BigInt.
After initialization, the first mint still needs tick bounds that are multiples of the fee tier's tick spacing (60 for the 0.3% / fee 3000 tier), otherwise the position call reverts even though the pool exists.
The ethers snippets in the original are v5 (ethers.providers.JsonRpcProvider, ethers.utils.parseEther); on ethers v6 these are ethers.JsonRpcProvider and ethers.parseEther. The sqrtPriceX96 math itself is unchanged.
Source: adapted from a Stack Exchange answer by emtothed (question by emtothed), https://ethereum.stackexchange.com/questions/161745/how-to-calculate-sqrtpricex96-for-uniswap-pool-creation — CC BY-SA 4.0