@kamino-finance/kliquidity-sdk
Version:
Typescript SDK for interacting with the Kamino Liquidity (kliquidity) protocol
869 lines • 312 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Kamino = void 0;
const hubble_config_1 = require("@hubbleprotocol/hubble-config");
const kit_1 = require("@solana/kit");
const bs58_1 = __importDefault(require("bs58"));
const accounts_1 = require("./@codegen/kliquidity/accounts");
const decimal_js_1 = __importDefault(require("decimal.js"));
const instructions_1 = require("./@codegen/whirlpools/instructions");
const accounts_2 = require("./@codegen/whirlpools/accounts");
const whirlpools_core_1 = require("@orca-so/whirlpools-core");
const models_1 = require("./models");
const scope_sdk_1 = require("@kamino-finance/scope-sdk");
const utils_1 = require("./utils");
const instructions_2 = require("./@codegen/kliquidity/instructions");
const bn_js_1 = __importDefault(require("bn.js"));
const StrategyStatus_1 = require("./@codegen/kliquidity/types/StrategyStatus");
const constants_1 = require("./constants");
const types_1 = require("./@codegen/kliquidity/types");
const accounts_3 = require("./@codegen/raydium/accounts");
const services_1 = require("./services");
const StrategyConfigOption_1 = require("./@codegen/kliquidity/types/StrategyConfigOption");
const DefaultStrategyConfig_1 = require("./constants/DefaultStrategyConfig");
const pubkeys_1 = require("./constants/pubkeys");
const CreationParameters_1 = require("./utils/CreationParameters");
const deposit_method_1 = require("./constants/deposit_method");
const JupService_1 = require("./services/JupService");
const PoolSimulationService_1 = require("./services/PoolSimulationService");
const RebalanceType_1 = require("./@codegen/kliquidity/types/RebalanceType");
const transactions_1 = require("./utils/transactions");
const rebalance_methods_1 = require("./rebalance_methods");
const priceReferenceTypes_1 = require("./utils/priceReferenceTypes");
const utils_2 = require("./rebalance_methods/utils");
const consts_1 = require("./rebalance_methods/consts");
const autodriftRebalance_1 = require("./rebalance_methods/autodriftRebalance");
const whirlpools_1 = require("./utils/whirlpools");
const MeteoraService_1 = require("./services/MeteoraService");
const meteora_1 = require("./utils/meteora");
const accounts_4 = require("./@codegen/meteora/accounts");
const instructions_3 = require("./@codegen/meteora/instructions");
const lib_1 = require("@raydium-io/raydium-sdk-v2/lib");
const token_2022_1 = require("@solana-program/token-2022");
const token_1 = require("@solana-program/token");
const system_1 = require("@solana-program/system");
const sysvars_1 = require("@solana/sysvars");
const compat_1 = require("@solana/compat");
const address_lookup_table_1 = require("@solana-program/address-lookup-table");
const lookupTable_1 = require("./utils/lookupTable");
const compat_2 = require("./utils/compat");
const addressEncoder = (0, kit_1.getAddressEncoder)();
class Kamino {
_cluster;
_rpc;
_legacyConnection;
_config;
_globalConfig;
_scope;
_kliquidityProgramId;
_orcaService;
_raydiumService;
_meteoraService;
_jupBaseAPI = JupService_1.DEFAULT_JUP_API_ENDPOINT;
/**
* Create a new instance of the Kamino SDK class.
* @param cluster Name of the Solana cluster
* @param rpc Connection to the Solana cluster
* @param legacyConnection Connection to the Solana cluster
* @param globalConfig override kamino global config
* @param programId override kamino program id
* @param whirlpoolProgramId override whirlpool program id
* @param raydiumProgramId override raydium program id
* @param meteoraProgramId
* @param jupBaseAPI
*/
constructor(cluster, rpc, legacyConnection, globalConfig, programId, whirlpoolProgramId, raydiumProgramId, meteoraProgramId, jupBaseAPI) {
this._cluster = cluster;
this._rpc = rpc;
this._legacyConnection = legacyConnection;
this._config = (0, hubble_config_1.getConfigByCluster)(cluster);
if (programId && programId === pubkeys_1.STAGING_KAMINO_PROGRAM_ID) {
this._kliquidityProgramId = programId;
this._globalConfig = pubkeys_1.STAGING_GLOBAL_CONFIG;
}
else {
this._kliquidityProgramId = programId ? programId : (0, compat_1.fromLegacyPublicKey)(this._config.kamino.programId);
this._globalConfig = globalConfig ? globalConfig : (0, compat_1.fromLegacyPublicKey)(this._config.kamino.globalConfig);
}
this._scope = new scope_sdk_1.Scope(cluster, rpc);
this._orcaService = new services_1.OrcaService(rpc, legacyConnection, whirlpoolProgramId);
this._raydiumService = new services_1.RaydiumService(rpc, legacyConnection, raydiumProgramId);
this._meteoraService = new MeteoraService_1.MeteoraService(rpc, meteoraProgramId);
if (jupBaseAPI) {
this._jupBaseAPI = jupBaseAPI;
}
}
getConnection = () => this._rpc;
getLegacyConnection = () => this._legacyConnection;
getProgramID = () => this._kliquidityProgramId;
setGlobalConfig = (globalConfig) => {
this._globalConfig = globalConfig;
};
getGlobalConfig = () => this._globalConfig;
getDepositableTokens = async () => {
const collateralInfos = await this.getCollateralInfos();
return collateralInfos.filter((x) => x.mint !== pubkeys_1.DEFAULT_PUBLIC_KEY);
};
getCollateralInfos = async () => {
const config = await this.getGlobalConfigState(this._globalConfig);
if (!config) {
throw Error(`Could not fetch globalConfig with pubkey ${this.getGlobalConfig().toString()}`);
}
return this.getCollateralInfo(config.tokenInfos);
};
getDisabledTokensPrices = async (collateralInfos) => {
const collInfos = collateralInfos ? collateralInfos : await this.getCollateralInfos();
const disabledTokens = collInfos.filter((x) => x.disabled && x.mint !== pubkeys_1.DEFAULT_PUBLIC_KEY);
return JupService_1.JupService.getDollarPrices(disabledTokens.map((x) => x.mint), this._jupBaseAPI);
};
getSupportedDexes = () => ['ORCA', 'RAYDIUM', 'METEORA'];
// todo: see if we can read this dynamically
getFeeTiersForDex = (dex) => {
if (dex === 'ORCA') {
return [new decimal_js_1.default(0.0001), new decimal_js_1.default(0.0005), new decimal_js_1.default(0.003), new decimal_js_1.default(0.01)];
}
else if (dex === 'RAYDIUM') {
return [new decimal_js_1.default(0.0001), new decimal_js_1.default(0.0005), new decimal_js_1.default(0.0025), new decimal_js_1.default(0.01)];
}
else if (dex === 'METEORA') {
return [new decimal_js_1.default(0.0001), new decimal_js_1.default(0.0005), new decimal_js_1.default(0.0025), new decimal_js_1.default(0.01)];
}
else {
throw new Error(`Dex ${dex} is not supported`);
}
};
getRebalanceMethods = () => {
return [
CreationParameters_1.ManualRebalanceMethod,
CreationParameters_1.PricePercentageRebalanceMethod,
CreationParameters_1.PricePercentageWithResetRangeRebalanceMethod,
CreationParameters_1.DriftRebalanceMethod,
CreationParameters_1.TakeProfitMethod,
CreationParameters_1.PeriodicRebalanceMethod,
CreationParameters_1.ExpanderMethod,
CreationParameters_1.AutodriftMethod,
];
};
getEnabledRebalanceMethods = () => {
return this.getRebalanceMethods().filter((x) => x.enabled);
};
getPriceReferenceTypes = () => {
return [priceReferenceTypes_1.PoolPriceReferenceType, priceReferenceTypes_1.TwapPriceReferenceType];
};
getDefaultRebalanceMethod = () => CreationParameters_1.PricePercentageRebalanceMethod;
getDefaultParametersForNewVault = async () => {
const dex = CreationParameters_1.DefaultDex;
const tokenMintA = CreationParameters_1.DefaultMintTokenA;
const tokenMintB = CreationParameters_1.DefaultMintTokenB;
const rebalanceMethod = this.getDefaultRebalanceMethod();
const feeTier = CreationParameters_1.DefaultFeeTierOrca;
const tickSpacing = CreationParameters_1.DefaultTickSpacing;
const rebalancingParameters = await this.getDefaultRebalanceFields(dex, tokenMintA, tokenMintB, tickSpacing, rebalanceMethod);
const defaultParameters = {
dex,
tokenMintA,
tokenMintB,
feeTier,
rebalancingParameters,
};
return defaultParameters;
};
/**
* Retunrs what type of rebalance method the fields represent
*/
getRebalanceTypeFromRebalanceFields = (rebalanceFields) => {
return (0, utils_2.getRebalanceTypeFromRebalanceFields)(rebalanceFields);
};
/**
* Retunrs the rebalance method the fields represent with more details (description, enabled, etc)
*/
getRebalanceMethodFromRebalanceFields = (rebalanceFields) => {
return (0, utils_2.getRebalanceMethodFromRebalanceFields)(rebalanceFields);
};
getReferencePriceTypeForStrategy = async (strategy) => {
const strategyWithAddress = await this.getStrategyStateIfNotFetched(strategy);
return (0, utils_1.numberToReferencePriceType)(strategyWithAddress.strategy.rebalanceRaw.referencePriceType);
};
getFieldsForRebalanceMethod = (rebalanceMethod, dex, fieldOverrides, tokenAMint, tokenBMint, tickSpacing, poolPrice) => {
switch (rebalanceMethod) {
case CreationParameters_1.ManualRebalanceMethod:
return this.getFieldsForManualRebalanceMethod(dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice);
case CreationParameters_1.PricePercentageRebalanceMethod:
return this.getFieldsForPricePercentageMethod(dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice);
case CreationParameters_1.PricePercentageWithResetRangeRebalanceMethod:
return this.getFieldsForPricePercentageWithResetMethod(dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice);
case CreationParameters_1.DriftRebalanceMethod:
return this.getFieldsForDriftRebalanceMethod(dex, fieldOverrides, tickSpacing, tokenAMint, tokenBMint, poolPrice);
case CreationParameters_1.TakeProfitMethod:
return this.getFieldsForTakeProfitRebalanceMethod(dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice);
case CreationParameters_1.PeriodicRebalanceMethod:
return this.getFieldsForPeriodicRebalanceMethod(dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice);
case CreationParameters_1.ExpanderMethod:
return this.getFieldsForExpanderRebalanceMethod(dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice);
case CreationParameters_1.AutodriftMethod:
return this.getFieldsForAutodriftRebalanceMethod(dex, fieldOverrides, tokenAMint, tokenBMint, tickSpacing, poolPrice);
default:
throw new Error(`Rebalance method ${rebalanceMethod} is not supported`);
}
};
getFieldsForManualRebalanceMethod = async (dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice) => {
const price = poolPrice ? poolPrice : new decimal_js_1.default(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = (0, rebalance_methods_1.getDefaultManualRebalanceFieldInfos)(price);
let lowerPrice = defaultFields.find((x) => x.label === 'rangePriceLower').value;
const lowerPriceInput = fieldOverrides.find((x) => x.label === 'rangePriceLower');
if (lowerPriceInput) {
lowerPrice = lowerPriceInput.value;
}
let upperPrice = defaultFields.find((x) => x.label === 'rangePriceUpper').value;
const upperPriceInput = fieldOverrides.find((x) => x.label === 'rangePriceUpper');
if (upperPriceInput) {
upperPrice = upperPriceInput.value;
}
return (0, rebalance_methods_1.getManualRebalanceFieldInfos)(new decimal_js_1.default(lowerPrice), new decimal_js_1.default(upperPrice));
};
getFieldsForPricePercentageMethod = async (dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice) => {
const price = poolPrice ? poolPrice : new decimal_js_1.default(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = (0, rebalance_methods_1.getDefaultPricePercentageRebalanceFieldInfos)(price);
let lowerPriceDifferenceBPS = defaultFields.find((x) => x.label === 'lowerRangeBps').value;
const lowerPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label === 'lowerRangeBps');
if (lowerPriceDifferenceBPSInput) {
lowerPriceDifferenceBPS = lowerPriceDifferenceBPSInput.value;
}
let upperPriceDifferenceBPS = defaultFields.find((x) => x.label === 'upperRangeBps').value;
const upperPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label === 'upperRangeBps');
if (upperPriceDifferenceBPSInput) {
upperPriceDifferenceBPS = upperPriceDifferenceBPSInput.value;
}
return (0, rebalance_methods_1.getPricePercentageRebalanceFieldInfos)(price, new decimal_js_1.default(lowerPriceDifferenceBPS), new decimal_js_1.default(upperPriceDifferenceBPS));
};
getFieldsForPricePercentageWithResetMethod = async (dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice) => {
const price = poolPrice ? poolPrice : new decimal_js_1.default(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = (0, rebalance_methods_1.getDefaultPricePercentageWithResetRebalanceFieldInfos)(price);
let lowerPriceDifferenceBPS = defaultFields.find((x) => x.label === 'lowerRangeBps').value;
const lowerPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label === 'lowerRangeBps');
if (lowerPriceDifferenceBPSInput) {
lowerPriceDifferenceBPS = lowerPriceDifferenceBPSInput.value;
}
let upperPriceDifferenceBPS = defaultFields.find((x) => x.label === 'upperRangeBps').value;
const upperPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label === 'upperRangeBps');
if (upperPriceDifferenceBPSInput) {
upperPriceDifferenceBPS = upperPriceDifferenceBPSInput.value;
}
let lowerResetPriceDifferenceBPS = defaultFields.find((x) => x.label === 'resetLowerRangeBps').value;
const lowerResetPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label === 'resetLowerRangeBps');
if (lowerResetPriceDifferenceBPSInput) {
lowerResetPriceDifferenceBPS = lowerResetPriceDifferenceBPSInput.value;
}
let upperResetPriceDifferenceBPS = defaultFields.find((x) => x.label === 'resetUpperRangeBps').value;
const upperResetPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label === 'resetUpperRangeBps');
if (upperResetPriceDifferenceBPSInput) {
upperResetPriceDifferenceBPS = upperResetPriceDifferenceBPSInput.value;
}
return (0, rebalance_methods_1.getPricePercentageWithResetRebalanceFieldInfos)(price, new decimal_js_1.default(lowerPriceDifferenceBPS), new decimal_js_1.default(upperPriceDifferenceBPS), new decimal_js_1.default(lowerResetPriceDifferenceBPS), new decimal_js_1.default(upperResetPriceDifferenceBPS));
};
getFieldsForDriftRebalanceMethod = async (dex, fieldOverrides, tickSpacing, tokenAMint, tokenBMint, poolPrice) => {
const tokenADecimals = await (0, utils_1.getMintDecimals)(this._rpc, tokenAMint);
const tokenBDecimals = await (0, utils_1.getMintDecimals)(this._rpc, tokenBMint);
const price = poolPrice ? poolPrice : new decimal_js_1.default(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = (0, rebalance_methods_1.getDefaultDriftRebalanceFieldInfos)(dex, tickSpacing, price, tokenADecimals, tokenBDecimals);
let startMidTick = defaultFields.find((x) => x.label === 'startMidTick').value;
const startMidTickInput = fieldOverrides.find((x) => x.label === 'startMidTick');
if (startMidTickInput) {
startMidTick = startMidTickInput.value;
}
let ticksBelowMid = defaultFields.find((x) => x.label === 'ticksBelowMid').value;
const ticksBelowMidInput = fieldOverrides.find((x) => x.label === 'ticksBelowMid');
if (ticksBelowMidInput) {
ticksBelowMid = ticksBelowMidInput.value;
}
let ticksAboveMid = defaultFields.find((x) => x.label === 'ticksAboveMid').value;
const ticksAboveMidInput = fieldOverrides.find((x) => x.label === 'ticksAboveMid');
if (ticksAboveMidInput) {
ticksAboveMid = ticksAboveMidInput.value;
}
let secondsPerTick = defaultFields.find((x) => x.label === 'secondsPerTick').value;
const secondsPerTickInput = fieldOverrides.find((x) => x.label === 'secondsPerTick');
if (secondsPerTickInput) {
secondsPerTick = secondsPerTickInput.value;
}
let direction = defaultFields.find((x) => x.label === 'direction').value;
const directionInput = fieldOverrides.find((x) => x.label === 'direction');
if (directionInput) {
direction = directionInput.value;
}
const fieldInfos = (0, rebalance_methods_1.getDriftRebalanceFieldInfos)(dex, tokenADecimals, tokenBDecimals, tickSpacing, new decimal_js_1.default(startMidTick), new decimal_js_1.default(ticksBelowMid), new decimal_js_1.default(ticksAboveMid), new decimal_js_1.default(secondsPerTick), new decimal_js_1.default(direction));
return fieldInfos;
};
getFieldsForTakeProfitRebalanceMethod = async (dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice) => {
const price = poolPrice ? poolPrice : new decimal_js_1.default(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = (0, rebalance_methods_1.getDefaultTakeProfitRebalanceFieldsInfos)(price);
let lowerRangePrice = defaultFields.find((x) => x.label === 'rangePriceLower').value;
const lowerRangePriceInput = fieldOverrides.find((x) => x.label === 'rangePriceLower');
if (lowerRangePriceInput) {
lowerRangePrice = lowerRangePriceInput.value;
}
let upperRangePrice = defaultFields.find((x) => x.label === 'rangePriceUpper').value;
const upperRangePriceInput = fieldOverrides.find((x) => x.label === 'rangePriceUpper');
if (upperRangePriceInput) {
upperRangePrice = upperRangePriceInput.value;
}
let destinationToken = defaultFields.find((x) => x.label === 'destinationToken').value;
const destinationTokenInput = fieldOverrides.find((x) => x.label === 'destinationToken');
if (destinationTokenInput) {
destinationToken = destinationTokenInput.value;
}
return (0, rebalance_methods_1.getTakeProfitRebalanceFieldsInfos)(new decimal_js_1.default(lowerRangePrice), new decimal_js_1.default(upperRangePrice), new decimal_js_1.default(destinationToken), true);
};
getFieldsForPeriodicRebalanceMethod = async (dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice) => {
const price = poolPrice ? poolPrice : new decimal_js_1.default(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = (0, rebalance_methods_1.getDefaultPeriodicRebalanceFieldInfos)(price);
let period = new decimal_js_1.default(defaultFields.find((x) => x.label === 'period').value);
const periodInput = fieldOverrides.find((x) => x.label === 'period');
if (periodInput) {
period = new decimal_js_1.default(periodInput.value);
}
let lowerPriceDifferenceBPS = defaultFields.find((x) => x.label === 'lowerRangeBps').value;
const lowerPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label === 'lowerRangeBps');
if (lowerPriceDifferenceBPSInput) {
lowerPriceDifferenceBPS = lowerPriceDifferenceBPSInput.value;
}
let upperPriceDifferenceBPS = defaultFields.find((x) => x.label === 'upperRangeBps').value;
const upperPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label === 'upperRangeBps');
if (upperPriceDifferenceBPSInput) {
upperPriceDifferenceBPS = upperPriceDifferenceBPSInput.value;
}
return (0, rebalance_methods_1.getPeriodicRebalanceRebalanceFieldInfos)(price, period, new decimal_js_1.default(lowerPriceDifferenceBPS), new decimal_js_1.default(upperPriceDifferenceBPS));
};
getFieldsForExpanderRebalanceMethod = async (dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice) => {
const price = poolPrice ? poolPrice : new decimal_js_1.default(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = (0, rebalance_methods_1.getDefaultExpanderRebalanceFieldInfos)(price);
let lowerPriceDifferenceBPS = defaultFields.find((x) => x.label === 'lowerRangeBps').value;
const lowerPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label === 'lowerRangeBps');
if (lowerPriceDifferenceBPSInput) {
lowerPriceDifferenceBPS = lowerPriceDifferenceBPSInput.value;
}
let upperPriceDifferenceBPS = defaultFields.find((x) => x.label === 'upperRangeBps').value;
const upperPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label === 'upperRangeBps');
if (upperPriceDifferenceBPSInput) {
upperPriceDifferenceBPS = upperPriceDifferenceBPSInput.value;
}
let lowerResetPriceDifferenceBPS = defaultFields.find((x) => x.label === 'resetLowerRangeBps').value;
const lowerResetPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label === 'resetLowerRangeBps');
if (lowerResetPriceDifferenceBPSInput) {
lowerResetPriceDifferenceBPS = lowerResetPriceDifferenceBPSInput.value;
}
let upperResetPriceDifferenceBPS = defaultFields.find((x) => x.label === 'resetUpperRangeBps').value;
const upperResetPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label === 'resetUpperRangeBps');
if (upperResetPriceDifferenceBPSInput) {
upperResetPriceDifferenceBPS = upperResetPriceDifferenceBPSInput.value;
}
let expansionBPS = defaultFields.find((x) => x.label === 'expansionBps').value;
const expansionBPSInput = fieldOverrides.find((x) => x.label === 'expansionBps');
if (expansionBPSInput) {
expansionBPS = expansionBPSInput.value;
}
let maxNumberOfExpansions = defaultFields.find((x) => x.label === 'maxNumberOfExpansions').value;
const maxNumberOfExpansionsInput = fieldOverrides.find((x) => x.label === 'maxNumberOfExpansions');
if (maxNumberOfExpansionsInput) {
maxNumberOfExpansions = maxNumberOfExpansionsInput.value;
}
let swapUnevenAllowed = defaultFields.find((x) => x.label === 'swapUnevenAllowed').value;
const swapUnevenAllowedInput = fieldOverrides.find((x) => x.label === 'swapUnevenAllowed');
if (swapUnevenAllowedInput) {
swapUnevenAllowed = swapUnevenAllowedInput.value;
}
return (0, rebalance_methods_1.getExpanderRebalanceFieldInfos)(price, new decimal_js_1.default(lowerPriceDifferenceBPS), new decimal_js_1.default(upperPriceDifferenceBPS), new decimal_js_1.default(lowerResetPriceDifferenceBPS), new decimal_js_1.default(upperResetPriceDifferenceBPS), new decimal_js_1.default(expansionBPS), new decimal_js_1.default(maxNumberOfExpansions), new decimal_js_1.default(swapUnevenAllowed));
};
getFieldsForAutodriftRebalanceMethod = async (dex, fieldOverrides, tokenAMint, tokenBMint, tickSpacing, poolPrice) => {
const tokenADecimals = await (0, utils_1.getMintDecimals)(this._rpc, tokenAMint);
const tokenBDecimals = await (0, utils_1.getMintDecimals)(this._rpc, tokenBMint);
const price = poolPrice ? poolPrice : new decimal_js_1.default(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
// TODO: maybe we will need to get real staking price instead of pool price for this to be accurate.
const defaultFields = (0, autodriftRebalance_1.getDefaultAutodriftRebalanceFieldInfos)(dex, price, tokenADecimals, tokenBDecimals, tickSpacing);
const lastMidTick = defaultFields.find((x) => x.label === 'lastMidTick').value;
let initDriftTicksPerEpoch = defaultFields.find((x) => x.label === 'initDriftTicksPerEpoch').value;
const initDriftTicksPerEpochInput = fieldOverrides.find((x) => x.label === 'initDriftTicksPerEpoch');
if (initDriftTicksPerEpochInput) {
initDriftTicksPerEpoch = initDriftTicksPerEpochInput.value;
}
let ticksBelowMid = defaultFields.find((x) => x.label === 'ticksBelowMid').value;
const ticksBelowMidInput = fieldOverrides.find((x) => x.label === 'ticksBelowMid');
if (ticksBelowMidInput) {
ticksBelowMid = ticksBelowMidInput.value;
}
let ticksAboveMid = defaultFields.find((x) => x.label === 'ticksAboveMid').value;
const ticksAboveMidInput = fieldOverrides.find((x) => x.label === 'ticksAboveMid');
if (ticksAboveMidInput) {
ticksAboveMid = ticksAboveMidInput.value;
}
let frontrunMultiplierBps = defaultFields.find((x) => x.label === 'frontrunMultiplierBps').value;
const frontrunMultiplierBpsInput = fieldOverrides.find((x) => x.label === 'frontrunMultiplierBps');
if (frontrunMultiplierBpsInput) {
frontrunMultiplierBps = frontrunMultiplierBpsInput.value;
}
let stakingRateASource = defaultFields.find((x) => x.label === 'stakingRateASource').value;
const stakingRateASourceInput = fieldOverrides.find((x) => x.label === 'stakingRateASource');
if (stakingRateASourceInput) {
stakingRateASource = stakingRateASourceInput.value;
}
let stakingRateBSource = defaultFields.find((x) => x.label === 'stakingRateBSource').value;
const stakingRateBSourceInput = fieldOverrides.find((x) => x.label === 'stakingRateBSource');
if (stakingRateBSourceInput) {
stakingRateBSource = stakingRateBSourceInput.value;
}
let initialDriftDirection = defaultFields.find((x) => x.label === 'initialDriftDirection').value;
const initialDriftDirectionInput = fieldOverrides.find((x) => x.label === 'initialDriftDirection');
if (initialDriftDirectionInput) {
initialDriftDirection = initialDriftDirectionInput.value;
}
const fieldInfos = (0, autodriftRebalance_1.getAutodriftRebalanceFieldInfos)(dex, tokenADecimals, tokenBDecimals, tickSpacing, new decimal_js_1.default(lastMidTick), new decimal_js_1.default(initDriftTicksPerEpoch), new decimal_js_1.default(ticksBelowMid), new decimal_js_1.default(ticksAboveMid), new decimal_js_1.default(frontrunMultiplierBps), new decimal_js_1.default(stakingRateASource), new decimal_js_1.default(stakingRateBSource), new decimal_js_1.default(initialDriftDirection));
return fieldInfos;
};
/**
* Get the price for a given pair of tokens in a given dex; The price comes from any pool having those tokens, not a specific one, so the price may not be exactly the same between different pools with the same tokens. For a specific pool price use getPoolPrice
* @param strategy
* @param amountA
*/
getPriceForPair = async (dex, poolTokenA, poolTokenB) => {
if (dex === 'ORCA') {
const pools = await this.getOrcaPoolsForTokens(poolTokenA, poolTokenB);
if (pools.length === 0) {
throw new Error(`No pool found for ${poolTokenA.toString()} and ${poolTokenB.toString()}`);
}
return Number(pools[0].price);
}
else if (dex === 'RAYDIUM') {
const pools = await this.getRaydiumPoolsForTokens(poolTokenA, poolTokenB);
if (pools.length === 0) {
throw new Error(`No pool found for ${poolTokenA.toString()} and ${poolTokenB.toString()}`);
}
return pools[0].price;
}
else if (dex === 'METEORA') {
const pools = await this.getMeteoraPoolsForTokens(poolTokenA, poolTokenB);
if (pools.length === 0) {
throw new Error(`No pool found for ${poolTokenA.toString()} and ${poolTokenB.toString()}`);
}
const decimalsX = await (0, utils_1.getMintDecimals)(this._rpc, poolTokenA);
const decimalsY = await (0, utils_1.getMintDecimals)(this._rpc, poolTokenB);
return (0, meteora_1.getPriceOfBinByBinIdWithDecimals)(pools[0].pool.activeId, pools[0].pool.binStep, decimalsX, decimalsY).toNumber();
}
else {
throw new Error(`Dex ${dex} is not supported`);
}
};
getDefaultRebalanceFields = async (dex, poolTokenA, poolTokenB, tickSpacing, rebalanceMethod) => {
const price = new decimal_js_1.default(await this.getPriceForPair(dex, poolTokenA, poolTokenB));
const tokenADecimals = await (0, utils_1.getMintDecimals)(this._rpc, poolTokenA);
const tokenBDecimals = await (0, utils_1.getMintDecimals)(this._rpc, poolTokenB);
switch (rebalanceMethod) {
case CreationParameters_1.ManualRebalanceMethod:
return (0, rebalance_methods_1.getDefaultManualRebalanceFieldInfos)(price);
case CreationParameters_1.PricePercentageRebalanceMethod:
return (0, rebalance_methods_1.getDefaultPricePercentageRebalanceFieldInfos)(price);
case CreationParameters_1.PricePercentageWithResetRangeRebalanceMethod:
return (0, rebalance_methods_1.getDefaultPricePercentageWithResetRebalanceFieldInfos)(price);
case CreationParameters_1.DriftRebalanceMethod:
return (0, rebalance_methods_1.getDefaultDriftRebalanceFieldInfos)(dex, tickSpacing, price, tokenADecimals, tokenBDecimals);
case CreationParameters_1.TakeProfitMethod:
return (0, rebalance_methods_1.getDefaultTakeProfitRebalanceFieldsInfos)(price);
case CreationParameters_1.PeriodicRebalanceMethod:
return (0, rebalance_methods_1.getDefaultPeriodicRebalanceFieldInfos)(price);
case CreationParameters_1.ExpanderMethod:
return (0, rebalance_methods_1.getDefaultExpanderRebalanceFieldInfos)(price);
case CreationParameters_1.AutodriftMethod:
return (0, autodriftRebalance_1.getDefaultAutodriftRebalanceFieldInfos)(dex, price, tokenADecimals, tokenBDecimals, tickSpacing);
default:
throw new Error(`Rebalance method ${rebalanceMethod} is not supported`);
}
};
/**
* Return a the pubkey of the pool in a given dex, for given mints and fee tier; if that pool doesn't exist, return default pubkey
*/
getPoolInitializedForDexPairTier = async (dex, poolTokenA, poolTokenB, feeBPS) => {
if (dex === 'ORCA') {
let pool = pubkeys_1.DEFAULT_PUBLIC_KEY;
const orcaPools = await this.getOrcaPoolsForTokens(poolTokenA, poolTokenB);
orcaPools.forEach((element) => {
if (element.feeRate * CreationParameters_1.FullBPS === feeBPS.toNumber()) {
pool = (0, kit_1.address)(element.address);
}
});
return pool;
}
else if (dex === 'RAYDIUM') {
const pools = [];
const raydiumPools = await this.getRaydiumPoolsForTokens(poolTokenA, poolTokenB);
raydiumPools.forEach((element) => {
if (new decimal_js_1.default(element.ammConfig.tradeFeeRate).div(CreationParameters_1.FullBPS).div(CreationParameters_1.FullPercentage).equals(feeBPS.div(CreationParameters_1.FullBPS))) {
pools.push(element);
}
});
if (pools.length === 0) {
return pubkeys_1.DEFAULT_PUBLIC_KEY;
}
let pool = pubkeys_1.DEFAULT_PUBLIC_KEY;
let tickSpacing = Number.MAX_VALUE;
pools.forEach((element) => {
if (element.ammConfig.tickSpacing < tickSpacing) {
pool = (0, kit_1.address)(element.id);
tickSpacing = element.ammConfig.tickSpacing;
}
});
return pool;
}
else if (dex === 'METEORA') {
let pool = pubkeys_1.DEFAULT_PUBLIC_KEY;
const pools = await this.getMeteoraPoolsForTokens(poolTokenA, poolTokenB);
pools.forEach((element) => {
const feeRateBps = element.pool.parameters.baseFactor * element.pool.binStep;
if (feeRateBps === feeBPS.toNumber()) {
pool = (0, kit_1.address)(element.key);
}
});
return pool;
}
else {
throw new Error(`Dex ${dex} is not supported`);
}
};
/**
* Return generic information for all pools in a given dex, for given mints and fee tier
*/
async getExistentPoolsForPair(dex, tokenMintA, tokenMintB) {
if (dex === 'ORCA') {
const pools = await this.getOrcaPoolsForTokens(tokenMintA, tokenMintB);
const genericPoolInfos = await Promise.all(pools.map(async (pool) => {
const positionsCount = new decimal_js_1.default(await this.getPositionsCountForPool(dex, (0, kit_1.address)(pool.address)));
// read price from pool
const poolData = await this._orcaService.getOrcaWhirlpool((0, kit_1.address)(pool.address));
if (!poolData) {
throw new Error(`Pool ${pool.address} not found`);
}
const poolInfo = {
dex,
address: (0, kit_1.address)(pool.address),
price: new decimal_js_1.default((0, whirlpools_core_1.sqrtPriceToPrice)(BigInt(poolData.sqrtPrice), pool.tokenA.decimals, pool.tokenB.decimals)),
tokenMintA: (0, kit_1.address)(pool.tokenMintA),
tokenMintB: (0, kit_1.address)(pool.tokenMintB),
tvl: pool.tvlUsdc ? new decimal_js_1.default(pool.tvlUsdc) : undefined,
feeRate: new decimal_js_1.default(pool.feeRate).mul(CreationParameters_1.FullBPS),
volumeOnLast7d: pool.stats['7d'] ? new decimal_js_1.default(pool.stats['7d'].volume) : undefined,
tickSpacing: new decimal_js_1.default(pool.tickSpacing),
positions: positionsCount,
};
return poolInfo;
}));
return genericPoolInfos;
}
else if (dex === 'RAYDIUM') {
const pools = await this.getRaydiumPoolsForTokens(tokenMintA, tokenMintB);
const genericPoolInfos = await Promise.all(pools.map(async (pool) => {
const positionsCount = new decimal_js_1.default(await this.getPositionsCountForPool(dex, (0, kit_1.address)(pool.id)));
const poolInfo = {
dex,
address: (0, kit_1.address)(pool.id),
price: new decimal_js_1.default(pool.price),
tokenMintA: (0, kit_1.address)(pool.mintA),
tokenMintB: (0, kit_1.address)(pool.mintB),
tvl: new decimal_js_1.default(pool.tvl),
feeRate: new decimal_js_1.default(pool.ammConfig.tradeFeeRate).div(new decimal_js_1.default(CreationParameters_1.FullPercentage)),
volumeOnLast7d: new decimal_js_1.default(pool.week.volume),
tickSpacing: new decimal_js_1.default(pool.ammConfig.tickSpacing),
positions: positionsCount,
};
return poolInfo;
}));
return genericPoolInfos;
}
else if (dex === 'METEORA') {
const pools = await this.getMeteoraPoolsForTokens(tokenMintA, tokenMintB);
const genericPoolInfos = await Promise.all(pools.map(async (pool) => {
const positionsCount = new decimal_js_1.default(await this.getPositionsCountForPool(dex, pool.key));
const decimalsX = await (0, utils_1.getMintDecimals)(this._rpc, pool.pool.tokenXMint);
const decimalsY = await (0, utils_1.getMintDecimals)(this._rpc, pool.pool.tokenYMint);
const price = (0, meteora_1.getPriceOfBinByBinIdWithDecimals)(pool.pool.activeId, pool.pool.binStep, decimalsX, decimalsY);
const poolInfo = {
dex,
address: pool.key,
price,
tokenMintA: pool.pool.tokenXMint,
tokenMintB: pool.pool.tokenYMint,
tvl: new decimal_js_1.default(0),
feeRate: (0, MeteoraService_1.computeMeteoraFee)(pool.pool).div(1e2), // Transform it to rate
volumeOnLast7d: new decimal_js_1.default(0),
tickSpacing: new decimal_js_1.default(pool.pool.binStep),
positions: positionsCount,
};
return poolInfo;
}));
return genericPoolInfos;
}
else {
throw new Error(`Dex ${dex} is not supported`);
}
}
getOrcaPoolsForTokens = async (poolTokenA, poolTokenB) => {
const pools = [];
const poolTokenAString = poolTokenA.toString();
const poolTokenBString = poolTokenB.toString();
const whirlpools = await this._orcaService.getOrcaWhirlpools();
whirlpools.forEach((element) => {
if ((element.tokenMintA === poolTokenAString && element.tokenMintB === poolTokenBString) ||
(element.tokenMintA === poolTokenBString && element.tokenMintB === poolTokenAString))
pools.push(element);
});
return pools;
};
getRaydiumPoolsForTokens = async (poolTokenA, poolTokenB) => {
const pools = [];
const poolTokenAString = poolTokenA.toString();
const poolTokenBString = poolTokenB.toString();
const raydiumPools = await this._raydiumService.getRaydiumWhirlpools();
raydiumPools.data.forEach((element) => {
if ((element.mintA === poolTokenAString && element.mintB === poolTokenBString) ||
(element.mintA === poolTokenBString && element.mintB === poolTokenAString)) {
pools.push(element);
}
});
return pools;
};
getMeteoraPoolsForTokens = async (poolTokenA, poolTokenB) => {
const pools = [];
const meteoraPools = await this._meteoraService.getMeteoraPools();
meteoraPools.forEach((element) => {
if ((element.pool.tokenXMint === poolTokenA && element.pool.tokenYMint === poolTokenB) ||
(element.pool.tokenXMint === poolTokenB && element.pool.tokenYMint === poolTokenA)) {
pools.push(element);
}
});
return pools;
};
/**
* Return a list of all Kamino whirlpool strategies
* @param strategies Limit results to these strategy addresses
*/
getStrategies = async (strategies) => {
if (!strategies) {
strategies = (await this.getAllStrategiesWithFilters({})).map((x) => x.address);
}
return await (0, utils_1.batchFetch)(strategies, (chunk) => this.getWhirlpoolStrategies(chunk));
};
/**
* Return a list of all Kamino whirlpool strategies with their addresses
* @param strategies Limit results to these strategy addresses
*/
getStrategiesWithAddresses = async (strategies) => {
if (!strategies) {
return this.getAllStrategiesWithFilters({});
}
const result = [];
const states = await (0, utils_1.batchFetch)(strategies, (chunk) => this.getWhirlpoolStrategies(chunk));
for (let i = 0; i < strategies.length; i++) {
if (states[i]) {
result.push({ address: strategies[i], strategy: states[i] });
}
else {
throw Error(`Could not fetch strategy state for ${strategies[i].toString()}`);
}
}
return result;
};
getAllStrategiesWithFilters = async (strategyFilters) => {
const filters = [];
filters.push({
dataSize: BigInt(accounts_1.WhirlpoolStrategy.layout.span + 8),
});
filters.push({
memcmp: {
offset: 0n,
bytes: bs58_1.default.encode(accounts_1.WhirlpoolStrategy.discriminator),
encoding: 'base58',
},
});
if (strategyFilters.owner) {
filters.push({
memcmp: {
offset: 8n,
bytes: strategyFilters.owner.toString(),
encoding: 'base58',
},
});
}
if (strategyFilters.strategyCreationStatus) {
filters.push({
memcmp: {
offset: 1625n,
bytes: (0, utils_1.strategyCreationStatusToBase58)(strategyFilters.strategyCreationStatus),
encoding: 'base58',
},
});
}
if (strategyFilters.strategyType) {
filters.push({
memcmp: {
offset: 1120n,
bytes: (0, utils_1.strategyTypeToBase58)(strategyFilters.strategyType).toString(),
encoding: 'base58',
},
});
}
if (strategyFilters.isCommunity !== undefined && strategyFilters.isCommunity !== null) {
const value = !strategyFilters.isCommunity ? '1' : '2';
filters.push({
memcmp: {
offset: 1664n,
bytes: value,
encoding: 'base58',
},
});
}
return (await this._rpc
.getProgramAccounts(this.getProgramID(), {
filters,
encoding: 'base64',
})
.send()).map((x) => {
const res = {
strategy: accounts_1.WhirlpoolStrategy.decode(Buffer.from(x.account.data[0], 'base64')),
address: x.pubkey,
};
return res;
});
};
/**
* Get a Kamino whirlpool strategy by its public key address
* @param address
*/
getStrategyByAddress = (address) => this.getWhirlpoolStrategy(address);
/**
* Get a Kamino whirlpool strategy by its kToken mint address
* @param kTokenMint - mint address of the kToken
*/
getStrategyByKTokenMint = async (kTokenMint) => {
const filters = [
{
dataSize: BigInt(accounts_1.WhirlpoolStrategy.layout.span + 8),
},
{
memcmp: {
offset: 0n,
bytes: bs58_1.default.encode(accounts_1.WhirlpoolStrategy.discriminator),
encoding: 'base58',
},
},
{
memcmp: {
bytes: kTokenMint.toString(),
offset: 720n,
encoding: 'base58',
},
},
];
const matchingStrategies = await this._rpc
.getProgramAccounts(this.getProgramID(), {
filters,
encoding: 'base64',
})
.send();
if (matchingStrategies.length === 0) {
return null;
}
if (matchingStrategies.length > 1) {
throw new Error(`Multiple strategies found for kToken mint: ${kTokenMint}. Strategies found: ${matchingStrategies.map((x) => x.pubkey)}`);
}
const decodedStrategy = accounts_1.WhirlpoolStrategy.decode(Buffer.from(matchingStrategies[0].account.data[0], 'base64'));
return {
address: matchingStrategies[0].pubkey,
strategy: decodedStrategy,
};
};
/**
* Get the strategy share data (price + balances) of the specified Kamino whirlpool strategy
* @param strategy
* @param scopePrices
*/
getStrategyShareData = async (strategy, scopePrices) => {
const strategyState = await this.getStrategyStateIfNotFetched(strategy);
const sharesFactor = decimal_js_1.default.pow(10, strategyState.strategy.sharesMintDecimals.toString());
const sharesIssued = new decimal_js_1.default(strategyState.strategy.sharesIssued.toString());
const balances = await this.getStrategyBalances(strategyState.strategy, scopePrices);
if (sharesIssued.isZero()) {
return { price: new decimal_js_1.default(1), balance: balances };
}
else {
return { price: balances.computedHoldings.totalSum.div(sharesIssued).mul(sharesFactor), balance: balances };
}
};
/**
* Get the token A and B per share for the specified Kamino whirlpool strategy
* @param strategy
*/
getTokenAAndBPerShare = async (strategy) => {
const strategyState = await this.getStrategyStateIfNotFetched(strategy);
const sharesIssued = new decimal_js_1.default(strategyState.strategy.sharesIssued.toString());
const balances = await this.getStrategyBalances(strategyState.strategy);
if (sharesIssued.isZero()) {
return { a: new decimal_js_1.default(0), b: new decimal_js_1.default(0) };
}
return { a: balances.tokenAAmounts.div(sharesIssued), b: balances.tokenBAmounts.div(sharesIssued) };
};
/**
* Batch fetch share data for all or a filtered list of strategies
* @param strategyFilters strategy filters or a list of strategy public keys
*/
getStrategiesShareData = async (strategyFilters, stratsWithAddresses, collateralInfos, disabledTokensPrices) => {
const result = [];
const strategiesWithAddresses = stratsWithAddresses
? stratsWithAddresses
: Array.isArray(strategyFilters)
? await this.getStrategiesWithAddresses(strategyFilters)
: await this.getAllStrategiesWithFilters(strategyFilters);
const fetchBalances = [];
const allScopePrices = strategiesWithAddresses.map((x) => x.strategy.scopePrices);
const scopePrices = await this._scope.getMultipleOraclePrices(allScopePrices);
const scopePricesMap = scopePrices.reduce((map, [address, price]) => {
map[address] = price;
return map;
}, {});
const raydiumStrategies = strategiesWithAddresses.filter((x) => x.strategy.strategyDex.toNumber() === (0, utils_1.dexToNumber)('RAYDIUM') && x.strategy.position !== pubkeys_1.DEFAULT_PUBLIC_KEY);
const raydiumPoolsPromise = this.getRaydiumPools(raydiumStrategies.map((x) => x.strategy.pool));
const raydiumPositionsPromise = this.getRaydiumPositions(raydiumStrategies.map((x) => x.strategy.position));
const orcaStrategies = strategiesWithAddresses.filter((x) => x.strategy.strategyDex.toNumber() === (0, utils_1.dexToNumber)('ORCA') && x.strategy.position !== pubkeys_1.DEFAULT_PUBLIC_KEY);
const orcaPoolsPromise = this.getWhirlpools(orcaStrategies.map((x) => x.strategy.pool));
const orcaPositionsPromise = this.getOrcaPositions(orcaStrategies.map((x) => x.strategy.position));
const meteoraStrategies = strategiesWithAddresses.filter((x) => x.strategy.strategyDex.toNumber() === (0, utils_1.dexToNumber)('METEORA') && x.strategy.position !== pubkeys_1.DEFAULT_PUBLIC_KEY);
const meteoraPoolsPromise = this.getMeteoraPools(meteoraStrategies.map((x) => x.strategy.pool));
const meteoraPositionsPromise = this.getMeteoraPositions(meteoraStrategies.map((x) => x.strategy.position));
const [raydiumPools, raydiumPositions, orcaPools, orcaPositions, meteoraPools, meteoraPositions] = await Promise.all([
raydiumPoolsPromise,
raydiumPositionsPromise,
orcaPoolsPromise,
orcaPositionsPromise,
meteoraPoolsPromise,
meteoraPositionsPromise,
]);
const inactiveStrategies = strategiesWithAddresses.filter((x) => x.strategy.position === pubkeys_1.DEFAULT_PUBLIC_KEY);
const collInfos = collateralInfos ? collateralInfos : await this.getCollateralInfos();
const disabledPrices = disabledTokensPrices ? disabledTokensPrices : await this.getDisabledTokensPrices(collInfos);
for (const { strategy, address } of inactiveStrategies) {
const strategyPrices = await this.getStrategyPrices(strategy, collInfos, scopePricesMap[strategy.scopePrices], disabledPrices);
result.push({
address,
strategy,
shareData: (0, models_1.getEmptyShareData)({
...strategyPrices,
poolPrice: utils_1.ZERO,
upperPrice: utils_1.ZERO,
lowerPrice: utils_1.ZERO,
twapPrice: utils_1.ZERO,
lowerResetPrice: utils_1.ZERO,
upperResetPrice: utils_1.ZERO,
}),
});
}
fetchBalances.push(...this.getBalance(raydiumStrategies, raydiumPools, raydiumPositions, this.getRaydiumBalances, collInfos, scopePricesMap, disabledPrices));
fetchBalances.p