@marinade.finance/kamino-sdk
Version:
870 lines • 216 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 programId_1 = require("./kamino-client/programId");
const accounts_1 = require("./kamino-client/accounts");
const decimal_js_1 = __importDefault(require("decimal.js"));
const whirpools_client_1 = require("./whirpools-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("./whirpools-client/programId");
const scope_sdk_1 = require("@hubbleprotocol/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("@project-serum/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 kamino_json_1 = __importDefault(require("./kamino-client/kamino.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 market_1 = require("@project-serum/serum/lib/market");
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 manualRebalance_1 = require("./rebalance_methods/manualRebalance");
const pricePercentageRebalance_1 = require("./rebalance_methods/pricePercentageRebalance");
const pricePercentageWithResetRebalance_1 = require("./rebalance_methods/pricePercentageWithResetRebalance");
const driftRebalance_1 = require("./rebalance_methods/driftRebalance");
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");
exports.KAMINO_IDL = kamino_json_1.default;
class Kamino {
_cluster;
_connection;
_config;
_globalConfig;
_scope;
_provider;
_kaminoProgram;
_kaminoProgramId;
_orcaService;
_raydiumService;
_jupService;
/**
* 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) {
this._cluster = cluster;
this._connection = connection;
this._config = (0, hubble_config_1.getConfigByCluster)(cluster);
this._globalConfig = globalConfig ? globalConfig : new web3_js_1.PublicKey(this._config.kamino.globalConfig);
this._provider = new anchor_1.Provider(connection, (0, utils_1.getReadOnlyWallet)(), {
commitment: connection.commitment,
});
this._kaminoProgramId = programId ? programId : this._config.kamino.programId;
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 (cluster === 'localnet') {
if (raydiumProgramId) {
(0, programId_3.setRaydiumProgramId)(raydiumProgramId);
}
}
this._orcaService = new services_1.OrcaService(connection, cluster, this._globalConfig);
this._raydiumService = new services_1.RaydiumService(connection, cluster);
this._jupService = new JupService_1.JupService(connection, cluster);
}
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.toString() != web3_js_1.SystemProgram.programId.toString());
};
getCollateralInfos = async () => {
const config = await accounts_1.GlobalConfig.fetch(this._connection, this._globalConfig);
if (!config) {
throw Error(`Could not fetch globalConfig with pubkey ${this.getGlobalConfig().toString()}`);
}
return this.getCollateralInfo(config.tokenInfos);
};
getSupportedDexes = () => ['ORCA', 'RAYDIUM'];
// 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 {
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,
];
};
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;
let rebalancingParameters = await this.getDefaultRebalanceFields(dex, tokenMintA, tokenMintB, rebalanceMethod);
let defaultParameters = {
dex,
tokenMintA,
tokenMintB,
feeTier,
rebalancingParameters,
};
return defaultParameters;
};
getRebalanceTypeFromRebalanceFields = (rebalanceFields) => {
return (0, utils_2.getRebalanceTypeFromRebalanceFields)(rebalanceFields);
};
getRebalanceMethodFromRebalanceFields = (rebalanceFields) => {
return (0, utils_2.getRebalanceMethodFromRebalanceFields)(rebalanceFields);
};
getFieldsForRebalanceMethod = (rebalanceMethod, dex, fieldOverrides, tokenAMint, tokenBMint, 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, 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);
default:
throw new Error(`Rebalance method ${rebalanceMethod} is not supported`);
}
};
getFieldsForManualRebalanceMethod = async (dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice) => {
let price = poolPrice ? poolPrice : new decimal_js_1.default(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = (0, manualRebalance_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, manualRebalance_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, pricePercentageRebalance_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, pricePercentageRebalance_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, pricePercentageWithResetRebalance_1.getDefaultPricePercentageWithResetRebalanceFieldInfos)(price);
let lowerPriceDifferenceBPS = defaultFields.find((x) => x.label == 'lowerRangeBps').value;
let lowerPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label == 'lowerRangeBps');
if (lowerPriceDifferenceBPSInput) {
lowerPriceDifferenceBPS = lowerPriceDifferenceBPSInput.value;
}
let upperPriceDifferenceBPS = defaultFields.find((x) => x.label == 'upperRangeBps').value;
let upperPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label == 'upperRangeBps');
if (upperPriceDifferenceBPSInput) {
upperPriceDifferenceBPS = upperPriceDifferenceBPSInput.value;
}
let lowerResetPriceDifferenceBPS = defaultFields.find((x) => x.label == 'resetLowerRangeBps').value;
let lowerResetPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label == 'resetLowerRangeBps');
if (lowerResetPriceDifferenceBPSInput) {
lowerResetPriceDifferenceBPS = lowerResetPriceDifferenceBPSInput.value;
}
let upperResetPriceDifferenceBPS = defaultFields.find((x) => x.label == 'resetUpperRangeBps').value;
let upperResetPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label == 'resetUpperRangeBps');
if (upperResetPriceDifferenceBPSInput) {
upperResetPriceDifferenceBPS = upperResetPriceDifferenceBPSInput.value;
}
return (0, pricePercentageWithResetRebalance_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, tokenAMint, tokenBMint, poolPrice) => {
const tokenADecimals = await (0, market_1.getMintDecimals)(this._connection, tokenAMint);
const tokenBDecimals = await (0, market_1.getMintDecimals)(this._connection, tokenBMint);
const price = poolPrice ? poolPrice : new decimal_js_1.default(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = (0, driftRebalance_1.getDefaultDriftRebalanceFieldInfos)(dex, price, tokenADecimals, tokenBDecimals);
let startMidTick = defaultFields.find((x) => x.label == 'startMidTick').value;
let startMidTickInput = fieldOverrides.find((x) => x.label == 'startMidTick');
if (startMidTickInput) {
startMidTick = startMidTickInput.value;
}
let ticksBelowMid = defaultFields.find((x) => x.label == 'ticksBelowMid').value;
let ticksBelowMidInput = fieldOverrides.find((x) => x.label == 'ticksBelowMid');
if (ticksBelowMidInput) {
ticksBelowMid = ticksBelowMidInput.value;
}
let ticksAboveMid = defaultFields.find((x) => x.label == 'ticksAboveMid').value;
let ticksAboveMidInput = fieldOverrides.find((x) => x.label == 'ticksAboveMid');
if (ticksAboveMidInput) {
ticksAboveMid = ticksAboveMidInput.value;
}
let secondsPerTick = defaultFields.find((x) => x.label == 'secondsPerTick').value;
let secondsPerTickInput = fieldOverrides.find((x) => x.label == 'secondsPerTick');
if (secondsPerTickInput) {
secondsPerTick = secondsPerTickInput.value;
}
let direction = defaultFields.find((x) => x.label == 'direction').value;
let directionInput = fieldOverrides.find((x) => x.label == 'direction');
if (directionInput) {
direction = directionInput.value;
}
let fieldInfos = (0, driftRebalance_1.getDriftRebalanceFieldInfos)(dex, tokenADecimals, tokenBDecimals, 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);
// pub lower_range_price: u128,
// pub upper_range_price: u128,
// // Which token we want the full amount in
// // Will wait until the position is fully in this token (0 or 1, representing A or B)
// pub destination_token: RebalanceTakeProfitToken,
let lowerRangePrice = defaultFields.find((x) => x.label == 'rangePriceLower').value;
let lowerRangePriceInput = fieldOverrides.find((x) => x.label == 'rangePriceLower');
if (lowerRangePriceInput) {
lowerRangePrice = lowerRangePriceInput.value;
}
let upperRangePrice = defaultFields.find((x) => x.label == 'rangePriceUpper').value;
let upperRangePriceInput = fieldOverrides.find((x) => x.label == 'rangePriceUpper');
if (upperRangePriceInput) {
upperRangePrice = upperRangePriceInput.value;
}
let destinationToken = defaultFields.find((x) => x.label == 'destinationToken').value;
let 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);
let 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;
let lowerPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label == 'lowerRangeBps');
if (lowerPriceDifferenceBPSInput) {
lowerPriceDifferenceBPS = lowerPriceDifferenceBPSInput.value;
}
let upperPriceDifferenceBPS = defaultFields.find((x) => x.label == 'upperRangeBps').value;
let 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;
let lowerPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label == 'lowerRangeBps');
if (lowerPriceDifferenceBPSInput) {
lowerPriceDifferenceBPS = lowerPriceDifferenceBPSInput.value;
}
let upperPriceDifferenceBPS = defaultFields.find((x) => x.label == 'upperRangeBps').value;
let upperPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label == 'upperRangeBps');
if (upperPriceDifferenceBPSInput) {
upperPriceDifferenceBPS = upperPriceDifferenceBPSInput.value;
}
let lowerResetPriceDifferenceBPS = defaultFields.find((x) => x.label == 'resetLowerRangeBps').value;
let lowerResetPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label == 'resetLowerRangeBps');
if (lowerResetPriceDifferenceBPSInput) {
lowerResetPriceDifferenceBPS = lowerResetPriceDifferenceBPSInput.value;
}
let upperResetPriceDifferenceBPS = defaultFields.find((x) => x.label == 'resetUpperRangeBps').value;
let upperResetPriceDifferenceBPSInput = fieldOverrides.find((x) => x.label == 'resetUpperRangeBps');
if (upperResetPriceDifferenceBPSInput) {
upperResetPriceDifferenceBPS = upperResetPriceDifferenceBPSInput.value;
}
let expansionBPS = defaultFields.find((x) => x.label == 'expansionBps').value;
let expansionBPSInput = fieldOverrides.find((x) => x.label == 'expansionBps');
if (expansionBPSInput) {
expansionBPS = expansionBPSInput.value;
}
let maxNumberOfExpansions = defaultFields.find((x) => x.label == 'maxNumberOfExpansions').value;
let maxNumberOfExpansionsInput = fieldOverrides.find((x) => x.label == 'maxNumberOfExpansions');
if (maxNumberOfExpansionsInput) {
maxNumberOfExpansions = maxNumberOfExpansionsInput.value;
}
let swapUnevenAllowed = defaultFields.find((x) => x.label == 'swapUnevenAllowed').value;
let 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));
};
getPriceForPair = async (dex, poolTokenA, poolTokenB) => {
if (dex == 'ORCA') {
let 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') {
let 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 {
throw new Error(`Dex ${dex} is not supported`);
}
};
getDefaultRebalanceFields = async (dex, poolTokenA, poolTokenB, rebalanceMethod) => {
let price = new decimal_js_1.default(await this.getPriceForPair(dex, poolTokenA, poolTokenB));
switch (rebalanceMethod) {
case CreationParameters_1.ManualRebalanceMethod:
return (0, manualRebalance_1.getDefaultManualRebalanceFieldInfos)(price);
case CreationParameters_1.PricePercentageRebalanceMethod:
return (0, pricePercentageRebalance_1.getDefaultPricePercentageRebalanceFieldInfos)(price);
case CreationParameters_1.PricePercentageWithResetRangeRebalanceMethod:
return (0, pricePercentageWithResetRebalance_1.getDefaultPricePercentageWithResetRebalanceFieldInfos)(price);
case CreationParameters_1.DriftRebalanceMethod:
let tokenADecimals = await (0, market_1.getMintDecimals)(this._connection, poolTokenA);
let tokenBDecimals = await (0, market_1.getMintDecimals)(this._connection, poolTokenB);
return (0, driftRebalance_1.getDefaultDriftRebalanceFieldInfos)(dex, 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);
default:
throw new Error(`Rebalance method ${rebalanceMethod} is not supported`);
}
};
getPoolInitializedForDexPairTier = async (dex, poolTokenA, poolTokenB, feeBPS) => {
if (dex == 'ORCA') {
let pool = web3_js_1.PublicKey.default;
let 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') {
let pool = web3_js_1.PublicKey.default;
let 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))) {
pool = new web3_js_1.PublicKey(element.id);
}
});
return pool;
}
else {
throw new Error(`Dex ${dex} is not supported`);
}
};
async getExistentPoolsForPair(dex, tokenMintA, tokenMintB) {
if (dex == 'ORCA') {
let pools = await this.getOrcaPoolsForTokens(tokenMintA, tokenMintB);
let genericPoolInfos = await Promise.all(pools.map(async (pool) => {
let positionsCount = new decimal_js_1.default(await this.getPositionsCountForPool(dex, new web3_js_1.PublicKey(pool.address)));
// read price from pool
let poolData = await this._orcaService.getPool(new web3_js_1.PublicKey(pool.address));
if (!poolData) {
throw new Error(`Pool ${pool.address} not found`);
}
let 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') {
let pools = await this.getRaydiumPoolsForTokens(tokenMintA, tokenMintB);
let genericPoolInfos = await Promise.all(pools.map(async (pool) => {
let positionsCount = new decimal_js_1.default(await this.getPositionsCountForPool(dex, new web3_js_1.PublicKey(pool.id)));
let 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 {
throw new Error(`Dex ${dex} is not supported`);
}
}
getOrcaPoolsForTokens = async (poolTokenA, poolTokenB) => {
let pools = [];
let whirlpools = await this._orcaService.getOrcaWhirlpools();
whirlpools.whirlpools.forEach((element) => {
if ((element.tokenA.mint.toString() == poolTokenA.toString() &&
element.tokenB.mint.toString() == poolTokenB.toString()) ||
(element.tokenA.mint.toString() == poolTokenB.toString() &&
element.tokenB.mint.toString() == poolTokenA.toString()))
pools.push(element);
});
return pools;
};
getRaydiumPoolsForTokens = async (poolTokenA, poolTokenB) => {
let pools = [];
let raydiumPools = await this._raydiumService.getRaydiumWhirlpools();
raydiumPools.data.forEach((element) => {
if ((element.mintA.toString() == poolTokenA.toString() && element.mintB.toString() == poolTokenB.toString()) ||
(element.mintA.toString() == poolTokenB.toString() && element.mintB.toString() == poolTokenA.toString())) {
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) => accounts_1.WhirlpoolStrategy.fetchMultiple(this._connection, 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) => accounts_1.WhirlpoolStrategy.fetchMultiple(this._connection, 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) => {
let filters = [];
filters.push({
dataSize: 4064,
});
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) {
let value = !strategyFilters.isCommunity ? '1' : '2';
filters.push({
memcmp: {
bytes: value,
offset: 1664,
},
});
}
return (await this._kaminoProgram.account.whirlpoolStrategy.all(filters)).map((x) => {
const res = {
strategy: new accounts_1.WhirlpoolStrategy(x.account),
address: x.publicKey,
};
return res;
});
};
/**
* Get a Kamino whirlpool strategy by its public key address
* @param address
*/
getStrategyByAddress = (address) => accounts_1.WhirlpoolStrategy.fetch(this._connection, 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._kaminoProgram.account.whirlpoolStrategy.all([
{
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.publicKey.toBase58())}`);
}
const decodedStrategy = new accounts_1.WhirlpoolStrategy(matchingStrategies[0].account);
return {
address: matchingStrategies[0].publicKey,
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 };
}
};
getStrategiesForSharedData = async (strategyFilters) => {
return Array.isArray(strategyFilters)
? await this.getStrategiesWithAddresses(strategyFilters)
: await this.getAllStrategiesWithFilters(strategyFilters);
};
/**
* 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) => {
return await this.getStrategiesShareDataWithFn(strategyFilters, this.getStrategiesForSharedData);
};
getStrategiesShareDataWithFn = async (strategyFilters, getStrategiesFn) => {
const result = [];
const strategiesWithAddresses = await getStrategiesFn(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.toString() !== web3_js_1.PublicKey.default.toString());
const raydiumPools = await this.getRaydiumPools(raydiumStrategies.map((x) => x.strategy.pool));
const raydiumPositions = await 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.toString() !== web3_js_1.PublicKey.default.toString());
const orcaPools = await this.getWhirlpools(orcaStrategies.map((x) => x.strategy.pool));
const orcaPositions = await this.getOrcaPositions(orcaStrategies.map((x) => x.strategy.position));
const inactiveStrategies = strategiesWithAddresses.filter((x) => x.strategy.position.toString() === web3_js_1.PublicKey.default.toString());
const collateralInfos = await this.getCollateralInfos();
for (const { strategy, address } of inactiveStrategies) {
const strategyPrices = await this.getStrategyPrices(strategy, collateralInfos, 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,
}),
});
}
fetchBalances.push(...this.getBalance(raydiumStrategies, raydiumPools, raydiumPositions, this.getRaydiumBalances, collateralInfos, scopePricesMap));
fetchBalances.push(...this.getBalance(orcaStrategies, orcaPools, orcaPositions, this.getOrcaBalances, collateralInfos, 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,
shareData: { price: new decimal_js_1.default(1), balance },
});
}
else {
result.push({
address: strategyWithAddress.address,
strategy: strategyWithAddress.strategy,
shareData: { price: balance.computedHoldings.totalSum.div(sharesIssued).mul(sharesFactor), balance },
});
}
}
return result;
};
getBalance = (strategies, pools, positions, fetchBalance, collateralInfos, prices) => {
const fetchBalances = [];
for (let i = 0; i < strategies.length; i++) {
const { strategy, address } = strategies[i];
const pool = pools[i];
const position = positions[i];
if (!pool) {
throw new Error(`Pool ${strategy.pool.toString()} could not be found.`);
}
if (!position) {
throw new Error(`Position ${strategy.position.toString()} could not be found.`);
}
fetchBalances.push(fetchBalance(strategy, pool, position, collateralInfos, prices ? prices[strategy.scopePrices.toBase58()] : undefined).then((balance) => {
return { balance, strategyWithAddress: { strategy, address } };
}));
}
return fetchBalances;
};
getRaydiumBalances = async (strategy, pool, position, collateralInfos, prices) => {
const strategyPrices = await this.getStrategyPrices(strategy, collateralInfos, prices);
const tokenHoldings = await this.getRaydiumTokensBalances(strategy, pool, position);
let computedHoldings = this.getStrategyHoldingsUsd(tokenHoldings.available.a, tokenHoldings.available.b, tokenHoldings.invested.a, tokenHoldings.invested.b, new decimal_js_1.default(strategy.tokenAMintDecimals.toString()), new decimal_js_1.default(strategy.tokenBMintDecimals.toString()), strategyPrices.aPrice, strategyPrices.bPrice);
let decimalsA = strategy.tokenAMintDecimals.toNumber();
let decimalsB = strategy.tokenBMintDecimals.toNumber();
const poolPrice = raydium_sdk_1.SqrtPriceMath.sqrtPriceX64ToPrice(pool.sqrtPriceX64, decimalsA, decimalsB);
const upperPrice = raydium_sdk_1.SqrtPriceMath.sqrtPriceX64ToPrice(raydium_sdk_1.SqrtPriceMath.getSqrtPriceX64FromTick(position.tickUpperIndex), decimalsA, decimalsB);
const lowerPrice = raydium_sdk_1.SqrtPriceMath.sqrtPriceX64ToPrice(raydium_sdk_1.SqrtPriceMath.getSqrtPriceX64FromTick(position.tickLowerIndex), decimalsA, decimalsB);
const balance = {
computedHoldings,
prices: { ...strategyPrices, poolPrice, lowerPrice, upperPrice },
tokenAAmounts: tokenHoldings.available.a.plus(tokenHoldings.invested.a),
tokenBAmounts: tokenHoldings.available.b.plus(tokenHoldings.invested.b),
};
return balance;
};
getRaydiumTokensBalances = async (strategy, pool, position) => {
const lowerSqrtPriceX64 = raydium_sdk_1.SqrtPriceMath.getSqrtPriceX64FromTick(position.tickLowerIndex);
const upperSqrtPriceX64 = raydium_sdk_1.SqrtPriceMath.getSqrtPriceX64FromTick(position.tickUpperIndex);
const { amountA, amountB } = raydium_sdk_1.LiquidityMath.getAmountsFromLiquidity(pool.sqrtPriceX64, new bn_js_1.default(lowerSqrtPriceX64), new bn_js_1.default(upperSqrtPriceX64), position.liquidity, false // round down so the holdings are not overestimated
);
const aAvailable = new decimal_js_1.default(strategy.tokenAAmounts.toString());
const bAvailable = new decimal_js_1.default(strategy.tokenBAmounts.toString());
const aInvested = new decimal_js_1.default(amountA.toString());
const bInvested = new decimal_js_1.default(amountB.toString());
let holdings = {
available: {
a: aAvailable,
b: bAvailable,
},
invested: {
a: aInvested,
b: bInvested,
},
};
return holdings;
};
getOrcaBalances = async (strategy, pool, position, collateralInfos, prices) => {
const strategyPrices = await this.getStrategyPrices(strategy, collateralInfos, prices);
let tokenHoldings = await this.getOrcaTokensBalances(strategy, pool, position);
const computedHoldings = this.getStrategyHoldingsUsd(tokenHoldings.available.a, tokenHoldings.available.b, tokenHoldings.invested.a, tokenHoldings.invested.b, new decimal_js_1.default(strategy.tokenAMintDecimals.toString()), new decimal_js_1.default(strategy.tokenBMintDecimals.toString()), strategyPrices.aPrice, strategyPrices.bPrice);
const decimalsA = strategy.tokenAMintDecimals.toNumber();
const decimalsB = strategy.tokenBMintDecimals.toNumber();
const poolPrice = (0, whirlpool_sdk_1.sqrtPriceX64ToPrice)(pool.sqrtPrice, decimalsA, decimalsB);
const upperPrice = (0, whirlpool_sdk_1.tickIndexToPrice)(position.tickUpperIndex, decimalsA, decimalsB);
const lowerPrice = (0, whirlpool_sdk_1.tickIndexToPrice)(position.tickLowerIndex, decimalsA, decimalsB);
const balance = {
computedHoldings,
prices: { ...strategyPrices, poolPrice, upperPrice, lowerPrice },
tokenAAmounts: tokenHoldings.available.a.plus(tokenHoldings.invested.a),
tokenBAmounts: tokenHoldings.available.b.plus(tokenHoldings.invested.b),
};
return balance;
};
getOrcaTokensBalances = async (strategy, pool, position) => {
const quote = (0, whirlpool_sdk_1.getRemoveLiquidityQuote)({
positionAddress: strategy.position,
liquidity: position.liquidity,
slippageTolerance: whirlpool_sdk_1.Percentage.fromFraction(0, 1000),
sqrtPrice: pool.sqrtPrice,
tickLowerIndex: position.tickLowerIndex,
tickUpperIndex: position.tickUpperIndex,
tickCurrentIndex: pool.tickCurrentIndex,
});
const aAvailable = new decimal_js_1.default(strategy.tokenAAmounts.toString());
const bAvailable = new decimal_js_1.default(strategy.tokenBAmounts.toString());
const aInvested = new decimal_js_1.default(quote.estTokenA.toString());
const bInvested = new decimal_js_1.default(quote.estTokenB.toString());
let holdings = {
available: {
a: aAvailable,
b: bAvailable,
},
invested: {
a: aInvested,
b: bInvested,
},
};
return holdings;
};
/**
* Get the strategies share data (price + balances) of the Kamino whirlpool strategies that match the filters
* @param strategyFilters
*/
getStrategyShareDataForStrategies = async (strategyFilters) => {
// weird name of method, but want to keep this method backwards compatible and not rename it
return this.getStrategiesShareData(strategyFilters);
};
/**
* Get the strategy share price of the specified Kamino whirlpool strategy
* @param strategy
*/
getStrategySharePrice = async (strategy) => {
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);
if (sharesIssued.isZero()) {
return new decimal_js_1.default(1);
}
else {
return balances.computedHoldings.totalSum.div(sharesIssued).mul(sharesFactor);
}
};
getTokenAccountBalance = async (tokenAccount) => {
const tokenAccountBalance = await this._connection.getTokenAccountBalance(tokenAccount);
if (!tokenAccountBalance.value) {
throw new Error(`Could not get token account balance for ${tokenAccount.toString()}.`);
}
return new decimal_js_1.default(tokenAccountBalance.value.uiAmountString);
};
// getTokenAccountBalanceOrZero return 0 if the token balance is not initialized
getTokenAccountBalanceOrZero = async (tokenAccount) => {
let tokenAccountExists = await (0, transactions_1.checkIfAccountExists)(this._connection, tokenAccount);
if (tokenAccountExists) {
return await this.getTokenAccountBalance(tokenAccount);
}
else {
return new decimal_js_1.default(0);
}
};
getStrategyBalances = async (strategy, scopePrices) => {
const collateralInfos = await this.getCollateralInfos();
if (strategy.strategyDex.toNumber() == (0, utils_1.dexToNumber)('ORCA')) {
return this.getStrategyBalancesOrca(strategy, collateralInfos, scopePrices);
}
else if (strategy.strategyDex.toNumber() == (0, utils_1.dexToNumber)('RAYDIUM')) {
return this.getStrategyBalancesRaydium(strategy, collateralInfos, scopePrices);
}
else {
throw new Error(`Invalid dex ${strategy.strategyDex.toString()}`);
}
};
getStrategyTokensBalances = async (strategy) => {
if (strategy.strategyDex.toNumber() == (0, utils_1.dexToNumber)('ORCA')) {
const [whirlpool, position] = await Promise.all([
whirpools_client_1.Whirlpool.fetch(this._connection, strategy.pool),
whirpools_client_1.Position.fetch(this._connection, strategy.position),
]);
if (!whirlpool) {
throw Error(`Could not fetch whirlpool state with pubkey ${strategy.pool.toString()}`);
}
if (!position) {
throw Error(`Could not fetch position state with pubkey ${strategy.position.toString()}`);
}
return this.getOrcaTokensBalances(strategy, whirlpool, position);
}
else if (strategy.strategyDex.toNumber() == (0, utils_1.dexToNumber)('RAYDIUM')) {
const [poolState, position] = await Promise.all([
raydium_client_1.PoolState.fetch(this._connection, strategy.pool),
raydium_client_1.PersonalPositionState.fetch(this._connection, strategy.position),
]);
if (!poolState) {
throw Error(`Could not fetch Raydium pool state with pubkey ${strategy.pool.toString()}`);
}
if (!position) {
throw Error(`Could not fetch position state with pubkey ${strategy.position.toString()}`);
}
return this.getRaydiumTokensBalances(strategy, poolState, position);
}
else {
throw new Error(`Invalid dex ${strategy.strategyDex.toString()}`);
}
};
/**
* Get amount of specified token in all Kamino live strategies
* @param tokenMint token mint pubkey
*/
getTotalTokensInStrategies = async (tokenMint) => {
const strategies = await this.get