@kamino-finance/klend-sdk 7.1.10 → 7.2.0

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.
@@ -7,7 +7,6 @@ import {
7
7
  Base58EncodedBytes,
8
8
  fetchEncodedAccount,
9
9
  generateKeyPairSigner,
10
- GetAccountInfoApi,
11
10
  getAddressEncoder,
12
11
  getBase58Decoder,
13
12
  GetProgramAccountsDatasizeFilter,
@@ -78,6 +77,7 @@ import { PROGRAM_ID } from '../@codegen/klend/programId';
78
77
  import { ReserveWithAddress } from './reserve';
79
78
  import { Fraction } from './fraction';
80
79
  import {
80
+ CDN_ENDPOINT,
81
81
  createAtasIdempotent,
82
82
  createWsolAtaIfMissing,
83
83
  getAllStandardTokenProgramTokenAccounts,
@@ -107,12 +107,13 @@ import {
107
107
  } from './vault_types';
108
108
  import { batchFetch, collToLamportsDecimal, ZERO } from '@kamino-finance/kliquidity-sdk';
109
109
  import { FullBPSDecimal } from '@kamino-finance/kliquidity-sdk/dist/utils/CreationParameters';
110
- import { FarmIncentives, FarmState } from '@kamino-finance/farms-sdk/dist';
110
+ import { FarmIncentives, FarmState, getUserStatePDA } from '@kamino-finance/farms-sdk/dist';
111
111
  import { getAccountsInLut, initLookupTableIx, insertIntoLookupTableIxs } from '../utils/lookupTable';
112
112
  import {
113
113
  getFarmStakeIxs,
114
114
  getFarmUnstakeAndWithdrawIxs,
115
115
  getSharesInFarmUserPosition,
116
+ getUserPendingRewardsInFarm,
116
117
  getUserSharesInTokensStakedInFarm,
117
118
  } from './farm_utils';
118
119
  import { getCreateAccountInstruction, SYSTEM_PROGRAM_ADDRESS } from '@solana-program/system';
@@ -173,6 +174,10 @@ export class KaminoVaultClient {
173
174
  return this._kaminoVaultProgramId;
174
175
  }
175
176
 
177
+ getRpc() {
178
+ return this._rpc;
179
+ }
180
+
176
181
  hasFarm() {
177
182
  return;
178
183
  }
@@ -191,7 +196,7 @@ export class KaminoVaultClient {
191
196
  return;
192
197
  }
193
198
 
194
- const kaminoVault = new KaminoVault(vaultPubkey, vault, this._kaminoVaultProgramId);
199
+ const kaminoVault = KaminoVault.loadWithClientAndState(this, vaultPubkey, vault);
195
200
  const vaultName = this.decodeVaultName(vault.name);
196
201
  const slot = await this.getConnection().getSlot({ commitment: 'confirmed' }).send();
197
202
  const tokensPerShare = await this.getTokensPerShareSingleVault(kaminoVault, slot);
@@ -203,7 +208,7 @@ export class KaminoVaultClient {
203
208
 
204
209
  console.log('Name: ', vaultName);
205
210
  console.log('Shares issued: ', sharesIssued);
206
- printHoldings(holdings);
211
+ holdings.print();
207
212
  console.log('Tokens per share: ', tokensPerShare);
208
213
  }
209
214
 
@@ -408,7 +413,7 @@ export class KaminoVaultClient {
408
413
  reserveAllocationConfig: ReserveAllocationConfig,
409
414
  vaultAdminAuthority?: TransactionSigner
410
415
  ): Promise<UpdateReserveAllocationIxs> {
411
- const vaultState: VaultState = await vault.getState(this.getConnection());
416
+ const vaultState: VaultState = await vault.getState();
412
417
  const reserveState: Reserve = reserveAllocationConfig.getReserveState();
413
418
 
414
419
  const cTokenVault = await getCTokenVaultPda(
@@ -480,7 +485,7 @@ export class KaminoVaultClient {
480
485
  unallocatedWeight?: BN,
481
486
  unallocatedCap?: BN
482
487
  ) {
483
- const vaultState = await vault.getState(this.getConnection());
488
+ const vaultState = await vault.getState();
484
489
 
485
490
  const unallocatedWeightToUse = unallocatedWeight ? unallocatedWeight : vaultState.unallocatedWeight;
486
491
  const unallocatedCapToUse = unallocatedCap ? unallocatedCap : vaultState.unallocatedTokensCap;
@@ -522,7 +527,7 @@ export class KaminoVaultClient {
522
527
  reserve: Address,
523
528
  vaultAdminAuthority?: TransactionSigner
524
529
  ): Promise<WithdrawAndBlockReserveIxs> {
525
- const vaultState = await vault.getState(this.getConnection());
530
+ const vaultState = await vault.getState();
526
531
 
527
532
  const reserveIsPartOfAllocation = vaultState.vaultAllocationStrategy.some(
528
533
  (allocation) => allocation.reserve === reserve
@@ -570,7 +575,7 @@ export class KaminoVaultClient {
570
575
  vaultReservesMap?: Map<Address, KaminoReserve>,
571
576
  payer?: TransactionSigner
572
577
  ): Promise<WithdrawAndBlockReserveIxs> {
573
- const vaultState = await vault.getState(this.getConnection());
578
+ const vaultState = await vault.getState();
574
579
 
575
580
  const reserves = this.getVaultReserves(vaultState);
576
581
  const withdrawAndBlockReserveIxs: WithdrawAndBlockReserveIxs = {
@@ -613,7 +618,7 @@ export class KaminoVaultClient {
613
618
  vaultReservesMap?: Map<Address, KaminoReserve>,
614
619
  payer?: TransactionSigner
615
620
  ): Promise<DisinvestAllReservesIxs> {
616
- const vaultState = await vault.getState(this.getConnection());
621
+ const vaultState = await vault.getState();
617
622
 
618
623
  const reserves = this.getVaultReserves(vaultState);
619
624
  const disinvestAllReservesIxs: DisinvestAllReservesIxs = {
@@ -666,7 +671,7 @@ export class KaminoVaultClient {
666
671
  reserve: Address,
667
672
  vaultAdminAuthority?: TransactionSigner
668
673
  ): Promise<Instruction | undefined> {
669
- const vaultState = await vault.getState(this.getConnection());
674
+ const vaultState = await vault.getState();
670
675
  const vaultAdmin = parseVaultAdmin(vaultState, vaultAdminAuthority);
671
676
 
672
677
  const reserveIsPartOfAllocation = vaultState.vaultAllocationStrategy.some(
@@ -700,7 +705,7 @@ export class KaminoVaultClient {
700
705
  value: string,
701
706
  vaultAdminAuthority?: TransactionSigner
702
707
  ): Promise<UpdateVaultConfigIxs> {
703
- const vaultState: VaultState = await vault.getState(this.getConnection());
708
+ const vaultState: VaultState = await vault.getState();
704
709
  const admin = parseVaultAdmin(vaultState, vaultAdminAuthority);
705
710
 
706
711
  const updateVaultConfigAccs: UpdateVaultConfigAccounts = {
@@ -802,7 +807,7 @@ export class KaminoVaultClient {
802
807
  errorOnOverride: boolean = true,
803
808
  vaultAdminAuthority?: TransactionSigner
804
809
  ): Promise<UpdateVaultConfigIxs> {
805
- const vaultHasFarm = await vault.hasFarm(this.getConnection());
810
+ const vaultHasFarm = await vault.hasFarm();
806
811
  if (vaultHasFarm && errorOnOverride) {
807
812
  throw new Error('Vault already has a farm, if you want to override it set errorOnOverride to false');
808
813
  }
@@ -868,7 +873,7 @@ export class KaminoVaultClient {
868
873
  vault: KaminoVault,
869
874
  pendingAdmin?: TransactionSigner
870
875
  ): Promise<AcceptVaultOwnershipIxs> {
871
- const vaultState: VaultState = await vault.getState(this.getConnection());
876
+ const vaultState: VaultState = await vault.getState();
872
877
  const signer = parseVaultPendingAdmin(vaultState, pendingAdmin);
873
878
 
874
879
  const acceptOwneshipAccounts: UpdateAdminAccounts = {
@@ -929,7 +934,7 @@ export class KaminoVaultClient {
929
934
  maxAmountToGiveUp: Decimal,
930
935
  vaultAdminAuthority?: TransactionSigner
931
936
  ): Promise<Instruction> {
932
- const vaultState: VaultState = await vault.getState(this.getConnection());
937
+ const vaultState: VaultState = await vault.getState();
933
938
  const vaultAdmin = parseVaultAdmin(vaultState, vaultAdminAuthority);
934
939
 
935
940
  const giveUpPendingFeesAccounts: GiveUpPendingFeesAccounts = {
@@ -964,7 +969,7 @@ export class KaminoVaultClient {
964
969
  vaultReservesMap?: Map<Address, KaminoReserve>,
965
970
  vaultAdminAuthority?: TransactionSigner
966
971
  ): Promise<Instruction[]> {
967
- const vaultState: VaultState = await vault.getState(this.getConnection());
972
+ const vaultState: VaultState = await vault.getState();
968
973
  const vaultAdmin = parseVaultAdmin(vaultState, vaultAdminAuthority);
969
974
  const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
970
975
  const [{ ata: adminTokenAta, createAtaIx }] = await createAtasIdempotent(vaultAdmin, [
@@ -1057,7 +1062,7 @@ export class KaminoVaultClient {
1057
1062
  vaultReservesMap?: Map<Address, KaminoReserve>,
1058
1063
  farmState?: FarmState
1059
1064
  ): Promise<DepositIxs> {
1060
- const vaultState = await vault.getState(this.getConnection());
1065
+ const vaultState = await vault.getState();
1061
1066
 
1062
1067
  const tokenProgramID = vaultState.tokenProgram;
1063
1068
  const userTokenAta = await getAssociatedTokenAddress(vaultState.tokenMint, user.address, tokenProgramID);
@@ -1125,7 +1130,7 @@ export class KaminoVaultClient {
1125
1130
  };
1126
1131
 
1127
1132
  // if there is no farm, we can return the deposit instructions, otherwise include the stake ix in the response
1128
- if (!(await vault.hasFarm(this.getConnection()))) {
1133
+ if (!(await vault.hasFarm())) {
1129
1134
  return depositIxs;
1130
1135
  }
1131
1136
 
@@ -1149,7 +1154,7 @@ export class KaminoVaultClient {
1149
1154
  sharesAmount?: Decimal,
1150
1155
  farmState?: FarmState
1151
1156
  ): Promise<Instruction[]> {
1152
- const vaultState = await vault.getState(this.getConnection());
1157
+ const vaultState = await vault.getState();
1153
1158
 
1154
1159
  let sharesToStakeLamports = new Decimal(U64_MAX);
1155
1160
  if (sharesAmount) {
@@ -1157,7 +1162,7 @@ export class KaminoVaultClient {
1157
1162
  }
1158
1163
 
1159
1164
  // if tokens to be staked are 0 or vault has no farm there is no stake needed
1160
- if (sharesToStakeLamports.lte(0) || !(await vault.hasFarm(this.getConnection()))) {
1165
+ if (sharesToStakeLamports.lte(0) || !(await vault.hasFarm())) {
1161
1166
  return [];
1162
1167
  }
1163
1168
 
@@ -1183,9 +1188,8 @@ export class KaminoVaultClient {
1183
1188
  vaultReservesMap?: Map<Address, KaminoReserve>,
1184
1189
  farmState?: FarmState
1185
1190
  ): Promise<WithdrawIxs> {
1186
- const vaultState = await vault.getState(this.getConnection());
1187
- const kaminoVault = new KaminoVault(vault.address, vaultState, vault.programId);
1188
- const hasFarm = await vault.hasFarm(this.getConnection());
1191
+ const vaultState = await vault.getState();
1192
+ const hasFarm = await vault.hasFarm();
1189
1193
 
1190
1194
  const withdrawIxs: WithdrawIxs = {
1191
1195
  unstakeFromFarmIfNeededIxs: [],
@@ -1262,7 +1266,7 @@ export class KaminoVaultClient {
1262
1266
  if (vaultAllocation) {
1263
1267
  const withdrawFromVaultIxs = await this.withdrawWithReserveIxs(
1264
1268
  user,
1265
- kaminoVault,
1269
+ vault,
1266
1270
  sharesToWithdraw,
1267
1271
  totalUserShares,
1268
1272
  slot,
@@ -1270,7 +1274,7 @@ export class KaminoVaultClient {
1270
1274
  );
1271
1275
  withdrawIxs.withdrawIxs = withdrawFromVaultIxs;
1272
1276
  } else {
1273
- const withdrawFromVaultIxs = await this.withdrawFromAvailableIxs(user, kaminoVault, sharesToWithdraw);
1277
+ const withdrawFromVaultIxs = await this.withdrawFromAvailableIxs(user, vault, sharesToWithdraw);
1274
1278
  withdrawIxs.withdrawIxs = withdrawFromVaultIxs;
1275
1279
  }
1276
1280
 
@@ -1310,8 +1314,7 @@ export class KaminoVaultClient {
1310
1314
  vault: KaminoVault,
1311
1315
  shareAmount: Decimal
1312
1316
  ): Promise<Instruction[]> {
1313
- const vaultState = await vault.getState(this.getConnection());
1314
- const kaminoVault = new KaminoVault(vault.address, vaultState, vault.programId);
1317
+ const vaultState = await vault.getState();
1315
1318
 
1316
1319
  const userSharesAta = await getAssociatedTokenAddress(vaultState.sharesMint, user.address);
1317
1320
  const [{ ata: userTokenAta, createAtaIx }] = await createAtasIdempotent(user, [
@@ -1324,7 +1327,7 @@ export class KaminoVaultClient {
1324
1327
  const shareLamportsToWithdraw = collToLamportsDecimal(shareAmount, vaultState.sharesMintDecimals.toNumber());
1325
1328
  const withdrawFromAvailableIxn = await this.withdrawFromAvailableIx(
1326
1329
  user,
1327
- kaminoVault,
1330
+ vault,
1328
1331
  vaultState,
1329
1332
  userSharesAta,
1330
1333
  userTokenAta,
@@ -1342,7 +1345,7 @@ export class KaminoVaultClient {
1342
1345
  slot: Slot,
1343
1346
  vaultReservesMap?: Map<Address, KaminoReserve>
1344
1347
  ): Promise<Instruction[]> {
1345
- const vaultState = await vault.getState(this.getConnection());
1348
+ const vaultState = await vault.getState();
1346
1349
 
1347
1350
  const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
1348
1351
  const userSharesAta = await getAssociatedTokenAddress(vaultState.sharesMint, user.address);
@@ -1463,7 +1466,7 @@ export class KaminoVaultClient {
1463
1466
  vault: KaminoVault,
1464
1467
  skipComputationChecks: boolean = false
1465
1468
  ): Promise<Instruction[]> {
1466
- const vaultState = await vault.reloadState(this.getConnection());
1469
+ const vaultState = await vault.reloadState();
1467
1470
  const minInvestAmount = vaultState.minInvestAmount;
1468
1471
  const allReserves = this.getVaultReserves(vaultState);
1469
1472
  if (allReserves.length === 0) {
@@ -1576,7 +1579,7 @@ export class KaminoVaultClient {
1576
1579
  vaultReservesMap?: Map<Address, KaminoReserve>,
1577
1580
  createAtaIfNeeded: boolean = true
1578
1581
  ): Promise<Instruction[]> {
1579
- const vaultState = await vault.getState(this.getConnection());
1582
+ const vaultState = await vault.getState();
1580
1583
  const cTokenVault = await getCTokenVaultPda(vault.address, reserve.address, this._kaminoVaultProgramId);
1581
1584
  const [lendingMarketAuth] = await lendingMarketAuthPda(reserve.state.lendingMarket, this._kaminoLendProgramId);
1582
1585
 
@@ -1784,7 +1787,7 @@ export class KaminoVaultClient {
1784
1787
  vault: KaminoVault,
1785
1788
  vaultReservesMap?: Map<Address, KaminoReserve>
1786
1789
  ): Promise<SyncVaultLUTIxs> {
1787
- const vaultState = await vault.getState(this.getConnection());
1790
+ const vaultState = await vault.getState();
1788
1791
  const allAccountsToBeInserted = [
1789
1792
  vault.address,
1790
1793
  vaultState.vaultAdminAuthority,
@@ -1940,7 +1943,7 @@ export class KaminoVaultClient {
1940
1943
  * @returns - user share balance in tokens (not lamports)
1941
1944
  */
1942
1945
  async getUserSharesBalanceSingleVault(user: Address, vault: KaminoVault): Promise<UserSharesForVault> {
1943
- const vaultState = await vault.getState(this.getConnection());
1946
+ const vaultState = await vault.getState();
1944
1947
 
1945
1948
  const userShares: UserSharesForVault = {
1946
1949
  unstakedShares: new Decimal(0),
@@ -1964,7 +1967,7 @@ export class KaminoVaultClient {
1964
1967
  return acc;
1965
1968
  }, new Decimal(0));
1966
1969
 
1967
- if (await vault.hasFarm(this.getConnection())) {
1970
+ if (await vault.hasFarm()) {
1968
1971
  const userSharesInFarm = await getUserSharesInTokensStakedInFarm(
1969
1972
  this.getConnection(),
1970
1973
  user,
@@ -2019,7 +2022,7 @@ export class KaminoVaultClient {
2019
2022
  });
2020
2023
  userSharesTokenAccountsPerVault.set(vault.address, userSharesTokenAccounts);
2021
2024
 
2022
- if (await vault.hasFarm(this.getConnection())) {
2025
+ if (await vault.hasFarm()) {
2023
2026
  const userFarmState = allUserFarmStatesMap.get(state.vaultFarm);
2024
2027
  if (userFarmState) {
2025
2028
  console.log('there is a farm state for vault', vault.address);
@@ -2096,7 +2099,7 @@ export class KaminoVaultClient {
2096
2099
  currentSlot?: Slot
2097
2100
  ): Promise<Decimal> {
2098
2101
  // Determine if we have a KaminoVault or VaultState
2099
- const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState(this.getConnection()) : vaultOrState;
2102
+ const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState() : vaultOrState;
2100
2103
 
2101
2104
  if (vaultState.sharesIssued.isZero()) {
2102
2105
  return new Decimal(0);
@@ -2203,7 +2206,7 @@ export class KaminoVaultClient {
2203
2206
  throw Error(`kaminoVault with pubkey ${kaminoVault.address} could not be decoded`);
2204
2207
  }
2205
2208
 
2206
- return new KaminoVault(kaminoVault.address, kaminoVaultAccount, this._kaminoVaultProgramId);
2209
+ return KaminoVault.loadWithClientAndState(this, kaminoVault.address, kaminoVaultAccount);
2207
2210
  });
2208
2211
  }
2209
2212
 
@@ -2219,7 +2222,7 @@ export class KaminoVaultClient {
2219
2222
  const vaultStates = await batchFetch(vaults, (chunk) => this.getVaultsStates(chunk));
2220
2223
  return vaults.map((vault, index) => {
2221
2224
  const state = vaultStates[index];
2222
- return state ? new KaminoVault(vault, state, this._kaminoVaultProgramId) : null;
2225
+ return state ? KaminoVault.loadWithClientAndState(this, vault, state) : null;
2223
2226
  });
2224
2227
  }
2225
2228
 
@@ -2325,7 +2328,7 @@ export class KaminoVaultClient {
2325
2328
  slot: Slot,
2326
2329
  vaultReservesMap: Map<Address, KaminoReserve>
2327
2330
  ): Promise<Map<Address, Decimal>> {
2328
- const vaultState = await vault.getState(this.getConnection());
2331
+ const vaultState = await vault.getState();
2329
2332
 
2330
2333
  const reserveAllocationAvailableLiquidityToWithdraw = new Map<Address, Decimal>();
2331
2334
  vaultState.vaultAllocationStrategy.forEach((allocationStrategy) => {
@@ -2572,13 +2575,13 @@ export class KaminoVaultClient {
2572
2575
  vaultReserves?: Map<Address, KaminoReserve>,
2573
2576
  currentSlot?: Slot
2574
2577
  ): Promise<VaultHoldings> {
2575
- const vaultHoldings: VaultHoldings = {
2578
+ const vaultHoldings: VaultHoldings = new VaultHoldings({
2576
2579
  available: new Decimal(vault.tokenAvailable.toString()),
2577
2580
  invested: new Decimal(0),
2578
2581
  investedInReserves: new Map<Address, Decimal>(),
2579
2582
  totalAUMIncludingFees: new Decimal(0),
2580
2583
  pendingFees: new Decimal(0),
2581
- };
2584
+ });
2582
2585
 
2583
2586
  const currentSlotToUse = currentSlot ?? (await this.getConnection().getSlot({ commitment: 'confirmed' }).send());
2584
2587
  const vaultReservesState = vaultReserves ? vaultReserves : await this.loadVaultReserves(vault);
@@ -2643,13 +2646,13 @@ export class KaminoVaultClient {
2643
2646
  const totalAvailableDecimal = lamportsToDecimal(vaultHoldings.available, decimals);
2644
2647
  const totalInvestedDecimal = lamportsToDecimal(vaultHoldings.invested, decimals);
2645
2648
  const pendingFees = lamportsToDecimal(totalPendingFees, decimals);
2646
- return {
2649
+ return new VaultHoldings({
2647
2650
  available: totalAvailableDecimal,
2648
2651
  invested: totalInvestedDecimal,
2649
2652
  investedInReserves: vaultHoldings.investedInReserves,
2650
2653
  totalAUMIncludingFees: totalAvailableDecimal.add(totalInvestedDecimal),
2651
2654
  pendingFees: pendingFees,
2652
- };
2655
+ });
2653
2656
  }
2654
2657
 
2655
2658
  /**
@@ -2696,7 +2699,7 @@ export class KaminoVaultClient {
2696
2699
  * @returns an VaultOverview object with details about the tokens available and invested in the vault, denominated in tokens and USD, along sie APYs
2697
2700
  */
2698
2701
  async getVaultOverview(
2699
- vault: VaultState,
2702
+ vault: KaminoVault,
2700
2703
  vaultTokenPrice: Decimal,
2701
2704
  slot?: Slot,
2702
2705
  vaultReservesMap?: Map<Address, KaminoReserve>,
@@ -2704,10 +2707,11 @@ export class KaminoVaultClient {
2704
2707
  currentSlot?: Slot,
2705
2708
  tokensPrices?: Map<Address, Decimal>
2706
2709
  ): Promise<VaultOverview> {
2707
- const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vault);
2710
+ const vaultState = await vault.getState();
2711
+ const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
2708
2712
 
2709
2713
  const vaultHoldingsWithUSDValuePromise = this.getVaultHoldingsWithPrice(
2710
- vault,
2714
+ vaultState,
2711
2715
  vaultTokenPrice,
2712
2716
  slot,
2713
2717
  vaultReservesState,
@@ -2717,15 +2721,20 @@ export class KaminoVaultClient {
2717
2721
  const slotForOverview = slot ? slot : await this.getConnection().getSlot().send();
2718
2722
  const farmsClient = new Farms(this.getConnection());
2719
2723
 
2720
- const vaultTheoreticalAPYPromise = this.getVaultTheoreticalAPY(vault, slotForOverview, vaultReservesState);
2721
- const vaultActualAPYPromise = this.getVaultActualAPY(vault, slotForOverview, vaultReservesState);
2724
+ const vaultTheoreticalAPYPromise = this.getVaultTheoreticalAPY(vaultState, slotForOverview, vaultReservesState);
2725
+ const vaultActualAPYPromise = this.getVaultActualAPY(vaultState, slotForOverview, vaultReservesState);
2722
2726
  const totalInvestedAndBorrowedPromise = this.getTotalBorrowedAndInvested(
2723
- vault,
2727
+ vaultState,
2724
2728
  slotForOverview,
2725
2729
  vaultReservesState
2726
2730
  );
2727
- const vaultCollateralsPromise = this.getVaultCollaterals(vault, slotForOverview, vaultReservesState, kaminoMarkets);
2728
- const reservesOverviewPromise = this.getVaultReservesDetails(vault, slotForOverview, vaultReservesState);
2731
+ const vaultCollateralsPromise = this.getVaultCollaterals(
2732
+ vaultState,
2733
+ slotForOverview,
2734
+ vaultReservesState,
2735
+ kaminoMarkets
2736
+ );
2737
+ const reservesOverviewPromise = this.getVaultReservesDetails(vaultState, slotForOverview, vaultReservesState);
2729
2738
  const vaultFarmIncentivesPromise = this.getVaultRewardsAPY(
2730
2739
  vault,
2731
2740
  vaultTokenPrice,
@@ -2741,6 +2750,13 @@ export class KaminoVaultClient {
2741
2750
  vaultReservesState,
2742
2751
  tokensPrices
2743
2752
  );
2753
+ const vaultDelegatedFarmIncentivesPromise = this.getVaultDelegatedFarmRewardsAPY(
2754
+ vault,
2755
+ vaultTokenPrice,
2756
+ farmsClient,
2757
+ slotForOverview,
2758
+ tokensPrices
2759
+ );
2744
2760
 
2745
2761
  // 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
2746
2762
  const [
@@ -2752,6 +2768,7 @@ export class KaminoVaultClient {
2752
2768
  reservesOverview,
2753
2769
  vaultFarmIncentives,
2754
2770
  vaultReservesFarmIncentives,
2771
+ vaultDelegatedFarmIncentives,
2755
2772
  ] = await Promise.all([
2756
2773
  vaultHoldingsWithUSDValuePromise,
2757
2774
  vaultTheoreticalAPYPromise,
@@ -2761,6 +2778,7 @@ export class KaminoVaultClient {
2761
2778
  reservesOverviewPromise,
2762
2779
  vaultFarmIncentivesPromise,
2763
2780
  vaultReservesFarmIncentivesPromise,
2781
+ vaultDelegatedFarmIncentivesPromise,
2764
2782
  ]);
2765
2783
 
2766
2784
  return {
@@ -2771,6 +2789,7 @@ export class KaminoVaultClient {
2771
2789
  theoreticalSupplyAPY: vaultTheoreticalAPYs,
2772
2790
  vaultFarmIncentives: vaultFarmIncentives,
2773
2791
  reservesFarmsIncentives: vaultReservesFarmIncentives,
2792
+ delegatedFarmIncentives: vaultDelegatedFarmIncentives,
2774
2793
  totalBorrowed: totalInvestedAndBorrowed.totalBorrowed,
2775
2794
  totalBorrowedUSD: totalInvestedAndBorrowed.totalBorrowed.mul(vaultTokenPrice),
2776
2795
  utilizationRatio: totalInvestedAndBorrowed.utilizationRatio,
@@ -3080,11 +3099,64 @@ export class KaminoVaultClient {
3080
3099
  user: Address
3081
3100
  ): Promise<ProgramDerivedAddress> {
3082
3101
  return getProgramDerivedAddress({
3083
- seeds: [addressEncoder.encode(vault), addressEncoder.encode(reserve), addressEncoder.encode(user)],
3102
+ seeds: [addressEncoder.encode(reserve), addressEncoder.encode(vault), addressEncoder.encode(user)],
3084
3103
  programAddress: farmsProgramId,
3085
3104
  });
3086
3105
  }
3087
3106
 
3107
+ /**
3108
+ * Compute the delegatee PDA for the user farm state for a vault delegate farm
3109
+ * @param farmProgramID - the program ID of the farm program
3110
+ * @param vault - the address of the vault
3111
+ * @param farm - the address of the delegated farm
3112
+ * @param user - the address of the user
3113
+ * @returns the PDA of the delegatee user farm state for the delegated farm
3114
+ */
3115
+ async computeUserFarmStateDelegateePDAForUserInDelegatedVaultFarm(
3116
+ farmProgramID: Address,
3117
+ vault: Address,
3118
+ farm: Address,
3119
+ user: Address
3120
+ ): Promise<ProgramDerivedAddress> {
3121
+ return getProgramDerivedAddress({
3122
+ seeds: [addressEncoder.encode(vault), addressEncoder.encode(farm), addressEncoder.encode(user)],
3123
+ programAddress: farmProgramID,
3124
+ });
3125
+ }
3126
+
3127
+ /**
3128
+ * Compute the user state PDA for a user in a delegated vault farm
3129
+ * @param farmProgramID - the program ID of the farm program
3130
+ * @param vault - the address of the vault
3131
+ * @param farm - the address of the delegated farm
3132
+ * @param user - the address of the user
3133
+ * @returns the PDA of the user state for the delegated farm
3134
+ */
3135
+ async computeUserStatePDAForUserInDelegatedVaultFarm(
3136
+ farmProgramID: Address,
3137
+ vault: Address,
3138
+ farm: Address,
3139
+ user: Address
3140
+ ): Promise<Address> {
3141
+ const delegateePDA = await this.computeDelegateeForUserInDelegatedFarm(farmProgramID, vault, farm, user);
3142
+ return getUserStatePDA(farmProgramID, farm, delegateePDA);
3143
+ }
3144
+
3145
+ async computeDelegateeForUserInDelegatedFarm(
3146
+ farmProgramID: Address,
3147
+ vault: Address,
3148
+ farm: Address,
3149
+ user: Address
3150
+ ): Promise<Address> {
3151
+ const delegateePDA = await this.computeUserFarmStateDelegateePDAForUserInDelegatedVaultFarm(
3152
+ farmProgramID,
3153
+ vault,
3154
+ farm,
3155
+ user
3156
+ );
3157
+ return delegateePDA[0];
3158
+ }
3159
+
3088
3160
  /**
3089
3161
  * Read the APY of the farm built on top of the vault (farm in vaultState.vaultFarm)
3090
3162
  * @param vault - the vault to read the farm APY for
@@ -3101,7 +3173,7 @@ export class KaminoVaultClient {
3101
3173
  tokensPrices?: Map<Address, Decimal>
3102
3174
  ): Promise<FarmIncentives> {
3103
3175
  // Determine if we have a KaminoVault or VaultState
3104
- const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState(this.getConnection()) : vaultOrState;
3176
+ const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState() : vaultOrState;
3105
3177
  if (vaultState.vaultFarm === DEFAULT_PUBLIC_KEY) {
3106
3178
  return {
3107
3179
  incentivesStats: [],
@@ -3117,6 +3189,39 @@ export class KaminoVaultClient {
3117
3189
  return getFarmIncentives(kFarmsClient, vaultState.vaultFarm, sharePrice, stakedTokenMintDecimals, tokensPrices);
3118
3190
  }
3119
3191
 
3192
+ /**
3193
+ * Read the APY of the delegated farm providing incentives for vault depositors
3194
+ * @param vault - the vault to read the farm APY for
3195
+ * @param vaultTokenPrice - the price of the vault token in USD (e.g. 1.0 for USDC)
3196
+ * @param [farmsClient] - the farms client to use. Optional. If not provided, the function will create a new one
3197
+ * @param [slot] - the slot to read the farm APY for. Optional. If not provided, the function will read the current slot
3198
+ * @param [tokensPrices] - the prices of the tokens in USD. Optional. If not provided, the function will fetch the prices
3199
+ * @returns the APY of the delegated farm providing incentives for vault depositors
3200
+ */
3201
+ async getVaultDelegatedFarmRewardsAPY(
3202
+ vault: KaminoVault,
3203
+ vaultTokenPrice: Decimal,
3204
+ farmsClient?: Farms,
3205
+ slot?: Slot,
3206
+ tokensPrices?: Map<Address, Decimal>
3207
+ ): Promise<FarmIncentives> {
3208
+ const delegatedFarm = await this.getDelegatedFarmForVault(vault.address);
3209
+ if (!delegatedFarm) {
3210
+ return {
3211
+ incentivesStats: [],
3212
+ totalIncentivesApy: 0,
3213
+ };
3214
+ }
3215
+
3216
+ const vaultState = await vault.getState();
3217
+ const tokensPerShare = await this.getTokensPerShareSingleVault(vaultState, slot);
3218
+ const sharePrice = tokensPerShare.mul(vaultTokenPrice);
3219
+ const stakedTokenMintDecimals = vaultState.sharesMintDecimals.toNumber();
3220
+
3221
+ const kFarmsClient = farmsClient ? farmsClient : new Farms(this.getConnection());
3222
+ return getFarmIncentives(kFarmsClient, delegatedFarm, sharePrice, stakedTokenMintDecimals, tokensPrices);
3223
+ }
3224
+
3120
3225
  /**
3121
3226
  * Get all the token mints of the vault, vault farm rewards and the allocation rewards
3122
3227
  * @param vaults - the vaults to get the token mints for
@@ -3137,9 +3242,9 @@ export class KaminoVaultClient {
3137
3242
  const reservesToFetch = new Set<Address>();
3138
3243
 
3139
3244
  for (const vault of vaults) {
3140
- const vaultState = await vault.getState(this.getConnection());
3245
+ const vaultState = await vault.getState();
3141
3246
  vaultsTokenMints.add(vaultState.tokenMint);
3142
- const hasFarm = await vault.hasFarm(this.getConnection());
3247
+ const hasFarm = await vault.hasFarm();
3143
3248
  if (hasFarm) {
3144
3249
  const farmAddress = vaultState.vaultFarm;
3145
3250
  if (!kFarmsMap.has(farmAddress)) {
@@ -3225,10 +3330,10 @@ export class KaminoVaultClient {
3225
3330
  vaultReservesMap?: Map<Address, KaminoReserve>,
3226
3331
  tokensPrices?: Map<Address, Decimal>
3227
3332
  ): Promise<VaultReservesFarmsIncentives> {
3228
- const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState(this.getConnection()) : vaultOrState;
3333
+ const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState() : vaultOrState;
3229
3334
 
3230
3335
  const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
3231
- const currentSlot = slot ? slot : await this.getConnection().getSlot({ commitment: 'confirmed' }).send();
3336
+ const currentSlot = slot ?? (await this.getConnection().getSlot({ commitment: 'confirmed' }).send());
3232
3337
 
3233
3338
  const holdings = await this.getVaultHoldings(vaultState, currentSlot, vaultReservesState);
3234
3339
 
@@ -3264,7 +3369,6 @@ export class KaminoVaultClient {
3264
3369
  kFarmsClient,
3265
3370
  currentSlot,
3266
3371
  reserveState.state,
3267
- undefined,
3268
3372
  tokensPrices
3269
3373
  );
3270
3374
  vaultReservesFarmsIncentives.set(reserveAddress, reserveFarmIncentives.collateralFarmIncentives);
@@ -3282,6 +3386,323 @@ export class KaminoVaultClient {
3282
3386
  };
3283
3387
  }
3284
3388
 
3389
+ /// reads the pending rewards for a user in the vault farm
3390
+ /// @param user - the user address
3391
+ /// @param vault - the vault
3392
+ /// @returns a map of the pending rewards token mint and amount in lamports
3393
+ async getUserPendingRewardsInVaultFarm(user: Address, vault: KaminoVault): Promise<Map<Address, Decimal>> {
3394
+ const vaultState = await vault.getState();
3395
+ const hasFarm = await vault.hasFarm();
3396
+ if (!hasFarm) {
3397
+ return new Map<Address, Decimal>();
3398
+ }
3399
+
3400
+ const farmClient = new Farms(this.getConnection());
3401
+ const userState = await getUserStatePDA(farmClient.getProgramID(), vaultState.vaultFarm, user);
3402
+ return getUserPendingRewardsInFarm(this.getConnection(), userState, vaultState.vaultFarm);
3403
+ }
3404
+
3405
+ /// reads the pending rewards for a user in a delegated vault farm
3406
+ /// @param user - the user address
3407
+ /// @param vaultAddress - the address of the vault
3408
+ /// @returns a map of the pending rewards token mint and amount in lamports
3409
+ async getUserPendingRewardsInVaultDelegatedFarm(
3410
+ user: Address,
3411
+ vaultAddress: Address
3412
+ ): Promise<Map<Address, Decimal>> {
3413
+ const delegatedFarm = await this.getDelegatedFarmForVault(vaultAddress);
3414
+ if (!delegatedFarm) {
3415
+ return new Map<Address, Decimal>();
3416
+ }
3417
+
3418
+ const farmClient = new Farms(this.getConnection());
3419
+ const userState = await this.computeUserStatePDAForUserInDelegatedVaultFarm(
3420
+ farmClient.getProgramID(),
3421
+ vaultAddress,
3422
+ delegatedFarm,
3423
+ user
3424
+ );
3425
+
3426
+ return getUserPendingRewardsInFarm(this.getConnection(), userState, delegatedFarm);
3427
+ }
3428
+
3429
+ /// gets the delegated farm for a vault
3430
+ async getDelegatedFarmForVault(vault: Address): Promise<Address | undefined> {
3431
+ const response = await fetch(`${CDN_ENDPOINT}/resources.json`);
3432
+ if (!response.ok) {
3433
+ console.log(`Failed to fetch CDN for user pending rewards in vault delegated farm: ${response.statusText}`);
3434
+ return undefined;
3435
+ }
3436
+ const data = (await response.json()) as { 'mainnet-beta'?: { delegatedVaultFarms: any } };
3437
+ const delegatedVaultFarms = data['mainnet-beta']?.delegatedVaultFarms;
3438
+ if (!delegatedVaultFarms) {
3439
+ return undefined;
3440
+ }
3441
+ const delegatedFarmWithVault = delegatedVaultFarms.find((vaultWithFarm: any) => vaultWithFarm.vault === vault);
3442
+ if (!delegatedFarmWithVault) {
3443
+ return undefined;
3444
+ }
3445
+ return address(delegatedFarmWithVault.farm);
3446
+ }
3447
+
3448
+ /// reads the pending rewards for a user in the reserves farms of a vault
3449
+ /// @param user - the user address
3450
+ /// @param vault - the vault
3451
+ /// @param [vaultReservesMap] - the vault reserves map to get the reserves for; if not provided, the function will fetch the reserves
3452
+ /// @returns a map of the pending rewards token mint and amount in lamports
3453
+ async getUserPendingRewardsInVaultReservesFarms(
3454
+ user: Address,
3455
+ vault: KaminoVault,
3456
+ vaultReservesMap?: Map<Address, KaminoReserve>
3457
+ ): Promise<Map<Address, Decimal>> {
3458
+ const vaultState = await vault.getState();
3459
+
3460
+ const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
3461
+
3462
+ const vaultReserves = vaultState.vaultAllocationStrategy
3463
+ .map((allocationStrategy) => allocationStrategy.reserve)
3464
+ .filter((reserve) => reserve !== DEFAULT_PUBLIC_KEY);
3465
+ const pendingRewardsPerToken: Map<Address, Decimal> = new Map();
3466
+
3467
+ const farmClient = new Farms(this.getConnection());
3468
+ for (const reserveAddress of vaultReserves) {
3469
+ const reserveState = vaultReservesState.get(reserveAddress);
3470
+ if (!reserveState) {
3471
+ console.log(`Reserve to read farm incentives for not found: ${reserveAddress}`);
3472
+ continue;
3473
+ }
3474
+
3475
+ if (reserveState.state.farmCollateral === DEFAULT_PUBLIC_KEY) {
3476
+ continue;
3477
+ }
3478
+
3479
+ const delegatee = await this.computeUserFarmStateDelegateePDAForUserInVault(
3480
+ farmClient.getProgramID(),
3481
+ vault.address,
3482
+ reserveAddress,
3483
+ user
3484
+ );
3485
+ const userState = await getUserStatePDA(
3486
+ farmClient.getProgramID(),
3487
+ reserveState.state.farmCollateral,
3488
+ delegatee[0]
3489
+ );
3490
+ const pendingRewards = await getUserPendingRewardsInFarm(
3491
+ this.getConnection(),
3492
+ userState,
3493
+ reserveState.state.farmCollateral
3494
+ );
3495
+ pendingRewards.forEach((reward, token) => {
3496
+ const existingReward = pendingRewardsPerToken.get(token);
3497
+ if (existingReward) {
3498
+ pendingRewardsPerToken.set(token, existingReward.add(reward));
3499
+ } else {
3500
+ pendingRewardsPerToken.set(token, reward);
3501
+ }
3502
+ });
3503
+ }
3504
+
3505
+ return pendingRewardsPerToken;
3506
+ }
3507
+
3508
+ /// reads the pending rewards for a user in the vault farm, the reserves farms of the vault and the delegated vault farm
3509
+ /// @param user - the user address
3510
+ /// @param vault - the vault
3511
+ /// @param [vaultReservesMap] - the vault reserves map to get the reserves for; if not provided, the function will fetch the reserves
3512
+ /// @returns a struct containing the pending rewards in the vault farm, the reserves farms of the vault and the delegated vault farm, and the total pending rewards in lamports
3513
+ async getAllPendingRewardsForUserInVault(
3514
+ user: Address,
3515
+ vault: KaminoVault,
3516
+ vaultReservesMap?: Map<Address, KaminoReserve>
3517
+ ): Promise<PendingRewardsForUserInVault> {
3518
+ const pendingRewardsInVaultFarm = await this.getUserPendingRewardsInVaultFarm(user, vault);
3519
+ const pendingRewardsInVaultReservesFarms = await this.getUserPendingRewardsInVaultReservesFarms(
3520
+ user,
3521
+ vault,
3522
+ vaultReservesMap
3523
+ );
3524
+ const pendingRewardsInVaultDelegatedFarm = await this.getUserPendingRewardsInVaultDelegatedFarm(
3525
+ user,
3526
+ vault.address
3527
+ );
3528
+
3529
+ const totalPendingRewards = new Map<Address, Decimal>();
3530
+ pendingRewardsInVaultFarm.forEach((reward, token) => {
3531
+ const existingReward = totalPendingRewards.get(token);
3532
+ if (existingReward) {
3533
+ totalPendingRewards.set(token, existingReward.add(reward));
3534
+ } else {
3535
+ totalPendingRewards.set(token, reward);
3536
+ }
3537
+ });
3538
+ pendingRewardsInVaultReservesFarms.forEach((reward, token) => {
3539
+ const existingReward = totalPendingRewards.get(token);
3540
+ if (existingReward) {
3541
+ totalPendingRewards.set(token, existingReward.add(reward));
3542
+ } else {
3543
+ totalPendingRewards.set(token, reward);
3544
+ }
3545
+ });
3546
+ pendingRewardsInVaultDelegatedFarm.forEach((reward, token) => {
3547
+ const existingReward = totalPendingRewards.get(token);
3548
+ if (existingReward) {
3549
+ totalPendingRewards.set(token, existingReward.add(reward));
3550
+ } else {
3551
+ totalPendingRewards.set(token, reward);
3552
+ }
3553
+ });
3554
+
3555
+ return {
3556
+ pendingRewardsInVaultFarm,
3557
+ pendingRewardsInVaultReservesFarms,
3558
+ pendingRewardsInVaultDelegatedFarm,
3559
+ totalPendingRewards,
3560
+ };
3561
+ }
3562
+
3563
+ /**
3564
+ * This function will return the instructions to claim the rewards for the farm of a vault, the delegated farm of the vault and the reserves farms of the vault
3565
+ * @param user - the user to claim the rewards
3566
+ * @param vault - the vault
3567
+ * @param [vaultReservesMap] - the vault reserves map to get the reserves for; if not provided, the function will fetch the reserves
3568
+ * @returns the instructions to claim the rewards for the farm of the vault, the delegated farm of the vault and the reserves farms of the vault
3569
+ */
3570
+ async getClaimAllRewardsForVaultIxs(
3571
+ user: TransactionSigner,
3572
+ vault: KaminoVault,
3573
+ vaultReservesMap?: Map<Address, KaminoReserve>
3574
+ ): Promise<Instruction[]> {
3575
+ const [vaultFarmIxs, delegatedFarmIxs, reservesFarmsIxs] = await Promise.all([
3576
+ this.getClaimVaultFarmRewardsIxs(user, vault),
3577
+ this.getClaimVaultDelegatedFarmRewardsIxs(user, vault),
3578
+ this.getClaimVaultReservesFarmsRewardsIxs(user, vault, vaultReservesMap),
3579
+ ]);
3580
+
3581
+ return [...new Set([...vaultFarmIxs, ...delegatedFarmIxs, ...reservesFarmsIxs])];
3582
+ }
3583
+
3584
+ /**
3585
+ * This function will return the instructions to claim the rewards for the farm of a vault
3586
+ * @param user - the user to claim the rewards
3587
+ * @param vault - the vault
3588
+ * @returns the instructions to claim the rewards for the farm of the vault
3589
+ */
3590
+ async getClaimVaultFarmRewardsIxs(user: TransactionSigner, vault: KaminoVault): Promise<Instruction[]> {
3591
+ const vaultState = await vault.getState();
3592
+ const hasFarm = await vault.hasFarm();
3593
+ if (!hasFarm) {
3594
+ return [];
3595
+ }
3596
+
3597
+ const farmClient = new Farms(this.getConnection());
3598
+ const pendingRewardsInVaultFarm = await this.getUserPendingRewardsInVaultFarm(user.address, vault);
3599
+ // if there are no pending rewards of their total is 0 no ix is needed
3600
+ const totalPendingRewards = Array.from(pendingRewardsInVaultFarm.values()).reduce(
3601
+ (acc, reward) => acc.add(reward),
3602
+ new Decimal(0)
3603
+ );
3604
+ if (totalPendingRewards.eq(0)) {
3605
+ return [];
3606
+ }
3607
+ return farmClient.claimForUserForFarmAllRewardsIx(user, vaultState.vaultFarm, false);
3608
+ }
3609
+
3610
+ /**
3611
+ * This function will return the instructions to claim the rewards for the delegated farm of a vault
3612
+ * @param user - the user to claim the rewards
3613
+ * @param vault - the vault
3614
+ * @returns the instructions to claim the rewards for the delegated farm of the vault
3615
+ */
3616
+ async getClaimVaultDelegatedFarmRewardsIxs(user: TransactionSigner, vault: KaminoVault): Promise<Instruction[]> {
3617
+ const delegatedFarm = await this.getDelegatedFarmForVault(vault.address);
3618
+ if (!delegatedFarm) {
3619
+ return [];
3620
+ }
3621
+
3622
+ const farmClient = new Farms(this.getConnection());
3623
+
3624
+ const delegatee = await this.computeDelegateeForUserInDelegatedFarm(
3625
+ farmClient.getProgramID(),
3626
+ vault.address,
3627
+ delegatedFarm,
3628
+ user.address
3629
+ );
3630
+ const userState = await getUserStatePDA(farmClient.getProgramID(), delegatedFarm, delegatee);
3631
+ // check if the user state exists
3632
+ const userStateExists = await fetchEncodedAccount(this.getConnection(), userState);
3633
+ if (!userStateExists.exists) {
3634
+ return [];
3635
+ }
3636
+
3637
+ return farmClient.claimForUserForFarmAllRewardsIx(user, delegatedFarm, true, [delegatee]);
3638
+ }
3639
+
3640
+ /**
3641
+ * This function will return the instructions to claim the rewards for the reserves farms of a vault
3642
+ * @param user - the user to claim the rewards
3643
+ * @param vault - the vault
3644
+ * @param [vaultReservesMap] - the vault reserves map to get the reserves for; if not provided, the function will fetch the reserves
3645
+ * @returns the instructions to claim the rewards for the reserves farms of the vault
3646
+ */
3647
+ async getClaimVaultReservesFarmsRewardsIxs(
3648
+ user: TransactionSigner,
3649
+ vault: KaminoVault,
3650
+ vaultReservesMap?: Map<Address, KaminoReserve>
3651
+ ): Promise<Instruction[]> {
3652
+ const vaultState = await vault.getState();
3653
+
3654
+ const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
3655
+
3656
+ const vaultReserves = vaultState.vaultAllocationStrategy
3657
+ .map((allocationStrategy) => allocationStrategy.reserve)
3658
+ .filter((reserve) => reserve !== DEFAULT_PUBLIC_KEY);
3659
+
3660
+ const ixs: Instruction[] = [];
3661
+ const farmClient = new Farms(this.getConnection());
3662
+ for (const reserveAddress of vaultReserves) {
3663
+ const reserveState = vaultReservesState.get(reserveAddress);
3664
+ if (!reserveState) {
3665
+ console.log(`Reserve to read farm incentives for not found: ${reserveAddress}`);
3666
+ continue;
3667
+ }
3668
+
3669
+ if (reserveState.state.farmCollateral === DEFAULT_PUBLIC_KEY) {
3670
+ continue;
3671
+ }
3672
+
3673
+ const delegatee = await this.computeUserFarmStateDelegateePDAForUserInVault(
3674
+ farmClient.getProgramID(),
3675
+ vault.address,
3676
+ reserveAddress,
3677
+ user.address
3678
+ );
3679
+ const userState = await getUserStatePDA(
3680
+ farmClient.getProgramID(),
3681
+ reserveState.state.farmCollateral,
3682
+ delegatee[0]
3683
+ );
3684
+
3685
+ const pendingRewards = await getUserPendingRewardsInFarm(
3686
+ this.getConnection(),
3687
+ userState,
3688
+ reserveState.state.farmCollateral
3689
+ );
3690
+ const totalPendingRewards = Array.from(pendingRewards.values()).reduce(
3691
+ (acc, reward) => acc.add(reward),
3692
+ new Decimal(0)
3693
+ );
3694
+ if (totalPendingRewards.eq(0)) {
3695
+ continue;
3696
+ }
3697
+ const ix = await farmClient.claimForUserForFarmAllRewardsIx(user, reserveState.state.farmCollateral, true, [
3698
+ delegatee[0],
3699
+ ]);
3700
+ ixs.push(...ix);
3701
+ }
3702
+
3703
+ return ixs;
3704
+ }
3705
+
3285
3706
  private appendRemainingAccountsForVaultReserves(
3286
3707
  ix: Instruction,
3287
3708
  vaultReserves: Address[],
@@ -3310,16 +3731,33 @@ export class KaminoVault {
3310
3731
  readonly address: Address;
3311
3732
  state: VaultState | undefined | null;
3312
3733
  programId: Address;
3734
+ client: KaminoVaultClient;
3735
+ vaultReservesStateCache: Map<Address, KaminoReserve> | undefined;
3313
3736
 
3314
- constructor(vaultAddress: Address, state?: VaultState, programId: Address = kaminoVaultId) {
3737
+ constructor(
3738
+ rpc: Rpc<SolanaRpcApi>,
3739
+ vaultAddress: Address,
3740
+ state?: VaultState,
3741
+ programId: Address = kaminoVaultId,
3742
+ recentSlotDurationMs: number = DEFAULT_RECENT_SLOT_DURATION_MS
3743
+ ) {
3315
3744
  this.address = vaultAddress;
3316
3745
  this.state = state;
3317
3746
  this.programId = programId;
3747
+ this.client = new KaminoVaultClient(rpc, recentSlotDurationMs);
3748
+ }
3749
+
3750
+ static loadWithClientAndState(client: KaminoVaultClient, vaultAddress: Address, state: VaultState): KaminoVault {
3751
+ const vault = new KaminoVault(client.getConnection(), vaultAddress);
3752
+ vault.state = state;
3753
+ vault.programId = client.getProgramID();
3754
+ vault.client = client;
3755
+ return vault;
3318
3756
  }
3319
3757
 
3320
- async getState(rpc: Rpc<GetAccountInfoApi>): Promise<VaultState> {
3758
+ async getState(): Promise<VaultState> {
3321
3759
  if (!this.state) {
3322
- const res = await VaultState.fetch(rpc, this.address, this.programId);
3760
+ const res = await VaultState.fetch(this.client.getConnection(), this.address, this.programId);
3323
3761
  if (!res) {
3324
3762
  throw new Error('Invalid vault');
3325
3763
  }
@@ -3330,18 +3768,141 @@ export class KaminoVault {
3330
3768
  }
3331
3769
  }
3332
3770
 
3333
- async reloadState(rpc: Rpc<GetAccountInfoApi>): Promise<VaultState> {
3334
- this.state = await VaultState.fetch(rpc, this.address, this.programId);
3771
+ async reloadVaultReserves(): Promise<void> {
3772
+ this.vaultReservesStateCache = await this.client.loadVaultReserves(this.state!);
3773
+ }
3774
+
3775
+ async reloadState(): Promise<VaultState> {
3776
+ this.state = await VaultState.fetch(this.client.getConnection(), this.address, this.programId);
3335
3777
  if (!this.state) {
3336
3778
  throw new Error('Could not fetch vault');
3337
3779
  }
3338
3780
  return this.state;
3339
3781
  }
3340
3782
 
3341
- async hasFarm(rpc: Rpc<GetAccountInfoApi>): Promise<boolean> {
3342
- const state = await this.getState(rpc);
3783
+ async hasFarm(): Promise<boolean> {
3784
+ const state = await this.getState();
3343
3785
  return state.vaultFarm !== DEFAULT_PUBLIC_KEY;
3344
3786
  }
3787
+
3788
+ /**
3789
+ * This will return an VaultHoldings object which contains the amount available (uninvested) in vault, total amount invested in reseves and a breakdown of the amount invested in each reserve
3790
+ * @returns an VaultHoldings object representing the amount available (uninvested) in vault, total amount invested in reseves and a breakdown of the amount invested in each reserve
3791
+ */
3792
+ async getVaultHoldings(): Promise<VaultHoldings> {
3793
+ if (!this.state || !this.vaultReservesStateCache) {
3794
+ await this.reloadState();
3795
+ await this.reloadVaultReserves();
3796
+ }
3797
+
3798
+ return await this.client.getVaultHoldings(this.state!, undefined, this.vaultReservesStateCache!, undefined);
3799
+ }
3800
+
3801
+ /**
3802
+ * This will return the a map between reserve pubkey and the allocation overview for the reserve
3803
+ * @returns a map between reserve pubkey and the allocation overview for the reserve
3804
+ */
3805
+ async getVaultAllocations(): Promise<Map<Address, ReserveAllocationOverview>> {
3806
+ if (!this.state) {
3807
+ await this.reloadState();
3808
+ }
3809
+
3810
+ return this.client.getVaultAllocations(this.state!);
3811
+ }
3812
+
3813
+ /**
3814
+ * This will return the APY of the vault based on the current invested amounts and the theoretical APY if all the available tokens were invested
3815
+ * @returns a struct containing actualAPY and theoreticalAPY for the vault
3816
+ */
3817
+ async getAPYs(slot?: Slot): Promise<VaultAPYs> {
3818
+ if (!this.state || !this.vaultReservesStateCache) {
3819
+ await this.reloadState();
3820
+ await this.reloadVaultReserves();
3821
+ }
3822
+
3823
+ const latestSlot = slot ?? (await this.client.getConnection().getSlot({ commitment: 'confirmed' }).send());
3824
+ const actualApy = await this.client.getVaultActualAPY(this.state!, latestSlot, this.vaultReservesStateCache!);
3825
+ const theoreticalApy = await this.client.getVaultTheoreticalAPY(
3826
+ this.state!,
3827
+ latestSlot,
3828
+ this.vaultReservesStateCache!
3829
+ );
3830
+
3831
+ return {
3832
+ actualAPY: actualApy,
3833
+ theoreticalAPY: theoreticalApy,
3834
+ };
3835
+ }
3836
+
3837
+ /**
3838
+ * This method returns the exchange rate of the vault (tokens per share)
3839
+ * @returns - Decimal representing the exchange rate (tokens per share)
3840
+ */
3841
+ async getExchangeRate(slot?: Slot): Promise<Decimal> {
3842
+ if (!this.state || !this.vaultReservesStateCache) {
3843
+ await this.reloadState();
3844
+ await this.reloadVaultReserves();
3845
+ }
3846
+
3847
+ const latestSlot = slot ?? (await this.client.getConnection().getSlot({ commitment: 'confirmed' }).send());
3848
+ const tokensPerShare = await this.client.getTokensPerShareSingleVault(this.state!, latestSlot);
3849
+ return tokensPerShare;
3850
+ }
3851
+
3852
+ /**
3853
+ * This method returns the user shares balance for a given vault
3854
+ * @param user - user to calculate the shares balance for
3855
+ * @param vault - vault to calculate shares balance for
3856
+ * @returns - a struct of user share balance (staked in vault farm if the vault has a farm and unstaked) in decimal (not lamports)
3857
+ */
3858
+ async getUserShares(user: Address): Promise<UserSharesForVault> {
3859
+ return this.client.getUserSharesBalanceSingleVault(user, this);
3860
+ }
3861
+
3862
+ /**
3863
+ * This function creates instructions to deposit into a vault. It will also create ATA creation instructions for the vault shares that the user receives in return
3864
+ * @param user - user to deposit
3865
+ * @param tokenAmount - token amount to be deposited, in decimals (will be converted in lamports)
3866
+ * @param [vaultReservesMap] - optional parameter; a 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
3867
+ * @param [farmState] - the state of the vault farm, if the vault has a farm. Optional. If not provided, it will be fetched
3868
+ * @returns - an instance of DepositIxs which contains the instructions to deposit in vault and the instructions to stake the shares in the farm if the vault has a farm
3869
+ */
3870
+ async depositIxs(
3871
+ user: TransactionSigner,
3872
+ tokenAmount: Decimal,
3873
+ vaultReservesMap?: Map<Address, KaminoReserve>,
3874
+ farmState?: FarmState
3875
+ ): Promise<DepositIxs> {
3876
+ if (vaultReservesMap) {
3877
+ this.vaultReservesStateCache = vaultReservesMap;
3878
+ }
3879
+ return this.client.depositIxs(user, this, tokenAmount, this.vaultReservesStateCache, farmState);
3880
+ }
3881
+
3882
+ /**
3883
+ * This function will return the missing ATA creation instructions, as well as one or multiple withdraw instructions, based on how many reserves it's needed to withdraw from. This might have to be split in multiple transactions
3884
+ * @param user - user to withdraw
3885
+ * @param shareAmount - share amount to withdraw (in tokens, not lamports), in order to withdraw everything, any value > user share amount
3886
+ * @param slot - current slot, used to estimate the interest earned in the different reserves with allocation from the vault
3887
+ * @param [vaultReservesMap] - optional parameter; a hashmap from each reserve pubkey to the reserve state. If provided the function will be significantly faster as it will not have to fetch the reserves
3888
+ * @param [farmState] - the state of the vault farm, if the vault has a farm. Optional. If not provided, it will be fetched
3889
+ * @returns an array of instructions to create missing ATAs if needed and the withdraw instructions
3890
+ */
3891
+ async withdrawIxs(
3892
+ user: TransactionSigner,
3893
+ shareAmount: Decimal,
3894
+ slot?: Slot,
3895
+ vaultReservesMap?: Map<Address, KaminoReserve>,
3896
+ farmState?: FarmState
3897
+ ): Promise<WithdrawIxs> {
3898
+ if (vaultReservesMap) {
3899
+ this.vaultReservesStateCache = vaultReservesMap;
3900
+ }
3901
+
3902
+ const currentSlot = slot ?? (await this.client.getConnection().getSlot({ commitment: 'confirmed' }).send());
3903
+
3904
+ return this.client.withdrawIxs(user, this, shareAmount, currentSlot, this.vaultReservesStateCache, farmState);
3905
+ }
3345
3906
  }
3346
3907
 
3347
3908
  /**
@@ -3456,13 +4017,56 @@ export type VaultHolder = {
3456
4017
  amount: Decimal;
3457
4018
  };
3458
4019
 
3459
- export type VaultHoldings = {
4020
+ export type APY = {
4021
+ grossAPY: Decimal;
4022
+ netAPY: Decimal;
4023
+ };
4024
+
4025
+ export type VaultAPYs = {
4026
+ theoreticalAPY: APY;
4027
+ actualAPY: APY;
4028
+ };
4029
+
4030
+ export class VaultHoldings {
3460
4031
  available: Decimal;
3461
4032
  invested: Decimal;
3462
4033
  investedInReserves: Map<Address, Decimal>;
3463
4034
  pendingFees: Decimal;
3464
4035
  totalAUMIncludingFees: Decimal;
3465
- };
4036
+
4037
+ constructor(params: {
4038
+ available: Decimal;
4039
+ invested: Decimal;
4040
+ investedInReserves: Map<Address, Decimal>;
4041
+ pendingFees: Decimal;
4042
+ totalAUMIncludingFees: Decimal;
4043
+ }) {
4044
+ this.available = params.available;
4045
+ this.invested = params.invested;
4046
+ this.investedInReserves = params.investedInReserves;
4047
+ this.pendingFees = params.pendingFees;
4048
+ this.totalAUMIncludingFees = params.totalAUMIncludingFees;
4049
+ }
4050
+
4051
+ asJSON() {
4052
+ return {
4053
+ available: this.available.toString(),
4054
+ invested: this.invested.toString(),
4055
+ totalAUMIncludingFees: this.totalAUMIncludingFees.toString(),
4056
+ pendingFees: this.pendingFees.toString(),
4057
+ investedInReserves: pubkeyHashMapToJson(this.investedInReserves),
4058
+ };
4059
+ }
4060
+
4061
+ print() {
4062
+ console.log('Holdings:');
4063
+ console.log(' Available:', this.available.toString());
4064
+ console.log(' Invested:', this.invested.toString());
4065
+ console.log(' Total AUM including fees:', this.totalAUMIncludingFees.toString());
4066
+ console.log(' Pending fees:', this.pendingFees.toString());
4067
+ console.log(' Invested in reserves:', pubkeyHashMapToJson(this.investedInReserves));
4068
+ }
4069
+ }
3466
4070
 
3467
4071
  /**
3468
4072
  * earnedInterest represents the interest earned from now until the slot provided in the future
@@ -3518,6 +4122,7 @@ export type VaultOverview = {
3518
4122
  actualSupplyAPY: APYs;
3519
4123
  vaultFarmIncentives: FarmIncentives;
3520
4124
  reservesFarmsIncentives: VaultReservesFarmsIncentives;
4125
+ delegatedFarmIncentives: FarmIncentives;
3521
4126
  totalBorrowed: Decimal;
3522
4127
  totalBorrowedUSD: Decimal;
3523
4128
  totalSupplied: Decimal;
@@ -3545,11 +4150,9 @@ export type VaultCumulativeInterestWithTimestamp = {
3545
4150
  timestamp: number;
3546
4151
  };
3547
4152
 
3548
- export function printHoldings(holdings: VaultHoldings) {
3549
- console.log('Holdings:');
3550
- console.log(' Available:', holdings.available.toString());
3551
- console.log(' Invested:', holdings.invested.toString());
3552
- console.log(' Total AUM including fees:', holdings.totalAUMIncludingFees.toString());
3553
- console.log(' Pending fees:', holdings.pendingFees.toString());
3554
- console.log(' Invested in reserves:', pubkeyHashMapToJson(holdings.investedInReserves));
3555
- }
4153
+ export type PendingRewardsForUserInVault = {
4154
+ pendingRewardsInVaultFarm: Map<Address, Decimal>;
4155
+ pendingRewardsInVaultDelegatedFarm: Map<Address, Decimal>;
4156
+ pendingRewardsInVaultReservesFarms: Map<Address, Decimal>;
4157
+ totalPendingRewards: Map<Address, Decimal>;
4158
+ };