UNPKG

@uniswap/v4-sdk

Version:

⚒️ An SDK for building applications on top of Uniswap V4

273 lines 14.8 kB
"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