@kamino-finance/kliquidity-sdk
Version:
Typescript SDK for interacting with the Kamino Liquidity (kliquidity) protocol
330 lines (303 loc) • 10.8 kB
text/typescript
import Decimal from 'decimal.js';
import { PositionRange, RebalanceFieldInfo } from '../utils/types';
import {
DefaultLowerPercentageBPSDecimal,
DefaultUpperPercentageBPSDecimal,
FullBPSDecimal,
} from '../utils/CreationParameters';
import { RebalanceRaw } from '../@codegen/kliquidity/types';
import { RebalanceTypeLabelName } from './consts';
import { Dex, readBigUint128LE } from '../utils';
import { sqrtPriceX64ToPrice } from '@orca-so/whirlpool-sdk';
import BN from 'bn.js';
import { upsertManyRebalanceFieldInfos } from './utils';
import { getPriceRangeFromPriceAndDiffBPS, getResetRangeFromPriceAndDiffBPS } from './math_utils';
import { getPriceFromQ64Price } from '../utils/meteora';
export const PricePercentageWithResetRebalanceTypeName = 'pricePercentageWithReset';
export function getPricePercentageWithResetRebalanceFieldInfos(
price: Decimal,
lowerPercentageBPS: Decimal,
upperPercentageBPS: Decimal,
resetLowerPercentageBPS: Decimal,
resetUpperPercentageBPS: Decimal,
enabled: boolean = true
): RebalanceFieldInfo[] {
const rebalanceType: RebalanceFieldInfo = {
label: RebalanceTypeLabelName,
type: 'string',
value: PricePercentageWithResetRebalanceTypeName,
enabled,
};
const lowerBpsRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'lowerRangeBps',
type: 'number',
value: lowerPercentageBPS,
enabled,
};
const upperBpsRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'upperRangeBps',
type: 'number',
value: upperPercentageBPS,
enabled,
};
const resetLowerBpsRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'resetLowerRangeBps',
type: 'number',
value: resetLowerPercentageBPS,
enabled,
};
const resetUpperBpsRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'resetUpperRangeBps',
type: 'number',
value: resetUpperPercentageBPS,
enabled,
};
const { lowerPrice, upperPrice } = getPositionRangeFromPricePercentageWithResetParams(
price,
lowerPercentageBPS,
upperPercentageBPS
);
const lowerRangeRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'rangePriceLower',
type: 'number',
value: lowerPrice,
enabled: false,
};
const upperRangeRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'rangePriceUpper',
type: 'number',
value: upperPrice,
enabled: false,
};
const { lowerPrice: resetLowerPrice, upperPrice: resetUpperPrice } =
getPositionResetRangeFromPricePercentageWithResetParams(
price,
lowerPercentageBPS,
upperPercentageBPS,
resetLowerPercentageBPS,
resetUpperPercentageBPS
);
const resetLowerRangeRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'resetPriceLower',
type: 'number',
value: resetLowerPrice,
enabled: false,
};
const resetUpperRangeRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'resetPriceUpper',
type: 'number',
value: resetUpperPrice,
enabled: false,
};
return [
rebalanceType,
lowerBpsRebalanceFieldInfo,
upperBpsRebalanceFieldInfo,
resetLowerBpsRebalanceFieldInfo,
resetUpperBpsRebalanceFieldInfo,
lowerRangeRebalanceFieldInfo,
upperRangeRebalanceFieldInfo,
resetLowerRangeRebalanceFieldInfo,
resetUpperRangeRebalanceFieldInfo,
];
}
export function getPositionRangeFromPricePercentageWithResetParams(
price: Decimal,
lowerPercentageBPS: Decimal,
upperPercentageBPS: Decimal
): PositionRange {
return getPriceRangeFromPriceAndDiffBPS(price, lowerPercentageBPS, upperPercentageBPS);
}
export function getPositionResetRangeFromPricePercentageWithResetParams(
price: Decimal,
lowerPercentageBPS: Decimal,
upperPercentageBPS: Decimal,
resetLowerPercentageBPS: Decimal,
resetUpperPercentageBPS: Decimal
): PositionRange {
return getResetRangeFromPriceAndDiffBPS(
price,
lowerPercentageBPS,
upperPercentageBPS,
resetLowerPercentageBPS,
resetUpperPercentageBPS
);
}
export function getDefaultPricePercentageWithResetRebalanceFieldInfos(price: Decimal): RebalanceFieldInfo[] {
const fieldInfos = getPricePercentageWithResetRebalanceFieldInfos(
price,
DefaultLowerPercentageBPSDecimal,
DefaultUpperPercentageBPSDecimal,
DefaultLowerPercentageBPSDecimal,
DefaultUpperPercentageBPSDecimal
);
return fieldInfos;
}
export function readPricePercentageWithResetRebalanceParamsFromStrategy(
rebalanceRaw: RebalanceRaw
): RebalanceFieldInfo[] {
const paramsBuffer = Buffer.from(rebalanceRaw.params);
const lowerBpsRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'lowerRangeBps',
type: 'number',
value: new Decimal(paramsBuffer.readUint16LE(0)),
enabled: true,
};
const upperBpsRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'upperRangeBps',
type: 'number',
value: new Decimal(paramsBuffer.readUint16LE(2)),
enabled: true,
};
const resetLowerBpsRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'resetLowerRangeBps',
type: 'number',
value: new Decimal(paramsBuffer.readUint16LE(4)),
enabled: true,
};
const resetUpperBpsRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'resetUpperRangeBps',
type: 'number',
value: new Decimal(paramsBuffer.readUint16LE(6)),
enabled: true,
};
return [
lowerBpsRebalanceFieldInfo,
upperBpsRebalanceFieldInfo,
resetLowerBpsRebalanceFieldInfo,
resetUpperBpsRebalanceFieldInfo,
];
}
export function readRawPricePercentageWithResetRebalanceStateFromStrategy(
rebalanceRaw: RebalanceRaw
): RebalanceFieldInfo[] {
const stateBuffer = Buffer.from(rebalanceRaw.state);
const lowerRangeRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'lastRebalanceLowerResetPoolPrice',
type: 'number',
value: new Decimal(readBigUint128LE(stateBuffer, 0).toString()),
enabled: false,
};
const upperRangeRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'lastRebalanceUpperResetPoolPrice',
type: 'number',
value: new Decimal(readBigUint128LE(stateBuffer, 16).toString()),
enabled: false,
};
return [lowerRangeRebalanceFieldInfo, upperRangeRebalanceFieldInfo];
}
export function readPricePercentageWithResetRebalanceStateFromStrategy(
dex: Dex,
tokenADecimals: number,
tokenBDecimals: number,
rebalanceRaw: RebalanceRaw
): RebalanceFieldInfo[] {
const stateBuffer = Buffer.from(rebalanceRaw.state);
const params = readPricePercentageWithResetRebalanceParamsFromStrategy(rebalanceRaw);
const lowerRangeBps = new Decimal(params.find((param) => param.label == 'lowerRangeBps')?.value.toString()!);
const upperRangeBps = new Decimal(params.find((param) => param.label == 'upperRangeBps')?.value!.toString()!);
const resetLowerRangeBps = new Decimal(
params.find((param) => param.label == 'resetLowerRangeBps')?.value.toString()!
);
const resetUpperRangeBps = new Decimal(
params.find((param) => param.label == 'resetUpperRangeBps')?.value!.toString()!
);
const lowerResetSqrtPriceX64 = readBigUint128LE(stateBuffer, 0).toString();
const upperResetSqrtPriceX64 = readBigUint128LE(stateBuffer, 16).toString();
let lowerResetPrice: Decimal, upperResetPrice: Decimal;
if (dex == 'ORCA') {
lowerResetPrice = sqrtPriceX64ToPrice(new BN(lowerResetSqrtPriceX64.toString()), tokenADecimals, tokenBDecimals);
upperResetPrice = sqrtPriceX64ToPrice(new BN(upperResetSqrtPriceX64.toString()), tokenADecimals, tokenBDecimals);
} else if (dex == 'RAYDIUM') {
lowerResetPrice = sqrtPriceX64ToPrice(new BN(lowerResetSqrtPriceX64.toString()), tokenADecimals, tokenBDecimals);
upperResetPrice = sqrtPriceX64ToPrice(new BN(upperResetSqrtPriceX64.toString()), tokenADecimals, tokenBDecimals);
} else if (dex == 'METEORA') {
lowerResetPrice = getPriceFromQ64Price(
new Decimal(lowerResetSqrtPriceX64.toString()),
tokenADecimals,
tokenBDecimals
);
upperResetPrice = getPriceFromQ64Price(
new Decimal(upperResetSqrtPriceX64.toString()),
tokenADecimals,
tokenBDecimals
);
} else {
throw new Error(`Unknown DEX ${dex}`);
}
const resetLowerFactor = resetLowerRangeBps.mul(lowerRangeBps).div(FullBPSDecimal);
const resetUpperFactor = resetUpperRangeBps.mul(upperRangeBps).div(FullBPSDecimal);
const lowerPositionPrice = lowerResetPrice
.mul(FullBPSDecimal.sub(lowerRangeBps))
.div(FullBPSDecimal.sub(resetLowerFactor));
const upperPositionPrice = upperResetPrice
.mul(FullBPSDecimal.add(upperRangeBps))
.div(FullBPSDecimal.add(resetUpperFactor));
const lowerBpsRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'rangePriceLower',
type: 'number',
value: lowerPositionPrice,
enabled: true,
};
const upperBpsRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'rangePriceUpper',
type: 'number',
value: upperPositionPrice,
enabled: true,
};
const resetLowerBpsRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'resetPriceLower',
type: 'number',
value: lowerResetPrice,
enabled: true,
};
const resetUpperBpsRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'resetPriceUpper',
type: 'number',
value: upperResetPrice,
enabled: true,
};
return [
lowerBpsRebalanceFieldInfo,
upperBpsRebalanceFieldInfo,
resetLowerBpsRebalanceFieldInfo,
resetUpperBpsRebalanceFieldInfo,
];
}
export function deserializePricePercentageWithResetRebalanceFromOnchainParams(
price: Decimal,
rebalanceRaw: RebalanceRaw
): RebalanceFieldInfo[] {
const params = readPricePercentageWithResetRebalanceParamsFromStrategy(rebalanceRaw);
const lowerRangeBps = new Decimal(params.find((param) => param.label == 'lowerRangeBps')?.value.toString()!);
const upperRangeBps = new Decimal(params.find((param) => param.label == 'upperRangeBps')?.value.toString()!);
const lowerResetRangeBps = new Decimal(
params.find((param) => param.label == 'resetLowerRangeBps')?.value.toString()!
);
const upperResetRangeBps = new Decimal(
params.find((param) => param.label == 'resetUpperRangeBps')?.value.toString()!
);
return getPricePercentageWithResetRebalanceFieldInfos(
price,
lowerRangeBps,
upperRangeBps,
lowerResetRangeBps,
upperResetRangeBps
);
}
export function deserializePricePercentageWithResetRebalanceWithStateOverride(
dex: Dex,
tokenADecimals: number,
tokenBDecimals: number,
price: Decimal,
rebalanceRaw: RebalanceRaw
): RebalanceFieldInfo[] {
const stateFields = readPricePercentageWithResetRebalanceStateFromStrategy(
dex,
tokenADecimals,
tokenBDecimals,
rebalanceRaw
);
const fields = deserializePricePercentageWithResetRebalanceFromOnchainParams(price, rebalanceRaw);
return upsertManyRebalanceFieldInfos(fields, stateFields);
}