@marinade.finance/kamino-sdk
Version:
266 lines (234 loc) • 8.09 kB
text/typescript
import Decimal from 'decimal.js';
import { PositionRange, RebalanceFieldInfo, RebalanceFieldsDict } from '../utils/types';
import { Dex } from '../utils';
import { priceToTickIndex, sqrtPriceX64ToPrice, tickIndexToPrice } from '@orca-so/whirlpool-sdk';
import { SqrtPriceMath } from '@raydium-io/raydium-sdk';
import { RebalanceRaw } from '../kamino-client/types';
import { RebalanceTypeLabelName } from './consts';
import { upsertManyRebalanceFieldInfos } from './utils';
export const DEFAULT_TICKS_BELOW_MID = new Decimal(10);
export const DEFAULT_TICKS_ABOVE_MID = new Decimal(10);
export const DEFAULT_SECONDS_PER_TICK = new Decimal(60 * 60 * 24 * 3); // 3 days; todo: get a reasonable default from Matt
export const DEFAULT_DIRECTION = new Decimal(1);
export const DriftRebalanceTypeName = 'drift';
export function getDriftRebalanceFieldInfos(
dex: Dex,
tokenADecimals: number,
tokenBDecimals: number,
startMidTick: Decimal,
ticksBelowMid: Decimal,
ticksAboveMid: Decimal,
secondsPerTick: Decimal,
direction: Decimal,
enabled: boolean = true
): RebalanceFieldInfo[] {
let rebalanceType: RebalanceFieldInfo = {
label: RebalanceTypeLabelName,
type: 'string',
value: DriftRebalanceTypeName,
enabled,
};
let startMidTickRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'startMidTick',
type: 'number',
value: startMidTick,
enabled,
};
let ticksBelowMidRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'ticksBelowMid',
type: 'number',
value: ticksBelowMid,
enabled,
};
let ticksAboveMidRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'ticksAboveMid',
type: 'number',
value: ticksAboveMid,
enabled,
};
let secondsPerTickRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'secondsPerTick',
type: 'number',
value: secondsPerTick,
enabled,
};
let directionRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'direction',
type: 'number',
value: direction,
enabled,
};
let { lowerPrice, upperPrice } = getPositionRangeFromDriftParams(
dex,
tokenADecimals,
tokenBDecimals,
startMidTick,
ticksBelowMid,
ticksAboveMid
);
let lowerRangeRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'rangePriceLower',
type: 'number',
value: lowerPrice,
enabled: false,
};
let upperRangeRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'rangePriceUpper',
type: 'number',
value: upperPrice,
enabled: false,
};
return [
rebalanceType,
startMidTickRebalanceFieldInfo,
ticksBelowMidRebalanceFieldInfo,
ticksAboveMidRebalanceFieldInfo,
secondsPerTickRebalanceFieldInfo,
directionRebalanceFieldInfo,
lowerRangeRebalanceFieldInfo,
upperRangeRebalanceFieldInfo,
];
}
// todo(silviu): see if this is needed
export function getPositionRangeFromTakeProfitFieldInfos(fieldInfos: RebalanceFieldInfo[]) {}
export function getPositionRangeFromDriftParams(
dex: Dex,
tokenADecimals: number,
tokenBDecimals: number,
startMidTick: Decimal,
ticksBelowMid: Decimal,
ticksAboveMid: Decimal
): PositionRange {
let lowerTickIndex = startMidTick.sub(ticksBelowMid);
let upperTickIndex = startMidTick.add(ticksAboveMid);
if (dex == 'ORCA') {
let lowerPrice = tickIndexToPrice(lowerTickIndex.toNumber(), tokenADecimals, tokenBDecimals);
let upperPrice = tickIndexToPrice(upperTickIndex.toNumber(), tokenADecimals, tokenBDecimals);
return { lowerPrice, upperPrice };
} else if (dex == 'RAYDIUM') {
let lowerPrice = sqrtPriceX64ToPrice(
SqrtPriceMath.getSqrtPriceX64FromTick(lowerTickIndex.toNumber()),
tokenADecimals,
tokenBDecimals
);
let upperPrice = sqrtPriceX64ToPrice(
SqrtPriceMath.getSqrtPriceX64FromTick(upperTickIndex.toNumber()),
tokenADecimals,
tokenBDecimals
);
return { lowerPrice, upperPrice };
} else {
throw new Error(`Unknown DEX ${dex}`);
}
}
// todo(silviu): get sensible default params from Matt
export function getDefaultDriftRebalanceFieldInfos(
dex: Dex,
price: Decimal,
tokenADecimals: number,
tokenBDecimals: number
): RebalanceFieldInfo[] {
let currentTickIndex = priceToTickIndex(price, tokenADecimals, tokenBDecimals);
let startMidTick = new Decimal(currentTickIndex);
return getDriftRebalanceFieldInfos(
dex,
tokenADecimals,
tokenBDecimals,
startMidTick,
DEFAULT_TICKS_BELOW_MID,
DEFAULT_TICKS_ABOVE_MID,
DEFAULT_SECONDS_PER_TICK,
DEFAULT_DIRECTION
);
}
export function readDriftRebalanceParamsFromStrategy(rebalanceRaw: RebalanceRaw): RebalanceFieldsDict {
let paramsBuffer = Buffer.from(rebalanceRaw.params);
let params: RebalanceFieldsDict = {};
params['startMidTick'] = new Decimal(paramsBuffer.readInt32LE(0));
params['ticksBelowMid'] = new Decimal(paramsBuffer.readInt32LE(4));
params['ticksAboveMid'] = new Decimal(paramsBuffer.readInt32LE(8));
params['secondsPerTick'] = new Decimal(paramsBuffer.readBigUint64LE(12).toString());
params['direction'] = new Decimal(paramsBuffer.readUint8(20));
return params;
}
export function readRawDriftRebalanceStateFromStrategy(rebalanceRaw: RebalanceRaw) {
let stateBuffer = Buffer.from(rebalanceRaw.state);
let state: RebalanceFieldsDict = {};
state['step'] = new Decimal(stateBuffer.readUInt8(0));
state['lastDriftTimestamp'] = new Decimal(stateBuffer.readBigUint64LE(1).toString());
state['lastMidTick'] = new Decimal(stateBuffer.readInt32LE(9));
return state;
}
export function readDriftRebalanceStateFromStrategy(
dex: Dex,
tokenADecimals: number,
tokenBDecimals: number,
rebalanceRaw: RebalanceRaw
) {
let stateBuffer = Buffer.from(rebalanceRaw.state);
let paramsBuffer = Buffer.from(rebalanceRaw.params);
let lastMidTick = new Decimal(stateBuffer.readInt32LE(9));
let ticksBelowMid = new Decimal(paramsBuffer.readInt32LE(4));
let ticksAboveMid = new Decimal(paramsBuffer.readInt32LE(8));
let lowerTickIndex = lastMidTick.sub(ticksBelowMid);
let upperTickIndex = lastMidTick.add(ticksAboveMid);
let lowerPrice: Decimal, upperPrice: Decimal;
if (dex == 'ORCA') {
lowerPrice = tickIndexToPrice(lowerTickIndex.toNumber(), tokenADecimals, tokenBDecimals);
upperPrice = tickIndexToPrice(upperTickIndex.toNumber(), tokenADecimals, tokenBDecimals);
} else if (dex == 'RAYDIUM') {
lowerPrice = sqrtPriceX64ToPrice(
SqrtPriceMath.getSqrtPriceX64FromTick(lowerTickIndex.toNumber()),
tokenADecimals,
tokenBDecimals
);
upperPrice = sqrtPriceX64ToPrice(
SqrtPriceMath.getSqrtPriceX64FromTick(upperTickIndex.toNumber()),
tokenADecimals,
tokenBDecimals
);
} else {
throw new Error(`Unknown DEX ${dex}`);
}
let lowerRangeRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'rangePriceLower',
type: 'number',
value: lowerPrice,
enabled: false,
};
let upperRangeRebalanceFieldInfo: RebalanceFieldInfo = {
label: 'rangePriceUpper',
type: 'number',
value: upperPrice,
enabled: false,
};
return [lowerRangeRebalanceFieldInfo, upperRangeRebalanceFieldInfo];
}
export function deserializeDriftRebalanceFromOnchainParams(
dex: Dex,
tokenADecimals: number,
tokenBDecimals: number,
rebalanceRaw: RebalanceRaw
): RebalanceFieldInfo[] {
let params = readDriftRebalanceParamsFromStrategy(rebalanceRaw);
return getDriftRebalanceFieldInfos(
dex,
tokenADecimals,
tokenBDecimals,
params['startMidTick'],
params['ticksBelowMid'],
params['ticksAboveMid'],
params['secondsPerTick'],
params['direction']
);
}
export function deserializeDriftRebalanceWithStateOverride(
dex: Dex,
tokenADecimals: number,
tokenBDecimals: number,
rebalanceRaw: RebalanceRaw
): RebalanceFieldInfo[] {
const stateFields = readDriftRebalanceStateFromStrategy(dex, tokenADecimals, tokenBDecimals, rebalanceRaw);
let fields = deserializeDriftRebalanceFromOnchainParams(dex, tokenADecimals, tokenBDecimals, rebalanceRaw);
return upsertManyRebalanceFieldInfos(fields, stateFields);
}