UNPKG

@kamino-finance/kliquidity-sdk

Version:

Typescript SDK for interacting with the Kamino Liquidity (kliquidity) protocol

869 lines 312 kB
"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