Skip to content

Instantly share code, notes, and snippets.

@ahmedali8
Created March 9, 2025 12:11
Show Gist options
  • Select an option

  • Save ahmedali8/3145761bda88d8dad4ca1f01e8b9f989 to your computer and use it in GitHub Desktop.

Select an option

Save ahmedali8/3145761bda88d8dad4ca1f01e8b9f989 to your computer and use it in GitHub Desktop.
Amounts from Liquidity & Swap Calculations - Uniswap V4
import { SqrtPriceMath, TickMath } from "@uniswap/v3-sdk";
import Decimal from "decimal.js";
import { ethers } from "ethers";
import JSBI from "jsbi";
export const JSBI_ZERO = JSBI.BigInt(0);
export function getSqrtPriceAtTick(tick: string): string {
return new Decimal(1.0001).pow(tick).sqrt().mul(new Decimal(2).pow(96)).toFixed(0);
}
export function getAmount0Delta(sqrtPriceAX96: JSBI, sqrtPriceBX96: JSBI, liquidity: JSBI): JSBI {
if (JSBI.lessThan(liquidity, JSBI_ZERO)) {
return SqrtPriceMath.getAmount0Delta(sqrtPriceAX96, sqrtPriceBX96, JSBI.unaryMinus(liquidity), false);
} else {
return JSBI.unaryMinus(SqrtPriceMath.getAmount0Delta(sqrtPriceAX96, sqrtPriceBX96, liquidity, true));
}
}
export function getAmount1Delta(sqrtPriceAX96: JSBI, sqrtPriceBX96: JSBI, liquidity: JSBI): JSBI {
if (JSBI.lessThan(liquidity, JSBI_ZERO)) {
return SqrtPriceMath.getAmount1Delta(sqrtPriceAX96, sqrtPriceBX96, JSBI.unaryMinus(liquidity), false);
} else {
return JSBI.unaryMinus(SqrtPriceMath.getAmount1Delta(sqrtPriceAX96, sqrtPriceBX96, liquidity, true));
}
}
export function modifyLiquidity(
_tickLower: string,
_tickUpper: string,
_liquidity: string,
slot0Tick: string,
slot0Price: string,
): string[] {
const liquidity = JSBI.BigInt(_liquidity);
if (JSBI.EQ(liquidity, 0)) {
return [JSBI_ZERO.toString(), JSBI_ZERO.toString()];
}
// State values in slot0.
const tick = JSBI.BigInt(slot0Tick);
const sqrtPriceX96 = JSBI.BigInt(slot0Price);
// Position lower and upper ticks.
const tickLower = JSBI.BigInt(_tickLower);
const tickUpper = JSBI.BigInt(_tickUpper);
const delta: string[] = [];
if (JSBI.LT(tick, tickLower)) {
// The current tick is less than the lowest tick of the position, so the position is entirely in token0.
const priceLower = JSBI.BigInt(getSqrtPriceAtTick(_tickLower));
const priceUpper = JSBI.BigInt(getSqrtPriceAtTick(_tickUpper));
const amount0 = getAmount0Delta(priceLower, priceUpper, liquidity);
delta.push(amount0.toString());
delta.push(JSBI_ZERO.toString());
} else if (JSBI.LT(tick, tickUpper)) {
// In-range liquidity. As we are just calculating the values of the delta.
// We do not update any global state variable for state.liquidity, but note that the protocol increments state liquidity in this case.
const priceUpper = JSBI.BigInt(getSqrtPriceAtTick(_tickUpper));
let priceLower = JSBI.BigInt(getSqrtPriceAtTick(_tickLower));
// When tickLower == the current tick, the price returned from getSqrtPriceATick has a slight error in JS.
// In solidity because the calculations for getSqrtRatioAtTick(tickLower) == currentSqrtPriceX96, the
// numerator becomes 0, and the amount becomes 0.
// So instead of using the price from getSqrtRatioAtTick, we set the priceLower to the current price.
if (JSBI.EQ(tickLower, tick)) {
priceLower = sqrtPriceX96;
}
const amount0 = getAmount0Delta(sqrtPriceX96, priceUpper, liquidity);
const amount1 = getAmount1Delta(priceLower, sqrtPriceX96, liquidity);
delta.push(amount0.toString());
delta.push(amount1.toString());
} else {
// The current tick is greater than the highest tick of the position, meaning the position is entirely in token1.
const priceLower = JSBI.BigInt(getSqrtPriceAtTick(_tickLower));
const priceUpper = JSBI.BigInt(getSqrtPriceAtTick(_tickUpper));
const amount1 = getAmount1Delta(priceLower, priceUpper, liquidity);
delta.push(JSBI_ZERO.toString());
delta.push(amount1.toString());
}
return delta;
}
export function getInt128Array(delta: string[]): string {
return ethers.utils.defaultAbiCoder.encode(["int128[]"], [delta]);
}
// ------------------------------------------------------------------------------------------------------------------- //
const currentTick = 175052;
// First token1 Then token0
const liquidity = JSBI.BigInt("4899712312116710985145008");
const sqrtPriceX96 = JSBI.BigInt("501066558273621464884319660560427");
const priceUpper = JSBI.BigInt(getSqrtPriceAtTick((-193620).toString()));
const priceLower = JSBI.BigInt(getSqrtPriceAtTick((-194040).toString()));
const amount0 = getAmount0Delta(sqrtPriceX96, priceUpper, liquidity);
const amount1 = getAmount1Delta(priceLower, sqrtPriceX96, liquidity);
console.log("amount0: ", amount0.toString()); // -2000130431716761975 wei (2.000130431716761975 eth)
console.log("amount1: ", amount1.toString()); // -79999999999999999989440096 wei tokens (79999999.999999999989440096 tokens)
// const delta = modifyLiquidity(
// TickMath.MIN_TICK.toString(),
// TickMath.MAX_TICK.toString(),
// liquidity.toString(),
// currentTick.toString(),
// sqrtPriceX96.toString(),
// );
// console.log("delta: ", delta);
// console.log("int128Array: ", getInt128Array(delta));
...
"dependencies": {
"@uniswap/sdk-core": "^7.5.0",
"@uniswap/v3-sdk": "^3.24.0",
"@uniswap/v4-sdk": "^1.18.1",
"decimal.js": "^10.5.0",
"ethers": "5.7.1",
"hardhat": "2.14.1",
"jsbi": "^3.1.4"
},
...
import { TOKEN_ADDRESS } from "./constants";
import { stateView } from "./contracts";
import { ChainId, type Currency, CurrencyAmount, Ether, Token } from "@uniswap/sdk-core";
import {
ADDRESS_ZERO,
FeeAmount,
TICK_SPACINGS,
TickConstructorArgs,
TickMath,
nearestUsableTick,
} from "@uniswap/v3-sdk";
import { Pool, Position } from "@uniswap/v4-sdk";
import { ethers } from "hardhat";
import JSBI from "jsbi";
async function main() {
const [owner] = await ethers.getSigners();
console.log(`Owner ${owner.address}, balance: ${ethers.utils.formatEther(await owner.getBalance())} ETH`);
const chainId = ChainId.SEPOLIA;
// Currencies
const currencyNative = Ether.onChain(chainId);
const currency0: Currency = new Token(chainId, ADDRESS_ZERO, 18, "ETH", "Ether");
const currency1: Currency = new Token(chainId, TOKEN_ADDRESS, 18, "TKN3", "VToken3");
const fee = FeeAmount.HIGH; // 1%
const tickSpacing = TICK_SPACINGS[fee]; // 200 for HIGH
const poolId = Pool.getPoolId(currencyNative, currency1, fee, tickSpacing, ADDRESS_ZERO);
// console.log("PoolId: ", poolId);
const poolKey = Pool.getPoolKey(currencyNative, currency1, fee, tickSpacing, ADDRESS_ZERO);
// console.log("PoolKey: ", poolKey);
// Provide full-range liquidity to the pool
const tickLower = nearestUsableTick(TickMath.MIN_TICK, poolKey.tickSpacing);
const tickUpper = nearestUsableTick(TickMath.MAX_TICK, poolKey.tickSpacing);
// Get Slot0 values
const slot0 = await stateView.getSlot0(poolId);
// Get Liquidity
const _liquidity = await stateView.getLiquidity(poolId);
// Slot0 values
const sqrtRatioX96 = JSBI.BigInt(slot0.sqrtPriceX96.toString()); // JSBI.BigInt("501066558273621464884319660560427");
const tick = slot0.tick; // TickMath.getTickAtSqrtRatio(sqrtRatioX96);
const liquidity = JSBI.BigInt(_liquidity.toString()); // JSBI.BigInt("12649523095253078069592");
// Tick Bitmap
const tickBitmap: TickConstructorArgs[] = [
{
index: tickLower,
liquidityNet: liquidity,
liquidityGross: liquidity,
},
{
index: tickUpper,
liquidityNet: JSBI.multiply(liquidity, JSBI.BigInt(-1)),
liquidityGross: liquidity,
},
];
// Pool Instance
const pool = new Pool(
currencyNative,
currency1,
fee,
tickSpacing,
ADDRESS_ZERO,
sqrtRatioX96,
liquidity,
tick,
tickBitmap,
);
console.log("Pool", pool);
// Position Instance
const position = new Position({
pool,
liquidity,
tickLower,
tickUpper,
});
const { amount0, amount1 } = position.mintAmounts;
console.log("Amount0: ", amount0.toString());
console.log("Amount1: ", amount1.toString());
/// SWAP ///
{
console.log("ETH -> TKN3");
const inputAmount = CurrencyAmount.fromRawAmount(currencyNative, 1e18); // 1 ETH
const [outputAmount] = await pool.getOutputAmount(inputAmount);
console.log("Output Amount", outputAmount.quotient.toString());
console.log("Output Amount", outputAmount.toExact());
}
{
console.log("TKN3 -> ETH");
const inputAmount = CurrencyAmount.fromRawAmount(currency1, 1_000_000e18); // 1,000,000 TKN3
const [outputAmount] = await pool.getOutputAmount(inputAmount);
console.log("Output Amount", outputAmount.quotient.toString());
console.log("Output Amount", outputAmount.toExact());
}
{
console.log("ETH <- TKN3");
const outputAmount = CurrencyAmount.fromRawAmount(currency1, 1_000_000e18); // 1,000,000 TKN3
const [inputAmount] = await pool.getInputAmount(outputAmount);
console.log("Input Amount", inputAmount.quotient.toString());
console.log("Input Amount", inputAmount.toExact());
}
{
console.log("Three Swaps of ETH <- TKN3");
console.log("Current SqrtRatioX96", pool.sqrtRatioX96.toString());
const outputAmount = CurrencyAmount.fromRawAmount(currency1, 1_000_000e18); // 1,000,000 TKN3
const [inputAmount, updatedPool] = await pool.getInputAmount(outputAmount);
console.log("Input Amount", inputAmount.toExact());
const [inputAmount1, updatedPool2] = await updatedPool.getInputAmount(outputAmount);
console.log("Input Amount 1", inputAmount1.toExact());
const [inputAmount2, updatedPool3] = await updatedPool2.getInputAmount(outputAmount);
console.log("Input Amount 2", inputAmount2.toExact());
console.log("Latest SqrtRatioX96", updatedPool3.sqrtRatioX96.toString());
}
console.log("a: ", CurrencyAmount.fromFractionalAmount(currency1, 1_000_000, 18));
console.log("b: ", CurrencyAmount.fromFractionalAmount(currency1, 1_000_000, 18).quotient.toString());
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
@quardz
Copy link

quardz commented Sep 10, 2025

Would love to see example for NFT v4 NFT id to current amounts, fees collected, ranges and currencies in ts. Happy to pay.

@ahmedali8
Copy link
Author

sure, my TG is @devv0x

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment