kamino-sdk-beta
Version:
Typescript SDK for interacting with the Kamino Liquidity (kliquidity) protocol
1,326 lines (1,219 loc) • 303 kB
text/typescript
import { getConfigByCluster, HubbleConfig, SolanaCluster } from '@hubbleprotocol/hubble-config';
import {
AccountInfo,
Connection,
GetProgramAccountsFilter,
PublicKey,
SystemProgram,
SYSVAR_INSTRUCTIONS_PUBKEY,
SYSVAR_RENT_PUBKEY,
TransactionInstruction,
AddressLookupTableAccount,
MessageV0,
TransactionMessage,
Keypair,
AddressLookupTableProgram,
Transaction,
ParsedAccountData,
} from '@solana/web3.js';
import bs58 from 'bs58';
import { setKaminoProgramId } from './kamino-client/programId';
import { GlobalConfig, TermsSignature, WhirlpoolStrategy, CollateralInfos } from './kamino-client/accounts';
import Decimal from 'decimal.js';
import {
initializeTickArray,
InitializeTickArrayAccounts,
InitializeTickArrayArgs,
Position,
TickArray,
Whirlpool,
} from './whirlpools-client';
import {
AddLiquidityQuote,
AddLiquidityQuoteParam,
defaultSlippagePercentage,
getNearestValidTickIndexFromTickIndex,
getNextValidTickIndex,
getStartTickIndex,
OrcaNetwork,
OrcaWhirlpoolClient,
Percentage,
priceToTickIndex,
sqrtPriceX64ToPrice,
tickIndexToPrice,
} from '@orca-so/whirlpool-sdk';
import { OrcaDAL } from '@orca-so/whirlpool-sdk/dist/dal/orca-dal';
import { OrcaPosition } from '@orca-so/whirlpool-sdk/dist/position/orca-position';
import {
Data,
getEmptyShareData,
Holdings,
KaminoPosition,
KaminoStrategyWithShareMint,
MintToPriceMap,
ShareData,
ShareDataWithAddress,
StrategyBalances,
StrategyBalanceWithAddress,
StrategyHolder,
StrategyProgramAddress,
StrategyVaultTokens,
StrategyWithPendingFees,
TokenAmounts,
TokenHoldings,
TotalStrategyVaultTokens,
TreasuryFeeVault,
} from './models';
import { setWhirlpoolsProgramId } from './whirlpools-client/programId';
import { OraclePrices, Scope } from '@kamino-finance/scope-sdk';
import {
batchFetch,
collToLamportsDecimal,
createAssociatedTokenAccountInstruction,
DepositAmountsForSwap,
Dex,
dexToNumber,
GenericPoolInfo,
GenericPositionRangeInfo,
getAssociatedTokenAddress,
getAssociatedTokenAddressAndData,
getDexProgramId,
getReadOnlyWallet,
buildStrategyRebalanceParams,
getUpdateStrategyConfigIx,
LiquidityDistribution,
numberToRebalanceType,
RebalanceFieldInfo,
sendTransactionWithLogs,
StrategiesFilters,
strategyCreationStatusToBase58,
strategyTypeToBase58,
VaultParameters,
ZERO,
PositionRange,
numberToDex,
TokensBalances,
isSOLMint,
SwapperIxBuilder,
lamportsToNumberDecimal,
DECIMALS_SOL,
InstructionsWithLookupTables,
ProfiledFunctionExecution,
noopProfiledFunctionExecution,
MaybeTokensBalances,
PerformanceFees,
PriceReferenceType,
InputRebalanceFieldInfo,
getTickArray,
rebalanceFieldsDictToInfo,
InitStrategyIxs,
WithdrawShares,
MetadataProgramAddressesOrca,
MetadataProgramAddressesRaydium,
LowerAndUpperTickPubkeys,
isVaultInitialized,
WithdrawAllAndCloseIxns,
InitPoolTickIfNeeded,
numberToReferencePriceType,
stripTwapZeros,
getTokenNameFromCollateralInfo,
keyOrDefault,
getMintDecimals,
} from './utils';
import {
ASSOCIATED_TOKEN_PROGRAM_ID,
createCloseAccountInstruction,
TOKEN_PROGRAM_ID,
unpackMint,
} from '@solana/spl-token';
import {
checkExpectedVaultsBalances,
CheckExpectedVaultsBalancesAccounts,
CheckExpectedVaultsBalancesArgs,
closeStrategy,
CloseStrategyAccounts,
collectFeesAndRewards,
CollectFeesAndRewardsAccounts,
deposit,
DepositAccounts,
DepositArgs,
executiveWithdraw,
ExecutiveWithdrawAccounts,
ExecutiveWithdrawArgs,
initializeStrategy,
InitializeStrategyAccounts,
InitializeStrategyArgs,
invest,
InvestAccounts,
openLiquidityPosition,
OpenLiquidityPositionAccounts,
OpenLiquidityPositionArgs,
singleTokenDepositWithMin,
SingleTokenDepositWithMinAccounts,
SingleTokenDepositWithMinArgs,
updateRewardMapping,
UpdateRewardMappingAccounts,
UpdateRewardMappingArgs,
updateStrategyConfig,
UpdateStrategyConfigAccounts,
UpdateStrategyConfigArgs,
withdraw,
WithdrawAccounts,
WithdrawArgs,
withdrawFromTopup,
WithdrawFromTopupAccounts,
WithdrawFromTopupArgs,
} from './kamino-client/instructions';
import BN from 'bn.js';
import StrategyWithAddress from './models/StrategyWithAddress';
import { Idl, Program, AnchorProvider } from '@coral-xyz/anchor';
import { Rebalancing, Uninitialized } from './kamino-client/types/StrategyStatus';
import { FRONTEND_KAMINO_STRATEGY_URL, METADATA_PROGRAM_ID, U64_MAX } from './constants';
import {
CollateralInfo,
ExecutiveWithdrawActionKind,
RebalanceType,
RebalanceTypeKind,
ReferencePriceTypeKind,
StrategyConfigOption,
StrategyStatusKind,
} from './kamino-client/types';
import { AmmConfig, PersonalPositionState, PoolState } from './raydium_client';
import { setRaydiumProgramId } from './raydium_client/programId';
import {
getPdaProtocolPositionAddress,
i32ToBytes,
LiquidityMath,
SqrtPriceMath,
TickMath,
TickUtils,
} from '@raydium-io/raydium-sdk';
import KaminoIdl from './kamino-client/idl.json';
import { OrcaService, RaydiumService, Whirlpool as OrcaPool, WhirlpoolAprApy } from './services';
import {
getAddLiquidityQuote,
InternalAddLiquidityQuote,
InternalAddLiquidityQuoteParam,
} from '@orca-so/whirlpool-sdk/dist/position/quotes/add-liquidity';
import { signTerms, SignTermsAccounts, SignTermsArgs } from './kamino-client/instructions';
import { Pool } from './services/RaydiumPoolsResponse';
import {
UpdateWithdrawFee,
UpdateReward0Fee,
UpdateReward1Fee,
UpdateReward2Fee,
UpdateCollectFeesFee,
UpdateRebalanceType,
UpdateLookupTable,
UpdateReferencePriceType,
} from './kamino-client/types/StrategyConfigOption';
import { DefaultPerformanceFeeBps } from './constants/DefaultStrategyConfig';
import {
ADDRESS_LUT_PROGRAM_ID,
CONSENSUS_ID,
LUT_OWNER_KEY,
STAGING_GLOBAL_CONFIG,
STAGING_KAMINO_PROGRAM_ID,
MEMO_PROGRAM_ID,
TOKEN_2022_PROGRAM_ID,
} from './constants/pubkeys';
import {
AutodriftMethod,
DefaultDex,
DefaultFeeTierOrca,
DefaultMintTokenA,
DefaultMintTokenB,
DefaultTickSpacing,
DriftRebalanceMethod,
ExpanderMethod,
FullBPS,
FullPercentage,
ManualRebalanceMethod,
PeriodicRebalanceMethod,
PricePercentageRebalanceMethod,
PricePercentageWithResetRangeRebalanceMethod,
RebalanceMethod,
TakeProfitMethod,
} from './utils/CreationParameters';
import { DOLAR_BASED, PROPORTION_BASED } from './constants/deposit_method';
import { JupService } from './services/JupService';
import {
simulateManualPool,
simulatePercentagePool,
SimulationPercentagePoolParameters,
} from './services/PoolSimulationService';
import {
Expander,
Manual,
PricePercentage,
PricePercentageWithReset,
Drift,
TakeProfit,
PeriodicRebalance,
Autodrift,
} from './kamino-client/types/RebalanceType';
import {
checkIfAccountExists,
createWsolAtaIfMissing,
getAtasWithCreateIxnsIfMissing,
MAX_ACCOUNTS_PER_TRANSACTION,
removeBudgetAndAtaIxns,
} from './utils/transactions';
import { SwapResponse } from '@jup-ag/api';
import { StrategyPrices } from './models';
import { getDefaultManualRebalanceFieldInfos, getManualRebalanceFieldInfos } from './rebalance_methods';
import {
deserializePricePercentageRebalanceFromOnchainParams,
deserializePricePercentageRebalanceWithStateOverride,
getDefaultPricePercentageRebalanceFieldInfos,
getPositionRangeFromPercentageRebalanceParams,
getPricePercentageRebalanceFieldInfos,
readPricePercentageRebalanceParamsFromStrategy,
readRawPricePercentageRebalanceStateFromStrategy,
} from './rebalance_methods';
import {
deserializePricePercentageWithResetRebalanceFromOnchainParams,
deserializePricePercentageWithResetRebalanceWithStateOverride,
getDefaultPricePercentageWithResetRebalanceFieldInfos,
getPositionRangeFromPricePercentageWithResetParams,
getPricePercentageWithResetRebalanceFieldInfos,
readPricePercentageWithResetRebalanceParamsFromStrategy,
readRawPricePercentageWithResetRebalanceStateFromStrategy,
} from './rebalance_methods';
import {
deserializeDriftRebalanceFromOnchainParams,
deserializeDriftRebalanceWithStateOverride,
getDefaultDriftRebalanceFieldInfos,
getDriftRebalanceFieldInfos,
getPositionRangeFromDriftParams,
readDriftRebalanceParamsFromStrategy,
readRawDriftRebalanceStateFromStrategy,
} from './rebalance_methods';
import {
deserializeExpanderRebalanceWithStateOverride,
deserializePeriodicRebalanceFromOnchainParams,
deserializeTakeProfitRebalanceFromOnchainParams,
getDefaultExpanderRebalanceFieldInfos,
getDefaultPeriodicRebalanceFieldInfos,
getDefaultTakeProfitRebalanceFieldsInfos,
getExpanderRebalanceFieldInfos,
getPeriodicRebalanceRebalanceFieldInfos,
getPositionRangeFromExpanderParams,
getPositionRangeFromPeriodicRebalanceParams,
getTakeProfitRebalanceFieldsInfos,
readExpanderRebalanceFieldInfosFromStrategy,
readExpanderRebalanceParamsFromStrategy,
readPeriodicRebalanceRebalanceParamsFromStrategy,
readPeriodicRebalanceRebalanceStateFromStrategy,
readRawExpanderRebalanceStateFromStrategy,
readTakeProfitRebalanceParamsFromStrategy,
readTakeProfitRebalanceStateFromStrategy,
} from './rebalance_methods';
import { PoolPriceReferenceType, TwapPriceReferenceType } from './utils/priceReferenceTypes';
import {
extractPricesFromDeserializedState,
getRebalanceMethodFromRebalanceFields,
getRebalanceTypeFromRebalanceFields,
} from './rebalance_methods/utils';
import { RebalanceTypeLabelName } from './rebalance_methods/consts';
import WhirlpoolWithAddress from './models/WhirlpoolWithAddress';
import { PoolSimulationResponse } from './models/PoolSimulationResponseData';
import {
deserializeAutodriftRebalanceWithStateOverride,
deserializeAutodriftRebalanceFromOnchainParams,
readAutodriftRebalanceParamsFromStrategy,
readRawAutodriftRebalanceStateFromStrategy,
getAutodriftRebalanceFieldInfos,
getDefaultAutodriftRebalanceFieldInfos,
getPositionRangeFromAutodriftParams,
} from './rebalance_methods/autodriftRebalance';
import { KaminoPrices, OraclePricesAndCollateralInfos } from './models';
import { getRemoveLiquidityQuote } from './whirlpools-client/shim/remove-liquidity';
import { setMeteoraProgramId } from './meteora_client/programId';
import { computeMeteoraFee, MeteoraPool, MeteoraService } from './services/MeteoraService';
import {
binIdToBinArrayIndex,
deriveBinArray,
getBinFromBinArray,
getBinFromBinArrays,
getBinIdFromPriceWithDecimals,
getPriceOfBinByBinIdWithDecimals,
MeteoraPosition,
} from './utils/meteora';
import { BinArray, LbPair, PositionV2 } from './meteora_client/accounts';
import LbPairWithAddress from './models/LbPairWithAddress';
import { initializeBinArray, InitializeBinArrayAccounts, InitializeBinArrayArgs } from './meteora_client/instructions';
import { PubkeyHashMap } from './utils/pubkey';
export const KAMINO_IDL = KaminoIdl;
export class Kamino {
private readonly _cluster: SolanaCluster;
private readonly _connection: Connection;
readonly _config: HubbleConfig;
private _globalConfig: PublicKey;
private readonly _scope: Scope;
private readonly _provider: AnchorProvider;
private readonly _kaminoProgram: Program;
private readonly _kaminoProgramId: PublicKey;
private readonly _orcaService: OrcaService;
private readonly _raydiumService: RaydiumService;
private readonly _meteoraService: 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: SolanaCluster,
connection: Connection,
globalConfig?: PublicKey,
programId?: PublicKey,
whirlpoolProgramId?: PublicKey,
raydiumProgramId?: PublicKey,
meteoraProgramId?: PublicKey
) {
this._cluster = cluster;
this._connection = connection;
this._config = getConfigByCluster(cluster);
this._provider = new AnchorProvider(connection, getReadOnlyWallet(), {
commitment: connection.commitment,
});
if (programId && programId.equals(STAGING_KAMINO_PROGRAM_ID)) {
this._kaminoProgramId = programId;
this._globalConfig = STAGING_GLOBAL_CONFIG;
} else {
this._kaminoProgramId = programId ? programId : this._config.kamino.programId;
this._globalConfig = globalConfig ? globalConfig : new PublicKey(this._config.kamino.globalConfig);
}
this._kaminoProgram = new Program(KAMINO_IDL as Idl, this._kaminoProgramId, this._provider);
this._scope = new Scope(cluster, connection);
setKaminoProgramId(this._kaminoProgramId);
if (whirlpoolProgramId) {
setWhirlpoolsProgramId(whirlpoolProgramId);
}
if (raydiumProgramId) {
setRaydiumProgramId(raydiumProgramId);
}
if (meteoraProgramId) {
setMeteoraProgramId(meteoraProgramId);
}
this._orcaService = new OrcaService(connection, cluster, whirlpoolProgramId);
this._raydiumService = new RaydiumService(connection, raydiumProgramId);
this._meteoraService = new MeteoraService(connection, meteoraProgramId);
}
getConnection = () => this._connection;
getProgramID = () => this._kaminoProgramId;
getProgram = () => this._kaminoProgram;
setGlobalConfig = (globalConfig: PublicKey) => {
this._globalConfig = globalConfig;
};
getGlobalConfig = () => this._globalConfig;
getDepositableTokens = async (): Promise<CollateralInfo[]> => {
const collateralInfos = await this.getCollateralInfos();
return collateralInfos.filter((x) => !x.mint.equals(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 = (): Dex[] => ['ORCA', 'RAYDIUM', 'METEORA'];
// todo: see if we can read this dynamically
getFeeTiersForDex = (dex: Dex): Decimal[] => {
if (dex == 'ORCA') {
return [new Decimal(0.0001), new Decimal(0.0005), new Decimal(0.003), new Decimal(0.01)];
} else if (dex == 'RAYDIUM') {
return [new Decimal(0.0001), new Decimal(0.0005), new Decimal(0.0025), new Decimal(0.01)];
} else if (dex == 'METEORA') {
return [new Decimal(0.0001), new Decimal(0.0005), new Decimal(0.0025), new Decimal(0.01)];
} else {
throw new Error(`Dex ${dex} is not supported`);
}
};
getRebalanceMethods = (): RebalanceMethod[] => {
return [
ManualRebalanceMethod,
PricePercentageRebalanceMethod,
PricePercentageWithResetRangeRebalanceMethod,
DriftRebalanceMethod,
TakeProfitMethod,
PeriodicRebalanceMethod,
ExpanderMethod,
AutodriftMethod,
];
};
getEnabledRebalanceMethods = (): RebalanceMethod[] => {
return this.getRebalanceMethods().filter((x) => x.enabled);
};
getPriceReferenceTypes = (): PriceReferenceType[] => {
return [PoolPriceReferenceType, TwapPriceReferenceType];
};
getDefaultRebalanceMethod = (): RebalanceMethod => PricePercentageRebalanceMethod;
getDefaultParametersForNewVault = async (): Promise<VaultParameters> => {
const dex = DefaultDex;
const tokenMintA = DefaultMintTokenA;
const tokenMintB = DefaultMintTokenB;
const rebalanceMethod = this.getDefaultRebalanceMethod();
const feeTier = DefaultFeeTierOrca;
const tickSpacing = DefaultTickSpacing;
const rebalancingParameters = await this.getDefaultRebalanceFields(
dex,
tokenMintA,
tokenMintB,
tickSpacing,
rebalanceMethod
);
const defaultParameters: VaultParameters = {
dex,
tokenMintA,
tokenMintB,
feeTier,
rebalancingParameters,
};
return defaultParameters;
};
/**
* Retunrs what type of rebalance method the fields represent
*/
getRebalanceTypeFromRebalanceFields = (rebalanceFields: RebalanceFieldInfo[]): RebalanceTypeKind => {
return getRebalanceTypeFromRebalanceFields(rebalanceFields);
};
/**
* Retunrs the rebalance method the fields represent with more details (description, enabled, etc)
*/
getRebalanceMethodFromRebalanceFields = (rebalanceFields: RebalanceFieldInfo[]): RebalanceMethod => {
return getRebalanceMethodFromRebalanceFields(rebalanceFields);
};
getReferencePriceTypeForStrategy = async (strategy: PublicKey | StrategyWithAddress): Promise<PriceReferenceType> => {
const strategyWithAddress = await this.getStrategyStateIfNotFetched(strategy);
return numberToReferencePriceType(strategyWithAddress.strategy.rebalanceRaw.referencePriceType);
};
getFieldsForRebalanceMethod = (
rebalanceMethod: RebalanceMethod,
dex: Dex,
fieldOverrides: RebalanceFieldInfo[],
tokenAMint: PublicKey,
tokenBMint: PublicKey,
tickSpacing: number,
poolPrice?: Decimal
): Promise<RebalanceFieldInfo[]> => {
switch (rebalanceMethod) {
case ManualRebalanceMethod:
return this.getFieldsForManualRebalanceMethod(dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice);
case PricePercentageRebalanceMethod:
return this.getFieldsForPricePercentageMethod(dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice);
case PricePercentageWithResetRangeRebalanceMethod:
return this.getFieldsForPricePercentageWithResetMethod(dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice);
case DriftRebalanceMethod:
return this.getFieldsForDriftRebalanceMethod(
dex,
fieldOverrides,
tickSpacing,
tokenAMint,
tokenBMint,
poolPrice
);
case TakeProfitMethod:
return this.getFieldsForTakeProfitRebalanceMethod(dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice);
case PeriodicRebalanceMethod:
return this.getFieldsForPeriodicRebalanceMethod(dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice);
case ExpanderMethod:
return this.getFieldsForExpanderRebalanceMethod(dex, fieldOverrides, tokenAMint, tokenBMint, poolPrice);
case AutodriftMethod:
return this.getFieldsForAutodriftRebalanceMethod(
dex,
fieldOverrides,
tokenAMint,
tokenBMint,
tickSpacing,
poolPrice
);
default:
throw new Error(`Rebalance method ${rebalanceMethod} is not supported`);
}
};
getFieldsForManualRebalanceMethod = async (
dex: Dex,
fieldOverrides: RebalanceFieldInfo[],
tokenAMint: PublicKey,
tokenBMint: PublicKey,
poolPrice?: Decimal
): Promise<RebalanceFieldInfo[]> => {
const price = poolPrice ? poolPrice : new Decimal(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = 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 getManualRebalanceFieldInfos(new Decimal(lowerPrice), new Decimal(upperPrice));
};
getFieldsForPricePercentageMethod = async (
dex: Dex,
fieldOverrides: RebalanceFieldInfo[],
tokenAMint: PublicKey,
tokenBMint: PublicKey,
poolPrice?: Decimal
): Promise<RebalanceFieldInfo[]> => {
const price = poolPrice ? poolPrice : new Decimal(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = 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 getPricePercentageRebalanceFieldInfos(
price,
new Decimal(lowerPriceDifferenceBPS),
new Decimal(upperPriceDifferenceBPS)
);
};
getFieldsForPricePercentageWithResetMethod = async (
dex: Dex,
fieldOverrides: RebalanceFieldInfo[],
tokenAMint: PublicKey,
tokenBMint: PublicKey,
poolPrice?: Decimal
): Promise<RebalanceFieldInfo[]> => {
const price = poolPrice ? poolPrice : new Decimal(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = 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 getPricePercentageWithResetRebalanceFieldInfos(
price,
new Decimal(lowerPriceDifferenceBPS),
new Decimal(upperPriceDifferenceBPS),
new Decimal(lowerResetPriceDifferenceBPS),
new Decimal(upperResetPriceDifferenceBPS)
);
};
getFieldsForDriftRebalanceMethod = async (
dex: Dex,
fieldOverrides: RebalanceFieldInfo[],
tickSpacing: number,
tokenAMint: PublicKey,
tokenBMint: PublicKey,
poolPrice?: Decimal
): Promise<RebalanceFieldInfo[]> => {
const tokenADecimals = await getMintDecimals(this._connection, tokenAMint);
const tokenBDecimals = await getMintDecimals(this._connection, tokenBMint);
const price = poolPrice ? poolPrice : new Decimal(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = 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 = getDriftRebalanceFieldInfos(
dex,
tokenADecimals,
tokenBDecimals,
tickSpacing,
new Decimal(startMidTick),
new Decimal(ticksBelowMid),
new Decimal(ticksAboveMid),
new Decimal(secondsPerTick),
new Decimal(direction)
);
return fieldInfos;
};
getFieldsForTakeProfitRebalanceMethod = async (
dex: Dex,
fieldOverrides: RebalanceFieldInfo[],
tokenAMint: PublicKey,
tokenBMint: PublicKey,
poolPrice?: Decimal
): Promise<RebalanceFieldInfo[]> => {
const price = poolPrice ? poolPrice : new Decimal(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = 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 getTakeProfitRebalanceFieldsInfos(
new Decimal(lowerRangePrice),
new Decimal(upperRangePrice),
new Decimal(destinationToken),
true
);
};
getFieldsForPeriodicRebalanceMethod = async (
dex: Dex,
fieldOverrides: RebalanceFieldInfo[],
tokenAMint: PublicKey,
tokenBMint: PublicKey,
poolPrice?: Decimal
): Promise<RebalanceFieldInfo[]> => {
const price = poolPrice ? poolPrice : new Decimal(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = getDefaultPeriodicRebalanceFieldInfos(price);
let period: Decimal = new Decimal(defaultFields.find((x) => x.label == 'period')!.value);
const periodInput = fieldOverrides.find((x) => x.label == 'period');
if (periodInput) {
period = new Decimal(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 getPeriodicRebalanceRebalanceFieldInfos(
price,
period,
new Decimal(lowerPriceDifferenceBPS),
new Decimal(upperPriceDifferenceBPS)
);
};
getFieldsForExpanderRebalanceMethod = async (
dex: Dex,
fieldOverrides: RebalanceFieldInfo[],
tokenAMint: PublicKey,
tokenBMint: PublicKey,
poolPrice?: Decimal
): Promise<RebalanceFieldInfo[]> => {
const price = poolPrice ? poolPrice : new Decimal(await this.getPriceForPair(dex, tokenAMint, tokenBMint));
const defaultFields = 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 getExpanderRebalanceFieldInfos(
price,
new Decimal(lowerPriceDifferenceBPS),
new Decimal(upperPriceDifferenceBPS),
new Decimal(lowerResetPriceDifferenceBPS),
new Decimal(upperResetPriceDifferenceBPS),
new Decimal(expansionBPS),
new Decimal(maxNumberOfExpansions),
new Decimal(swapUnevenAllowed)
);
};
getFieldsForAutodriftRebalanceMethod = async (
dex: Dex,
fieldOverrides: RebalanceFieldInfo[],
tokenAMint: PublicKey,
tokenBMint: PublicKey,
tickSpacing: number,
poolPrice?: Decimal
): Promise<RebalanceFieldInfo[]> => {
const tokenADecimals = await getMintDecimals(this._connection, tokenAMint);
const tokenBDecimals = await getMintDecimals(this._connection, tokenBMint);
const price = poolPrice ? poolPrice : new Decimal(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 = 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 = getAutodriftRebalanceFieldInfos(
dex,
tokenADecimals,
tokenBDecimals,
tickSpacing,
new Decimal(lastMidTick),
new Decimal(initDriftTicksPerEpoch),
new Decimal(ticksBelowMid),
new Decimal(ticksAboveMid),
new Decimal(frontrunMultiplierBps),
new Decimal(stakingRateASource),
new Decimal(stakingRateBSource),
new Decimal(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: Dex, poolTokenA: PublicKey, poolTokenB: PublicKey): Promise<number> => {
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 getMintDecimals(this._connection, poolTokenA);
const decimalsY = await getMintDecimals(this._connection, poolTokenB);
return getPriceOfBinByBinIdWithDecimals(
pools[0].pool.activeId,
pools[0].pool.binStep,
decimalsX,
decimalsY
).toNumber();
} else {
throw new Error(`Dex ${dex} is not supported`);
}
};
getDefaultRebalanceFields = async (
dex: Dex,
poolTokenA: PublicKey,
poolTokenB: PublicKey,
tickSpacing: number,
rebalanceMethod: RebalanceMethod
): Promise<RebalanceFieldInfo[]> => {
const price = new Decimal(await this.getPriceForPair(dex, poolTokenA, poolTokenB));
const tokenADecimals = await getMintDecimals(this._connection, poolTokenA);
const tokenBDecimals = await getMintDecimals(this._connection, poolTokenB);
switch (rebalanceMethod) {
case ManualRebalanceMethod:
return getDefaultManualRebalanceFieldInfos(price);
case PricePercentageRebalanceMethod:
return getDefaultPricePercentageRebalanceFieldInfos(price);
case PricePercentageWithResetRangeRebalanceMethod:
return getDefaultPricePercentageWithResetRebalanceFieldInfos(price);
case DriftRebalanceMethod:
return getDefaultDriftRebalanceFieldInfos(dex, tickSpacing, price, tokenADecimals, tokenBDecimals);
case TakeProfitMethod:
return getDefaultTakeProfitRebalanceFieldsInfos(price);
case PeriodicRebalanceMethod:
return getDefaultPeriodicRebalanceFieldInfos(price);
case ExpanderMethod:
return getDefaultExpanderRebalanceFieldInfos(price);
case AutodriftMethod:
return 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: Dex,
poolTokenA: PublicKey,
poolTokenB: PublicKey,
feeBPS: Decimal
): Promise<PublicKey> => {
if (dex == 'ORCA') {
let pool = PublicKey.default;
const orcaPools = await this.getOrcaPoolsForTokens(poolTokenA, poolTokenB);
orcaPools.forEach((element) => {
if (element.lpFeeRate * FullBPS == feeBPS.toNumber()) {
pool = new PublicKey(element.address);
}
});
return pool;
} else if (dex == 'RAYDIUM') {
const pools: Pool[] = [];
const raydiumPools = await this.getRaydiumPoolsForTokens(poolTokenA, poolTokenB);
raydiumPools.forEach((element) => {
if (new Decimal(element.ammConfig.tradeFeeRate).div(FullBPS).div(FullPercentage).equals(feeBPS.div(FullBPS))) {
pools.push(element);
}
});
if (pools.length == 0) {
return PublicKey.default;
}
let pool = PublicKey.default;
let tickSpacing = Number.MAX_VALUE;
pools.forEach((element) => {
if (element.ammConfig.tickSpacing < tickSpacing) {
pool = new PublicKey(element.id);
tickSpacing = element.ammConfig.tickSpacing;
}
});
return pool;
} else if (dex == 'METEORA') {
let pool = 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 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: Dex, tokenMintA: PublicKey, tokenMintB: PublicKey): Promise<GenericPoolInfo[]> {
if (dex == 'ORCA') {
const pools = await this.getOrcaPoolsForTokens(tokenMintA, tokenMintB);
const genericPoolInfos: GenericPoolInfo[] = await Promise.all(
pools.map(async (pool: OrcaPool) => {
const positionsCount = new Decimal(await this.getPositionsCountForPool(dex, new PublicKey(pool.address)));
// read price from pool
const poolData = await this._orcaService.getPool(new PublicKey(pool.address));
if (!poolData) {
throw new Error(`Pool ${pool.address} not found`);
}
const poolInfo: GenericPoolInfo = {
dex,
address: new PublicKey(pool.address),
price: poolData.price,
tokenMintA: new PublicKey(pool.tokenA.mint),
tokenMintB: new PublicKey(pool.tokenB.mint),
tvl: pool.tvl ? new Decimal(pool.tvl) : undefined,
feeRate: new Decimal(pool.lpFeeRate).mul(FullBPS),
volumeOnLast7d: pool.volume ? new Decimal(pool.volume.week) : undefined,
tickSpacing: new Decimal(pool.tickSpacing),
positions: positionsCount,
};
return poolInfo;
})
);
return genericPoolInfos;
} else if (dex == 'RAYDIUM') {
const pools = await this.getRaydiumPoolsForTokens(tokenMintA, tokenMintB);
const genericPoolInfos: GenericPoolInfo[] = await Promise.all(
pools.map(async (pool: Pool) => {
const positionsCount = new Decimal(await this.getPositionsCountForPool(dex, new PublicKey(pool.id)));
const poolInfo: GenericPoolInfo = {
dex,
address: new PublicKey(pool.id),
price: new Decimal(pool.price),
tokenMintA: new PublicKey(pool.mintA),
tokenMintB: new PublicKey(pool.mintB),
tvl: new Decimal(pool.tvl),
feeRate: new Decimal(pool.ammConfig.tradeFeeRate).div(new Decimal(FullPercentage)),
volumeOnLast7d: new Decimal(pool.week.volume),
tickSpacing: new Decimal(pool.ammConfig.tickSpacing),
positions: positionsCount,
};
return poolInfo;
})
);
return genericPoolInfos;
} else if (dex == 'METEORA') {
const pools = await this.getMeteoraPoolsForTokens(tokenMintA, tokenMintB);
const genericPoolInfos: GenericPoolInfo[] = await Promise.all(
pools.map(async (pool: MeteoraPool) => {
const positionsCount = new Decimal(await this.getPositionsCountForPool(dex, pool.key));
const decimalsX = await getMintDecimals(this._connection, pool.pool.tokenXMint);
const decimalsY = await getMintDecimals(this._connection, pool.pool.tokenYMint);
const price = getPriceOfBinByBinIdWithDecimals(pool.pool.activeId, pool.pool.binStep, decimalsX, decimalsY);
const poolInfo: GenericPoolInfo = {
dex,
address: pool.key,
price,
tokenMintA: pool.pool.tokenXMint,
tokenMintB: pool.pool.tokenYMint,
tvl: new Decimal(0),
feeRate: computeMeteoraFee(pool.pool).div(1e2), // Transform it to rate
volumeOnLast7d: new Decimal(0),
tickSpacing: new Decimal(pool.pool.binStep),
positions: positionsCount,
};
return poolInfo;
})
);
return genericPoolInfos;
} else {
throw new Error(`Dex ${dex} is not supported`);
}
}
getOrcaPoolsForTokens = async (poolTokenA: PublicKey, poolTokenB: PublicKey): Promise<OrcaPool[]> => {
const pools: OrcaPool[] = [];
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: PublicKey, poolTokenB: PublicKey): Promise<Pool[]> => {
const pools: Pool[] = [];
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: PublicKey, poolTokenB: PublicKey): Promise<MeteoraPool[]> => {
const pools: MeteoraPool[] = [];
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?: Array<PublicKey>): Promise<Array<WhirlpoolStrategy | null>> => {
if (!strategies) {
strategies = (await this.getAllStrategiesWithFilters({})).map((x) => x.address);
}
return await 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?: Array<PublicKey>): Promise<Array<StrategyWithAddress>> => {
if (!strategies) {
return this.getAllStrategiesWithFilters({});
}
const result: StrategyWithAddress[] = [];
const states = await 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: StrategiesFilters): Promise<Array<StrategyWithAddress>> => {
const filters: GetProgramAccountsFilter[] = [];
filters.push({
dataSize: WhirlpoolStrategy.layout.span + 8,
});
filters.push({
memcmp: {
offset: 0,
bytes: bs58.encode(WhirlpoolStrategy.discriminator),
},
});
if (strategyFilters.owner) {
filters.push({
memcmp: {
bytes: strategyFilters.owner.toBase58(),
offset: 8,
},
});
}
if (strategyFilters.strategyCreationStatus) {
filters.push({
memcmp: {
bytes: strategyCreationStatusToBase58(strategyFilters.strategyCreationStatus),
offset: 1625,
},
});
}
if (strategyFilters.strategyType) {
filters.push({
memcmp: {
bytes: 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: StrategyWithAddress = {
strategy: WhirlpoolStrategy.decode(x.account.data),
address: x.pubkey,
};
return res;
});
};
/**
* Get a Kamino whirlpool strategy by its public key address
* @param address
*/
getStrategyByAddress = (address: PublicKey) => this.getWhirlpoolStrategy(address);
/**
* Get a Kamino whirlpool strategy by its kToken mint address
* @param kTokenMint - mint address of the kToken
*/
getStrategyByKTokenMint = async (kTokenMint: PublicKey): Promise<StrategyWithAddress | null> => {
const matchingStrategies = await this._connection.getProgramAccounts(this._kaminoProgram.programId, {
filters: [
{
dataSize: WhirlpoolStrategy.layout.span + 8,
},
{
memcmp: {
offset: 0,
bytes: bs58.encode(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: ${