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);
});
@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