UNPKG

@kamino-finance/kliquidity-sdk

Version:

Typescript SDK for interacting with the Kamino Liquidity (kliquidity) protocol

372 lines (337 loc) 12 kB
import Decimal from 'decimal.js'; import { PositionRange, RebalanceFieldInfo, RebalanceFieldsDict } from '../utils/types'; import { Dex, readPriceOption } from '../utils'; import { priceToTickIndex, sqrtPriceToPrice as orcaSqrtPriceToPrice, tickIndexToPrice } from '@orca-so/whirlpools-core'; import { RebalanceRaw } from '../@codegen/kliquidity/types'; import { RebalanceTypeLabelName } from './consts'; import { upsertManyRebalanceFieldInfos } from './utils'; import { getPriceOfBinByBinIdWithDecimals } from '../utils/meteora'; import { SqrtPriceMath } from '@raydium-io/raydium-sdk-v2/lib'; export const DEFAULT_DRIFT_TICKS_PER_EPOCH = new Decimal(1); export const DEFAULT_TICKS_BELOW_MID = new Decimal(10); export const DEFAULT_TICKS_ABOVE_MID = new Decimal(10); export const DEFAULT_FRONTRUN_MULTIPLIER_BPS = new Decimal(10_000); export const DEFAULT_STAKING_RATE_SOURCE_A = new Decimal(0); export const DEFAULT_STAKING_RATE_SOURCE_B = new Decimal(0); export const DEFAULT_DIRECTION = new Decimal(1); export const AutodriftRebalanceTypeName = 'autodrift'; export function getAutodriftRebalanceFieldInfos( dex: Dex, tokenADecimals: number, tokenBDecimals: number, tickSpacing: number, lastMidTick: Decimal, initDriftTicksPerEpoch: Decimal, ticksBelowMid: Decimal, ticksAboveMid: Decimal, frontrunMultiplierBps: Decimal, stakingRateASource: Decimal, stakingRateBSource: Decimal, initialDriftDirection: Decimal, enabled: boolean = true ): RebalanceFieldInfo[] { const rebalanceType: RebalanceFieldInfo = { label: RebalanceTypeLabelName, type: 'string', value: AutodriftRebalanceTypeName, enabled, }; const lastMidTickRebalanceFieldInfo: RebalanceFieldInfo = { label: 'lastMidTick', type: 'number', value: lastMidTick, enabled: false, }; const initDriftTicksPerEpochRebalanceFieldInfo: RebalanceFieldInfo = { label: 'initDriftTicksPerEpoch', type: 'number', value: initDriftTicksPerEpoch, enabled, }; const ticksBelowMidRebalanceFieldInfo: RebalanceFieldInfo = { label: 'ticksBelowMid', type: 'number', value: ticksBelowMid, enabled, }; const ticksAboveMidRebalanceFieldInfo: RebalanceFieldInfo = { label: 'ticksAboveMid', type: 'number', value: ticksAboveMid, enabled, }; const fronturnMultiplierBpsRebalanceFieldInfo: RebalanceFieldInfo = { label: 'frontrunMultiplierBps', type: 'number', value: frontrunMultiplierBps, enabled, }; const stakingRateASourceRebalanceFieldInfo: RebalanceFieldInfo = { label: 'stakingRateASource', type: 'number', value: stakingRateASource, enabled, }; const stakingRateBSourceRebalanceFieldInfo: RebalanceFieldInfo = { label: 'stakingRateBSource', type: 'number', value: stakingRateBSource, enabled, }; const initialDriftDirectionRebalanceFieldInfo: RebalanceFieldInfo = { label: 'initialDriftDirection', type: 'number', value: initialDriftDirection, enabled, }; const { lowerPrice, upperPrice } = getPositionRangeFromAutodriftParams( dex, tokenADecimals, tokenBDecimals, lastMidTick, ticksBelowMid, ticksAboveMid, tickSpacing ); const lowerRangeRebalanceFieldInfo: RebalanceFieldInfo = { label: 'rangePriceLower', type: 'number', value: lowerPrice, enabled: false, }; const upperRangeRebalanceFieldInfo: RebalanceFieldInfo = { label: 'rangePriceUpper', type: 'number', value: upperPrice, enabled: false, }; return [ rebalanceType, ticksBelowMidRebalanceFieldInfo, ticksAboveMidRebalanceFieldInfo, initialDriftDirectionRebalanceFieldInfo, lowerRangeRebalanceFieldInfo, upperRangeRebalanceFieldInfo, fronturnMultiplierBpsRebalanceFieldInfo, stakingRateASourceRebalanceFieldInfo, stakingRateBSourceRebalanceFieldInfo, initDriftTicksPerEpochRebalanceFieldInfo, lastMidTickRebalanceFieldInfo, ]; } export function getPositionRangeFromAutodriftParams( dex: Dex, tokenADecimals: number, tokenBDecimals: number, startMidTick: Decimal, ticksBelowMid: Decimal, ticksAboveMid: Decimal, tickSpacing: number ): PositionRange { const lowerTickIndex = startMidTick.sub(ticksBelowMid); const upperTickIndex = startMidTick.add(ticksAboveMid); if (dex == 'ORCA') { const lowerPrice = new Decimal(tickIndexToPrice(lowerTickIndex.toNumber(), tokenADecimals, tokenBDecimals)); const upperPrice = new Decimal(tickIndexToPrice(upperTickIndex.toNumber(), tokenADecimals, tokenBDecimals)); return { lowerPrice, upperPrice }; } else if (dex == 'RAYDIUM') { const lowerPrice = new Decimal( SqrtPriceMath.sqrtPriceX64ToPrice( SqrtPriceMath.getSqrtPriceX64FromTick(lowerTickIndex.toNumber()), tokenADecimals, tokenBDecimals ) ); const upperPrice = new Decimal( SqrtPriceMath.sqrtPriceX64ToPrice( SqrtPriceMath.getSqrtPriceX64FromTick(upperTickIndex.toNumber()), tokenADecimals, tokenBDecimals ) ); return { lowerPrice, upperPrice }; } else if (dex == 'METEORA') { const lowerPrice = getPriceOfBinByBinIdWithDecimals( lowerTickIndex.toNumber(), tickSpacing, tokenADecimals, tokenBDecimals ); const upperPrice = getPriceOfBinByBinIdWithDecimals( upperTickIndex.toNumber(), tickSpacing, tokenADecimals, tokenBDecimals ); return { lowerPrice, upperPrice }; } else { throw new Error(`Unknown DEX ${dex}`); } } // todo(silviu): get sensible default params from Matt export function getDefaultAutodriftRebalanceFieldInfos( dex: Dex, price: Decimal, tokenADecimals: number, tokenBDecimals: number, tickSpacing: number ): RebalanceFieldInfo[] { const currentTickIndex = priceToTickIndex(price.toNumber(), tokenADecimals, tokenBDecimals); const startMidTick = new Decimal(currentTickIndex); return getAutodriftRebalanceFieldInfos( dex, tokenADecimals, tokenBDecimals, tickSpacing, startMidTick, DEFAULT_DRIFT_TICKS_PER_EPOCH, DEFAULT_TICKS_BELOW_MID, DEFAULT_TICKS_ABOVE_MID, DEFAULT_FRONTRUN_MULTIPLIER_BPS, DEFAULT_STAKING_RATE_SOURCE_A, DEFAULT_STAKING_RATE_SOURCE_B, DEFAULT_DIRECTION ); } export function readAutodriftRebalanceParamsFromStrategy(rebalanceRaw: RebalanceRaw): RebalanceFieldsDict { const paramsBuffer = Buffer.from(rebalanceRaw.params); const params: RebalanceFieldsDict = {}; params['initDriftTicksPerEpoch'] = new Decimal(paramsBuffer.readUInt32LE(0)); params['ticksBelowMid'] = new Decimal(paramsBuffer.readInt32LE(4)); params['ticksAboveMid'] = new Decimal(paramsBuffer.readInt32LE(8)); params['frontrunMultiplierBps'] = new Decimal(paramsBuffer.readUInt16LE(12)); params['stakingRateASource'] = new Decimal(paramsBuffer.readUint8(14)); params['stakingRateBSource'] = new Decimal(paramsBuffer.readUint8(15)); params['initialDriftDirection'] = new Decimal(paramsBuffer.readUint8(16)); return params; } export function readRawAutodriftRebalanceStateFromStrategy(rebalanceRaw: RebalanceRaw) { const stateBuffer = Buffer.from(rebalanceRaw.state); const state: RebalanceFieldsDict = {}; // prettier-ignore { let offset = 0; [offset, state['last_window_staking_rate_a'] ] = readPriceOption(stateBuffer, offset); [offset, state['last_window_staking_rate_b'] ] = readPriceOption(stateBuffer, offset); [offset, state['last_window_epoch'] ] = [offset + 8, new Decimal(stateBuffer.readBigUInt64LE(offset).toString())]; [offset, state['last_window_theoretical_tick'] ] = [offset + 4, new Decimal(stateBuffer.readInt32LE(offset))]; [offset, state['last_window_strat_mid_tick'] ] = [offset + 4, new Decimal(stateBuffer.readInt32LE(offset))]; [offset, state['current_window_staking_rate_a'] ] = readPriceOption(stateBuffer, offset); [offset, state['current_window_staking_rate_b'] ] = readPriceOption(stateBuffer, offset); [offset, state['current_window_epoch'] ] = [offset + 8, new Decimal(stateBuffer.readBigUInt64LE(offset).toString())]; [offset, state['current_window_theoretical_tick']] = [offset + 4, new Decimal(stateBuffer.readInt32LE(offset))]; [offset, state['current_window_strat_mid_tick'] ] = [offset + 4, new Decimal(stateBuffer.readInt32LE(offset))]; [offset, state['autodrift_step'] ] = [offset + 1, new Decimal(stateBuffer.readInt8(offset))]; } return state; } export function readAutodriftRebalanceStateFromStrategy( dex: Dex, tokenADecimals: number, tokenBDecimals: number, tickSpacing: number, rebalanceRaw: RebalanceRaw ) { const params = readAutodriftRebalanceParamsFromStrategy(rebalanceRaw); const state = readRawAutodriftRebalanceStateFromStrategy(rebalanceRaw); const lastMidTick = state['current_window_strat_mid_tick']; const ticksBelowMid = params['ticksBelowMid']; const ticksAboveMid = params['ticksAboveMid']; const lowerTickIndex = lastMidTick.sub(ticksBelowMid); const upperTickIndex = lastMidTick.add(ticksAboveMid); let lowerPrice: Decimal, upperPrice: Decimal; if (dex == 'ORCA') { lowerPrice = new Decimal(tickIndexToPrice(lowerTickIndex.toNumber(), tokenADecimals, tokenBDecimals)); upperPrice = new Decimal(tickIndexToPrice(upperTickIndex.toNumber(), tokenADecimals, tokenBDecimals)); } else if (dex == 'RAYDIUM') { lowerPrice = new Decimal( orcaSqrtPriceToPrice( BigInt(SqrtPriceMath.getSqrtPriceX64FromTick(lowerTickIndex.toNumber()).toString()), tokenADecimals, tokenBDecimals ) ); upperPrice = new Decimal( orcaSqrtPriceToPrice( BigInt(SqrtPriceMath.getSqrtPriceX64FromTick(upperTickIndex.toNumber()).toString()), tokenADecimals, tokenBDecimals ) ); } else if (dex == 'METEORA') { lowerPrice = getPriceOfBinByBinIdWithDecimals( lowerTickIndex.toNumber(), tickSpacing, tokenADecimals, tokenBDecimals ); upperPrice = getPriceOfBinByBinIdWithDecimals( upperTickIndex.toNumber(), tickSpacing, tokenADecimals, tokenBDecimals ); } else { throw new Error(`Unknown DEX ${dex}`); } const lowerRangeRebalanceFieldInfo: RebalanceFieldInfo = { label: 'rangePriceLower', type: 'number', value: lowerPrice, enabled: false, }; const upperRangeRebalanceFieldInfo: RebalanceFieldInfo = { label: 'rangePriceUpper', type: 'number', value: upperPrice, enabled: false, }; return [lowerRangeRebalanceFieldInfo, upperRangeRebalanceFieldInfo]; } export function deserializeAutodriftRebalanceFromOnchainParams( dex: Dex, tokenADecimals: number, tokenBDecimals: number, tickSpacing: number, rebalanceRaw: RebalanceRaw ): RebalanceFieldInfo[] { const params = readAutodriftRebalanceParamsFromStrategy(rebalanceRaw); const state = readRawAutodriftRebalanceStateFromStrategy(rebalanceRaw); return getAutodriftRebalanceFieldInfos( dex, tokenADecimals, tokenBDecimals, tickSpacing, state['current_window_strat_mid_tick'], params['initDriftTicksPerEpoch'], params['ticksBelowMid'], params['ticksAboveMid'], params['frontrunMultiplierBps'], params['stakingRateASource'], params['stakingRateBSource'], params['initialDriftDirection'] ); } export function deserializeAutodriftRebalanceWithStateOverride( dex: Dex, tokenADecimals: number, tokenBDecimals: number, tickSpacing: number, rebalanceRaw: RebalanceRaw ): RebalanceFieldInfo[] { const stateFields = readAutodriftRebalanceStateFromStrategy( dex, tokenADecimals, tokenBDecimals, tickSpacing, rebalanceRaw ); const fields = deserializeAutodriftRebalanceFromOnchainParams( dex, tokenADecimals, tokenBDecimals, tickSpacing, rebalanceRaw ); return upsertManyRebalanceFieldInfos(fields, stateFields); }