UNPKG

@alcorexchange/alcor-swap-sdk

Version:

## Installation ​​ **npm** ``` npm i @alcorexchange/alcor-swap-sdk ``` **yarn** ``` yarn add @alcorexchange/alcor-swap-sdk ``` ## Usage ### Import:

643 lines (608 loc) 19.6 kB
import { CurrencyAmount, Price, Percent } from "./fractions"; import { Token } from "./token"; import { BigintIsh, MaxUint64, Q64 } from "../internalConstants"; import JSBI from "jsbi"; import invariant from "tiny-invariant"; import { ZERO } from "../internalConstants"; import { maxLiquidityForAmounts } from "../utils/maxLiquidityForAmounts"; import { tickToPrice } from "../utils/priceTickConversions"; import { SqrtPriceMath } from "../utils/sqrtPriceMath"; import { TickMath } from "../utils/tickMath"; import { encodeSqrtRatioX64 } from "../utils/encodeSqrtRatioX64"; import { Pool } from "./pool"; import { TickLibrary, subIn128 } from "../utils"; interface PositionConstructorArgs { id: number, owner: string, pool: Pool; tickLower: number; tickUpper: number; liquidity: BigintIsh; feeGrowthInsideALastX64: BigintIsh, feeGrowthInsideBLastX64: BigintIsh, feesA: BigintIsh, feesB: BigintIsh, } interface Fees { feesA: CurrencyAmount<Token>, feesB: CurrencyAmount<Token> } export class Position { public readonly id: number; public readonly owner: string; public readonly pool: Pool; public readonly tickLower: number; public readonly tickUpper: number; public readonly liquidity: JSBI; public readonly feesA: JSBI; public readonly feesB: JSBI; public readonly feeGrowthInsideALastX64: JSBI; public readonly feeGrowthInsideBLastX64: JSBI; // cached resuts for the getters private _tokenAAmount: CurrencyAmount<Token> | null = null; private _tokenBAmount: CurrencyAmount<Token> | null = null; private _mintAmounts: Readonly<{ amountA: JSBI; amountB: JSBI }> | null = null; /** * 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 lower The lower tick of the position * @param upper The upper tick of the position */ public constructor({ id, owner, pool, liquidity, tickLower, tickUpper, feeGrowthInsideALastX64 = 0, feeGrowthInsideBLastX64 = 0, feesA = 0, feesB = 0, }: PositionConstructorArgs) { invariant(tickLower < tickUpper, "TICK_ORDER"); invariant( tickLower >= TickMath.MIN_TICK && tickLower % pool.tickSpacing === 0, "TICK_LOWER" ); invariant( tickUpper <= TickMath.MAX_TICK && tickUpper % pool.tickSpacing === 0, "TICK_UPPER" ); this.id = id; this.owner = owner; this.pool = pool; this.tickLower = tickLower; this.tickUpper = tickUpper; this.liquidity = JSBI.BigInt(liquidity); this.feeGrowthInsideALastX64 = JSBI.BigInt(feeGrowthInsideALastX64); this.feeGrowthInsideBLastX64 = JSBI.BigInt(feeGrowthInsideBLastX64); this.feesA = JSBI.BigInt(feesA) this.feesB = JSBI.BigInt(feesB) } public get inRange(): boolean { return ( this.tickLower < this.pool.tickCurrent && this.pool.tickCurrent < this.tickUpper ); } /** * Returns the price of tokenA at the lower tick */ public get tokenAPriceLower(): Price<Token, Token> { return tickToPrice(this.pool.tokenA, this.pool.tokenB, this.tickLower); } /** * Returns the price of tokenA at the upper tick */ public get tokenAPriceUpper(): Price<Token, Token> { return tickToPrice(this.pool.tokenA, this.pool.tokenB, this.tickUpper); } /** * Returns the amount of tokenA that this position's liquidity could be burned for at the current pool price */ public get amountA(): CurrencyAmount<Token> { if (this._tokenAAmount === null) { if (this.pool.tickCurrent < this.tickLower) { this._tokenAAmount = CurrencyAmount.fromRawAmount( this.pool.tokenA, SqrtPriceMath.getAmountADelta( TickMath.getSqrtRatioAtTick(this.tickLower), TickMath.getSqrtRatioAtTick(this.tickUpper), this.liquidity, false ) ); } else if (this.pool.tickCurrent < this.tickUpper) { this._tokenAAmount = CurrencyAmount.fromRawAmount( this.pool.tokenA, SqrtPriceMath.getAmountADelta( this.pool.sqrtPriceX64, TickMath.getSqrtRatioAtTick(this.tickUpper), this.liquidity, false ) ); } else { this._tokenAAmount = CurrencyAmount.fromRawAmount( this.pool.tokenA, ZERO ); } } return this._tokenAAmount; } /** * Returns the amount of tokenB that this position's liquidity could be burned for at the current pool price */ public get amountB(): CurrencyAmount<Token> { if (this._tokenBAmount === null) { if (this.pool.tickCurrent < this.tickLower) { this._tokenBAmount = CurrencyAmount.fromRawAmount( this.pool.tokenB, ZERO ); } else if (this.pool.tickCurrent < this.tickUpper) { this._tokenBAmount = CurrencyAmount.fromRawAmount( this.pool.tokenB, SqrtPriceMath.getAmountBDelta( TickMath.getSqrtRatioAtTick(this.tickLower), this.pool.sqrtPriceX64, this.liquidity, false ) ); } else { this._tokenBAmount = CurrencyAmount.fromRawAmount( this.pool.tokenB, SqrtPriceMath.getAmountBDelta( TickMath.getSqrtRatioAtTick(this.tickLower), TickMath.getSqrtRatioAtTick(this.tickUpper), this.liquidity, false ) ); } } return this._tokenBAmount; } /** * 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 */ private ratiosAfterSlippage(slippageTolerance: Percent): { sqrtPriceX64Lower: JSBI; sqrtPriceX64Upper: JSBI; } { const priceLower = this.pool.tokenAPrice.asFraction.multiply( new Percent(1).subtract(slippageTolerance) ); const priceUpper = this.pool.tokenAPrice.asFraction.multiply( slippageTolerance.add(1) ); let sqrtPriceX64Lower = encodeSqrtRatioX64( priceLower.numerator, priceLower.denominator ); if (JSBI.lessThanOrEqual(sqrtPriceX64Lower, TickMath.MIN_SQRT_RATIO)) { sqrtPriceX64Lower = JSBI.add(TickMath.MIN_SQRT_RATIO, JSBI.BigInt(1)); } let sqrtPriceX64Upper = encodeSqrtRatioX64( priceUpper.numerator, priceUpper.denominator ); if (JSBI.greaterThanOrEqual(sqrtPriceX64Upper, TickMath.MAX_SQRT_RATIO)) { sqrtPriceX64Upper = JSBI.subtract( TickMath.MAX_SQRT_RATIO, JSBI.BigInt(1) ); } return { sqrtPriceX64Lower, sqrtPriceX64Upper, }; } /** * Returns the minimum amounts 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 */ public mintAmountsWithSlippage( slippageTolerance: Percent ): Readonly<{ amountA: JSBI; amountB: JSBI }> { // get lower/upper prices const { sqrtPriceX64Upper, sqrtPriceX64Lower } = this.ratiosAfterSlippage(slippageTolerance); // construct counterfactual pools const poolLower = new Pool({ id: this.pool.id, active: this.pool.active, tokenA: this.pool.tokenA, tokenB: this.pool.tokenB, fee: this.pool.fee, sqrtPriceX64: sqrtPriceX64Lower, liquidity: 0 /* liquidity doesn't matter */, tickCurrent: TickMath.getTickAtSqrtRatio(sqrtPriceX64Lower), feeGrowthGlobalAX64: this.feeGrowthInsideALastX64, feeGrowthGlobalBX64: this.feeGrowthInsideBLastX64, ticks: this.pool.tickDataProvider }); const poolUpper = new Pool({ id: this.pool.id, active: this.pool.active, tokenA: this.pool.tokenA, tokenB: this.pool.tokenB, fee: this.pool.fee, sqrtPriceX64: sqrtPriceX64Upper, liquidity: 0 /* liquidity doesn't matter */, tickCurrent: TickMath.getTickAtSqrtRatio(sqrtPriceX64Upper), feeGrowthGlobalAX64: this.feeGrowthInsideALastX64, feeGrowthGlobalBX64: this.feeGrowthInsideBLastX64, ticks: this.pool.tickDataProvider }); // because the router is imprecise, we need to calculate the position that will be created (assuming no slippage) const positionThatWillBeCreated = Position.fromAmounts({ id: this.id, owner: this.owner, pool: this.pool, tickLower: this.tickLower, tickUpper: this.tickUpper, ...this.mintAmounts, // the mint amounts are what will be passed as calldata useFullPrecision: false, feeGrowthInsideALastX64: this.feeGrowthInsideALastX64, feeGrowthInsideBLastX64: this.feeGrowthInsideBLastX64, feesA: this.feesA, feesB: this.feesB, }); // we want the smaller amounts... // ...which occurs at the upper price for amountA... const { amountA } = new Position({ id: this.id, owner: this.owner, pool: poolUpper, liquidity: positionThatWillBeCreated.liquidity, tickLower: this.tickLower, tickUpper: this.tickUpper, feeGrowthInsideALastX64: this.feeGrowthInsideALastX64, feeGrowthInsideBLastX64: this.feeGrowthInsideBLastX64, feesA: this.feesA, feesB: this.feesB, }).mintAmounts; // ...and the lower for amountB const { amountB } = new Position({ id: this.id, owner: this.owner, pool: poolLower, liquidity: positionThatWillBeCreated.liquidity, tickLower: this.tickLower, tickUpper: this.tickUpper, feeGrowthInsideALastX64: this.feeGrowthInsideALastX64, feeGrowthInsideBLastX64: this.feeGrowthInsideBLastX64, feesA: this.feesA, feesB: this.feesB, }).mintAmounts; return { amountA, amountB }; } /** * 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 */ public burnAmountsWithSlippage( slippageTolerance: Percent ): Readonly<{ amountA: CurrencyAmount<Token>; amountB: CurrencyAmount<Token> }> { // get lower/upper prices const { sqrtPriceX64Upper, sqrtPriceX64Lower } = this.ratiosAfterSlippage(slippageTolerance); // construct counterfactual pools const poolLower = new Pool({ id: this.pool.id, active: this.pool.active, tokenA: this.pool.tokenA, tokenB: this.pool.tokenB, fee: this.pool.fee, sqrtPriceX64: sqrtPriceX64Lower, liquidity: 0 /* liquidity doesn't matter */, tickCurrent: TickMath.getTickAtSqrtRatio(sqrtPriceX64Lower), feeGrowthGlobalAX64: this.feeGrowthInsideALastX64, feeGrowthGlobalBX64: this.feeGrowthInsideBLastX64, ticks: this.pool.tickDataProvider }); const poolUpper = new Pool({ id: this.pool.id, active: this.pool.active, tokenA: this.pool.tokenA, tokenB: this.pool.tokenB, fee: this.pool.fee, sqrtPriceX64: sqrtPriceX64Upper, liquidity: 0 /* liquidity doesn't matter */, tickCurrent: TickMath.getTickAtSqrtRatio(sqrtPriceX64Upper), feeGrowthGlobalAX64: this.feeGrowthInsideALastX64, feeGrowthGlobalBX64: this.feeGrowthInsideBLastX64, ticks: this.pool.tickDataProvider }); // we want the smaller amounts... // ...which occurs at the upper price for amountA... const amountA = new Position({ id: this.id, owner: this.owner, pool: poolUpper, liquidity: this.liquidity, tickLower: this.tickLower, tickUpper: this.tickUpper, feeGrowthInsideALastX64: this.feeGrowthInsideALastX64, feeGrowthInsideBLastX64: this.feeGrowthInsideBLastX64, feesA: this.feesA, feesB: this.feesB, }).amountA; // ...and the lower for amountB const amountB = new Position({ id: this.id, owner: this.owner, pool: poolLower, liquidity: this.liquidity, tickLower: this.tickLower, tickUpper: this.tickUpper, feeGrowthInsideALastX64: this.feeGrowthInsideALastX64, feeGrowthInsideBLastX64: this.feeGrowthInsideBLastX64, feesA: this.feesA, feesB: this.feesB, }).amountB; return { amountA: amountA, amountB: amountB }; } /** * 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 */ public get mintAmounts(): Readonly<{ amountA: JSBI; amountB: JSBI }> { if (this._mintAmounts === null) { if (this.pool.tickCurrent < this.tickLower) { return { amountA: SqrtPriceMath.getAmountADelta( TickMath.getSqrtRatioAtTick(this.tickLower), TickMath.getSqrtRatioAtTick(this.tickUpper), this.liquidity, true ), amountB: ZERO, }; } else if (this.pool.tickCurrent < this.tickUpper) { return { amountA: SqrtPriceMath.getAmountADelta( this.pool.sqrtPriceX64, TickMath.getSqrtRatioAtTick(this.tickUpper), this.liquidity, true ), amountB: SqrtPriceMath.getAmountBDelta( TickMath.getSqrtRatioAtTick(this.tickLower), this.pool.sqrtPriceX64, this.liquidity, true ), }; } else { return { amountA: ZERO, amountB: SqrtPriceMath.getAmountBDelta( TickMath.getSqrtRatioAtTick(this.tickLower), TickMath.getSqrtRatioAtTick(this.tickUpper), this.liquidity, true ), }; } } return this._mintAmounts; } /** * Computes the maximum amount of liquidity received for a given amount of tokenA, tokenB, * and the prices at the tick boundaries. * @param pool The pool for which the position should be created * @param lower The lower tick of the position * @param upper The upper tick of the position * @param amountA tokenA amount * @param amountB tokenB 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 */ public static fromAmounts({ id, owner, pool, tickLower, tickUpper, amountA, amountB, useFullPrecision, feeGrowthInsideALastX64, feeGrowthInsideBLastX64, feesA, feesB }: { id: number, owner: string, pool: Pool; tickLower: number; tickUpper: number; amountA: BigintIsh; amountB: BigintIsh; useFullPrecision: boolean; feeGrowthInsideALastX64: | BigintIsh, feeGrowthInsideBLastX64: | BigintIsh, feesA: BigintIsh, feesB: BigintIsh }) { const sqrtRatioLX64 = TickMath.getSqrtRatioAtTick(tickLower); const sqrtRatioUX64 = TickMath.getSqrtRatioAtTick(tickUpper); return new Position({ id, owner, pool, tickLower, tickUpper, liquidity: maxLiquidityForAmounts( pool.sqrtPriceX64, sqrtRatioLX64, sqrtRatioUX64, amountA, amountB, useFullPrecision ), feeGrowthInsideALastX64, feeGrowthInsideBLastX64, feesA, feesB }); } /** * Computes a position with the maximum amount of liquidity received for a given amount of tokenA, assuming an unlimited amount of tokenB * @param pool The pool for which the position is created * @param lower The lower tick * @param upper The upper tick * @param amountA The desired amount of tokenA * @param useFullPrecision If true, liquidity will be maximized according to what the router can calculate, * not what core can theoretically support * @returns The position */ public static fromAmountA({ id, owner, pool, tickLower, tickUpper, amountA, useFullPrecision, feeGrowthInsideALastX64, feeGrowthInsideBLastX64, feesA, feesB }: { id: number, owner: string, pool: Pool; tickLower: number; tickUpper: number; amountA: BigintIsh; useFullPrecision: boolean; feeGrowthInsideALastX64: | BigintIsh; feeGrowthInsideBLastX64: | BigintIsh; feesA: | BigintIsh; feesB: | BigintIsh; }) { return Position.fromAmounts({ id, owner, pool, tickLower, tickUpper, amountA, amountB: MaxUint64, useFullPrecision, feeGrowthInsideALastX64, feeGrowthInsideBLastX64, feesA, feesB }); } /** * Computes a position with the maximum amount of liquidity received for a given amount of tokenB, assuming an unlimited amount of tokenA * @param pool The pool for which the position is created * @param lower The lower tick * @param upper The upper tick * @param amountB The desired amount of tokenB * @returns The position */ public static fromAmountB({ id, owner, pool, tickLower, tickUpper, amountB, feeGrowthInsideALastX64, feeGrowthInsideBLastX64, feesA, feesB }: { id: number, owner: string, pool: Pool; tickLower: number; tickUpper: number; amountB: BigintIsh; feeGrowthInsideALastX64: | BigintIsh; feeGrowthInsideBLastX64: | BigintIsh; feesA: BigintIsh feesB: BigintIsh }) { // this function always uses full precision, return Position.fromAmounts({ id, owner, pool, tickLower, tickUpper, amountA: MaxUint64, amountB, useFullPrecision: true, feeGrowthInsideALastX64, feeGrowthInsideBLastX64, feesA, feesB }); } /** * Computes a position fees * @returns The position */ public async getFees(): Promise<Fees> { const { liquidity, tickLower, tickUpper, feeGrowthInsideALastX64, feeGrowthInsideBLastX64, pool } = this if ( JSBI.equal(liquidity, ZERO) && JSBI.equal(this.feesA, ZERO) && JSBI.equal(this.feesB, ZERO) ) { return { feesA: CurrencyAmount.fromRawAmount(this.pool.tokenA, ZERO), feesB: CurrencyAmount.fromRawAmount(this.pool.tokenB, ZERO) } } const lower = this.pool.tickDataProvider.getTick(tickLower) const upper = this.pool.tickDataProvider.getTick(tickUpper) const { feeGrowthGlobalAX64, feeGrowthGlobalBX64 } = pool const [feeGrowthInsideAX64, feeGrowthInsideBX64] = TickLibrary.getFeeGrowthInside( lower, upper, tickLower, tickUpper, pool.tickCurrent, feeGrowthGlobalAX64, feeGrowthGlobalBX64 ) const tokensOwedA = JSBI.divide( JSBI.multiply( subIn128(feeGrowthInsideAX64, feeGrowthInsideALastX64), liquidity ), Q64 ); const tokensOwedB = JSBI.divide( JSBI.multiply( subIn128(feeGrowthInsideBX64, feeGrowthInsideBLastX64), liquidity ), Q64 ); return { feesA: CurrencyAmount.fromRawAmount(this.pool.tokenA, JSBI.add(tokensOwedA, this.feesA)), feesB: CurrencyAmount.fromRawAmount(this.pool.tokenB, JSBI.add(tokensOwedB, this.feesB)) } } }