UNPKG

@raydium-io/raydium-sdk-v2

Version:

An SDK for building applications on top of Raydium.

229 lines (208 loc) 7.94 kB
import { Connection, PublicKey } from "@solana/web3.js"; import BN from "bn.js"; import { getMultipleAccountsInfo } from "../../../common"; import { TickArrayLayout } from "../layout"; import { MAX_TICK, MIN_TICK } from "./constants"; import { getPdaTickArrayAddress } from "./pda"; import { Tick, TICK_ARRAY_SIZE, TickArray, TickUtils } from "./tick"; import { TickArrayBitmapExtensionLayout } from "../layout"; export const FETCH_TICKARRAY_COUNT = 15; export declare type PoolVars = { key: PublicKey; tokenA: PublicKey; tokenB: PublicKey; fee: number; }; export class TickQuery { public static async getTickArrays( connection: Connection, programId: PublicKey, poolId: PublicKey, tickCurrent: number, tickSpacing: number, tickArrayBitmapArray: BN[], exTickArrayBitmap: ReturnType<typeof TickArrayBitmapExtensionLayout.decode>, ): Promise<{ [key: string]: TickArray }> { const tickArraysToFetch: PublicKey[] = []; const currentTickArrayStartIndex = TickUtils.getTickArrayStartIndexByTick(tickCurrent, tickSpacing); const startIndexArray = TickUtils.getInitializedTickArrayInRange( tickArrayBitmapArray, exTickArrayBitmap, tickSpacing, currentTickArrayStartIndex, Math.floor(FETCH_TICKARRAY_COUNT / 2), ); for (let i = 0; i < startIndexArray.length; i++) { const { publicKey: tickArrayAddress } = getPdaTickArrayAddress(programId, poolId, startIndexArray[i]); tickArraysToFetch.push(tickArrayAddress); } const fetchedTickArrays = (await getMultipleAccountsInfo(connection, tickArraysToFetch)).map((i) => i !== null ? TickArrayLayout.decode(i.data) : null, ); const tickArrayCache: { [key: string]: TickArray } = {}; for (let i = 0; i < tickArraysToFetch.length; i++) { const _info = fetchedTickArrays[i]; if (_info === null) continue; tickArrayCache[_info.startTickIndex] = { ..._info, address: tickArraysToFetch[i], }; } return tickArrayCache; } public static nextInitializedTick( programId: PublicKey, poolId: PublicKey, tickArrayCache: { [key: string]: TickArray }, tickIndex: number, tickSpacing: number, zeroForOne: boolean, ): { nextTick: Tick; tickArrayAddress: PublicKey | undefined; tickArrayStartTickIndex: number; } { let { initializedTick: nextTick, tickArrayAddress, tickArrayStartTickIndex, } = this.nextInitializedTickInOneArray(programId, poolId, tickArrayCache, tickIndex, tickSpacing, zeroForOne); while (nextTick == undefined || nextTick.liquidityGross.lten(0)) { tickArrayStartTickIndex = TickUtils.getNextTickArrayStartIndex(tickArrayStartTickIndex, tickSpacing, zeroForOne); if (this.checkIsValidStartIndex(tickArrayStartTickIndex, tickSpacing)) { throw new Error("No enough initialized tickArray"); } const cachedTickArray = tickArrayCache[tickArrayStartTickIndex]; if (cachedTickArray === undefined) continue; const { nextTick: _nextTick, tickArrayAddress: _tickArrayAddress, tickArrayStartTickIndex: _tickArrayStartTickIndex, } = this.firstInitializedTickInOneArray(programId, poolId, cachedTickArray, zeroForOne); [nextTick, tickArrayAddress, tickArrayStartTickIndex] = [_nextTick, _tickArrayAddress, _tickArrayStartTickIndex]; } if (nextTick == undefined) { throw new Error("No invaild tickArray cache"); } return { nextTick, tickArrayAddress, tickArrayStartTickIndex }; } public static nextInitializedTickArray( tickIndex: number, tickSpacing: number, zeroForOne: boolean, tickArrayBitmap: BN[], exBitmapInfo: ReturnType<typeof TickArrayBitmapExtensionLayout.decode>, ): { isExist: boolean; nextStartIndex: number; } { const currentOffset = Math.floor(tickIndex / TickQuery.tickCount(tickSpacing)); const result: number[] = zeroForOne ? TickUtils.searchLowBitFromStart(tickArrayBitmap, exBitmapInfo, currentOffset - 1, 1, tickSpacing) : TickUtils.searchHightBitFromStart(tickArrayBitmap, exBitmapInfo, currentOffset + 1, 1, tickSpacing); return result.length > 0 ? { isExist: true, nextStartIndex: result[0] } : { isExist: false, nextStartIndex: 0 }; } public static firstInitializedTickInOneArray( programId: PublicKey, poolId: PublicKey, tickArray: TickArray, zeroForOne: boolean, ): { nextTick: Tick | undefined; tickArrayAddress: PublicKey; tickArrayStartTickIndex: number; } { let nextInitializedTick: Tick | undefined = undefined; if (zeroForOne) { let i = TICK_ARRAY_SIZE - 1; while (i >= 0) { const tickInArray = tickArray.ticks[i]; if (tickInArray.liquidityGross.gtn(0)) { nextInitializedTick = tickInArray; break; } i = i - 1; } } else { let i = 0; while (i < TICK_ARRAY_SIZE) { const tickInArray = tickArray.ticks[i]; if (tickInArray.liquidityGross.gtn(0)) { nextInitializedTick = tickInArray; break; } i = i + 1; } } const { publicKey: tickArrayAddress } = getPdaTickArrayAddress(programId, poolId, tickArray.startTickIndex); return { nextTick: nextInitializedTick, tickArrayAddress, tickArrayStartTickIndex: tickArray.startTickIndex }; } public static nextInitializedTickInOneArray( programId: PublicKey, poolId: PublicKey, tickArrayCache: { [key: string]: TickArray }, tickIndex: number, tickSpacing: number, zeroForOne: boolean, ): { initializedTick: Tick | undefined; tickArrayAddress: PublicKey | undefined; tickArrayStartTickIndex: number; } { const startIndex = TickUtils.getTickArrayStartIndexByTick(tickIndex, tickSpacing); let tickPositionInArray = Math.floor((tickIndex - startIndex) / tickSpacing); const cachedTickArray = tickArrayCache[startIndex]; if (cachedTickArray == undefined) { return { initializedTick: undefined, tickArrayAddress: undefined, tickArrayStartTickIndex: startIndex, }; } let nextInitializedTick: Tick | undefined = undefined; if (zeroForOne) { while (tickPositionInArray >= 0) { const tickInArray = cachedTickArray.ticks[tickPositionInArray]; if (tickInArray.liquidityGross.gtn(0)) { nextInitializedTick = tickInArray; break; } tickPositionInArray = tickPositionInArray - 1; } } else { tickPositionInArray = tickPositionInArray + 1; while (tickPositionInArray < TICK_ARRAY_SIZE) { const tickInArray = cachedTickArray.ticks[tickPositionInArray]; if (tickInArray.liquidityGross.gtn(0)) { nextInitializedTick = tickInArray; break; } tickPositionInArray = tickPositionInArray + 1; } } const { publicKey: tickArrayAddress } = getPdaTickArrayAddress(programId, poolId, startIndex); return { initializedTick: nextInitializedTick, tickArrayAddress, tickArrayStartTickIndex: cachedTickArray.startTickIndex, }; } public static getArrayStartIndex(tickIndex: number, tickSpacing: number): number { const ticksInArray = this.tickCount(tickSpacing); const start = Math.floor(tickIndex / ticksInArray); return start * ticksInArray; } public static checkIsValidStartIndex(tickIndex: number, tickSpacing: number): boolean { if (TickUtils.checkIsOutOfBoundary(tickIndex)) { if (tickIndex > MAX_TICK) { return false; } const minStartIndex = TickUtils.getTickArrayStartIndexByTick(MIN_TICK, tickSpacing); return tickIndex == minStartIndex; } return tickIndex % this.tickCount(tickSpacing) == 0; } public static tickCount(tickSpacing: number): number { return TICK_ARRAY_SIZE * tickSpacing; } }