kamino-sdk-beta
Version:
Typescript SDK for interacting with the Kamino Liquidity (kliquidity) protocol
860 lines (859 loc) • 299 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 = exports.KAMINO_IDL = void 0;
const hubble_config_1 = require("@hubbleprotocol/hubble-config");
const web3_js_1 = require("@solana/web3.js");
const bs58_1 = __importDefault(require("bs58"));
const programId_1 = require("./kamino-client/programId");
const accounts_1 = require("./kamino-client/accounts");
const decimal_js_1 = __importDefault(require("decimal.js"));
const whirlpools_client_1 = require("./whirlpools-client");
const whirlpool_sdk_1 = require("@orca-so/whirlpool-sdk");
const orca_dal_1 = require("@orca-so/whirlpool-sdk/dist/dal/orca-dal");
const orca_position_1 = require("@orca-so/whirlpool-sdk/dist/position/orca-position");
const models_1 = require("./models");
const programId_2 = require("./whirlpools-client/programId");
const scope_sdk_1 = require("@kamino-finance/scope-sdk");
const utils_1 = require("./utils");
const spl_token_1 = require("@solana/spl-token");
const instructions_1 = require("./kamino-client/instructions");
const bn_js_1 = __importDefault(require("bn.js"));
const anchor_1 = require("@coral-xyz/anchor");
const StrategyStatus_1 = require("./kamino-client/types/StrategyStatus");
const constants_1 = require("./constants");
const types_1 = require("./kamino-client/types");
const raydium_client_1 = require("./raydium_client");
const programId_3 = require("./raydium_client/programId");
const raydium_sdk_1 = require("@raydium-io/raydium-sdk");
const idl_json_1 = __importDefault(require("./kamino-client/idl.json"));
const services_1 = require("./services");
const add_liquidity_1 = require("@orca-so/whirlpool-sdk/dist/position/quotes/add-liquidity");
const instructions_2 = require("./kamino-client/instructions");
const StrategyConfigOption_1 = require("./kamino-client/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("./kamino-client/types/RebalanceType");
const transactions_1 = require("./utils/transactions");
const rebalance_methods_1 = require("./rebalance_methods");
const rebalance_methods_2 = require("./rebalance_methods");
const rebalance_methods_3 = require("./rebalance_methods");
const rebalance_methods_4 = require("./rebalance_methods");
const rebalance_methods_5 = 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 remove_liquidity_1 = require("./whirlpools-client/shim/remove-liquidity");
const programId_4 = require("./meteora_client/programId");
const MeteoraService_1 = require("./services/MeteoraService");
const meteora_1 = require("./utils/meteora");
const accounts_2 = require("./meteora_client/accounts");
const instructions_3 = require("./meteora_client/instructions");
const pubkey_1 = require("./utils/pubkey");
exports.KAMINO_IDL = idl_json_1.default;
class Kamino {
_cluster;
_connection;
_config;
_globalConfig;
_scope;
_provider;
_kaminoProgram;
_kaminoProgramId;
_orcaService;
_raydiumService;
_meteoraService;
/**
* Create a new instance of the Kamino SDK class.
* @param cluster Name of the Solana cluster
* @param connection 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
*/
constructor(cluster, connection, globalConfig, programId, whirlpoolProgramId, raydiumProgramId, meteoraProgramId) {
this._cluster = cluster;
this._connection = connection;
this._config = (0, hubble_config_1.getConfigByCluster)(cluster);
this._provider = new anchor_1.AnchorProvider(connection, (0, utils_1.getReadOnlyWallet)(), {
commitment: connection.commitment,
});
if (programId && programId.equals(pubkeys_1.STAGING_KAMINO_PROGRAM_ID)) {
this._kaminoProgramId = programId;
this._globalConfig = pubkeys_1.STAGING_GLOBAL_CONFIG;
}
else {
this._kaminoProgramId = programId ? programId : this._config.kamino.programId;
this._globalConfig = globalConfig ? globalConfig : new web3_js_1.PublicKey(this._config.kamino.globalConfig);
}
this._kaminoProgram = new anchor_1.Program(exports.KAMINO_IDL, this._kaminoProgramId, this._provider);
this._scope = new scope_sdk_1.Scope(cluster, connection);
(0, programId_1.setKaminoProgramId)(this._kaminoProgramId);
if (whirlpoolProgramId) {
(0, programId_2.setWhirlpoolsProgramId)(whirlpoolProgramId);
}
if (raydiumProgramId) {
(0, programId_3.setRaydiumProgramId)(raydiumProgramId);
}
if (meteoraProgramId) {
(0, programId_4.setMeteoraProgramId)(meteoraProgramId);
}
this._orcaService = new services_1.OrcaService(connection, cluster, whirlpoolProgramId);
this._raydiumService = new services_1.RaydiumService(connection, raydiumProgramId);
this._meteoraService = new MeteoraService_1.MeteoraService(connection, meteoraProgramId);
}
getConnection = () => this._connection;
getProgramID = () => this._kaminoProgramId;
getProgram = () => this._kaminoProgram;
setGlobalConfig = (globalConfig) => {
this._globalConfig = globalConfig;
};
getGlobalConfig = () => this._globalConfig;
getDepositableTokens = async () => {
const collateralInfos = await this.getCollateralInfos();
return collateralInfos.filter((x) => !x.mint.equals(web3_js_1.SystemProgram.programId));
};
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);
};
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_2.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_2.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_3.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_3.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._connection, tokenAMint);
const tokenBDecimals = await (0, utils_1.getMintDecimals)(this._connection, tokenBMint);
const price = poolPrice ? poolPrice : new decimal_js_1.default(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = (0, rebalance_methods_4.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_4.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_5.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_5.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_5.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_5.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_5.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_5.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._connection, tokenAMint);
const tokenBDecimals = await (0, utils_1.getMintDecimals)(this._connection, 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 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._connection, poolTokenA);
const decimalsY = await (0, utils_1.getMintDecimals)(this._connection, 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._connection, poolTokenA);
const tokenBDecimals = await (0, utils_1.getMintDecimals)(this._connection, poolTokenB);
switch (rebalanceMethod) {
case CreationParameters_1.ManualRebalanceMethod:
return (0, rebalance_methods_1.getDefaultManualRebalanceFieldInfos)(price);
case CreationParameters_1.PricePercentageRebalanceMethod:
return (0, rebalance_methods_2.getDefaultPricePercentageRebalanceFieldInfos)(price);
case CreationParameters_1.PricePercentageWithResetRangeRebalanceMethod:
return (0, rebalance_methods_3.getDefaultPricePercentageWithResetRebalanceFieldInfos)(price);
case CreationParameters_1.DriftRebalanceMethod:
return (0, rebalance_methods_4.getDefaultDriftRebalanceFieldInfos)(dex, tickSpacing, price, tokenADecimals, tokenBDecimals);
case CreationParameters_1.TakeProfitMethod:
return (0, rebalance_methods_5.getDefaultTakeProfitRebalanceFieldsInfos)(price);
case CreationParameters_1.PeriodicRebalanceMethod:
return (0, rebalance_methods_5.getDefaultPeriodicRebalanceFieldInfos)(price);
case CreationParameters_1.ExpanderMethod:
return (0, rebalance_methods_5.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 = web3_js_1.PublicKey.default;
const orcaPools = await this.getOrcaPoolsForTokens(poolTokenA, poolTokenB);
orcaPools.forEach((element) => {
if (element.lpFeeRate * CreationParameters_1.FullBPS == feeBPS.toNumber()) {
pool = new web3_js_1.PublicKey(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 web3_js_1.PublicKey.default;
}
let pool = web3_js_1.PublicKey.default;
let tickSpacing = Number.MAX_VALUE;
pools.forEach((element) => {
if (element.ammConfig.tickSpacing < tickSpacing) {
pool = new web3_js_1.PublicKey(element.id);
tickSpacing = element.ammConfig.tickSpacing;
}
});
return pool;
}
else if (dex == 'METEORA') {
let pool = web3_js_1.PublicKey.default;
const pools = await this.getMeteoraPoolsForTokens(poolTokenA, poolTokenB);
pools.forEach((element) => {
const feeRateBps = element.pool.parameters.baseFactor * element.pool.binStep;
if (feeRateBps == feeBPS.toNumber()) {
pool = new web3_js_1.PublicKey(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, new web3_js_1.PublicKey(pool.address)));
// read price from pool
const poolData = await this._orcaService.getPool(new web3_js_1.PublicKey(pool.address));
if (!poolData) {
throw new Error(`Pool ${pool.address} not found`);
}
const poolInfo = {
dex,
address: new web3_js_1.PublicKey(pool.address),
price: poolData.price,
tokenMintA: new web3_js_1.PublicKey(pool.tokenA.mint),
tokenMintB: new web3_js_1.PublicKey(pool.tokenB.mint),
tvl: pool.tvl ? new decimal_js_1.default(pool.tvl) : undefined,
feeRate: new decimal_js_1.default(pool.lpFeeRate).mul(CreationParameters_1.FullBPS),
volumeOnLast7d: pool.volume ? new decimal_js_1.default(pool.volume.week) : 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, new web3_js_1.PublicKey(pool.id)));
const poolInfo = {
dex,
address: new web3_js_1.PublicKey(pool.id),
price: new decimal_js_1.default(pool.price),
tokenMintA: new web3_js_1.PublicKey(pool.mintA),
tokenMintB: new web3_js_1.PublicKey(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._connection, pool.pool.tokenXMint);
const decimalsY = await (0, utils_1.getMintDecimals)(this._connection, 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.whirlpools.forEach((element) => {
if ((element.tokenA.mint == poolTokenAString && element.tokenB.mint == poolTokenBString) ||
(element.tokenA.mint == poolTokenBString && element.tokenB.mint == 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.equals(poolTokenA) && element.pool.tokenYMint.equals(poolTokenB)) ||
(element.pool.tokenXMint.equals(poolTokenB) && element.pool.tokenYMint.equals(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: accounts_1.WhirlpoolStrategy.layout.span + 8,
});
filters.push({
memcmp: {
offset: 0,
bytes: bs58_1.default.encode(accounts_1.WhirlpoolStrategy.discriminator),
},
});
if (strategyFilters.owner) {
filters.push({
memcmp: {
bytes: strategyFilters.owner.toBase58(),
offset: 8,
},
});
}
if (strategyFilters.strategyCreationStatus) {
filters.push({
memcmp: {
bytes: (0, utils_1.strategyCreationStatusToBase58)(strategyFilters.strategyCreationStatus),
offset: 1625,
},
});
}
if (strategyFilters.strategyType) {
filters.push({
memcmp: {
bytes: (0, utils_1.strategyTypeToBase58)(strategyFilters.strategyType).toString(),
offset: 1120,
},
});
}
if (strategyFilters.isCommunity !== undefined && strategyFilters.isCommunity !== null) {
const value = !strategyFilters.isCommunity ? '1' : '2';
filters.push({
memcmp: {
bytes: value,
offset: 1664,
},
});
}
return (await this._connection.getProgramAccounts(this._kaminoProgramId, {
filters,
})).map((x) => {
const res = {
strategy: accounts_1.WhirlpoolStrategy.decode(x.account.data),
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 matchingStrategies = await this._connection.getProgramAccounts(this._kaminoProgram.programId, {
filters: [
{
dataSize: accounts_1.WhirlpoolStrategy.layout.span + 8,
},
{
memcmp: {
offset: 0,
bytes: bs58_1.default.encode(accounts_1.WhirlpoolStrategy.discriminator),
},
},
{
memcmp: {
bytes: kTokenMint.toBase58(),
offset: 720,
},
},
],
});
if (matchingStrategies.length === 0) {
return null;
}
if (matchingStrategies.length > 1) {
throw new Error(`Multiple strategies found for kToken mint: ${kTokenMint.toBase58()}. Strategies found: ${matchingStrategies.map((x) => x.pubkey.toBase58())}`);
}
const decodedStrategy = accounts_1.WhirlpoolStrategy.decode(matchingStrategies[0].account.data);
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 };
}
};
/**
* 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) => {
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.toBase58()] = price;
return map;
}, {});
const raydiumStrategies = strategiesWithAddresses.filter((x) => x.strategy.strategyDex.toNumber() === (0, utils_1.dexToNumber)('RAYDIUM') && !x.strategy.position.equals(web3_js_1.PublicKey.default));
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.equals(web3_js_1.PublicKey.default));
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.equals(web3_js_1.PublicKey.default));
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.equals(web3_js_1.PublicKey.default));
const collInfos = collateralInfos ? collateralInfos : await this.getCollateralInfos();
for (const { strategy, address } of inactiveStrategies) {
const strategyPrices = await this.getStrategyPrices(strategy, collInfos, scopePricesMap[strategy.scopePrices.toBase58()]);
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));
fetchBalances.push(...this.getBalance(orcaStrategies, orcaPools, orcaPositions, this.getOrcaBalances, collInfos, scopePricesMap));
fetchBalances.push(...this.getBalance(meteoraStrategies, meteoraPools, meteoraPositions, this.getMeteoraBalances, collInfos, scopePricesMap));
const strategyBalances = await Promise.all(fetchBalances);
for (const { balance, strategyWithAddress } of strategyBalances) {
const sharesFactor = decimal_js_1.default.pow(10, strategyWithAddress.strategy.sharesMintDecimals.toString());
const sharesIssued = new decimal_js_1.default(strategyWithAddress.strategy.sharesIssued.toString());
if (sharesIssued.isZero()) {
result.push({
address: strategyWithAddress.address,
strategy: strategyWithAddress.strategy,