UNPKG

kamino-sdk-beta

Version:

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

860 lines (859 loc) 299 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 = 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,