@uniswap/v4-sdk
Version:
⚒️ An SDK for building applications on top of Uniswap V4
273 lines • 14.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Position = void 0;
const tslib_1 = require("tslib");
const sdk_core_1 = require("@uniswap/sdk-core");
const jsbi_1 = tslib_1.__importDefault(require("jsbi"));
const tiny_invariant_1 = tslib_1.__importDefault(require("tiny-invariant"));
const pool_1 = require("./pool");
const v3_sdk_1 = require("@uniswap/v3-sdk");
const internalConstants_1 = require("../internalConstants");
const priceTickConversions_1 = require("../utils/priceTickConversions");
/**
* Represents a position on a Uniswap V4 Pool
* @dev Similar to the V3 implementation
* - using Currency instead of Token
* - keep in mind that Pool and liquidity must be fetched from the pool manager
*/
class Position {
/**
* Constructs a position for a given pool with the given liquidity
* @param pool For which pool the liquidity is assigned
* @param liquidity The amount of liquidity that is in the position
* @param tickLower The lower tick of the position
* @param tickUpper The upper tick of the position
*/
constructor({ pool, liquidity, tickLower, tickUpper }) {
// cached resuts for the getters
this._token0Amount = null;
this._token1Amount = null;
this._mintAmounts = null;
(0, tiny_invariant_1.default)(tickLower < tickUpper, 'TICK_ORDER');
(0, tiny_invariant_1.default)(tickLower >= v3_sdk_1.TickMath.MIN_TICK && tickLower % pool.tickSpacing === 0, 'TICK_LOWER');
(0, tiny_invariant_1.default)(tickUpper <= v3_sdk_1.TickMath.MAX_TICK && tickUpper % pool.tickSpacing === 0, 'TICK_UPPER');
this.pool = pool;
this.tickLower = tickLower;
this.tickUpper = tickUpper;
this.liquidity = jsbi_1.default.BigInt(liquidity);
}
/**
* Returns the price of token0 at the lower tick
*/
get token0PriceLower() {
return (0, priceTickConversions_1.tickToPrice)(this.pool.currency0, this.pool.currency1, this.tickLower);
}
/**
* Returns the price of token0 at the upper tick
*/
get token0PriceUpper() {
return (0, priceTickConversions_1.tickToPrice)(this.pool.currency0, this.pool.currency1, this.tickUpper);
}
/**
* Returns the amount of token0 that this position's liquidity could be burned for at the current pool price
*/
get amount0() {
if (!this._token0Amount) {
if (this.pool.tickCurrent < this.tickLower) {
this._token0Amount = sdk_core_1.CurrencyAmount.fromRawAmount(this.pool.currency0, v3_sdk_1.SqrtPriceMath.getAmount0Delta(v3_sdk_1.TickMath.getSqrtRatioAtTick(this.tickLower), v3_sdk_1.TickMath.getSqrtRatioAtTick(this.tickUpper), this.liquidity, false));
}
else if (this.pool.tickCurrent < this.tickUpper) {
this._token0Amount = sdk_core_1.CurrencyAmount.fromRawAmount(this.pool.currency0, v3_sdk_1.SqrtPriceMath.getAmount0Delta(this.pool.sqrtRatioX96, v3_sdk_1.TickMath.getSqrtRatioAtTick(this.tickUpper), this.liquidity, false));
}
else {
this._token0Amount = sdk_core_1.CurrencyAmount.fromRawAmount(this.pool.currency0, internalConstants_1.ZERO);
}
}
return this._token0Amount;
}
/**
* Returns the amount of token1 that this position's liquidity could be burned for at the current pool price
*/
get amount1() {
if (!this._token1Amount) {
if (this.pool.tickCurrent < this.tickLower) {
this._token1Amount = sdk_core_1.CurrencyAmount.fromRawAmount(this.pool.currency1, internalConstants_1.ZERO);
}
else if (this.pool.tickCurrent < this.tickUpper) {
this._token1Amount = sdk_core_1.CurrencyAmount.fromRawAmount(this.pool.currency1, v3_sdk_1.SqrtPriceMath.getAmount1Delta(v3_sdk_1.TickMath.getSqrtRatioAtTick(this.tickLower), this.pool.sqrtRatioX96, this.liquidity, false));
}
else {
this._token1Amount = sdk_core_1.CurrencyAmount.fromRawAmount(this.pool.currency1, v3_sdk_1.SqrtPriceMath.getAmount1Delta(v3_sdk_1.TickMath.getSqrtRatioAtTick(this.tickLower), v3_sdk_1.TickMath.getSqrtRatioAtTick(this.tickUpper), this.liquidity, false));
}
}
return this._token1Amount;
}
/**
* Returns the lower and upper sqrt ratios if the price 'slips' up to slippage tolerance percentage
* @param slippageTolerance The amount by which the price can 'slip' before the transaction will revert
* @returns The sqrt ratios after slippage
*/
ratiosAfterSlippage(slippageTolerance) {
const priceLower = this.pool.token0Price.asFraction.multiply(new sdk_core_1.Percent(1).subtract(slippageTolerance));
const priceUpper = this.pool.token0Price.asFraction.multiply(slippageTolerance.add(1));
let sqrtRatioX96Lower = (0, v3_sdk_1.encodeSqrtRatioX96)(priceLower.numerator, priceLower.denominator);
if (jsbi_1.default.lessThanOrEqual(sqrtRatioX96Lower, v3_sdk_1.TickMath.MIN_SQRT_RATIO)) {
sqrtRatioX96Lower = jsbi_1.default.add(v3_sdk_1.TickMath.MIN_SQRT_RATIO, jsbi_1.default.BigInt(1));
}
let sqrtRatioX96Upper = (0, v3_sdk_1.encodeSqrtRatioX96)(priceUpper.numerator, priceUpper.denominator);
if (jsbi_1.default.greaterThanOrEqual(sqrtRatioX96Upper, v3_sdk_1.TickMath.MAX_SQRT_RATIO)) {
sqrtRatioX96Upper = jsbi_1.default.subtract(v3_sdk_1.TickMath.MAX_SQRT_RATIO, jsbi_1.default.BigInt(1));
}
return {
sqrtRatioX96Lower,
sqrtRatioX96Upper,
};
}
/**
* Returns the maximum amount of token0 and token1 that must be sent in order to safely mint the amount of liquidity held by the position
* with the given slippage tolerance
* @param slippageTolerance Tolerance of unfavorable slippage from the current price
* @returns The amounts, with slippage
* @dev In v4, minting and increasing is protected by maximum amounts of token0 and token1.
*/
mintAmountsWithSlippage(slippageTolerance) {
// get lower/upper prices
// these represent the lowest and highest prices that the pool is allowed to "slip" to
const { sqrtRatioX96Upper, sqrtRatioX96Lower } = this.ratiosAfterSlippage(slippageTolerance);
// construct counterfactual pools from the lower bounded price and the upper bounded price
const poolLower = new pool_1.Pool(this.pool.token0, this.pool.token1, this.pool.fee, this.pool.tickSpacing, this.pool.hooks, sqrtRatioX96Lower, 0 /* liquidity doesn't matter */, v3_sdk_1.TickMath.getTickAtSqrtRatio(sqrtRatioX96Lower));
const poolUpper = new pool_1.Pool(this.pool.token0, this.pool.token1, this.pool.fee, this.pool.tickSpacing, this.pool.hooks, sqrtRatioX96Upper, 0 /* liquidity doesn't matter */, v3_sdk_1.TickMath.getTickAtSqrtRatio(sqrtRatioX96Upper));
// Note: Slippage derivation in v4 is different from v3.
// When creating a position (minting) or adding to a position (increasing) slippage is bounded by the MAXIMUM amount in in token0 and token1.
// The largest amount of token1 will happen when the price slips up, so we use the poolUpper to get amount1.
// The largest amount of token0 will happen when the price slips down, so we use the poolLower to get amount0.
// Ie...We want the larger amounts, which occurs at the upper price for amount1...
const { amount1 } = new Position({
pool: poolUpper,
liquidity: this.liquidity,
tickLower: this.tickLower,
tickUpper: this.tickUpper,
}).mintAmounts;
// ...and the lower for amount0
const { amount0 } = new Position({
pool: poolLower,
liquidity: this.liquidity,
tickLower: this.tickLower,
tickUpper: this.tickUpper,
}).mintAmounts;
return { amount0, amount1 };
}
/**
* Returns the minimum amounts that should be requested in order to safely burn the amount of liquidity held by the
* position with the given slippage tolerance
* @param slippageTolerance tolerance of unfavorable slippage from the current price
* @returns The amounts, with slippage
*/
burnAmountsWithSlippage(slippageTolerance) {
// get lower/upper prices
const { sqrtRatioX96Upper, sqrtRatioX96Lower } = this.ratiosAfterSlippage(slippageTolerance);
// construct counterfactual pools
const poolLower = new pool_1.Pool(this.pool.currency0, this.pool.currency1, this.pool.fee, this.pool.tickSpacing, this.pool.hooks, sqrtRatioX96Lower, 0 /* liquidity doesn't matter */, v3_sdk_1.TickMath.getTickAtSqrtRatio(sqrtRatioX96Lower));
const poolUpper = new pool_1.Pool(this.pool.currency0, this.pool.currency1, this.pool.fee, this.pool.tickSpacing, this.pool.hooks, sqrtRatioX96Upper, 0 /* liquidity doesn't matter */, v3_sdk_1.TickMath.getTickAtSqrtRatio(sqrtRatioX96Upper));
// we want the smaller amounts...
// ...which occurs at the upper price for amount0...
const amount0 = new Position({
pool: poolUpper,
liquidity: this.liquidity,
tickLower: this.tickLower,
tickUpper: this.tickUpper,
}).amount0;
// ...and the lower for amount1
const amount1 = new Position({
pool: poolLower,
liquidity: this.liquidity,
tickLower: this.tickLower,
tickUpper: this.tickUpper,
}).amount1;
return { amount0: amount0.quotient, amount1: amount1.quotient };
}
/**
* Returns the minimum amounts that must be sent in order to mint the amount of liquidity held by the position at
* the current price for the pool
*/
get mintAmounts() {
if (this._mintAmounts === null) {
if (this.pool.tickCurrent < this.tickLower) {
return {
amount0: v3_sdk_1.SqrtPriceMath.getAmount0Delta(v3_sdk_1.TickMath.getSqrtRatioAtTick(this.tickLower), v3_sdk_1.TickMath.getSqrtRatioAtTick(this.tickUpper), this.liquidity, true),
amount1: internalConstants_1.ZERO,
};
}
else if (this.pool.tickCurrent < this.tickUpper) {
return {
amount0: v3_sdk_1.SqrtPriceMath.getAmount0Delta(this.pool.sqrtRatioX96, v3_sdk_1.TickMath.getSqrtRatioAtTick(this.tickUpper), this.liquidity, true),
amount1: v3_sdk_1.SqrtPriceMath.getAmount1Delta(v3_sdk_1.TickMath.getSqrtRatioAtTick(this.tickLower), this.pool.sqrtRatioX96, this.liquidity, true),
};
}
else {
return {
amount0: internalConstants_1.ZERO,
amount1: v3_sdk_1.SqrtPriceMath.getAmount1Delta(v3_sdk_1.TickMath.getSqrtRatioAtTick(this.tickLower), v3_sdk_1.TickMath.getSqrtRatioAtTick(this.tickUpper), this.liquidity, true),
};
}
}
return this._mintAmounts;
}
/**
* Returns the AllowanceTransferPermitBatch for adding liquidity to a position
* @param slippageTolerance The amount by which the price can 'slip' before the transaction will revert
* @param spender The spender of the permit (should usually be the PositionManager)
* @param nonce A valid permit2 nonce
* @param deadline The deadline for the permit
*/
permitBatchData(slippageTolerance, spender, nonce, deadline) {
const { amount0, amount1 } = this.mintAmountsWithSlippage(slippageTolerance);
return {
details: [
{
token: this.pool.currency0.wrapped.address,
amount: amount0,
expiration: deadline,
nonce: nonce,
},
{
token: this.pool.currency1.wrapped.address,
amount: amount1,
expiration: deadline,
nonce: nonce,
},
],
spender,
sigDeadline: deadline,
};
}
/**
* Computes the maximum amount of liquidity received for a given amount of token0, token1,
* and the prices at the tick boundaries.
* @param pool The pool for which the position should be created
* @param tickLower The lower tick of the position
* @param tickUpper The upper tick of the position
* @param amount0 token0 amountzw
* @param amount1 token1 amount
* @param useFullPrecision If false, liquidity will be maximized according to what the router can calculate,
* not what core can theoretically support
* @returns The amount of liquidity for the position
*/
static fromAmounts({ pool, tickLower, tickUpper, amount0, amount1, useFullPrecision, }) {
const sqrtRatioAX96 = v3_sdk_1.TickMath.getSqrtRatioAtTick(tickLower);
const sqrtRatioBX96 = v3_sdk_1.TickMath.getSqrtRatioAtTick(tickUpper);
return new Position({
pool,
tickLower,
tickUpper,
liquidity: (0, v3_sdk_1.maxLiquidityForAmounts)(pool.sqrtRatioX96, sqrtRatioAX96, sqrtRatioBX96, amount0, amount1, useFullPrecision),
});
}
/**
* Computes a position with the maximum amount of liquidity received for a given amount of token0, assuming an unlimited amount of token1
* @param pool The pool for which the position is created
* @param tickLower The lower tick
* @param tickUpper The upper tick
* @param amount0 The desired amount of token0
* @param useFullPrecision If true, liquidity will be maximized according to what the router can calculate,
* not what core can theoretically support
* @returns The position
*/
static fromAmount0({ pool, tickLower, tickUpper, amount0, useFullPrecision, }) {
return Position.fromAmounts({ pool, tickLower, tickUpper, amount0, amount1: sdk_core_1.MaxUint256, useFullPrecision });
}
/**
* Computes a position with the maximum amount of liquidity received for a given amount of token1, assuming an unlimited amount of token0
* @param pool The pool for which the position is created
* @param tickLower The lower tick
* @param tickUpper The upper tick
* @param amount1 The desired amount of token1
* @returns The position
*/
static fromAmount1({ pool, tickLower, tickUpper, amount1, }) {
// this function always uses full precision,
return Position.fromAmounts({ pool, tickLower, tickUpper, amount0: sdk_core_1.MaxUint256, amount1, useFullPrecision: true });
}
}
exports.Position = Position;
//# sourceMappingURL=position.js.map