UNPKG

@raydium-io/raydium-sdk-v2

Version:

An SDK for building applications on top of Raydium.

283 lines (251 loc) 10.3 kB
import BN from "bn.js"; import Decimal from "decimal.js"; import { getTransferAmountFeeV2, minExpirationTime } from "../../../common"; import { ClmmPositionLayout } from "../layout"; import { ClmmPoolInfo, ClmmPoolPersonalPosition, ClmmPoolRewardInfo, GetAmountParams, ReturnTypeGetLiquidityAmountOut } from "../type"; import { Q64 } from "./constants"; import { LiquidityMath, MathUtil, SqrtPriceMath } from "./math"; import { Tick } from "./tick"; export class PositionUtils { static getfeeGrowthInside( poolState: Pick<ClmmPoolInfo, "tickCurrent" | "feeGrowthGlobalX64A" | "feeGrowthGlobalX64B">, tickLowerState: Tick, tickUpperState: Tick, ): { feeGrowthInsideX64A: BN; feeGrowthInsideBX64: BN } { let feeGrowthBelowX64A = new BN(0); let feeGrowthBelowX64B = new BN(0); if (poolState.tickCurrent >= tickLowerState.tick) { feeGrowthBelowX64A = tickLowerState.feeGrowthOutsideX64A; feeGrowthBelowX64B = tickLowerState.feeGrowthOutsideX64B; } else { feeGrowthBelowX64A = poolState.feeGrowthGlobalX64A.sub(tickLowerState.feeGrowthOutsideX64A); feeGrowthBelowX64B = poolState.feeGrowthGlobalX64B.sub(tickLowerState.feeGrowthOutsideX64B); } let feeGrowthAboveX64A = new BN(0); let feeGrowthAboveX64B = new BN(0); if (poolState.tickCurrent < tickUpperState.tick) { feeGrowthAboveX64A = tickUpperState.feeGrowthOutsideX64A; feeGrowthAboveX64B = tickUpperState.feeGrowthOutsideX64B; } else { feeGrowthAboveX64A = poolState.feeGrowthGlobalX64A.sub(tickUpperState.feeGrowthOutsideX64A); feeGrowthAboveX64B = poolState.feeGrowthGlobalX64B.sub(tickUpperState.feeGrowthOutsideX64B); } const feeGrowthInsideX64A = MathUtil.wrappingSubU128( MathUtil.wrappingSubU128(poolState.feeGrowthGlobalX64A, feeGrowthBelowX64A), feeGrowthAboveX64A, ); const feeGrowthInsideBX64 = MathUtil.wrappingSubU128( MathUtil.wrappingSubU128(poolState.feeGrowthGlobalX64B, feeGrowthBelowX64B), feeGrowthAboveX64B, ); return { feeGrowthInsideX64A, feeGrowthInsideBX64 }; } static GetPositionFees( ammPool: ClmmPoolInfo, positionState: ClmmPoolPersonalPosition, tickLowerState: Tick, tickUpperState: Tick, ): { tokenFeeAmountA: BN; tokenFeeAmountB: BN } { const { feeGrowthInsideX64A, feeGrowthInsideBX64 } = this.getfeeGrowthInside( ammPool, tickLowerState, tickUpperState, ); const feeGrowthdeltaA = MathUtil.mulDivFloor( MathUtil.wrappingSubU128(feeGrowthInsideX64A, positionState.feeGrowthInsideLastX64A), positionState.liquidity, Q64, ); const tokenFeeAmountA = positionState.tokenFeesOwedA.add(feeGrowthdeltaA); const feeGrowthdelta1 = MathUtil.mulDivFloor( MathUtil.wrappingSubU128(feeGrowthInsideBX64, positionState.feeGrowthInsideLastX64B), positionState.liquidity, Q64, ); const tokenFeeAmountB = positionState.tokenFeesOwedB.add(feeGrowthdelta1); return { tokenFeeAmountA, tokenFeeAmountB }; } static GetPositionFeesV2( ammPool: Pick<ClmmPoolInfo, "tickCurrent" | "feeGrowthGlobalX64A" | "feeGrowthGlobalX64B">, positionState: ClmmPositionLayout, tickLowerState: Tick, tickUpperState: Tick, ): { tokenFeeAmountA: BN; tokenFeeAmountB: BN } { const { feeGrowthInsideX64A, feeGrowthInsideBX64 } = this.getfeeGrowthInside( ammPool, tickLowerState, tickUpperState, ); const feeGrowthdeltaA = MathUtil.mulDivFloor( MathUtil.wrappingSubU128(feeGrowthInsideX64A, positionState.feeGrowthInsideLastX64A), positionState.liquidity, Q64, ); const tokenFeeAmountA = positionState.tokenFeesOwedA.add(feeGrowthdeltaA); const feeGrowthdelta1 = MathUtil.mulDivFloor( MathUtil.wrappingSubU128(feeGrowthInsideBX64, positionState.feeGrowthInsideLastX64B), positionState.liquidity, Q64, ); const tokenFeeAmountB = positionState.tokenFeesOwedB.add(feeGrowthdelta1); return { tokenFeeAmountA, tokenFeeAmountB }; } static GetPositionRewardsV2( ammPool: Pick<ClmmPoolInfo, "tickCurrent" | "feeGrowthGlobalX64B"> & { rewardInfos: { rewardGrowthGlobalX64: BN }[]; }, positionState: ClmmPositionLayout, tickLowerState: Tick, tickUpperState: Tick, ): BN[] { const rewards: BN[] = []; const rewardGrowthsInside = this.getRewardGrowthInsideV2( ammPool.tickCurrent, tickLowerState, tickUpperState, ammPool.rewardInfos, ); for (let i = 0; i < rewardGrowthsInside.length; i++) { const rewardGrowthInside = rewardGrowthsInside[i]; const currRewardInfo = positionState.rewardInfos[i]; const rewardGrowthDelta = MathUtil.wrappingSubU128(rewardGrowthInside, currRewardInfo.growthInsideLastX64); const amountOwedDelta = MathUtil.mulDivFloor(rewardGrowthDelta, positionState.liquidity, Q64); const rewardAmountOwed = currRewardInfo.rewardAmountOwed.add(amountOwedDelta); rewards.push(rewardAmountOwed); } return rewards; } static GetPositionRewards( ammPool: ClmmPoolInfo, positionState: ClmmPoolPersonalPosition, tickLowerState: Tick, tickUpperState: Tick, ): BN[] { const rewards: BN[] = []; const rewardGrowthsInside = this.getRewardGrowthInside( ammPool.tickCurrent, tickLowerState, tickUpperState, ammPool.rewardInfos, ); for (let i = 0; i < rewardGrowthsInside.length; i++) { const rewardGrowthInside = rewardGrowthsInside[i]; const currRewardInfo = positionState.rewardInfos[i]; const rewardGrowthDelta = MathUtil.wrappingSubU128(rewardGrowthInside, currRewardInfo.growthInsideLastX64); const amountOwedDelta = MathUtil.mulDivFloor(rewardGrowthDelta, positionState.liquidity, Q64); const rewardAmountOwed = currRewardInfo.rewardAmountOwed.add(amountOwedDelta); rewards.push(rewardAmountOwed); } return rewards; } static getRewardGrowthInside( tickCurrentIndex: number, tickLowerState: Tick, tickUpperState: Tick, rewardInfos: ClmmPoolRewardInfo[], ): BN[] { const rewardGrowthsInside: BN[] = []; for (let i = 0; i < rewardInfos.length; i++) { let rewardGrowthsBelow = new BN(0); if (tickLowerState.liquidityGross.eqn(0)) { rewardGrowthsBelow = rewardInfos[i].rewardGrowthGlobalX64; } else if (tickCurrentIndex < tickLowerState.tick) { rewardGrowthsBelow = rewardInfos[i].rewardGrowthGlobalX64.sub(tickLowerState.rewardGrowthsOutsideX64[i]); } else { rewardGrowthsBelow = tickLowerState.rewardGrowthsOutsideX64[i]; } let rewardGrowthsAbove = new BN(0); if (tickUpperState.liquidityGross.eqn(0)) { // } else if (tickCurrentIndex < tickUpperState.tick) { rewardGrowthsAbove = tickUpperState.rewardGrowthsOutsideX64[i]; } else { rewardGrowthsAbove = rewardInfos[i].rewardGrowthGlobalX64.sub(tickUpperState.rewardGrowthsOutsideX64[i]); } rewardGrowthsInside.push( MathUtil.wrappingSubU128( MathUtil.wrappingSubU128(rewardInfos[i].rewardGrowthGlobalX64, rewardGrowthsBelow), rewardGrowthsAbove, ), ); } return rewardGrowthsInside; } static getRewardGrowthInsideV2( tickCurrentIndex: number, tickLowerState: Tick, tickUpperState: Tick, rewardInfos: Pick<ClmmPoolRewardInfo, "rewardGrowthGlobalX64">[], ): BN[] { const rewardGrowthsInside: BN[] = []; for (let i = 0; i < rewardInfos.length; i++) { let rewardGrowthsBelow = new BN(0); if (tickLowerState.liquidityGross.eqn(0)) { rewardGrowthsBelow = rewardInfos[i].rewardGrowthGlobalX64; } else if (tickCurrentIndex < tickLowerState.tick) { rewardGrowthsBelow = rewardInfos[i].rewardGrowthGlobalX64.sub(tickLowerState.rewardGrowthsOutsideX64[i]); } else { rewardGrowthsBelow = tickLowerState.rewardGrowthsOutsideX64[i]; } let rewardGrowthsAbove = new BN(0); if (tickUpperState.liquidityGross.eqn(0)) { // } else if (tickCurrentIndex < tickUpperState.tick) { rewardGrowthsAbove = tickUpperState.rewardGrowthsOutsideX64[i]; } else { rewardGrowthsAbove = rewardInfos[i].rewardGrowthGlobalX64.sub(tickUpperState.rewardGrowthsOutsideX64[i]); } rewardGrowthsInside.push( MathUtil.wrappingSubU128( MathUtil.wrappingSubU128(rewardInfos[i].rewardGrowthGlobalX64, rewardGrowthsBelow), rewardGrowthsAbove, ), ); } return rewardGrowthsInside; } static getAmountsFromLiquidity({ poolInfo, ownerPosition, liquidity, slippage, add, epochInfo, }: GetAmountParams): ReturnTypeGetLiquidityAmountOut { const sqrtPriceX64 = SqrtPriceMath.priceToSqrtPriceX64( new Decimal(poolInfo.price), poolInfo.mintA.decimals, poolInfo.mintB.decimals, ); const sqrtPriceX64A = SqrtPriceMath.getSqrtPriceX64FromTick(ownerPosition.tickLower); const sqrtPriceX64B = SqrtPriceMath.getSqrtPriceX64FromTick(ownerPosition.tickUpper); const coefficientRe = add ? 1 + slippage : 1 - slippage; const amounts = LiquidityMath.getAmountsFromLiquidity(sqrtPriceX64, sqrtPriceX64A, sqrtPriceX64B, liquidity, add); const [amountA, amountB] = [ getTransferAmountFeeV2(amounts.amountA, poolInfo.mintA.extensions?.feeConfig, epochInfo, true), getTransferAmountFeeV2(amounts.amountB, poolInfo.mintB.extensions?.feeConfig, epochInfo, true), ]; const [amountSlippageA, amountSlippageB] = [ getTransferAmountFeeV2( new BN(new Decimal(amounts.amountA.toString()).mul(coefficientRe).toFixed(0)), poolInfo.mintA.extensions?.feeConfig, epochInfo, true, ), getTransferAmountFeeV2( new BN(new Decimal(amounts.amountB.toString()).mul(coefficientRe).toFixed(0)), poolInfo.mintB.extensions?.feeConfig, epochInfo, true, ), ]; return { liquidity, amountA, amountB, amountSlippageA, amountSlippageB, expirationTime: minExpirationTime(amountA.expirationTime, amountB.expirationTime), }; } }