@kamino-finance/klend-sdk 7.0.3 → 7.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -125,6 +125,7 @@ import { getExtendLookupTableInstruction } from '@solana-program/address-lookup-
125
125
  import { Farms } from '@kamino-finance/farms-sdk';
126
126
  import { getFarmIncentives } from '@kamino-finance/farms-sdk/dist/utils/apy';
127
127
  import { computeReservesAllocation } from '../utils/vaultAllocation';
128
+ import { getReserveFarmRewardsAPY } from '../utils/farmUtils';
128
129
 
129
130
  export const kaminoVaultId = address('KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd');
130
131
  export const kaminoVaultStagingId = address('stKvQfwRsQiKnLtMNVLHKS3exFJmZFsgfzBPWHECUYK');
@@ -2065,19 +2066,21 @@ export class KaminoVaultClient {
2065
2066
 
2066
2067
  /**
2067
2068
  * This method calculates the token per share value. This will always change based on interest earned from the vault, but calculating it requires a bunch of rpc requests. Caching this for a short duration would be optimal
2068
- * @param vault - vault to calculate tokensPerShare for
2069
+ * @param vaultState - vault state to calculate tokensPerShare for
2069
2070
  * @param [slot] - the slot at which we retrieve the tokens per share. Optional. If not provided, the function will fetch the current slot
2070
2071
  * @param [vaultReservesMap] - hashmap from each reserve pubkey to the reserve state. Optional. If provided the function will be significantly faster as it will not have to fetch the reserves
2071
2072
  * @param [currentSlot] - the latest confirmed slot. Optional. If provided the function will be faster as it will not have to fetch the latest slot
2072
2073
  * @returns - token per share value
2073
2074
  */
2074
2075
  async getTokensPerShareSingleVault(
2075
- vault: KaminoVault,
2076
+ vaultOrState: KaminoVault | VaultState,
2076
2077
  slot?: Slot,
2077
2078
  vaultReservesMap?: Map<Address, KaminoReserve>,
2078
2079
  currentSlot?: Slot
2079
2080
  ): Promise<Decimal> {
2080
- const vaultState = await vault.getState(this.getConnection());
2081
+ // Determine if we have a KaminoVault or VaultState
2082
+ const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState(this.getConnection()) : vaultOrState;
2083
+
2081
2084
  if (vaultState.sharesIssued.isZero()) {
2082
2085
  return new Decimal(0);
2083
2086
  }
@@ -2649,16 +2652,16 @@ export class KaminoVaultClient {
2649
2652
  /**
2650
2653
  * This will return an VaultOverview object that encapsulates all the information about the vault, including the holdings, reserves details, theoretical APY, utilization ratio and total borrowed amount
2651
2654
  * @param vault - the kamino vault to get available liquidity to withdraw for
2652
- * @param price - the price of the token in the vault (e.g. USDC)
2655
+ * @param vaultTokenPrice - the price of the token in the vault (e.g. USDC)
2653
2656
  * @param [slot] - the slot for which to retrieve the vault overview for. Optional. If not provided the function will fetch the current slot
2654
2657
  * @param [vaultReservesMap] - hashmap from each reserve pubkey to the reserve state. Optional. If provided the function will be significantly faster as it will not have to fetch the reserves
2655
2658
  * @param [kaminoMarkets] - a list of all kamino markets. Optional. If provided the function will be significantly faster as it will not have to fetch the markets
2656
2659
  * @param [currentSlot] - the latest confirmed slot. Optional. If provided the function will be faster as it will not have to fetch the latest slot
2657
- * @returns an VaultOverview object with details about the tokens available and invested in the vault, denominated in tokens and USD
2660
+ * @returns an VaultOverview object with details about the tokens available and invested in the vault, denominated in tokens and USD, along sie APYs
2658
2661
  */
2659
2662
  async getVaultOverview(
2660
2663
  vault: VaultState,
2661
- price: Decimal,
2664
+ vaultTokenPrice: Decimal,
2662
2665
  slot?: Slot,
2663
2666
  vaultReservesMap?: Map<Address, KaminoReserve>,
2664
2667
  kaminoMarkets?: KaminoMarket[],
@@ -2666,30 +2669,34 @@ export class KaminoVaultClient {
2666
2669
  ): Promise<VaultOverview> {
2667
2670
  const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vault);
2668
2671
 
2669
- const vaultHoldingsWithUSDValuePromise = await this.getVaultHoldingsWithPrice(
2672
+ const vaultHoldingsWithUSDValuePromise = this.getVaultHoldingsWithPrice(
2670
2673
  vault,
2671
- price,
2674
+ vaultTokenPrice,
2672
2675
  slot,
2673
2676
  vaultReservesState,
2674
2677
  currentSlot
2675
2678
  );
2676
2679
 
2677
2680
  const slotForOverview = slot ? slot : await this.getConnection().getSlot().send();
2681
+ const farmsClient = new Farms(this.getConnection());
2678
2682
 
2679
- const vaultTheoreticalAPYPromise = await this.getVaultTheoreticalAPY(vault, slotForOverview, vaultReservesState);
2680
- const vaultActualAPYPromise = await this.getVaultActualAPY(vault, slotForOverview, vaultReservesState);
2681
- const totalInvestedAndBorrowedPromise = await this.getTotalBorrowedAndInvested(
2683
+ const vaultTheoreticalAPYPromise = this.getVaultTheoreticalAPY(vault, slotForOverview, vaultReservesState);
2684
+ const vaultActualAPYPromise = this.getVaultActualAPY(vault, slotForOverview, vaultReservesState);
2685
+ const totalInvestedAndBorrowedPromise = this.getTotalBorrowedAndInvested(
2682
2686
  vault,
2683
2687
  slotForOverview,
2684
2688
  vaultReservesState
2685
2689
  );
2686
- const vaultCollateralsPromise = await this.getVaultCollaterals(
2690
+ const vaultCollateralsPromise = this.getVaultCollaterals(vault, slotForOverview, vaultReservesState, kaminoMarkets);
2691
+ const reservesOverviewPromise = this.getVaultReservesDetails(vault, slotForOverview, vaultReservesState);
2692
+ const vaultFarmIncentivesPromise = this.getVaultRewardsAPY(vault, vaultTokenPrice, farmsClient, slotForOverview);
2693
+ const vaultReservesFarmIncentivesPromise = this.getVaultReservesFarmsIncentives(
2687
2694
  vault,
2695
+ vaultTokenPrice,
2696
+ farmsClient,
2688
2697
  slotForOverview,
2689
- vaultReservesState,
2690
- kaminoMarkets
2698
+ vaultReservesState
2691
2699
  );
2692
- const reservesOverviewPromise = await this.getVaultReservesDetails(vault, slotForOverview, vaultReservesState);
2693
2700
 
2694
2701
  // all the async part of the functions above just read the vaultReservesState which is read beforehand, so excepting vaultCollateralsPromise they should do no additional network calls
2695
2702
  const [
@@ -2699,6 +2706,8 @@ export class KaminoVaultClient {
2699
2706
  totalInvestedAndBorrowed,
2700
2707
  vaultCollaterals,
2701
2708
  reservesOverview,
2709
+ vaultFarmIncentives,
2710
+ vaultReservesFarmIncentives,
2702
2711
  ] = await Promise.all([
2703
2712
  vaultHoldingsWithUSDValuePromise,
2704
2713
  vaultTheoreticalAPYPromise,
@@ -2706,6 +2715,8 @@ export class KaminoVaultClient {
2706
2715
  totalInvestedAndBorrowedPromise,
2707
2716
  vaultCollateralsPromise,
2708
2717
  reservesOverviewPromise,
2718
+ vaultFarmIncentivesPromise,
2719
+ vaultReservesFarmIncentivesPromise,
2709
2720
  ]);
2710
2721
 
2711
2722
  return {
@@ -2714,11 +2725,13 @@ export class KaminoVaultClient {
2714
2725
  vaultCollaterals: vaultCollaterals,
2715
2726
  actualSupplyAPY: vaultActualAPYs,
2716
2727
  theoreticalSupplyAPY: vaultTheoreticalAPYs,
2728
+ vaultFarmIncentives: vaultFarmIncentives,
2729
+ reservesFarmsIncentives: vaultReservesFarmIncentives,
2717
2730
  totalBorrowed: totalInvestedAndBorrowed.totalBorrowed,
2718
- totalBorrowedUSD: totalInvestedAndBorrowed.totalBorrowed.mul(price),
2731
+ totalBorrowedUSD: totalInvestedAndBorrowed.totalBorrowed.mul(vaultTokenPrice),
2719
2732
  utilizationRatio: totalInvestedAndBorrowed.utilizationRatio,
2720
2733
  totalSupplied: totalInvestedAndBorrowed.totalInvested,
2721
- totalSuppliedUSD: totalInvestedAndBorrowed.totalInvested.mul(price),
2734
+ totalSuppliedUSD: totalInvestedAndBorrowed.totalInvested.mul(vaultTokenPrice),
2722
2735
  };
2723
2736
  }
2724
2737
 
@@ -3032,11 +3045,18 @@ export class KaminoVaultClient {
3032
3045
  * Read the APY of the farm built on top of the vault (farm in vaultState.vaultFarm)
3033
3046
  * @param vault - the vault to read the farm APY for
3034
3047
  * @param vaultTokenPrice - the price of the vault token in USD (e.g. 1.0 for USDC)
3048
+ * @param [farmsClient] - the farms client to use. Optional. If not provided, the function will create a new one
3035
3049
  * @param [slot] - the slot to read the farm APY for. Optional. If not provided, the function will read the current slot
3036
3050
  * @returns the APY of the farm built on top of the vault
3037
3051
  */
3038
- async getVaultRewardsAPY(vault: KaminoVault, vaultTokenPrice: Decimal, slot?: Slot): Promise<FarmIncentives> {
3039
- const vaultState = await vault.getState(this.getConnection());
3052
+ async getVaultRewardsAPY(
3053
+ vaultOrState: KaminoVault | VaultState,
3054
+ vaultTokenPrice: Decimal,
3055
+ farmsClient?: Farms,
3056
+ slot?: Slot
3057
+ ): Promise<FarmIncentives> {
3058
+ // Determine if we have a KaminoVault or VaultState
3059
+ const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState(this.getConnection()) : vaultOrState;
3040
3060
  if (vaultState.vaultFarm === DEFAULT_PUBLIC_KEY) {
3041
3061
  return {
3042
3062
  incentivesStats: [],
@@ -3044,12 +3064,74 @@ export class KaminoVaultClient {
3044
3064
  };
3045
3065
  }
3046
3066
 
3047
- const tokensPerShare = await this.getTokensPerShareSingleVault(vault, slot);
3067
+ const tokensPerShare = await this.getTokensPerShareSingleVault(vaultState, slot);
3048
3068
  const sharePrice = tokensPerShare.mul(vaultTokenPrice);
3049
3069
  const stakedTokenMintDecimals = vaultState.sharesMintDecimals.toNumber();
3050
3070
 
3051
- const farmsClient = new Farms(this.getConnection());
3052
- return getFarmIncentives(farmsClient, vaultState.vaultFarm, sharePrice, stakedTokenMintDecimals);
3071
+ const kFarmsClient = farmsClient ? farmsClient : new Farms(this.getConnection());
3072
+ return getFarmIncentives(kFarmsClient, vaultState.vaultFarm, sharePrice, stakedTokenMintDecimals);
3073
+ }
3074
+
3075
+ async getVaultReservesFarmsIncentives(
3076
+ vaultOrState: KaminoVault | VaultState,
3077
+ vaultTokenPrice: Decimal,
3078
+ farmsClient?: Farms,
3079
+ slot?: Slot,
3080
+ vaultReservesMap?: Map<Address, KaminoReserve>
3081
+ ): Promise<VaultReservesFarmsIncentives> {
3082
+ const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState(this.getConnection()) : vaultOrState;
3083
+
3084
+ const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
3085
+ const currentSlot = slot ? slot : await this.getConnection().getSlot({ commitment: 'confirmed' }).send();
3086
+
3087
+ const holdings = await this.getVaultHoldings(vaultState, currentSlot, vaultReservesState);
3088
+
3089
+ const vaultReservesAddresses = vaultState.vaultAllocationStrategy.map(
3090
+ (allocationStrategy) => allocationStrategy.reserve
3091
+ );
3092
+
3093
+ const vaultReservesFarmsIncentives = new Map<Address, FarmIncentives>();
3094
+ let totalIncentivesApy = new Decimal(0);
3095
+
3096
+ const kFarmsClient = farmsClient ? farmsClient : new Farms(this.getConnection());
3097
+ for (const reserveAddress of vaultReservesAddresses) {
3098
+ if (reserveAddress === DEFAULT_PUBLIC_KEY) {
3099
+ continue;
3100
+ }
3101
+
3102
+ const reserveState = vaultReservesState.get(reserveAddress);
3103
+ if (reserveState === undefined) {
3104
+ console.log(`Reserve to read farm incentives for not found: ${reserveAddress}`);
3105
+ vaultReservesFarmsIncentives.set(reserveAddress, {
3106
+ incentivesStats: [],
3107
+ totalIncentivesApy: 0,
3108
+ });
3109
+ continue;
3110
+ }
3111
+
3112
+ const reserveFarmIncentives = await getReserveFarmRewardsAPY(
3113
+ this._rpc,
3114
+ this.recentSlotDurationMs,
3115
+ reserveAddress,
3116
+ vaultTokenPrice,
3117
+ this._kaminoLendProgramId,
3118
+ kFarmsClient,
3119
+ currentSlot,
3120
+ reserveState.state
3121
+ );
3122
+ vaultReservesFarmsIncentives.set(reserveAddress, reserveFarmIncentives.collateralFarmIncentives);
3123
+
3124
+ const investedInReserve = holdings.investedInReserves.get(reserveAddress);
3125
+ const weightedReserveAPY = new Decimal(reserveFarmIncentives.collateralFarmIncentives.totalIncentivesApy)
3126
+ .mul(investedInReserve ?? 0)
3127
+ .div(holdings.totalAUMIncludingFees);
3128
+ totalIncentivesApy = totalIncentivesApy.add(weightedReserveAPY);
3129
+ }
3130
+
3131
+ return {
3132
+ reserveFarmsIncentives: vaultReservesFarmsIncentives,
3133
+ totalIncentivesAPY: totalIncentivesApy,
3134
+ };
3053
3135
  }
3054
3136
 
3055
3137
  private appendRemainingAccountsForVaultReserves(
@@ -3286,6 +3368,8 @@ export type VaultOverview = {
3286
3368
  vaultCollaterals: Map<Address, MarketOverview>;
3287
3369
  theoreticalSupplyAPY: APYs;
3288
3370
  actualSupplyAPY: APYs;
3371
+ vaultFarmIncentives: FarmIncentives;
3372
+ reservesFarmsIncentives: VaultReservesFarmsIncentives;
3289
3373
  totalBorrowed: Decimal;
3290
3374
  totalBorrowedUSD: Decimal;
3291
3375
  totalSupplied: Decimal;
@@ -3293,6 +3377,11 @@ export type VaultOverview = {
3293
3377
  utilizationRatio: Decimal;
3294
3378
  };
3295
3379
 
3380
+ export type VaultReservesFarmsIncentives = {
3381
+ reserveFarmsIncentives: Map<Address, FarmIncentives>;
3382
+ totalIncentivesAPY: Decimal;
3383
+ };
3384
+
3296
3385
  export type VaultFeesPct = {
3297
3386
  managementFeePct: Decimal;
3298
3387
  performanceFeePct: Decimal;
@@ -1,5 +1,6 @@
1
1
  import { Address, generateKeyPairSigner, TransactionSigner } from '@solana/kit';
2
2
  import {
3
+ DEFAULT_RECENT_SLOT_DURATION_MS,
3
4
  initFarmsForReserve as initFarmsForReserveIx,
4
5
  KaminoMarket,
5
6
  lendingMarketAuthPda,
@@ -11,7 +12,6 @@ import { getCreateAccountInstruction, SYSTEM_PROGRAM_ADDRESS } from '@solana-pro
11
12
  import { SYSVAR_RENT_ADDRESS } from '@solana/sysvars';
12
13
  import { CliEnv, SendTxMode } from '../tx/CliEnv';
13
14
  import { processTx } from '../tx/processor';
14
- import { DEFAULT_RECENT_SLOT_DURATION_MS } from '@kamino-finance/klend-sdk';
15
15
 
16
16
  export async function initFarmsForReserve(
17
17
  env: CliEnv,
@@ -1318,6 +1318,10 @@ async function main() {
1318
1318
  );
1319
1319
 
1320
1320
  console.log('vaultOverview', vaultOverview);
1321
+ vaultOverview.reservesFarmsIncentives.reserveFarmsIncentives.forEach((incentive, reserveAddress) => {
1322
+ console.log('reserve ', reserveAddress);
1323
+ console.log('reserve incentive', incentive);
1324
+ });
1321
1325
  });
1322
1326
 
1323
1327
  commands
@@ -0,0 +1,73 @@
1
+ import { Address, Rpc, Slot, SolanaRpcApi } from '@solana/kit';
2
+ import { Decimal } from 'decimal.js';
3
+ import { FarmIncentives, Farms } from '@kamino-finance/farms-sdk';
4
+ import { getFarmIncentives } from '@kamino-finance/farms-sdk/dist/utils/apy';
5
+ import { DEFAULT_PUBLIC_KEY } from '@kamino-finance/farms-sdk';
6
+ import { Reserve } from '../@codegen/klend/accounts';
7
+ import { KaminoReserve } from '../lib';
8
+ import { getMintDecimals } from '@kamino-finance/kliquidity-sdk';
9
+
10
+ export interface ReserveIncentives {
11
+ collateralFarmIncentives: FarmIncentives;
12
+ debtFarmIncentives: FarmIncentives;
13
+ }
14
+
15
+ export async function getReserveFarmRewardsAPY(
16
+ rpc: Rpc<SolanaRpcApi>,
17
+ recentSlotDurationMs: number,
18
+ reserve: Address,
19
+ reserveLiquidityTokenPrice: Decimal,
20
+ kaminoLendProgramId: Address,
21
+ farmsClient: Farms,
22
+ slot: Slot,
23
+ reserveState?: Reserve,
24
+ cTokenMintDecimals?: number
25
+ ): Promise<ReserveIncentives> {
26
+ const reserveIncentives: ReserveIncentives = {
27
+ collateralFarmIncentives: {
28
+ incentivesStats: [],
29
+ totalIncentivesApy: 0,
30
+ },
31
+ debtFarmIncentives: {
32
+ incentivesStats: [],
33
+ totalIncentivesApy: 0,
34
+ },
35
+ };
36
+
37
+ const reserveAccount = reserveState ? reserveState : await Reserve.fetch(rpc, reserve, kaminoLendProgramId);
38
+ if (!reserveAccount) {
39
+ throw new Error(`Reserve ${reserve} not found`);
40
+ }
41
+
42
+ const kaminoReserve = await KaminoReserve.initializeFromAddress(reserve, rpc, recentSlotDurationMs, reserveAccount);
43
+
44
+ const farmCollateral = kaminoReserve.state.farmCollateral;
45
+ const farmDebt = kaminoReserve.state.farmDebt;
46
+
47
+ const stakedTokenMintDecimals = kaminoReserve.getMintDecimals();
48
+ const reserveCtokenPrice = reserveLiquidityTokenPrice.div(kaminoReserve.getEstimatedCollateralExchangeRate(slot, 0));
49
+ const cTokenMint = kaminoReserve.getCTokenMint();
50
+ const cTokenDecimals = cTokenMintDecimals ? cTokenMintDecimals : await getMintDecimals(rpc, cTokenMint);
51
+
52
+ if (farmCollateral !== DEFAULT_PUBLIC_KEY) {
53
+ const farmIncentivesCollateral = await getFarmIncentives(
54
+ farmsClient,
55
+ farmCollateral,
56
+ reserveCtokenPrice,
57
+ cTokenDecimals
58
+ );
59
+ reserveIncentives.collateralFarmIncentives = farmIncentivesCollateral;
60
+ }
61
+
62
+ if (farmDebt !== DEFAULT_PUBLIC_KEY) {
63
+ const farmIncentivesDebt = await getFarmIncentives(
64
+ farmsClient,
65
+ farmDebt,
66
+ reserveLiquidityTokenPrice,
67
+ stakedTokenMintDecimals
68
+ );
69
+ reserveIncentives.debtFarmIncentives = farmIncentivesDebt;
70
+ }
71
+
72
+ return reserveIncentives;
73
+ }