@marinade.finance/kamino-sdk
Version:
193 lines (171 loc) • 7.02 kB
text/typescript
import { PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js';
import { WhirlpoolStrategy } from '../kamino-client/accounts';
import { WHIRLPOOL_PROGRAM_ID } from '../whirpools-client/programId';
import { PROGRAM_ID as RAYDIUM_PROGRAM_ID } from '../raydium_client/programId';
import Decimal from 'decimal.js';
import { RebalanceType, RebalanceTypeKind, StrategyConfigOptionKind } from '../kamino-client/types';
import {
UpdateStrategyConfigAccounts,
UpdateStrategyConfigArgs,
updateStrategyConfig,
} from '../kamino-client/instructions';
import { SqrtPriceMath } from '@raydium-io/raydium-sdk';
import { token } from '@project-serum/anchor/dist/cjs/utils';
import { RebalanceFieldInfo, RebalanceFieldsDict } from './types';
import BN from 'bn.js';
export const DolarBasedMintingMethod = new Decimal(0);
export const ProportionalMintingMethod = new Decimal(1);
export const RebalanceParamOffset = new Decimal(256);
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export const Dex = ['ORCA', 'RAYDIUM', 'CREMA'] as const;
export type Dex = (typeof Dex)[number];
export function dexToNumber(dex: Dex): number {
for (let i = 0; i < Dex.length; i++) {
if (Dex[i] === dex) {
return i;
}
}
throw new Error(`Unknown DEX ${dex}`);
}
export function numberToDex(num: number): Dex {
const dex = Dex[num];
if (!dex) {
throw new Error(`Unknown DEX ${num}`);
}
return dex;
}
export function getDexProgramId(strategyState: WhirlpoolStrategy): PublicKey {
if (strategyState.strategyDex.toNumber() == dexToNumber('ORCA')) {
return WHIRLPOOL_PROGRAM_ID;
} else if (strategyState.strategyDex.toNumber() == dexToNumber('RAYDIUM')) {
return RAYDIUM_PROGRAM_ID;
} else {
throw Error(`Invalid DEX ${strategyState.strategyDex.toString()}`);
}
}
export function getStrategyConfigValue(value: Decimal): number[] {
let buffer = Buffer.alloc(128);
buffer.writeBigUInt64LE(BigInt(value.toString()));
return [...buffer];
}
export function buildStrategyRebalanceParams(
params: Array<Decimal>,
rebalance_type: RebalanceTypeKind,
tokenADecimals?: number,
tokenBDecimals?: number
): number[] {
let buffer = Buffer.alloc(128);
if (rebalance_type.kind == RebalanceType.Manual.kind) {
// Manual has no params
} else if (rebalance_type.kind == RebalanceType.PricePercentage.kind) {
buffer.writeUint16LE(params[0].toNumber());
buffer.writeUint16LE(params[1].toNumber(), 2);
} else if (rebalance_type.kind == RebalanceType.PricePercentageWithReset.kind) {
buffer.writeUint16LE(params[0].toNumber());
buffer.writeUint16LE(params[1].toNumber(), 2);
buffer.writeUint16LE(params[2].toNumber(), 4);
buffer.writeUint16LE(params[3].toNumber(), 6);
} else if (rebalance_type.kind == RebalanceType.Drift.kind) {
buffer.writeInt32LE(params[0].toNumber());
buffer.writeInt32LE(params[1].toNumber(), 4);
buffer.writeInt32LE(params[2].toNumber(), 8);
buffer.writeBigUint64LE(BigInt(params[3].toString()), 12);
buffer.writeUint8(params[4].toNumber(), 20);
} else if (rebalance_type.kind == RebalanceType.TakeProfit.kind) {
const lowerPrice = SqrtPriceMath.priceToSqrtPriceX64(params[0], tokenADecimals!, tokenBDecimals!);
const upperPrice = SqrtPriceMath.priceToSqrtPriceX64(params[1], tokenADecimals!, tokenBDecimals!);
writeBigUint128LE(buffer, BigInt(lowerPrice.toString()), 0);
writeBigUint128LE(buffer, BigInt(upperPrice.toString()), 16);
buffer.writeUint8(params[2].toNumber(), 32);
} else if (rebalance_type.kind == RebalanceType.PeriodicRebalance.kind) {
buffer.writeBigUint64LE(BigInt(params[0].toString()), 0);
buffer.writeUInt16LE(params[1].toNumber(), 8);
buffer.writeUInt16LE(params[2].toNumber(), 10);
} else if (rebalance_type.kind == RebalanceType.Expander.kind) {
buffer.writeUInt16LE(params[0].toNumber(), 0);
buffer.writeUInt16LE(params[1].toNumber(), 2);
buffer.writeUInt16LE(params[2].toNumber(), 4);
buffer.writeUInt16LE(params[3].toNumber(), 6);
buffer.writeUInt16LE(params[4].toNumber(), 8);
buffer.writeUInt16LE(params[5].toNumber(), 10);
buffer.writeUInt8(params[6].toNumber(), 12);
} else {
throw 'Rebalance type not valid ' + rebalance_type;
}
return [...buffer];
}
export function numberToRebalanceType(rebalance_type: number): RebalanceTypeKind {
if (rebalance_type == 0) {
return new RebalanceType.Manual();
} else if (rebalance_type == 1) {
return new RebalanceType.PricePercentage();
} else if (rebalance_type == 2) {
return new RebalanceType.PricePercentageWithReset();
} else if (rebalance_type == 3) {
return new RebalanceType.Drift();
} else if (rebalance_type == 4) {
return new RebalanceType.TakeProfit();
} else if (rebalance_type == 5) {
return new RebalanceType.PeriodicRebalance();
} else if (rebalance_type == 6) {
return new RebalanceType.Expander();
} else {
throw new Error(`Invalid rebalance type ${rebalance_type.toString()}`);
}
}
export async function getUpdateStrategyConfigIx(
signer: PublicKey,
globalConfig: PublicKey,
strategy: PublicKey,
mode: StrategyConfigOptionKind,
amount: Decimal,
newAccount: PublicKey = PublicKey.default
): Promise<TransactionInstruction> {
let args: UpdateStrategyConfigArgs = {
mode: mode.discriminator,
value: getStrategyConfigValue(amount),
};
let accounts: UpdateStrategyConfigAccounts = {
adminAuthority: signer,
newAccount,
globalConfig,
strategy,
systemProgram: SystemProgram.programId,
};
return updateStrategyConfig(args, accounts);
}
export function collToLamportsDecimal(amount: Decimal, decimals: number): Decimal {
let factor = new Decimal(10).pow(decimals);
return amount.mul(factor);
}
export function lamportsToNumberDecimal(amount: Decimal.Value, decimals: number): Decimal {
const factor = new Decimal(10).pow(decimals);
return new Decimal(amount).div(factor);
}
export function readBigUint128LE(buffer: Buffer, offset: number): bigint {
return buffer.readBigUint64LE(offset) + (buffer.readBigUint64LE(offset + 8) << BigInt(64));
}
function writeBigUint128LE(buffer: Buffer, value: bigint, offset: number) {
const lower_half = value & ((BigInt(1) << BigInt(64)) - BigInt(64));
const upper_half = value >> BigInt(64);
buffer.writeBigUint64LE(lower_half, offset);
buffer.writeBigUint64LE(upper_half, offset + 8);
}
export function rebalanceFieldsDictToInfo(rebalanceFields: RebalanceFieldsDict): RebalanceFieldInfo[] {
let rebalanceFieldsInfo: RebalanceFieldInfo[] = [];
for (let key in rebalanceFields) {
let value = rebalanceFields[key];
rebalanceFieldsInfo.push({
label: key,
type: 'number',
value: value,
enabled: false,
});
}
return rebalanceFieldsInfo;
}
export function isVaultInitialized(vault: PublicKey, decimals: BN): boolean {
return !vault.equals(PublicKey.default) && decimals.toNumber() > 0;
}