@raydium-io/raydium-sdk-v2
Version:
An SDK for building applications on top of Raydium.
689 lines (588 loc) • 22.4 kB
text/typescript
import { FEE_RATE_DENOMINATOR_VALUE, getMultipleAccountsInfo } from "@/common";
import { Connection, PublicKey } from "@solana/web3.js";
import BN from "bn.js";
import Decimal from "decimal.js";
import { PoolInfoLayout, TickArrayBitmapExtensionLayout, TickArrayLayout, TickLayout } from "../layout";
import { mostSignificantBit, mulDivCeil, mulDivFloor } from "./bigNum";
import {
BIT_PRECISION,
BN_ONE,
BN_ZERO,
EXTENSION_TICKARRAY_BITMAP_SIZE,
LOG_B_2_X32,
LOG_B_P_ERR_MARGIN_LOWER_X64,
LOG_B_P_ERR_MARGIN_UPPER_X64,
MAX_SQRT_PRICE_X64,
MAX_TICK,
MIN_SQRT_PRICE_X64,
MIN_TICK,
Q64,
TICK_ARRAY_BITMAP_SIZE,
TICK_ARRAY_SIZE,
TICK_TO_SQRT_PRICE_FACTORS,
} from "./constants";
import { getPdaExBitmapAccount, getPdaTickArrayAddress } from "./pda";
export interface LimitOrderMatchResult {
amountIn: BN;
amountOut: BN;
ammFeeAmount: BN;
}
export class TickArrayBitmapUtil {
private static scanLinearBitmap({
bitmap,
tickSpacing,
offset,
checkInfo,
}: {
bitmap: Buffer;
tickSpacing: number;
offset: number;
checkInfo?: { tick: number; valueType: "lte" | "gte" };
}): number[] {
const result: number[] = [];
const totalBits = bitmap.length * 8;
let startBit = 0;
let endBit = totalBits - 1;
if (checkInfo) {
const threshold = checkInfo.tick / (tickSpacing * TICK_ARRAY_SIZE) - offset;
if (checkInfo.valueType === "gte") {
startBit = Math.max(0, Math.ceil(threshold));
} else {
endBit = Math.min(totalBits - 1, Math.floor(threshold));
}
}
if (startBit > endBit) return result;
const startByte = Math.floor(startBit / 8);
const endByte = Math.floor(endBit / 8);
for (let i = startByte; i <= endByte; i++) {
if (!bitmap[i]) continue;
const jStart = i === startByte ? startBit % 8 : 0;
const jEnd = i === endByte ? endBit % 8 : 7;
for (let j = jStart; j <= jEnd; j++) {
if (bitmap[i] & (1 << j)) {
result.push((i * 8 + j + offset) * tickSpacing * TICK_ARRAY_SIZE);
}
}
}
return result;
}
private static findPoolBitmap({
bitmap,
tickSpacing,
checkInfo,
}: {
bitmap: Buffer;
tickSpacing: number;
checkInfo?: { tick: number; valueType: "lte" | "gte" };
}): number[] {
if (checkInfo) {
const _i = Math.floor(checkInfo.tick / TICK_ARRAY_SIZE / tickSpacing);
if (checkInfo.valueType === "lte" && _i < -512) return [];
if (checkInfo.valueType === "gte" && _i > 512) return [];
}
return this.scanLinearBitmap({ bitmap, tickSpacing, offset: -TICK_ARRAY_BITMAP_SIZE, checkInfo });
}
private static findPositiveTickArrayBitmap({
bitmap,
tickSpacing,
checkInfo,
}: {
bitmap: Buffer;
tickSpacing: number;
checkInfo?: { tick: number; valueType: "lte" | "gte" };
}): number[] {
if (checkInfo) {
const _i = Math.floor(checkInfo.tick / TICK_ARRAY_SIZE / tickSpacing);
if (checkInfo.valueType === "lte" && _i < 512) return [];
}
return this.scanLinearBitmap({ bitmap, tickSpacing, offset: TICK_ARRAY_BITMAP_SIZE, checkInfo });
}
private static findNegativeTickArrayBitmap({
bitmap,
tickSpacing,
count,
checkInfo,
}: {
bitmap: Buffer;
tickSpacing: number;
count?: number;
checkInfo?: { tick: number; valueType: "lte" | "gte" };
}): number[] {
const result: number[] = [];
if (checkInfo) {
const _i = Math.floor(checkInfo.tick / TICK_ARRAY_SIZE / tickSpacing);
if (checkInfo.valueType === "gte" && _i >= -512) return result;
}
const maxFlatIndex =
checkInfo?.valueType === "lte" ? Math.floor(checkInfo.tick / (TICK_ARRAY_SIZE * tickSpacing)) + 7680 : Infinity;
const minFlatIndex =
checkInfo?.valueType === "gte" ? Math.ceil(checkInfo.tick / (TICK_ARRAY_SIZE * tickSpacing)) + 7680 : 0;
outer: for (let arrayIndex = 0; arrayIndex < EXTENSION_TICKARRAY_BITMAP_SIZE; arrayIndex++) {
const reversedIndex = EXTENSION_TICKARRAY_BITMAP_SIZE - 1 - arrayIndex;
for (let searchIndex = 0; searchIndex < 512; searchIndex++) {
const flatIndex = arrayIndex * 512 + searchIndex;
if (flatIndex > maxFlatIndex) break outer;
if (flatIndex < minFlatIndex) continue;
const byteOffset = reversedIndex * 64 + Math.floor(searchIndex / 8);
if (!bitmap[byteOffset]) {
searchIndex = Math.floor(searchIndex / 8) * 8 + 7;
continue;
}
if (bitmap[byteOffset] & (1 << searchIndex % 8)) {
const tick = (arrayIndex * 512 + searchIndex - 7680) * TICK_ARRAY_SIZE * tickSpacing;
result.push(tick);
if (count !== undefined && result.length >= count) break outer;
}
}
}
return result;
}
static findTickArrayStartIndex({
tickSpacing,
poolBitmap,
tickArrayBitmap,
findInfo,
}: {
tickSpacing: number;
poolBitmap: ReturnType<typeof PoolInfoLayout.decode>["tickArrayBitmap"];
tickArrayBitmap: ReturnType<typeof TickArrayBitmapExtensionLayout.decode>;
findInfo: { type: "zeroForOne" | "oneForZero"; count?: number; tickArrayCurrent: number } | { type: "all" };
}): number[] {
if (findInfo.type === "all") {
return [
...this.findNegativeTickArrayBitmap({ tickSpacing, bitmap: tickArrayBitmap.negativeTickArrayBitmap }),
...this.findPoolBitmap({ tickSpacing, bitmap: poolBitmap }),
...this.findPositiveTickArrayBitmap({ tickSpacing, bitmap: tickArrayBitmap.positiveTickArrayBitmap }),
];
}
const tickStart = TickArrayUtil.getTickArrayStartIndex(findInfo.tickArrayCurrent, tickSpacing);
const { count } = findInfo;
if (findInfo.type === "oneForZero") {
const checkInfo = { tick: tickStart, valueType: "gte" } as const;
const finders = [
() =>
this.findNegativeTickArrayBitmap({ tickSpacing, bitmap: tickArrayBitmap.negativeTickArrayBitmap, checkInfo }),
() => this.findPoolBitmap({ tickSpacing, bitmap: poolBitmap, checkInfo }),
() =>
this.findPositiveTickArrayBitmap({ tickSpacing, bitmap: tickArrayBitmap.positiveTickArrayBitmap, checkInfo }),
];
return this.collectUntil(finders, count);
}
if (findInfo.type === "zeroForOne") {
const checkInfo = { tick: tickStart, valueType: "lte" } as const;
const finders = [
() =>
this.findPositiveTickArrayBitmap({
tickSpacing,
bitmap: tickArrayBitmap.positiveTickArrayBitmap,
checkInfo,
}).sort((a, b) => b - a),
() => this.findPoolBitmap({ tickSpacing, bitmap: poolBitmap, checkInfo }).sort((a, b) => b - a),
() =>
this.findNegativeTickArrayBitmap({
tickSpacing,
bitmap: tickArrayBitmap.negativeTickArrayBitmap,
checkInfo,
}).sort((a, b) => b - a),
];
return this.collectUntil(finders, count);
}
throw new Error("find info type check error");
}
private static collectUntil(finders: Array<() => number[]>, count: number | undefined): number[] {
const collected: number[] = [];
for (const finder of finders) {
if (count !== undefined && collected.length >= count) break;
collected.push(...finder());
}
return collected.slice(0, count);
}
static findTickArrayAddress(params: {
programId: PublicKey;
poolId: PublicKey;
tickSpacing: number;
poolBitmap: ReturnType<typeof PoolInfoLayout.decode>["tickArrayBitmap"];
tickArrayBitmap: ReturnType<typeof TickArrayBitmapExtensionLayout.decode>;
findInfo: { type: "zeroForOne" | "oneForZero"; count?: number; tickArrayCurrent: number } | { type: "all" };
}) {
return this.findTickArrayStartIndex(params).map(
(i) => getPdaTickArrayAddress(params.programId, params.poolId, i).publicKey,
);
}
static maxTickInTickarrayBitmap(tickSpacing: number): number {
return tickSpacing * TICK_ARRAY_SIZE * TICK_ARRAY_BITMAP_SIZE;
}
}
export class TickArrayUtil {
static firstinitializedTick({
data,
zeroForOne,
}: {
data: ReturnType<typeof TickArrayLayout.decode>;
zeroForOne: boolean;
}) {
if (zeroForOne) {
for (let i = data.ticks.length - 1; i >= 0; i--) {
if (TickUtil.isInitialized({ data: data.ticks[i] })) return data.ticks[i];
}
} else {
for (let i = 0; i < data.ticks.length; i++) {
if (TickUtil.isInitialized({ data: data.ticks[i] })) return data.ticks[i];
}
}
}
static nextInitalizedTick({
data,
currentTickIndex,
tickSpacing,
zeroForOne,
}: {
data: ReturnType<typeof TickArrayLayout.decode>;
currentTickIndex: number;
tickSpacing: number;
zeroForOne: boolean;
}) {
const currentTickArrayStartIndex = this.getTickArrayStartIndex(currentTickIndex, tickSpacing);
if (currentTickArrayStartIndex !== data.startTickIndex) return undefined;
const offsetInArray = Math.floor((currentTickIndex - data.startTickIndex) / tickSpacing);
if (zeroForOne) {
for (let i = offsetInArray; i >= 0; i--) {
if (TickUtil.isInitialized({ data: data.ticks[i] })) {
return data.ticks[i];
}
}
} else {
for (let i = offsetInArray + 1; i < TICK_ARRAY_SIZE; i++) {
if (TickUtil.isInitialized({ data: data.ticks[i] })) {
return data.ticks[i];
}
}
}
return undefined;
}
static getTickArrayStartIndex(tickIndex: number, tickSpacing: number) {
const ticksInArray = this.tickCount(tickSpacing);
const start = Math.floor(tickIndex / ticksInArray);
return start * ticksInArray;
}
static getTickOffsetInArray(tick: number, tickSpacing: number): number {
if (tick % tickSpacing != 0) {
throw new Error("tickIndex % tickSpacing not equal 0");
}
const startIndex = this.getTickArrayStartIndex(tick, tickSpacing);
return Math.floor((tick - startIndex) / tickSpacing);
}
static tickCount(tickSpacing: number) {
return TICK_ARRAY_SIZE * tickSpacing;
}
static getMinTick(tickSpacing: number): number {
return Math.ceil(MIN_TICK / tickSpacing) * tickSpacing;
}
static getMaxTick(tickSpacing: number): number {
return Math.floor(MAX_TICK / tickSpacing) * tickSpacing;
}
}
export class TickUtil {
static isInitialized({ data }: { data: ReturnType<typeof TickLayout.decode> }): boolean {
return this.hasLiquidity({ data }) || this.hasLimitOrders({ data });
}
static hasLimitOrders({ data }: { data: ReturnType<typeof TickLayout.decode> }): boolean {
return !data.ordersAmount.isZero() || !data.partFilledOrdersRemaining.isZero();
}
static hasLiquidity({ data }: { data: ReturnType<typeof TickLayout.decode> }): boolean {
return !data.liquidityGross.isZero();
}
static isValidTick(tick: number): boolean {
return tick >= MIN_TICK && tick <= MAX_TICK;
}
static checkTick(tick: number): void {
if (!this.isValidTick(tick)) {
throw new Error(`Tick ${tick} is out of range [${MIN_TICK}, ${MAX_TICK}]`);
}
}
static getSqrtPriceAtTick(tick: number): BN {
this.checkTick(tick);
const absTick = Math.abs(tick);
let ratio = Q64.clone();
for (const { bit, factor } of TICK_TO_SQRT_PRICE_FACTORS) {
if ((absTick & (1 << bit)) !== 0) {
ratio = mulDivFloor(ratio, factor, Q64);
}
}
if (tick > 0) {
ratio = mulDivFloor(Q64, Q64, ratio);
}
return ratio;
}
static getLimitOrderOutput({ amountIn, tick, zeroForOne }: { amountIn: BN; tick: number; zeroForOne: boolean }): BN {
if (zeroForOne) {
const priceX64 = TickUtil.getPriceAtTick(tick, false);
return mulDivFloor(amountIn, priceX64, Q64);
} else {
const priceX64 = TickUtil.getPriceAtTick(tick, true);
return mulDivFloor(amountIn, Q64, priceX64);
}
}
static getLimitOrderInput({ amountOut, tick, zeroForOne }: { amountOut: BN; tick: number; zeroForOne: boolean }): BN {
if (zeroForOne) {
const priceX64 = TickUtil.getPriceAtTick(tick, true);
return mulDivCeil(amountOut, priceX64, Q64);
} else {
const priceX64 = TickUtil.getPriceAtTick(tick, false);
return mulDivCeil(amountOut, Q64, priceX64);
}
}
static limitOrderUnfilledAmount({ tick }: { tick: ReturnType<typeof TickLayout.decode> }): BN {
return tick.ordersAmount.add(tick.partFilledOrdersRemaining);
}
static matchLimitOrder({
tick,
swapAmount,
swapDirectionZeroForOne,
isBaseInput,
feeRate,
isFeeOnInput,
}: {
tick: ReturnType<typeof TickLayout.decode>;
swapAmount: BN;
swapDirectionZeroForOne: boolean;
isBaseInput: boolean;
feeRate: number;
isFeeOnInput: boolean;
}): LimitOrderMatchResult {
const result: LimitOrderMatchResult = {
amountIn: BN_ZERO,
amountOut: BN_ZERO,
ammFeeAmount: BN_ZERO,
};
const totalUnfilledAmount = this.limitOrderUnfilledAmount({ tick });
if (swapAmount.isZero() || totalUnfilledAmount.isZero()) {
return result;
}
if (isBaseInput) {
if (isFeeOnInput) {
result.ammFeeAmount = mulDivCeil(swapAmount, new BN(feeRate), FEE_RATE_DENOMINATOR_VALUE);
result.amountIn = swapAmount.sub(result.ammFeeAmount);
} else {
result.amountIn = swapAmount;
}
result.amountOut = this.getLimitOrderOutput({
amountIn: result.amountIn,
tick: tick.tick,
zeroForOne: swapDirectionZeroForOne,
});
if (result.amountOut.gt(totalUnfilledAmount)) {
result.amountOut = totalUnfilledAmount;
result.amountIn = this.getLimitOrderInput({
amountOut: totalUnfilledAmount,
tick: tick.tick,
zeroForOne: !swapDirectionZeroForOne,
});
if (isFeeOnInput) {
result.ammFeeAmount = mulDivCeil(
result.amountIn,
new BN(feeRate),
FEE_RATE_DENOMINATOR_VALUE.sub(new BN(feeRate)),
);
}
}
} else {
const netOutput = BN.min(swapAmount, totalUnfilledAmount);
if (isFeeOnInput) {
result.amountOut = netOutput;
} else {
result.amountOut = BN.min(
mulDivCeil(netOutput, FEE_RATE_DENOMINATOR_VALUE, FEE_RATE_DENOMINATOR_VALUE.sub(new BN(feeRate))),
totalUnfilledAmount,
);
}
result.amountIn = this.getLimitOrderInput({
amountOut: result.amountOut,
tick: tick.tick,
zeroForOne: !swapDirectionZeroForOne,
});
if (isFeeOnInput) {
result.ammFeeAmount = mulDivCeil(
result.amountIn,
new BN(feeRate),
FEE_RATE_DENOMINATOR_VALUE.sub(new BN(feeRate)),
);
}
}
let consumeFromPartRemaining = BN_ZERO;
if (tick.partFilledOrdersRemaining.gt(BN_ZERO)) {
consumeFromPartRemaining = BN.min(tick.partFilledOrdersRemaining, result.amountOut);
if (consumeFromPartRemaining.gt(BN_ZERO)) {
tick.unfilledRatioX64 = mulDivFloor(tick.unfilledRatioX64, tick.partFilledOrdersRemaining.sub(consumeFromPartRemaining), tick.partFilledOrdersRemaining)
}
tick.partFilledOrdersRemaining = tick.partFilledOrdersRemaining.sub(consumeFromPartRemaining);
}
const amountOutContinueToConsume = result.amountOut.sub(consumeFromPartRemaining);
if (amountOutContinueToConsume.gt(BN_ZERO)) {
if (!tick.partFilledOrdersRemaining.isZero()) throw Error("!tick.partFilledOrdersRemaining.isZero()");
if (tick.ordersAmount.lt(amountOutContinueToConsume)) throw Error("InvalidLimitOrderAmount");
tick.orderPhase = tick.orderPhase.add(BN_ONE);
tick.unfilledRatioX64 = mulDivFloor(Q64, tick.ordersAmount.sub(amountOutContinueToConsume), tick.ordersAmount);
tick.partFilledOrdersRemaining = tick.ordersAmount.sub(amountOutContinueToConsume);
tick.ordersAmount = BN_ZERO;
}
if (!isFeeOnInput) {
result.ammFeeAmount = mulDivCeil(result.amountOut, new BN(feeRate), FEE_RATE_DENOMINATOR_VALUE);
result.amountOut = result.amountOut.sub(result.ammFeeAmount);
}
return result;
}
private static getPriceAtTick(tick: number, roundUp: boolean): BN {
const sqrtPriceX64 = this.getSqrtPriceAtTick(tick);
if (roundUp) {
return sqrtPriceX64.mul(sqrtPriceX64).add(Q64.subn(1)).div(Q64);
} else {
return sqrtPriceX64.mul(sqrtPriceX64).div(Q64);
}
}
static getTickAtSqrtPrice(sqrtPriceX64: BN): number {
if (!(sqrtPriceX64.gte(MIN_SQRT_PRICE_X64) && sqrtPriceX64.lte(MAX_SQRT_PRICE_X64))) throw Error("SqrtPriceX64");
const msb = mostSignificantBit(sqrtPriceX64);
const msbMinus64 = msb - 64;
let log2pIntegerX32: BN;
if (msbMinus64 >= 0) {
log2pIntegerX32 = new BN(msbMinus64).shln(32);
} else {
log2pIntegerX32 = new BN(-msbMinus64).shln(32).neg();
}
let r: BN;
if (msb >= 64) {
r = sqrtPriceX64.shrn(msb - 63);
} else {
r = sqrtPriceX64.shln(63 - msb);
}
let log2pFractionX64 = new BN(0);
let bit = new BN(1).shln(63);
for (let precision = 0; precision < BIT_PRECISION && !bit.isZero(); precision++) {
r = r.mul(r);
const isRMoreThanTwo = r.shrn(127).toNumber();
r = r.shrn(63 + isRMoreThanTwo);
if (isRMoreThanTwo) {
log2pFractionX64 = log2pFractionX64.add(bit);
}
bit = bit.shrn(1);
}
const log2pFractionX32 = log2pFractionX64.shrn(32);
const log2pX32 = log2pIntegerX32.add(log2pFractionX32);
const logSqrt10001X64 = log2pX32.mul(LOG_B_2_X32);
const tickLowBN = logSqrt10001X64.sub(LOG_B_P_ERR_MARGIN_LOWER_X64);
const tickHighBN = logSqrt10001X64.add(LOG_B_P_ERR_MARGIN_UPPER_X64);
const tickLow = this.signedShrn64(tickLowBN);
const tickHigh = this.signedShrn64(tickHighBN);
if (tickLow === tickHigh) {
return tickLow;
}
const sqrtPriceAtTickHigh = TickUtil.getSqrtPriceAtTick(tickHigh);
if (sqrtPriceAtTickHigh.lte(sqrtPriceX64)) {
return tickHigh;
}
return tickLow;
}
private static signedShrn64(bn: BN): number {
if (bn.isNeg()) {
const Q64 = new BN(1).shln(64);
const result = bn.div(Q64);
if (!bn.mod(Q64).isZero() && bn.isNeg()) {
return result.subn(1).toNumber();
}
return result.toNumber();
} else {
return bn.shrn(64).toNumber();
}
}
static sqrtPriceX64ToPrice(sqrtPriceX64: BN, decimalsA: number, decimalsB: number): Decimal {
const sqrtPriceSquared = sqrtPriceX64.mul(sqrtPriceX64);
const decimalDiff = decimalsA - decimalsB;
const DECIMAL_PRECISION = 20;
const PRECISION_MULTIPLIER = new BN(10).pow(new BN(DECIMAL_PRECISION));
const numerator = sqrtPriceSquared.mul(PRECISION_MULTIPLIER);
const denominator = new BN(1).shln(128);
const scaledResult = numerator.div(denominator);
let resultStr = scaledResult.toString();
while (resultStr.length <= DECIMAL_PRECISION) {
resultStr = "0" + resultStr;
}
const integerPart = resultStr.slice(0, -DECIMAL_PRECISION);
const decimalPart = resultStr.slice(-DECIMAL_PRECISION);
const priceStr = integerPart + "." + decimalPart;
const price = new Decimal(priceStr).mul(new Decimal(10).pow(decimalDiff));
return price;
}
static tickToPrice(tick: number, decimalsA: number, decimalsB: number): Decimal {
const sqrtPriceX64 = TickUtil.getSqrtPriceAtTick(tick);
return this.sqrtPriceX64ToPrice(sqrtPriceX64, decimalsA, decimalsB);
}
static priceToTick(price: Decimal, decimalsA: number, decimalsB: number): number {
const adjustedPrice = price.div(Math.pow(10, decimalsA - decimalsB));
const tick = adjustedPrice.log().div(new Decimal(1.0001).log()).floor();
return Math.max(MIN_TICK, Math.min(MAX_TICK, tick.toNumber()));
}
static priceToSqrtPriceX64(price: Decimal, decimalsA: number, decimalsB: number): BN {
const adjustedPrice = price.div(Math.pow(10, decimalsA - decimalsB));
const sqrtPrice = adjustedPrice.sqrt();
const sqrtPriceX64 = sqrtPrice.mul(new Decimal(2).pow(64));
return new BN(sqrtPriceX64.toFixed(0));
}
static toTickIndex(tick: number, tickSpacing: number) {
if (tick >= 0) {
return tick - (tick % tickSpacing);
}
return tick - (tick % tickSpacing) - (tick % tickSpacing !== 0 ? tickSpacing : 0);
}
static getPriceAndTick({
price,
mintADecimals,
mintBDecimals,
zeroForOne,
tickSpacing,
}: {
price: Decimal;
mintADecimals: number;
mintBDecimals: number;
zeroForOne: boolean;
tickSpacing: number;
}): { tick: number; price: Decimal } {
let p = price.clamp(1 / 10 ** Math.max(mintADecimals, mintBDecimals), Number.MAX_SAFE_INTEGER);
if (!zeroForOne) p = new Decimal(1).div(p);
const newTick = TickUtil.toTickIndex(TickUtil.priceToTick(p, mintADecimals, mintBDecimals), tickSpacing);
const newPrice = TickUtil.tickToPrice(newTick, mintADecimals, mintBDecimals);
return {
price: zeroForOne ? newPrice : new Decimal(1).div(newPrice),
tick: newTick,
};
}
}
/**
* Fetch tick arrays for swap simulation
*/
export async function fetchTickArrays(
programId: PublicKey,
connection: Connection,
poolId: PublicKey,
currentTick: number,
tickSpacing: number,
tickArrayBitmap: Buffer,
zeroForOne = true,
): Promise<{ address: PublicKey; value: ReturnType<typeof TickArrayLayout.decode> }[]> {
const tickArrays: { address: PublicKey; value: ReturnType<typeof TickArrayLayout.decode> }[] = [];
const tickArrayBitmapExtension = getPdaExBitmapAccount(programId, poolId).publicKey;
const tickArrayBitmapExtensionRes = await connection.getAccountInfo(tickArrayBitmapExtension);
const tickArraysAddress = TickArrayBitmapUtil.findTickArrayAddress({
programId,
poolId,
poolBitmap: tickArrayBitmap,
tickArrayBitmap: TickArrayBitmapExtensionLayout.decode(tickArrayBitmapExtensionRes!.data),
tickSpacing,
findInfo: { type: zeroForOne ? "zeroForOne" : "oneForZero", tickArrayCurrent: currentTick },
});
const tickArrayRes = await getMultipleAccountsInfo(connection, tickArraysAddress);
tickArrayRes.forEach((res, idx) => {
if (res) tickArrays.push({ address: tickArraysAddress[idx], value: TickArrayLayout.decode(res.data) });
});
return tickArrays;
}