test-raydium-sdk-v2
Version:
An SDK for building applications on top of Raydium.
1,429 lines (1,284 loc) • 50 kB
text/typescript
import { PublicKey, Connection, EpochInfo } from "@solana/web3.js";
import BN from "bn.js";
import {
ClmmPoolInfo,
ClmmPoolRewardInfo,
ClmmPoolRewardLayoutInfo,
ReturnTypeGetLiquidityAmountOut,
TickArrayBitmapExtensionType,
ReturnTypeFetchExBitmaps,
ReturnTypeFetchMultiplePoolTickArrays,
SDKParsedConcentratedInfo,
ReturnTypeComputeAmountOut,
ReturnTypeComputeAmountOutFormat,
} from "../type";
import { ApiV3PoolInfoConcentratedItem } from "@/api/type";
import { ReturnTypeFetchMultipleMintInfos } from "@/raydium/type";
import { NEGATIVE_ONE, Q64, ZERO, MAX_TICK, MIN_TICK, MIN_SQRT_PRICE_X64, MAX_SQRT_PRICE_X64 } from "./constants";
import { MathUtil, SwapMath, SqrtPriceMath, LiquidityMath } from "./math";
import { getPdaTickArrayAddress, getPdaPersonalPositionAddress } from "./pda";
import { TickArray, TickUtils, TICK_ARRAY_BITMAP_SIZE, Tick } from "./tick";
import { TickArrayBitmap, TickArrayBitmapExtensionUtils } from "./tickarrayBitmap";
import { TickQuery } from "./tickQuery";
import { TickArrayBitmapExtensionLayout, PositionInfoLayout, TickArrayLayout } from "../layout";
import {
getMultipleAccountsInfo,
getMultipleAccountsInfoWithCustomFlags,
getTransferAmountFee,
getTransferAmountFeeV2,
minExpirationTime,
WSOLMint,
SOLMint,
solToWSol,
} from "../../../common";
import { SOL_INFO } from "../../token/constant";
import { TokenAccountRaw } from "../../account/types";
import { Price, Percent, TokenAmount, Token } from "../../../module";
import { PositionUtils } from "./position";
import Decimal from "decimal.js";
export class PoolUtils {
public static getOutputAmountAndRemainAccounts(
poolInfo: ClmmPoolInfo,
tickArrayCache: { [key: string]: TickArray },
inputTokenMint: PublicKey,
inputAmount: BN,
sqrtPriceLimitX64?: BN,
): {
expectedAmountOut: BN;
remainingAccounts: PublicKey[];
executionPrice: BN;
feeAmount: BN;
} {
const zeroForOne = inputTokenMint.equals(poolInfo.mintA.mint);
const allNeededAccounts: PublicKey[] = [];
const {
isExist,
startIndex: firstTickArrayStartIndex,
nextAccountMeta,
} = this.getFirstInitializedTickArray(poolInfo, zeroForOne);
if (!isExist || firstTickArrayStartIndex === undefined || !nextAccountMeta) throw new Error("Invalid tick array");
// try {
// const preTick = this.preInitializedTickArrayStartIndex(poolInfo, !zeroForOne)
// if (preTick.isExist) {
// const { publicKey: address } = getPdaTickArrayAddress(
// poolInfo.programId,
// poolInfo.id,
// preTick.nextStartIndex
// );
// allNeededAccounts.push(address)
// }
// } catch (e) { /* empty */ }
allNeededAccounts.push(nextAccountMeta);
const {
amountCalculated: outputAmount,
accounts: reaminAccounts,
sqrtPriceX64: executionPrice,
feeAmount,
} = SwapMath.swapCompute(
poolInfo.programId,
poolInfo.id,
tickArrayCache,
poolInfo.tickArrayBitmap,
poolInfo.exBitmapInfo,
zeroForOne,
poolInfo.ammConfig.tradeFeeRate,
poolInfo.liquidity,
poolInfo.tickCurrent,
poolInfo.tickSpacing,
poolInfo.sqrtPriceX64,
inputAmount,
firstTickArrayStartIndex,
sqrtPriceLimitX64,
);
allNeededAccounts.push(...reaminAccounts);
return {
expectedAmountOut: outputAmount.mul(NEGATIVE_ONE),
remainingAccounts: allNeededAccounts,
executionPrice,
feeAmount,
};
}
public static getInputAmountAndRemainAccounts(
poolInfo: ClmmPoolInfo,
tickArrayCache: { [key: string]: TickArray },
outputTokenMint: PublicKey,
outputAmount: BN,
sqrtPriceLimitX64?: BN,
): { expectedAmountIn: BN; remainingAccounts: PublicKey[]; executionPrice: BN; feeAmount: BN } {
const zeroForOne = outputTokenMint.equals(poolInfo.mintB.mint);
const allNeededAccounts: PublicKey[] = [];
const {
isExist,
startIndex: firstTickArrayStartIndex,
nextAccountMeta,
} = this.getFirstInitializedTickArray(poolInfo, zeroForOne);
if (!isExist || firstTickArrayStartIndex === undefined || !nextAccountMeta) throw new Error("Invalid tick array");
try {
const preTick = this.preInitializedTickArrayStartIndex(poolInfo, zeroForOne);
if (preTick.isExist) {
const { publicKey: address } = getPdaTickArrayAddress(poolInfo.programId, poolInfo.id, preTick.nextStartIndex);
allNeededAccounts.push(address);
}
} catch (e) {
/* empty */
}
allNeededAccounts.push(nextAccountMeta);
const {
amountCalculated: inputAmount,
accounts: reaminAccounts,
sqrtPriceX64: executionPrice,
feeAmount,
} = SwapMath.swapCompute(
poolInfo.programId,
poolInfo.id,
tickArrayCache,
poolInfo.tickArrayBitmap,
poolInfo.exBitmapInfo,
zeroForOne,
poolInfo.ammConfig.tradeFeeRate,
poolInfo.liquidity,
poolInfo.tickCurrent,
poolInfo.tickSpacing,
poolInfo.sqrtPriceX64,
outputAmount.mul(NEGATIVE_ONE),
firstTickArrayStartIndex,
sqrtPriceLimitX64,
);
allNeededAccounts.push(...reaminAccounts);
return { expectedAmountIn: inputAmount, remainingAccounts: allNeededAccounts, executionPrice, feeAmount };
}
public static getFirstInitializedTickArray(
poolInfo: ClmmPoolInfo,
zeroForOne: boolean,
):
| { isExist: true; startIndex: number; nextAccountMeta: PublicKey }
| { isExist: false; startIndex: undefined; nextAccountMeta: undefined } {
const { isInitialized, startIndex } = PoolUtils.isOverflowDefaultTickarrayBitmap(poolInfo.tickSpacing, [
poolInfo.tickCurrent,
])
? TickArrayBitmapExtensionUtils.checkTickArrayIsInit(
TickQuery.getArrayStartIndex(poolInfo.tickCurrent, poolInfo.tickSpacing),
poolInfo.tickSpacing,
poolInfo.exBitmapInfo,
)
: TickUtils.checkTickArrayIsInitialized(
TickUtils.mergeTickArrayBitmap(poolInfo.tickArrayBitmap),
poolInfo.tickCurrent,
poolInfo.tickSpacing,
);
if (isInitialized) {
const { publicKey: address } = getPdaTickArrayAddress(poolInfo.programId, poolInfo.id, startIndex);
return {
isExist: true,
startIndex,
nextAccountMeta: address,
};
}
const { isExist, nextStartIndex } = this.nextInitializedTickArrayStartIndex(
poolInfo,
TickQuery.getArrayStartIndex(poolInfo.tickCurrent, poolInfo.tickSpacing),
zeroForOne,
);
if (isExist) {
const { publicKey: address } = getPdaTickArrayAddress(poolInfo.programId, poolInfo.id, nextStartIndex);
return {
isExist: true,
startIndex: nextStartIndex,
nextAccountMeta: address,
};
}
return { isExist: false, nextAccountMeta: undefined, startIndex: undefined };
}
public static preInitializedTickArrayStartIndex(
poolInfo: ClmmPoolInfo,
zeroForOne: boolean,
): { isExist: boolean; nextStartIndex: number } {
const currentOffset = Math.floor(poolInfo.tickCurrent / TickQuery.tickCount(poolInfo.tickSpacing));
const result: number[] = !zeroForOne
? TickUtils.searchLowBitFromStart(
poolInfo.tickArrayBitmap,
poolInfo.exBitmapInfo,
currentOffset - 1,
1,
poolInfo.tickSpacing,
)
: TickUtils.searchHightBitFromStart(
poolInfo.tickArrayBitmap,
poolInfo.exBitmapInfo,
currentOffset + 1,
1,
poolInfo.tickSpacing,
);
return result.length > 0 ? { isExist: true, nextStartIndex: result[0] } : { isExist: false, nextStartIndex: 0 };
}
public static nextInitializedTickArrayStartIndex(
poolInfo:
| {
tickCurrent: number;
tickSpacing: number;
tickArrayBitmap: BN[];
exBitmapInfo: TickArrayBitmapExtensionType;
}
| ClmmPoolInfo,
lastTickArrayStartIndex: number,
zeroForOne: boolean,
): { isExist: boolean; nextStartIndex: number } {
lastTickArrayStartIndex = TickQuery.getArrayStartIndex(poolInfo.tickCurrent, poolInfo.tickSpacing);
// eslint-disable-next-line no-constant-condition
while (true) {
const { isInit: startIsInit, tickIndex: startIndex } = TickArrayBitmap.nextInitializedTickArrayStartIndex(
TickUtils.mergeTickArrayBitmap(poolInfo.tickArrayBitmap),
lastTickArrayStartIndex,
poolInfo.tickSpacing,
zeroForOne,
);
if (startIsInit) {
return { isExist: true, nextStartIndex: startIndex };
}
lastTickArrayStartIndex = startIndex;
const { isInit, tickIndex } = TickArrayBitmapExtensionUtils.nextInitializedTickArrayFromOneBitmap(
lastTickArrayStartIndex,
poolInfo.tickSpacing,
zeroForOne,
poolInfo.exBitmapInfo,
);
if (isInit) return { isExist: true, nextStartIndex: tickIndex };
lastTickArrayStartIndex = tickIndex;
if (lastTickArrayStartIndex < MIN_TICK || lastTickArrayStartIndex > MAX_TICK)
return { isExist: false, nextStartIndex: 0 };
}
// const tickArrayBitmap = TickUtils.mergeTickArrayBitmap(
// poolInfo.tickArrayBitmap
// );
// const currentOffset = TickUtils.getTickArrayOffsetInBitmapByTick(
// poolInfo.tickCurrent,
// poolInfo.tickSpacing
// );
// const result: number[] = zeroForOne ? TickUtils.searchLowBitFromStart(
// tickArrayBitmap,
// currentOffset - 1,
// 0,
// 1,
// poolInfo.tickSpacing
// ) : TickUtils.searchHightBitFromStart(
// tickArrayBitmap,
// currentOffset,
// 1024,
// 1,
// poolInfo.tickSpacing
// );
// return result.length > 0 ? { isExist: true, nextStartIndex: result[0] } : { isExist: false, nextStartIndex: 0 }
}
public static async updatePoolRewardInfos({
connection,
apiPoolInfo,
chainTime,
poolLiquidity,
rewardInfos,
}: {
connection: Connection;
apiPoolInfo: ApiV3PoolInfoConcentratedItem;
chainTime: number;
poolLiquidity: BN;
rewardInfos: ClmmPoolRewardLayoutInfo[];
}): Promise<ClmmPoolRewardInfo[]> {
const nRewardInfo: ClmmPoolRewardInfo[] = [];
for (let i = 0; i < rewardInfos.length; i++) {
const _itemReward = rewardInfos[i];
const apiRewardProgram =
apiPoolInfo.rewardDefaultInfos[i]?.mint.programId ??
(await connection.getAccountInfo(_itemReward.tokenMint))?.owner;
if (apiRewardProgram === undefined) throw Error("get new reward mint info error");
const itemReward: ClmmPoolRewardInfo = {
..._itemReward,
perSecond: MathUtil.x64ToDecimal(_itemReward.emissionsPerSecondX64),
remainingRewards: undefined,
tokenProgramId: new PublicKey(apiRewardProgram),
};
if (itemReward.tokenMint.equals(PublicKey.default)) continue;
if (chainTime <= itemReward.openTime.toNumber() || poolLiquidity.eq(ZERO)) {
nRewardInfo.push(itemReward);
continue;
}
const latestUpdateTime = new BN(Math.min(itemReward.endTime.toNumber(), chainTime));
const timeDelta = latestUpdateTime.sub(itemReward.lastUpdateTime);
const rewardGrowthDeltaX64 = MathUtil.mulDivFloor(timeDelta, itemReward.emissionsPerSecondX64, poolLiquidity);
const rewardGrowthGlobalX64 = itemReward.rewardGrowthGlobalX64.add(rewardGrowthDeltaX64);
const rewardEmissionedDelta = MathUtil.mulDivFloor(timeDelta, itemReward.emissionsPerSecondX64, Q64);
const rewardTotalEmissioned = itemReward.rewardTotalEmissioned.add(rewardEmissionedDelta);
nRewardInfo.push({
...itemReward,
rewardGrowthGlobalX64,
rewardTotalEmissioned,
lastUpdateTime: latestUpdateTime,
});
}
return nRewardInfo;
}
public static isOverflowDefaultTickarrayBitmap(tickSpacing: number, tickarrayStartIndexs: number[]): boolean {
const { maxTickBoundary, minTickBoundary } = this.tickRange(tickSpacing);
for (const tickIndex of tickarrayStartIndexs) {
const tickarrayStartIndex = TickUtils.getTickArrayStartIndexByTick(tickIndex, tickSpacing);
if (tickarrayStartIndex >= maxTickBoundary || tickarrayStartIndex < minTickBoundary) {
return true;
}
}
return false;
}
public static tickRange(tickSpacing: number): {
maxTickBoundary: number;
minTickBoundary: number;
} {
let maxTickBoundary = TickArrayBitmap.maxTickInTickarrayBitmap(tickSpacing);
let minTickBoundary = -maxTickBoundary;
if (maxTickBoundary > MAX_TICK) {
maxTickBoundary = MAX_TICK;
}
if (minTickBoundary < MIN_TICK) {
minTickBoundary = MIN_TICK;
}
return { maxTickBoundary, minTickBoundary };
}
public static get_tick_array_offset(tickarrayStartIndex: number, tickSpacing: number): number {
if (!TickQuery.checkIsValidStartIndex(tickarrayStartIndex, tickSpacing)) {
throw new Error("No enough initialized tickArray");
}
return (tickarrayStartIndex / TickQuery.tickCount(tickSpacing)) * TICK_ARRAY_BITMAP_SIZE;
}
static async fetchExBitmaps({
connection,
exBitmapAddress,
batchRequest,
}: {
connection: Connection;
exBitmapAddress: PublicKey[];
batchRequest: boolean;
}): Promise<ReturnTypeFetchExBitmaps> {
const fetchedBitmapAccount = await getMultipleAccountsInfoWithCustomFlags(
connection,
exBitmapAddress.map((i) => ({ pubkey: i })),
{ batchRequest },
);
const returnTypeFetchExBitmaps: ReturnTypeFetchExBitmaps = {};
for (const item of fetchedBitmapAccount) {
if (item.accountInfo === null) continue;
returnTypeFetchExBitmaps[item.pubkey.toString()] = TickArrayBitmapExtensionLayout.decode(item.accountInfo.data);
}
return returnTypeFetchExBitmaps;
}
// deprecated, new api doesn't need
// static async fetchMultiplePoolInfos({
// connection,
// poolKeys,
// ownerInfo,
// chainTime,
// batchRequest = false,
// updateOwnerRewardAndFee = true,
// }: {
// connection: Connection;
// poolKeys: ApiV3PoolInfoConcentratedItem[];
// ownerInfo?: { wallet: PublicKey; tokenAccounts: TokenAccountRaw[] };
// chainTime: number;
// batchRequest?: boolean;
// updateOwnerRewardAndFee?: boolean;
// }): Promise<ReturnTypeFetchMultiplePoolInfos> {
// const poolAccountInfos = await getMultipleAccountsInfo(
// connection,
// poolKeys.map((i) => new PublicKey(i.id)),
// { batchRequest },
// );
// const exBitmapAddress: { [poolId: string]: PublicKey } = {};
// for (let index = 0; index < poolKeys.length; index++) {
// const apiPoolInfo = poolKeys[index];
// const accountInfo = poolAccountInfos[index];
// if (accountInfo === null) continue;
// exBitmapAddress[apiPoolInfo.id] = getPdaExBitmapAccount(
// accountInfo.owner,
// new PublicKey(apiPoolInfo.id),
// ).publicKey;
// }
// const exBitmapAccountInfos = await this.fetchExBitmaps({
// connection,
// exBitmapAddress: Object.values(exBitmapAddress),
// batchRequest,
// });
// const programIds: PublicKey[] = [];
// const poolsInfo: ReturnTypeFetchMultiplePoolInfos = {};
// const updateRewardInfos: ClmmPoolRewardInfo[] = [];
// for (let index = 0; index < poolKeys.length; index++) {
// const apiPoolInfo = poolKeys[index];
// const accountInfo = poolAccountInfos[index];
// const exBitmapInfo = exBitmapAccountInfos[exBitmapAddress[apiPoolInfo.id].toString()];
// if (accountInfo === null) continue;
// const layoutAccountInfo = PoolInfoLayout.decode(accountInfo.data);
// poolsInfo[apiPoolInfo.id] = {
// state: {
// id: new PublicKey(apiPoolInfo.id),
// mintA: {
// programId: new PublicKey(apiPoolInfo.mintA.programId),
// mint: layoutAccountInfo.mintA,
// vault: layoutAccountInfo.vaultA,
// decimals: layoutAccountInfo.mintDecimalsA,
// },
// mintB: {
// programId: new PublicKey(apiPoolInfo.mintB.programId),
// mint: layoutAccountInfo.mintB,
// vault: layoutAccountInfo.vaultB,
// decimals: layoutAccountInfo.mintDecimalsB,
// },
// observationId: layoutAccountInfo.observationId,
// ammConfig: {
// ...apiPoolInfo.config,
// fundOwner: apiPoolInfo.config.id,
// id: new PublicKey(apiPoolInfo.config.id),
// },
// creator: layoutAccountInfo.creator,
// programId: accountInfo.owner,
// version: 6,
// tickSpacing: layoutAccountInfo.tickSpacing,
// liquidity: layoutAccountInfo.liquidity,
// sqrtPriceX64: layoutAccountInfo.sqrtPriceX64,
// currentPrice: SqrtPriceMath.sqrtPriceX64ToPrice(
// layoutAccountInfo.sqrtPriceX64,
// layoutAccountInfo.mintDecimalsA,
// layoutAccountInfo.mintDecimalsB,
// ),
// tickCurrent: layoutAccountInfo.tickCurrent,
// observationIndex: layoutAccountInfo.observationIndex,
// observationUpdateDuration: layoutAccountInfo.observationUpdateDuration,
// feeGrowthGlobalX64A: layoutAccountInfo.feeGrowthGlobalX64A,
// feeGrowthGlobalX64B: layoutAccountInfo.feeGrowthGlobalX64B,
// protocolFeesTokenA: layoutAccountInfo.protocolFeesTokenA,
// protocolFeesTokenB: layoutAccountInfo.protocolFeesTokenB,
// swapInAmountTokenA: layoutAccountInfo.swapInAmountTokenA,
// swapOutAmountTokenB: layoutAccountInfo.swapOutAmountTokenB,
// swapInAmountTokenB: layoutAccountInfo.swapInAmountTokenB,
// swapOutAmountTokenA: layoutAccountInfo.swapOutAmountTokenA,
// tickArrayBitmap: layoutAccountInfo.tickArrayBitmap,
// rewardInfos: await PoolUtils.updatePoolRewardInfos({
// connection,
// apiPoolInfo,
// chainTime,
// poolLiquidity: layoutAccountInfo.liquidity,
// rewardInfos: layoutAccountInfo.rewardInfos.filter((i) => !i.tokenMint.equals(PublicKey.default)),
// }),
// day: apiPoolInfo.day,
// week: apiPoolInfo.week,
// month: apiPoolInfo.month,
// tvl: apiPoolInfo.tvl,
// lookupTableAccount: new PublicKey(apiPoolInfo.lookupTableAccount),
// startTime: layoutAccountInfo.startTime.toNumber(),
// exBitmapInfo,
// },
// };
// if (ownerInfo) {
// updateRewardInfos.push(
// ...poolsInfo[apiPoolInfo.id].state.rewardInfos.filter((i) => i.creator.equals(ownerInfo.wallet)),
// );
// }
// if (!programIds.find((i) => i.equals(accountInfo.owner))) programIds.push(accountInfo.owner);
// }
// if (ownerInfo) {
// const allMint = ownerInfo.tokenAccounts
// .filter((i) => i.accountInfo.amount.eq(new BN(1)))
// .map((i) => i.accountInfo.mint);
// const allPositionKey: PublicKey[] = [];
// for (const itemMint of allMint) {
// for (const itemProgramId of programIds) {
// allPositionKey.push(getPdaPersonalPositionAddress(itemProgramId, itemMint).publicKey);
// }
// }
// const positionAccountInfos = await getMultipleAccountsInfo(connection, allPositionKey, { batchRequest });
// const keyToTickArrayAddress: { [key: string]: PublicKey } = {};
// for (const itemAccountInfo of positionAccountInfos) {
// if (itemAccountInfo === null) continue;
// const position = PositionInfoLayout.decode(itemAccountInfo.data);
// const itemPoolId = position.poolId.toString();
// const poolInfoA = poolsInfo[itemPoolId];
// if (poolInfoA === undefined) continue;
// const poolInfo = poolInfoA.state;
// const priceLower = TickUtils._getTickPriceLegacy({
// poolInfo,
// tick: position.tickLower,
// baseIn: true,
// });
// const priceUpper = TickUtils._getTickPriceLegacy({
// poolInfo,
// tick: position.tickUpper,
// baseIn: true,
// });
// const { amountA, amountB } = LiquidityMath.getAmountsFromLiquidity(
// poolInfo.sqrtPriceX64,
// priceLower.tickSqrtPriceX64,
// priceUpper.tickSqrtPriceX64,
// position.liquidity,
// false,
// );
// const leverage = 1 / (1 - Math.sqrt(Math.sqrt(priceLower.price.div(priceUpper.price).toNumber())));
// poolsInfo[itemPoolId].positionAccount = [
// ...(poolsInfo[itemPoolId].positionAccount ?? []),
// {
// poolId: position.poolId,
// nftMint: position.nftMint,
// priceLower: priceLower.price,
// priceUpper: priceUpper.price,
// amountA,
// amountB,
// tickLower: position.tickLower,
// tickUpper: position.tickUpper,
// liquidity: position.liquidity,
// feeGrowthInsideLastX64A: position.feeGrowthInsideLastX64A,
// feeGrowthInsideLastX64B: position.feeGrowthInsideLastX64B,
// tokenFeesOwedA: position.tokenFeesOwedA,
// tokenFeesOwedB: position.tokenFeesOwedB,
// rewardInfos: position.rewardInfos.map((i) => ({
// ...i,
// pendingReward: new BN(0),
// })),
// leverage,
// tokenFeeAmountA: new BN(0),
// tokenFeeAmountB: new BN(0),
// },
// ];
// const tickArrayLowerAddress = TickUtils.getTickArrayAddressByTick(
// poolsInfo[itemPoolId].state.programId,
// position.poolId,
// position.tickLower,
// poolsInfo[itemPoolId].state.tickSpacing,
// );
// const tickArrayUpperAddress = TickUtils.getTickArrayAddressByTick(
// poolsInfo[itemPoolId].state.programId,
// position.poolId,
// position.tickUpper,
// poolsInfo[itemPoolId].state.tickSpacing,
// );
// keyToTickArrayAddress[
// `${poolsInfo[itemPoolId].state.programId.toString()}-${position.poolId.toString()}-${position.tickLower}`
// ] = tickArrayLowerAddress;
// keyToTickArrayAddress[
// `${poolsInfo[itemPoolId].state.programId.toString()}-${position.poolId.toString()}-${position.tickUpper}`
// ] = tickArrayUpperAddress;
// }
// if (updateOwnerRewardAndFee) {
// const tickArrayKeys = Object.values(keyToTickArrayAddress);
// const tickArrayDatas = await getMultipleAccountsInfo(connection, tickArrayKeys, { batchRequest });
// const tickArrayLayout: { [key: string]: TickArray } = {};
// for (let index = 0; index < tickArrayKeys.length; index++) {
// const tickArrayData = tickArrayDatas[index];
// if (tickArrayData === null) continue;
// const key = tickArrayKeys[index];
// tickArrayLayout[key.toString()] = {
// address: key,
// ...TickArrayLayout.decode(tickArrayData.data),
// };
// }
// for (const { state, positionAccount } of Object.values(poolsInfo)) {
// if (!positionAccount) continue;
// for (const itemPA of positionAccount) {
// const keyLower = `${state.programId.toString()}-${state.id.toString()}-${itemPA.tickLower}`;
// const keyUpper = `${state.programId.toString()}-${state.id.toString()}-${itemPA.tickUpper}`;
// const tickArrayLower = tickArrayLayout[keyToTickArrayAddress[keyLower].toString()];
// const tickArrayUpper = tickArrayLayout[keyToTickArrayAddress[keyUpper].toString()];
// const tickLowerState: Tick =
// tickArrayLower.ticks[TickUtils.getTickOffsetInArray(itemPA.tickLower, state.tickSpacing)];
// const tickUpperState: Tick =
// tickArrayUpper.ticks[TickUtils.getTickOffsetInArray(itemPA.tickUpper, state.tickSpacing)];
// const { tokenFeeAmountA, tokenFeeAmountB } = PositionUtils.GetPositionFees(
// state,
// itemPA,
// tickLowerState,
// tickUpperState,
// );
// const rewardInfos = PositionUtils.GetPositionRewards(state, itemPA, tickLowerState, tickUpperState);
// itemPA.tokenFeeAmountA =
// tokenFeeAmountA.gte(ZERO) && tokenFeeAmountA.lt(U64_IGNORE_RANGE) ? tokenFeeAmountA : ZERO
// itemPA.tokenFeeAmountB =
// tokenFeeAmountB.gte(ZERO) && tokenFeeAmountA.lt(U64_IGNORE_RANGE) ? tokenFeeAmountB : ZERO
// for (let i = 0; i < rewardInfos.length; i++) {
// itemPA.rewardInfos[i].pendingReward = rewardInfos[i].gte(ZERO) ? rewardInfos[i] : ZERO;
// }
// }
// }
// }
// }
// if (updateRewardInfos.length > 0) {
// const vaults = updateRewardInfos.map((i) => i.tokenVault);
// const rewardVaultInfos = await getMultipleAccountsInfo(connection, vaults, { batchRequest });
// const rewardVaultAmount: { [mint: string]: BN } = {};
// for (let index = 0; index < vaults.length; index++) {
// const valutKey = vaults[index].toString();
// const itemRewardVaultInfo = rewardVaultInfos[index];
// if (itemRewardVaultInfo === null) continue;
// const info = splAccountLayout.decode(itemRewardVaultInfo.data);
// rewardVaultAmount[valutKey] = info.amount;
// }
// for (const item of updateRewardInfos) {
// const vaultAmount = rewardVaultAmount[item.tokenVault.toString()];
// item.remainingRewards =
// vaultAmount !== undefined ? vaultAmount.sub(item.rewardTotalEmissioned.sub(item.rewardClaimed)) : ZERO;
// }
// }
// return poolsInfo;
// }
static async fetchMultiplePoolTickArrays({
connection,
poolKeys,
batchRequest,
}: {
connection: Connection;
poolKeys: ClmmPoolInfo[];
batchRequest?: boolean;
}): Promise<ReturnTypeFetchMultiplePoolTickArrays> {
const tickArraysToPoolId: { [key: string]: PublicKey } = {};
const tickArrays: { pubkey: PublicKey }[] = [];
for (const itemPoolInfo of poolKeys) {
const currentTickArrayStartIndex = TickUtils.getTickArrayStartIndexByTick(
itemPoolInfo.tickCurrent,
itemPoolInfo.tickSpacing,
);
const startIndexArray = TickUtils.getInitializedTickArrayInRange(
itemPoolInfo.tickArrayBitmap,
itemPoolInfo.exBitmapInfo,
itemPoolInfo.tickSpacing,
currentTickArrayStartIndex,
7,
);
for (const itemIndex of startIndexArray) {
const { publicKey: tickArrayAddress } = getPdaTickArrayAddress(
itemPoolInfo.programId,
itemPoolInfo.id,
itemIndex,
);
tickArrays.push({ pubkey: tickArrayAddress });
tickArraysToPoolId[tickArrayAddress.toString()] = itemPoolInfo.id;
}
}
const fetchedTickArrays = await getMultipleAccountsInfoWithCustomFlags(connection, tickArrays, { batchRequest });
const tickArrayCache: ReturnTypeFetchMultiplePoolTickArrays = {};
for (const itemAccountInfo of fetchedTickArrays) {
if (!itemAccountInfo.accountInfo) continue;
const poolId = tickArraysToPoolId[itemAccountInfo.pubkey.toString()];
if (!poolId) continue;
if (tickArrayCache[poolId.toString()] === undefined) tickArrayCache[poolId.toString()] = {};
const accountLayoutData = TickArrayLayout.decode(itemAccountInfo.accountInfo.data);
tickArrayCache[poolId.toString()][accountLayoutData.startTickIndex] = {
...accountLayoutData,
address: itemAccountInfo.pubkey,
};
}
return tickArrayCache;
}
// deprecated, new api doesn't need
static async fetchPoolsAccountPosition({
pools,
connection,
ownerInfo,
batchRequest = false,
updateOwnerRewardAndFee = true,
}: {
pools: SDKParsedConcentratedInfo[];
connection: Connection;
ownerInfo: { wallet: PublicKey; tokenAccounts: TokenAccountRaw[] };
batchRequest?: boolean;
updateOwnerRewardAndFee?: boolean;
}): Promise<SDKParsedConcentratedInfo[]> {
const programIds: PublicKey[] = [];
for (let index = 0; index < pools.length; index++) {
const accountInfo = pools[index];
if (accountInfo === null) continue;
if (!programIds.find((i) => i.equals(accountInfo.state.programId))) programIds.push(accountInfo.state.programId);
}
if (ownerInfo) {
const allMint = ownerInfo.tokenAccounts.map((i) => i.accountInfo.mint);
const allPositionKey: PublicKey[] = [];
for (const itemMint of allMint) {
for (const itemProgramId of programIds) {
allPositionKey.push(getPdaPersonalPositionAddress(itemProgramId, itemMint).publicKey);
}
}
const positionAccountInfos = await getMultipleAccountsInfo(connection, allPositionKey, { batchRequest });
const keyToTickArrayAddress: { [key: string]: PublicKey } = {};
for (const itemAccountInfo of positionAccountInfos) {
if (itemAccountInfo === null) continue;
// TODO: add check
const position = PositionInfoLayout.decode(itemAccountInfo.data);
const itemPoolId = position.poolId.toString();
const poolInfoA = pools.find((pool) => pool.state.id.toBase58() === itemPoolId);
if (poolInfoA === undefined) continue;
const poolInfo = poolInfoA.state;
const priceLower = TickUtils._getTickPriceLegacy({
poolInfo,
tick: position.tickLower,
baseIn: true,
});
const priceUpper = TickUtils._getTickPriceLegacy({
poolInfo,
tick: position.tickUpper,
baseIn: true,
});
const { amountA, amountB } = LiquidityMath.getAmountsFromLiquidity(
poolInfo.sqrtPriceX64,
priceLower.tickSqrtPriceX64,
priceUpper.tickSqrtPriceX64,
position.liquidity,
false,
);
const leverage = 1 / (1 - Math.sqrt(Math.sqrt(priceLower.price.div(priceUpper.price).toNumber())));
poolInfoA.positionAccount = [
...(poolInfoA.positionAccount ?? []),
{
poolId: position.poolId,
nftMint: position.nftMint,
priceLower: priceLower.price,
priceUpper: priceUpper.price,
amountA,
amountB,
tickLower: position.tickLower,
tickUpper: position.tickUpper,
liquidity: position.liquidity,
feeGrowthInsideLastX64A: position.feeGrowthInsideLastX64A,
feeGrowthInsideLastX64B: position.feeGrowthInsideLastX64B,
tokenFeesOwedA: position.tokenFeesOwedA,
tokenFeesOwedB: position.tokenFeesOwedB,
rewardInfos: position.rewardInfos.map((i) => ({
...i,
pendingReward: new BN(0),
})),
leverage,
tokenFeeAmountA: new BN(0),
tokenFeeAmountB: new BN(0),
},
];
const tickArrayLowerAddress = await TickUtils.getTickArrayAddressByTick(
poolInfoA.state.programId,
position.poolId,
position.tickLower,
poolInfoA.state.tickSpacing,
);
const tickArrayUpperAddress = await TickUtils.getTickArrayAddressByTick(
poolInfoA.state.programId,
position.poolId,
position.tickUpper,
poolInfoA.state.tickSpacing,
);
keyToTickArrayAddress[
`${poolInfoA.state.programId.toString()}-${position.poolId.toString()}-${position.tickLower}`
] = tickArrayLowerAddress;
keyToTickArrayAddress[
`${poolInfoA.state.programId.toString()}-${position.poolId.toString()}-${position.tickUpper}`
] = tickArrayUpperAddress;
}
if (updateOwnerRewardAndFee) {
const tickArrayKeys = Object.values(keyToTickArrayAddress);
const tickArrayDatas = await getMultipleAccountsInfo(connection, tickArrayKeys, { batchRequest });
const tickArrayLayout = {};
for (let index = 0; index < tickArrayKeys.length; index++) {
const tickArrayData = tickArrayDatas[index];
if (tickArrayData === null) continue;
const key = tickArrayKeys[index].toString();
tickArrayLayout[key] = TickArrayLayout.decode(tickArrayData.data);
}
for (const { state, positionAccount } of pools) {
if (!positionAccount) continue;
for (const itemPA of positionAccount) {
const keyLower = `${state.programId.toString()}-${state.id.toString()}-${itemPA.tickLower}`;
const keyUpper = `${state.programId.toString()}-${state.id.toString()}-${itemPA.tickUpper}`;
const tickArrayLower = tickArrayLayout[keyToTickArrayAddress[keyLower].toString()];
const tickArrayUpper = tickArrayLayout[keyToTickArrayAddress[keyUpper].toString()];
const tickLowerState: Tick =
tickArrayLower.ticks[TickUtils.getTickOffsetInArray(itemPA.tickLower, state.tickSpacing)];
const tickUpperState: Tick =
tickArrayUpper.ticks[TickUtils.getTickOffsetInArray(itemPA.tickUpper, state.tickSpacing)];
const { tokenFeeAmountA, tokenFeeAmountB } = await PositionUtils.GetPositionFees(
state,
itemPA,
tickLowerState,
tickUpperState,
);
const rewardInfos = await PositionUtils.GetPositionRewards(state, itemPA, tickLowerState, tickUpperState);
itemPA.tokenFeeAmountA = tokenFeeAmountA.gte(new BN(0)) ? tokenFeeAmountA : new BN(0);
itemPA.tokenFeeAmountB = tokenFeeAmountB.gte(new BN(0)) ? tokenFeeAmountB : new BN(0);
for (let i = 0; i < rewardInfos.length; i++) {
itemPA.rewardInfos[i].pendingReward = rewardInfos[i].gte(new BN(0)) ? rewardInfos[i] : new BN(0);
}
}
}
}
}
return pools;
}
static computeAmountOut({
poolInfo,
tickArrayCache,
baseMint,
token2022Infos,
epochInfo,
amountIn,
slippage,
priceLimit = new Decimal(0),
}: {
poolInfo: ClmmPoolInfo;
tickArrayCache: { [key: string]: TickArray };
baseMint: PublicKey;
token2022Infos: ReturnTypeFetchMultipleMintInfos;
epochInfo: EpochInfo;
amountIn: BN;
slippage: number;
priceLimit?: Decimal;
}): ReturnTypeComputeAmountOut {
let sqrtPriceLimitX64: BN;
if (priceLimit.equals(new Decimal(0))) {
sqrtPriceLimitX64 = baseMint.equals(poolInfo.mintA.mint)
? MIN_SQRT_PRICE_X64.add(new BN(1))
: MAX_SQRT_PRICE_X64.sub(new BN(1));
} else {
sqrtPriceLimitX64 = SqrtPriceMath.priceToSqrtPriceX64(
priceLimit,
poolInfo.mintA.decimals,
poolInfo.mintB.decimals,
);
}
const realAmountIn = getTransferAmountFee(
amountIn,
token2022Infos[baseMint.toString()]?.feeConfig,
epochInfo,
false,
);
const {
expectedAmountOut: _expectedAmountOut,
remainingAccounts,
executionPrice: _executionPriceX64,
feeAmount,
} = PoolUtils.getOutputAmountAndRemainAccounts(
poolInfo,
tickArrayCache,
baseMint,
realAmountIn.amount.sub(realAmountIn.fee ?? ZERO),
sqrtPriceLimitX64,
);
const outMint = poolInfo.mintA.mint.equals(baseMint) ? poolInfo.mintB.mint : poolInfo.mintA.mint;
const amountOut = getTransferAmountFee(
_expectedAmountOut,
token2022Infos[outMint.toString()]?.feeConfig,
epochInfo,
false,
);
const _executionPrice = SqrtPriceMath.sqrtPriceX64ToPrice(
_executionPriceX64,
poolInfo.mintA.decimals,
poolInfo.mintB.decimals,
);
const executionPrice = baseMint.equals(poolInfo.mintA.mint) ? _executionPrice : new Decimal(1).div(_executionPrice);
const _minAmountOut = _expectedAmountOut
.mul(new BN(Math.floor((1 - slippage) * 10000000000)))
.div(new BN(10000000000));
const minAmountOut = getTransferAmountFee(
_minAmountOut,
token2022Infos[outMint.toString()]?.feeConfig,
epochInfo,
false,
);
const poolPrice = poolInfo.mintA.mint.equals(baseMint)
? poolInfo.currentPrice
: new Decimal(1).div(poolInfo.currentPrice);
const _numerator = new Decimal(executionPrice).sub(poolPrice).abs();
const _denominator = poolPrice;
const priceImpact = new Percent(
new Decimal(_numerator).mul(10 ** 15).toFixed(0),
new Decimal(_denominator).mul(10 ** 15).toFixed(0),
);
return {
realAmountIn,
amountOut,
minAmountOut,
expirationTime: minExpirationTime(realAmountIn.expirationTime, amountOut.expirationTime),
currentPrice: poolInfo.currentPrice,
executionPrice,
priceImpact,
fee: feeAmount,
remainingAccounts,
};
}
static async computeAmountOutFormat({
poolInfo,
tickArrayCache,
token2022Infos,
amountIn,
tokenOut: _tokenOut,
slippage,
epochInfo,
}: {
poolInfo: ClmmPoolInfo;
tickArrayCache: { [key: string]: TickArray };
token2022Infos: ReturnTypeFetchMultipleMintInfos;
amountIn: TokenAmount;
tokenOut: Token;
slippage: Percent;
epochInfo: EpochInfo;
}): Promise<ReturnTypeComputeAmountOutFormat> {
const inputMint = amountIn.token.equals(Token.WSOL) ? WSOLMint : amountIn.token.mint;
const _slippage = slippage.numerator.toNumber() / slippage.denominator.toNumber();
const tokenOut = _tokenOut.mint.equals(SOLMint)
? new Token({ mint: "sol", decimals: SOL_INFO.decimals })
: _tokenOut;
const {
realAmountIn: _realAmountIn,
amountOut: _amountOut,
minAmountOut: _minAmountOut,
expirationTime,
currentPrice,
executionPrice,
priceImpact,
fee,
remainingAccounts,
} = await PoolUtils.computeAmountOut({
poolInfo,
tickArrayCache,
baseMint: inputMint,
amountIn: amountIn.raw,
slippage: _slippage,
token2022Infos,
epochInfo,
});
const realAmountIn = {
..._realAmountIn,
amount: new TokenAmount(amountIn.token, _realAmountIn.amount),
fee: _realAmountIn.fee === undefined ? undefined : new TokenAmount(amountIn.token, _realAmountIn.fee),
};
const amountOut = {
..._amountOut,
amount: new TokenAmount(tokenOut, _amountOut.amount),
fee: _amountOut.fee === undefined ? undefined : new TokenAmount(tokenOut, _amountOut.fee),
};
const minAmountOut = {
..._minAmountOut,
amount: new TokenAmount(tokenOut, _minAmountOut.amount),
fee: _minAmountOut.fee === undefined ? undefined : new TokenAmount(tokenOut, _minAmountOut.fee),
};
const _currentPrice = new Price({
baseToken: amountIn.token,
denominator: new BN(10).pow(new BN(20 + amountIn.token.decimals)),
quoteToken: tokenOut,
numerator: currentPrice.mul(new Decimal(10 ** (20 + tokenOut.decimals))).toFixed(0),
});
const _executionPrice = new Price({
baseToken: amountIn.token,
denominator: new BN(10).pow(new BN(20 + amountIn.token.decimals)),
quoteToken: tokenOut,
numerator: executionPrice.mul(new Decimal(10 ** (20 + tokenOut.decimals))).toFixed(0),
});
const _fee = new TokenAmount(amountIn.token, fee);
return {
realAmountIn,
amountOut,
minAmountOut,
expirationTime,
currentPrice: _currentPrice,
executionPrice: _executionPrice,
priceImpact,
fee: _fee,
remainingAccounts,
};
}
static estimateAprsForPriceRangeMultiplier({
poolInfo,
aprType,
positionTickLowerIndex,
positionTickUpperIndex,
}: {
poolInfo: ApiV3PoolInfoConcentratedItem;
aprType: "day" | "week" | "month";
positionTickLowerIndex: number;
positionTickUpperIndex: number;
}): {
feeApr: number;
rewardsApr: number[];
apr: number;
} {
const aprInfo = poolInfo[aprType];
const priceLower = TickUtils.getTickPrice({
poolInfo,
tick: positionTickLowerIndex,
baseIn: true,
}).price.toNumber();
const priceUpper = TickUtils.getTickPrice({
poolInfo,
tick: positionTickUpperIndex,
baseIn: true,
}).price.toNumber();
const _minPrice = Math.max(priceLower, aprInfo.priceMin);
const _maxPrice = Math.min(priceUpper, aprInfo.priceMax);
const sub = _maxPrice - _minPrice;
const userRange = priceUpper - priceLower;
const tradeRange = aprInfo.priceMax - aprInfo.priceMin;
let p: number;
if (sub <= 0) p = 0;
else if (userRange === sub) p = tradeRange / sub;
else if (tradeRange === sub) p = sub / userRange;
else p = (sub / tradeRange) * (sub / userRange);
return {
feeApr: aprInfo.feeApr * p,
rewardsApr: [aprInfo.rewardApr[0] ?? 0 * p, aprInfo.rewardApr[1] ?? 0 * p, aprInfo.rewardApr[2] ?? 0 * p],
apr: aprInfo.apr * p,
};
}
static estimateAprsForPriceRangeDelta({
poolInfo,
poolLiquidity,
aprType,
mintPrice,
liquidity,
positionTickLowerIndex,
positionTickUpperIndex,
chainTime,
}: {
poolInfo: ApiV3PoolInfoConcentratedItem;
poolLiquidity: BN;
aprType: "day" | "week" | "month";
mintPrice: { [mint: string]: { value: number } };
liquidity: BN;
positionTickLowerIndex: number;
positionTickUpperIndex: number;
chainTime: number;
}): {
feeApr: number;
rewardsApr: number[];
apr: number;
} {
const aprTypeDay = aprType === "day" ? 1 : aprType === "week" ? 7 : aprType === "month" ? 30 : 0;
const aprInfo = poolInfo[aprType];
const mintPriceA = mintPrice[solToWSol(poolInfo.mintA.address).toString()];
const mintPriceB = mintPrice[solToWSol(poolInfo.mintB.address).toString()];
const mintDecimalsA = poolInfo.mintA.decimals;
const mintDecimalsB = poolInfo.mintB.decimals;
if (!aprInfo || !mintPriceA || !mintPriceB) return { feeApr: 0, rewardsApr: [0, 0, 0], apr: 0 };
const sqrtPriceX64 = SqrtPriceMath.priceToSqrtPriceX64(
new Decimal(poolInfo.price),
poolInfo.mintA.decimals,
poolInfo.mintB.decimals,
);
const sqrtPriceX64A = SqrtPriceMath.getSqrtPriceX64FromTick(positionTickLowerIndex);
const sqrtPriceX64B = SqrtPriceMath.getSqrtPriceX64FromTick(positionTickUpperIndex);
const { amountSlippageA: poolLiquidityA, amountSlippageB: poolLiquidityB } =
LiquidityMath.getAmountsFromLiquidityWithSlippage(
sqrtPriceX64,
sqrtPriceX64A,
sqrtPriceX64B,
poolLiquidity,
false,
false,
0,
);
const { amountSlippageA: userLiquidityA, amountSlippageB: userLiquidityB } =
LiquidityMath.getAmountsFromLiquidityWithSlippage(
sqrtPriceX64,
sqrtPriceX64A,
sqrtPriceX64B,
liquidity,
false,
false,
0,
);
const poolTvl = new Decimal(poolLiquidityA.toString())
.div(new Decimal(10).pow(mintDecimalsA))
.mul(mintPriceA.value)
.add(new Decimal(poolLiquidityB.toString()).div(new Decimal(10).pow(mintDecimalsB)).mul(mintPriceB.value));
const userTvl = new Decimal(userLiquidityA.toString())
.div(new Decimal(10).pow(mintDecimalsA))
.mul(mintPriceA.value)
.add(new Decimal(userLiquidityB.toString()).div(new Decimal(10).pow(mintDecimalsB)).mul(mintPriceB.value));
const p = userTvl.div(poolTvl.add(userTvl)).div(userTvl);
const feesPerYear = new Decimal(aprInfo.volumeFee).mul(365).div(aprTypeDay);
const feeApr = feesPerYear.mul(p).mul(100).toNumber();
const SECONDS_PER_YEAR = 3600 * 24 * 365;
const rewardsApr = poolInfo.rewardDefaultInfos.map((i) => {
const iDecimal = i.mint.decimals;
const iPrice = mintPrice[i.mint.address];
if (
chainTime < ((i as any).startTime ?? 0) ||
chainTime > ((i as any).endTime ?? 0) ||
!i.perSecond ||
!iPrice ||
iDecimal === undefined
)
return 0;
return new Decimal(iPrice.value)
.mul(new Decimal(i.perSecond).mul(SECONDS_PER_YEAR))
.div(new Decimal(10).pow(iDecimal))
.mul(p)
.mul(100)
.toNumber();
});
return {
feeApr,
rewardsApr,
apr: feeApr + rewardsApr.reduce((a, b) => a + b, 0),
};
}
static getLiquidityAmountOutFromAmountIn({
poolInfo,
inputA,
tickLower,
tickUpper,
amount,
slippage,
add,
epochInfo,
amountHasFee,
}: {
poolInfo: ApiV3PoolInfoConcentratedItem;
inputA: boolean;
tickLower: number;
tickUpper: number;
amount: BN;
slippage: number;
add: boolean;
epochInfo: EpochInfo;
amountHasFee: boolean;
}): Promise<ReturnTypeGetLiquidityAmountOut> {
const sqrtPriceX64 = SqrtPriceMath.priceToSqrtPriceX64(
new Decimal(poolInfo.price),
poolInfo.mintA.decimals,
poolInfo.mintB.decimals,
);
const sqrtPriceX64A = SqrtPriceMath.getSqrtPriceX64FromTick(tickLower);
const sqrtPriceX64B = SqrtPriceMath.getSqrtPriceX64FromTick(tickUpper);
const coefficient = add ? 1 - slippage : 1 + slippage;
const addFeeAmount = getTransferAmountFeeV2(
amount,
poolInfo[inputA ? "mintA" : "mintB"].extensions?.feeConfig,
epochInfo,
!amountHasFee,
);
const _amount = new BN(
new Decimal(addFeeAmount.amount.sub(addFeeAmount.fee ?? ZERO).toString()).mul(coefficient).toFixed(0),
);
let liquidity: BN;
if (sqrtPriceX64.lte(sqrtPriceX64A)) {
liquidity = inputA
? LiquidityMath.getLiquidityFromTokenAmountA(sqrtPriceX64A, sqrtPriceX64B, _amount, !add)
: new BN(0);
} else if (sqrtPriceX64.lte(sqrtPriceX64B)) {
const liquidity0 = LiquidityMath.getLiquidityFromTokenAmountA(sqrtPriceX64, sqrtPriceX64B, _amount, !add);
const liquidity1 = LiquidityMath.getLiquidityFromTokenAmountB(sqrtPriceX64A, sqrtPriceX64, _amount);
liquidity = inputA ? liquidity0 : liquidity1;
} else {
liquidity = inputA
? new BN(0)
: LiquidityMath.getLiquidityFromTokenAmountB(sqrtPriceX64A, sqrtPriceX64B, _amount);
}
return PoolUtils.getAmountsFromLiquidity({
epochInfo,
poolInfo,
tickLower,
tickUpper,
liquidity,
slippage,
add,
});
}
static async getAmountsFromLiquidity({
epochInfo,
poolInfo,
tickLower,
tickUpper,
liquidity,
slippage,
add,
}: {
epochInfo: EpochInfo;
poolInfo: ApiV3PoolInfoConcentratedItem;
tickLower: number;
tickUpper: number;
liquidity: BN;
slippage: number;
add: boolean;
}): Promise<ReturnTypeGetLiquidityAmountOut> {
const sqrtPriceX64A = SqrtPriceMath.getSqrtPriceX64FromTick(tickLower);
const sqrtPriceX64B = SqrtPriceMath.getSqrtPriceX64FromTick(tickUpper);
const coefficientRe = add ? 1 + slippage : 1 - slippage;
const amounts = LiquidityMath.getAmountsFromLiquidity(
SqrtPriceMath.priceToSqrtPriceX64(new Decimal(poolInfo.price), poolInfo.mintA.decimals, poolInfo.mintB.decimals),
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(
amounts.amountA.muln(coefficientRe),
poolInfo.mintA.extensions?.feeConfig,
epochInfo,
true,
),
getTransferAmountFeeV2(
amounts.amountB.muln(coefficientRe),
poolInfo.mintB.extensions?.feeConfig,
epochInfo,
true,
),
];
return {
liquidity,
amountA,
amountB,
amountSlippageA,
amountSlippageB,
expirationTime: minExpirationTime(amountA.expirationTime, amountB.expirationTime),
};
}
}
export function getLiquidityFromAmounts({
poolInfo,
tickLower,
tickUpper,
amountA,
amountB,
slippage,
add,
epochInfo,
amountHasFee,
}: {
poolInfo: ApiV3PoolInfoConcentratedItem;
tickLower: number;
tickUpper: number;
amountA: BN;
amountB: BN;
slippage: number;
add: boolean;
epochInfo: EpochInfo;
amountHasFee: boolean;
}): ReturnTypeGetLiquidityAmountOut {
const [_tickLower, _tickUpper, _amountA, _amountB] =
tickLower < tickUpper ? [tickLower, tickUpper, amountA, amountB] : [tickUpper, tickLower, amountB, amountA];
const sqrtPriceX64 = SqrtPriceMath.priceToSqrtPriceX64(
new Decimal(poolInfo.price),
poolInfo.mintA.decimals,
poolInfo.mintB.decimals,
);
const sqrtPriceX64A = SqrtPriceMath.getSqrtPriceX64FromTick(_tickLower);
const sqrtPriceX64B = SqrtPriceMath.getSqrtPriceX64FromTick(_tickUpper);
const [amountFeeA, amountFeeB] = [
getTransferAmountFeeV2(_amountA, poolInfo.mintA.extensions?.feeConfig, epochInfo, !amountHasFee),
getTransferAmountFeeV2(_amountB, poolInfo.mintB.extensions?.feeConfig, epochInfo, !amountHasFee),
];
const liquidity = LiquidityMath.getLiquidityFromTokenAmounts(
sqrtPriceX64,
sqrtPriceX64A,
sqrtPriceX64B,
amountFeeA.amount.sub(amountFeeA.fee ?? ZERO),
amountFeeB.amount.sub(amountFeeB.fee ?? ZERO),
);
return LiquidityMath.getAmountsOutFromLiquidity({
poolInfo,
tickLower,
tickUpper,
liquidity,
slippage,
add,
epochInfo,
a