@kamino-finance/klend-sdk 7.1.10 → 7.2.1

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: [],
@@ -1226,7 +1230,7 @@ export class KaminoVaultClient {
1226
1230
 
1227
1231
  // if not enough shares in ATA unstake from farm
1228
1232
  const sharesInAtaAreEnoughForWithdraw = sharesToWithdraw.lte(userSharesAtaBalance);
1229
- if (hasFarm && !sharesInAtaAreEnoughForWithdraw) {
1233
+ if (hasFarm && !sharesInAtaAreEnoughForWithdraw && userSharesInFarm.gt(0)) {
1230
1234
  // if we need to unstake we need to make sure share ata is created
1231
1235
  const [{ createAtaIx }] = await createAtasIdempotent(user, [
1232
1236
  {
@@ -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);
@@ -1408,7 +1411,7 @@ export class KaminoVaultClient {
1408
1411
  reserveWithSharesAmountToWithdraw.push({ reserve: key, shares: new Decimal(U64_MAX.toString()) });
1409
1412
  } else {
1410
1413
  // round up to the nearest integer the shares to withdraw
1411
- const sharesToWithdrawFromReserve = tokensToWithdrawFromReserve.mul(sharesPerToken).ceil();
1414
+ const sharesToWithdrawFromReserve = tokensToWithdrawFromReserve.mul(sharesPerToken).floor();
1412
1415
  reserveWithSharesAmountToWithdraw.push({ reserve: key, shares: sharesToWithdrawFromReserve });
1413
1416
  }
1414
1417
 
@@ -1427,13 +1430,6 @@ export class KaminoVaultClient {
1427
1430
  }
1428
1431
  const marketAddress = reserveState.state.lendingMarket;
1429
1432
 
1430
- const isLastWithdraw = reserveIndex === reserveWithSharesAmountToWithdraw.length - 1;
1431
- // if it is not last withdraw it means that we can pass all shares as we are withdrawing everything from that reserve
1432
- let sharesToWithdraw = shareAmount;
1433
- if (isLastWithdraw) {
1434
- sharesToWithdraw = reserveWithTokens.shares;
1435
- }
1436
-
1437
1433
  const withdrawFromReserveIx = await this.withdrawIx(
1438
1434
  user,
1439
1435
  vault,
@@ -1442,7 +1438,7 @@ export class KaminoVaultClient {
1442
1438
  { address: reserveWithTokens.reserve, state: reserveState.state },
1443
1439
  userSharesAta,
1444
1440
  userTokenAta,
1445
- sharesToWithdraw,
1441
+ reserveWithTokens.shares,
1446
1442
  vaultReservesState
1447
1443
  );
1448
1444
  withdrawIxs.push(withdrawFromReserveIx);
@@ -1463,7 +1459,7 @@ export class KaminoVaultClient {
1463
1459
  vault: KaminoVault,
1464
1460
  skipComputationChecks: boolean = false
1465
1461
  ): Promise<Instruction[]> {
1466
- const vaultState = await vault.reloadState(this.getConnection());
1462
+ const vaultState = await vault.reloadState();
1467
1463
  const minInvestAmount = vaultState.minInvestAmount;
1468
1464
  const allReserves = this.getVaultReserves(vaultState);
1469
1465
  if (allReserves.length === 0) {
@@ -1576,7 +1572,7 @@ export class KaminoVaultClient {
1576
1572
  vaultReservesMap?: Map<Address, KaminoReserve>,
1577
1573
  createAtaIfNeeded: boolean = true
1578
1574
  ): Promise<Instruction[]> {
1579
- const vaultState = await vault.getState(this.getConnection());
1575
+ const vaultState = await vault.getState();
1580
1576
  const cTokenVault = await getCTokenVaultPda(vault.address, reserve.address, this._kaminoVaultProgramId);
1581
1577
  const [lendingMarketAuth] = await lendingMarketAuthPda(reserve.state.lendingMarket, this._kaminoLendProgramId);
1582
1578
 
@@ -1784,7 +1780,7 @@ export class KaminoVaultClient {
1784
1780
  vault: KaminoVault,
1785
1781
  vaultReservesMap?: Map<Address, KaminoReserve>
1786
1782
  ): Promise<SyncVaultLUTIxs> {
1787
- const vaultState = await vault.getState(this.getConnection());
1783
+ const vaultState = await vault.getState();
1788
1784
  const allAccountsToBeInserted = [
1789
1785
  vault.address,
1790
1786
  vaultState.vaultAdminAuthority,
@@ -1940,7 +1936,7 @@ export class KaminoVaultClient {
1940
1936
  * @returns - user share balance in tokens (not lamports)
1941
1937
  */
1942
1938
  async getUserSharesBalanceSingleVault(user: Address, vault: KaminoVault): Promise<UserSharesForVault> {
1943
- const vaultState = await vault.getState(this.getConnection());
1939
+ const vaultState = await vault.getState();
1944
1940
 
1945
1941
  const userShares: UserSharesForVault = {
1946
1942
  unstakedShares: new Decimal(0),
@@ -1964,7 +1960,7 @@ export class KaminoVaultClient {
1964
1960
  return acc;
1965
1961
  }, new Decimal(0));
1966
1962
 
1967
- if (await vault.hasFarm(this.getConnection())) {
1963
+ if (await vault.hasFarm()) {
1968
1964
  const userSharesInFarm = await getUserSharesInTokensStakedInFarm(
1969
1965
  this.getConnection(),
1970
1966
  user,
@@ -2019,7 +2015,7 @@ export class KaminoVaultClient {
2019
2015
  });
2020
2016
  userSharesTokenAccountsPerVault.set(vault.address, userSharesTokenAccounts);
2021
2017
 
2022
- if (await vault.hasFarm(this.getConnection())) {
2018
+ if (await vault.hasFarm()) {
2023
2019
  const userFarmState = allUserFarmStatesMap.get(state.vaultFarm);
2024
2020
  if (userFarmState) {
2025
2021
  console.log('there is a farm state for vault', vault.address);
@@ -2096,7 +2092,7 @@ export class KaminoVaultClient {
2096
2092
  currentSlot?: Slot
2097
2093
  ): Promise<Decimal> {
2098
2094
  // Determine if we have a KaminoVault or VaultState
2099
- const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState(this.getConnection()) : vaultOrState;
2095
+ const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState() : vaultOrState;
2100
2096
 
2101
2097
  if (vaultState.sharesIssued.isZero()) {
2102
2098
  return new Decimal(0);
@@ -2203,7 +2199,7 @@ export class KaminoVaultClient {
2203
2199
  throw Error(`kaminoVault with pubkey ${kaminoVault.address} could not be decoded`);
2204
2200
  }
2205
2201
 
2206
- return new KaminoVault(kaminoVault.address, kaminoVaultAccount, this._kaminoVaultProgramId);
2202
+ return KaminoVault.loadWithClientAndState(this, kaminoVault.address, kaminoVaultAccount);
2207
2203
  });
2208
2204
  }
2209
2205
 
@@ -2219,7 +2215,7 @@ export class KaminoVaultClient {
2219
2215
  const vaultStates = await batchFetch(vaults, (chunk) => this.getVaultsStates(chunk));
2220
2216
  return vaults.map((vault, index) => {
2221
2217
  const state = vaultStates[index];
2222
- return state ? new KaminoVault(vault, state, this._kaminoVaultProgramId) : null;
2218
+ return state ? KaminoVault.loadWithClientAndState(this, vault, state) : null;
2223
2219
  });
2224
2220
  }
2225
2221
 
@@ -2325,7 +2321,7 @@ export class KaminoVaultClient {
2325
2321
  slot: Slot,
2326
2322
  vaultReservesMap: Map<Address, KaminoReserve>
2327
2323
  ): Promise<Map<Address, Decimal>> {
2328
- const vaultState = await vault.getState(this.getConnection());
2324
+ const vaultState = await vault.getState();
2329
2325
 
2330
2326
  const reserveAllocationAvailableLiquidityToWithdraw = new Map<Address, Decimal>();
2331
2327
  vaultState.vaultAllocationStrategy.forEach((allocationStrategy) => {
@@ -2572,13 +2568,13 @@ export class KaminoVaultClient {
2572
2568
  vaultReserves?: Map<Address, KaminoReserve>,
2573
2569
  currentSlot?: Slot
2574
2570
  ): Promise<VaultHoldings> {
2575
- const vaultHoldings: VaultHoldings = {
2571
+ const vaultHoldings: VaultHoldings = new VaultHoldings({
2576
2572
  available: new Decimal(vault.tokenAvailable.toString()),
2577
2573
  invested: new Decimal(0),
2578
2574
  investedInReserves: new Map<Address, Decimal>(),
2579
2575
  totalAUMIncludingFees: new Decimal(0),
2580
2576
  pendingFees: new Decimal(0),
2581
- };
2577
+ });
2582
2578
 
2583
2579
  const currentSlotToUse = currentSlot ?? (await this.getConnection().getSlot({ commitment: 'confirmed' }).send());
2584
2580
  const vaultReservesState = vaultReserves ? vaultReserves : await this.loadVaultReserves(vault);
@@ -2643,13 +2639,13 @@ export class KaminoVaultClient {
2643
2639
  const totalAvailableDecimal = lamportsToDecimal(vaultHoldings.available, decimals);
2644
2640
  const totalInvestedDecimal = lamportsToDecimal(vaultHoldings.invested, decimals);
2645
2641
  const pendingFees = lamportsToDecimal(totalPendingFees, decimals);
2646
- return {
2642
+ return new VaultHoldings({
2647
2643
  available: totalAvailableDecimal,
2648
2644
  invested: totalInvestedDecimal,
2649
2645
  investedInReserves: vaultHoldings.investedInReserves,
2650
2646
  totalAUMIncludingFees: totalAvailableDecimal.add(totalInvestedDecimal),
2651
2647
  pendingFees: pendingFees,
2652
- };
2648
+ });
2653
2649
  }
2654
2650
 
2655
2651
  /**
@@ -2696,7 +2692,7 @@ export class KaminoVaultClient {
2696
2692
  * @returns an VaultOverview object with details about the tokens available and invested in the vault, denominated in tokens and USD, along sie APYs
2697
2693
  */
2698
2694
  async getVaultOverview(
2699
- vault: VaultState,
2695
+ vault: KaminoVault,
2700
2696
  vaultTokenPrice: Decimal,
2701
2697
  slot?: Slot,
2702
2698
  vaultReservesMap?: Map<Address, KaminoReserve>,
@@ -2704,10 +2700,11 @@ export class KaminoVaultClient {
2704
2700
  currentSlot?: Slot,
2705
2701
  tokensPrices?: Map<Address, Decimal>
2706
2702
  ): Promise<VaultOverview> {
2707
- const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vault);
2703
+ const vaultState = await vault.getState();
2704
+ const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
2708
2705
 
2709
2706
  const vaultHoldingsWithUSDValuePromise = this.getVaultHoldingsWithPrice(
2710
- vault,
2707
+ vaultState,
2711
2708
  vaultTokenPrice,
2712
2709
  slot,
2713
2710
  vaultReservesState,
@@ -2717,15 +2714,20 @@ export class KaminoVaultClient {
2717
2714
  const slotForOverview = slot ? slot : await this.getConnection().getSlot().send();
2718
2715
  const farmsClient = new Farms(this.getConnection());
2719
2716
 
2720
- const vaultTheoreticalAPYPromise = this.getVaultTheoreticalAPY(vault, slotForOverview, vaultReservesState);
2721
- const vaultActualAPYPromise = this.getVaultActualAPY(vault, slotForOverview, vaultReservesState);
2717
+ const vaultTheoreticalAPYPromise = this.getVaultTheoreticalAPY(vaultState, slotForOverview, vaultReservesState);
2718
+ const vaultActualAPYPromise = this.getVaultActualAPY(vaultState, slotForOverview, vaultReservesState);
2722
2719
  const totalInvestedAndBorrowedPromise = this.getTotalBorrowedAndInvested(
2723
- vault,
2720
+ vaultState,
2724
2721
  slotForOverview,
2725
2722
  vaultReservesState
2726
2723
  );
2727
- const vaultCollateralsPromise = this.getVaultCollaterals(vault, slotForOverview, vaultReservesState, kaminoMarkets);
2728
- const reservesOverviewPromise = this.getVaultReservesDetails(vault, slotForOverview, vaultReservesState);
2724
+ const vaultCollateralsPromise = this.getVaultCollaterals(
2725
+ vaultState,
2726
+ slotForOverview,
2727
+ vaultReservesState,
2728
+ kaminoMarkets
2729
+ );
2730
+ const reservesOverviewPromise = this.getVaultReservesDetails(vaultState, slotForOverview, vaultReservesState);
2729
2731
  const vaultFarmIncentivesPromise = this.getVaultRewardsAPY(
2730
2732
  vault,
2731
2733
  vaultTokenPrice,
@@ -2741,6 +2743,13 @@ export class KaminoVaultClient {
2741
2743
  vaultReservesState,
2742
2744
  tokensPrices
2743
2745
  );
2746
+ const vaultDelegatedFarmIncentivesPromise = this.getVaultDelegatedFarmRewardsAPY(
2747
+ vault,
2748
+ vaultTokenPrice,
2749
+ farmsClient,
2750
+ slotForOverview,
2751
+ tokensPrices
2752
+ );
2744
2753
 
2745
2754
  // 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
2755
  const [
@@ -2752,6 +2761,7 @@ export class KaminoVaultClient {
2752
2761
  reservesOverview,
2753
2762
  vaultFarmIncentives,
2754
2763
  vaultReservesFarmIncentives,
2764
+ vaultDelegatedFarmIncentives,
2755
2765
  ] = await Promise.all([
2756
2766
  vaultHoldingsWithUSDValuePromise,
2757
2767
  vaultTheoreticalAPYPromise,
@@ -2761,6 +2771,7 @@ export class KaminoVaultClient {
2761
2771
  reservesOverviewPromise,
2762
2772
  vaultFarmIncentivesPromise,
2763
2773
  vaultReservesFarmIncentivesPromise,
2774
+ vaultDelegatedFarmIncentivesPromise,
2764
2775
  ]);
2765
2776
 
2766
2777
  return {
@@ -2771,6 +2782,7 @@ export class KaminoVaultClient {
2771
2782
  theoreticalSupplyAPY: vaultTheoreticalAPYs,
2772
2783
  vaultFarmIncentives: vaultFarmIncentives,
2773
2784
  reservesFarmsIncentives: vaultReservesFarmIncentives,
2785
+ delegatedFarmIncentives: vaultDelegatedFarmIncentives,
2774
2786
  totalBorrowed: totalInvestedAndBorrowed.totalBorrowed,
2775
2787
  totalBorrowedUSD: totalInvestedAndBorrowed.totalBorrowed.mul(vaultTokenPrice),
2776
2788
  utilizationRatio: totalInvestedAndBorrowed.utilizationRatio,
@@ -3080,11 +3092,64 @@ export class KaminoVaultClient {
3080
3092
  user: Address
3081
3093
  ): Promise<ProgramDerivedAddress> {
3082
3094
  return getProgramDerivedAddress({
3083
- seeds: [addressEncoder.encode(vault), addressEncoder.encode(reserve), addressEncoder.encode(user)],
3095
+ seeds: [addressEncoder.encode(reserve), addressEncoder.encode(vault), addressEncoder.encode(user)],
3084
3096
  programAddress: farmsProgramId,
3085
3097
  });
3086
3098
  }
3087
3099
 
3100
+ /**
3101
+ * Compute the delegatee PDA for the user farm state for a vault delegate farm
3102
+ * @param farmProgramID - the program ID of the farm program
3103
+ * @param vault - the address of the vault
3104
+ * @param farm - the address of the delegated farm
3105
+ * @param user - the address of the user
3106
+ * @returns the PDA of the delegatee user farm state for the delegated farm
3107
+ */
3108
+ async computeUserFarmStateDelegateePDAForUserInDelegatedVaultFarm(
3109
+ farmProgramID: Address,
3110
+ vault: Address,
3111
+ farm: Address,
3112
+ user: Address
3113
+ ): Promise<ProgramDerivedAddress> {
3114
+ return getProgramDerivedAddress({
3115
+ seeds: [addressEncoder.encode(vault), addressEncoder.encode(farm), addressEncoder.encode(user)],
3116
+ programAddress: farmProgramID,
3117
+ });
3118
+ }
3119
+
3120
+ /**
3121
+ * Compute the user state PDA for a user in a delegated vault farm
3122
+ * @param farmProgramID - the program ID of the farm program
3123
+ * @param vault - the address of the vault
3124
+ * @param farm - the address of the delegated farm
3125
+ * @param user - the address of the user
3126
+ * @returns the PDA of the user state for the delegated farm
3127
+ */
3128
+ async computeUserStatePDAForUserInDelegatedVaultFarm(
3129
+ farmProgramID: Address,
3130
+ vault: Address,
3131
+ farm: Address,
3132
+ user: Address
3133
+ ): Promise<Address> {
3134
+ const delegateePDA = await this.computeDelegateeForUserInDelegatedFarm(farmProgramID, vault, farm, user);
3135
+ return getUserStatePDA(farmProgramID, farm, delegateePDA);
3136
+ }
3137
+
3138
+ async computeDelegateeForUserInDelegatedFarm(
3139
+ farmProgramID: Address,
3140
+ vault: Address,
3141
+ farm: Address,
3142
+ user: Address
3143
+ ): Promise<Address> {
3144
+ const delegateePDA = await this.computeUserFarmStateDelegateePDAForUserInDelegatedVaultFarm(
3145
+ farmProgramID,
3146
+ vault,
3147
+ farm,
3148
+ user
3149
+ );
3150
+ return delegateePDA[0];
3151
+ }
3152
+
3088
3153
  /**
3089
3154
  * Read the APY of the farm built on top of the vault (farm in vaultState.vaultFarm)
3090
3155
  * @param vault - the vault to read the farm APY for
@@ -3101,7 +3166,7 @@ export class KaminoVaultClient {
3101
3166
  tokensPrices?: Map<Address, Decimal>
3102
3167
  ): Promise<FarmIncentives> {
3103
3168
  // Determine if we have a KaminoVault or VaultState
3104
- const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState(this.getConnection()) : vaultOrState;
3169
+ const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState() : vaultOrState;
3105
3170
  if (vaultState.vaultFarm === DEFAULT_PUBLIC_KEY) {
3106
3171
  return {
3107
3172
  incentivesStats: [],
@@ -3117,6 +3182,39 @@ export class KaminoVaultClient {
3117
3182
  return getFarmIncentives(kFarmsClient, vaultState.vaultFarm, sharePrice, stakedTokenMintDecimals, tokensPrices);
3118
3183
  }
3119
3184
 
3185
+ /**
3186
+ * Read the APY of the delegated farm providing incentives for vault depositors
3187
+ * @param vault - the vault to read the farm APY for
3188
+ * @param vaultTokenPrice - the price of the vault token in USD (e.g. 1.0 for USDC)
3189
+ * @param [farmsClient] - the farms client to use. Optional. If not provided, the function will create a new one
3190
+ * @param [slot] - the slot to read the farm APY for. Optional. If not provided, the function will read the current slot
3191
+ * @param [tokensPrices] - the prices of the tokens in USD. Optional. If not provided, the function will fetch the prices
3192
+ * @returns the APY of the delegated farm providing incentives for vault depositors
3193
+ */
3194
+ async getVaultDelegatedFarmRewardsAPY(
3195
+ vault: KaminoVault,
3196
+ vaultTokenPrice: Decimal,
3197
+ farmsClient?: Farms,
3198
+ slot?: Slot,
3199
+ tokensPrices?: Map<Address, Decimal>
3200
+ ): Promise<FarmIncentives> {
3201
+ const delegatedFarm = await this.getDelegatedFarmForVault(vault.address);
3202
+ if (!delegatedFarm) {
3203
+ return {
3204
+ incentivesStats: [],
3205
+ totalIncentivesApy: 0,
3206
+ };
3207
+ }
3208
+
3209
+ const vaultState = await vault.getState();
3210
+ const tokensPerShare = await this.getTokensPerShareSingleVault(vaultState, slot);
3211
+ const sharePrice = tokensPerShare.mul(vaultTokenPrice);
3212
+ const stakedTokenMintDecimals = vaultState.sharesMintDecimals.toNumber();
3213
+
3214
+ const kFarmsClient = farmsClient ? farmsClient : new Farms(this.getConnection());
3215
+ return getFarmIncentives(kFarmsClient, delegatedFarm, sharePrice, stakedTokenMintDecimals, tokensPrices);
3216
+ }
3217
+
3120
3218
  /**
3121
3219
  * Get all the token mints of the vault, vault farm rewards and the allocation rewards
3122
3220
  * @param vaults - the vaults to get the token mints for
@@ -3137,9 +3235,9 @@ export class KaminoVaultClient {
3137
3235
  const reservesToFetch = new Set<Address>();
3138
3236
 
3139
3237
  for (const vault of vaults) {
3140
- const vaultState = await vault.getState(this.getConnection());
3238
+ const vaultState = await vault.getState();
3141
3239
  vaultsTokenMints.add(vaultState.tokenMint);
3142
- const hasFarm = await vault.hasFarm(this.getConnection());
3240
+ const hasFarm = await vault.hasFarm();
3143
3241
  if (hasFarm) {
3144
3242
  const farmAddress = vaultState.vaultFarm;
3145
3243
  if (!kFarmsMap.has(farmAddress)) {
@@ -3225,10 +3323,10 @@ export class KaminoVaultClient {
3225
3323
  vaultReservesMap?: Map<Address, KaminoReserve>,
3226
3324
  tokensPrices?: Map<Address, Decimal>
3227
3325
  ): Promise<VaultReservesFarmsIncentives> {
3228
- const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState(this.getConnection()) : vaultOrState;
3326
+ const vaultState = 'getState' in vaultOrState ? await vaultOrState.getState() : vaultOrState;
3229
3327
 
3230
3328
  const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
3231
- const currentSlot = slot ? slot : await this.getConnection().getSlot({ commitment: 'confirmed' }).send();
3329
+ const currentSlot = slot ?? (await this.getConnection().getSlot({ commitment: 'confirmed' }).send());
3232
3330
 
3233
3331
  const holdings = await this.getVaultHoldings(vaultState, currentSlot, vaultReservesState);
3234
3332
 
@@ -3264,7 +3362,6 @@ export class KaminoVaultClient {
3264
3362
  kFarmsClient,
3265
3363
  currentSlot,
3266
3364
  reserveState.state,
3267
- undefined,
3268
3365
  tokensPrices
3269
3366
  );
3270
3367
  vaultReservesFarmsIncentives.set(reserveAddress, reserveFarmIncentives.collateralFarmIncentives);
@@ -3282,6 +3379,323 @@ export class KaminoVaultClient {
3282
3379
  };
3283
3380
  }
3284
3381
 
3382
+ /// reads the pending rewards for a user in the vault farm
3383
+ /// @param user - the user address
3384
+ /// @param vault - the vault
3385
+ /// @returns a map of the pending rewards token mint and amount in lamports
3386
+ async getUserPendingRewardsInVaultFarm(user: Address, vault: KaminoVault): Promise<Map<Address, Decimal>> {
3387
+ const vaultState = await vault.getState();
3388
+ const hasFarm = await vault.hasFarm();
3389
+ if (!hasFarm) {
3390
+ return new Map<Address, Decimal>();
3391
+ }
3392
+
3393
+ const farmClient = new Farms(this.getConnection());
3394
+ const userState = await getUserStatePDA(farmClient.getProgramID(), vaultState.vaultFarm, user);
3395
+ return getUserPendingRewardsInFarm(this.getConnection(), userState, vaultState.vaultFarm);
3396
+ }
3397
+
3398
+ /// reads the pending rewards for a user in a delegated vault farm
3399
+ /// @param user - the user address
3400
+ /// @param vaultAddress - the address of the vault
3401
+ /// @returns a map of the pending rewards token mint and amount in lamports
3402
+ async getUserPendingRewardsInVaultDelegatedFarm(
3403
+ user: Address,
3404
+ vaultAddress: Address
3405
+ ): Promise<Map<Address, Decimal>> {
3406
+ const delegatedFarm = await this.getDelegatedFarmForVault(vaultAddress);
3407
+ if (!delegatedFarm) {
3408
+ return new Map<Address, Decimal>();
3409
+ }
3410
+
3411
+ const farmClient = new Farms(this.getConnection());
3412
+ const userState = await this.computeUserStatePDAForUserInDelegatedVaultFarm(
3413
+ farmClient.getProgramID(),
3414
+ vaultAddress,
3415
+ delegatedFarm,
3416
+ user
3417
+ );
3418
+
3419
+ return getUserPendingRewardsInFarm(this.getConnection(), userState, delegatedFarm);
3420
+ }
3421
+
3422
+ /// gets the delegated farm for a vault
3423
+ async getDelegatedFarmForVault(vault: Address): Promise<Address | undefined> {
3424
+ const response = await fetch(`${CDN_ENDPOINT}/resources.json`);
3425
+ if (!response.ok) {
3426
+ console.log(`Failed to fetch CDN for user pending rewards in vault delegated farm: ${response.statusText}`);
3427
+ return undefined;
3428
+ }
3429
+ const data = (await response.json()) as { 'mainnet-beta'?: { delegatedVaultFarms: any } };
3430
+ const delegatedVaultFarms = data['mainnet-beta']?.delegatedVaultFarms;
3431
+ if (!delegatedVaultFarms) {
3432
+ return undefined;
3433
+ }
3434
+ const delegatedFarmWithVault = delegatedVaultFarms.find((vaultWithFarm: any) => vaultWithFarm.vault === vault);
3435
+ if (!delegatedFarmWithVault) {
3436
+ return undefined;
3437
+ }
3438
+ return address(delegatedFarmWithVault.farm);
3439
+ }
3440
+
3441
+ /// reads the pending rewards for a user in the reserves farms of a vault
3442
+ /// @param user - the user address
3443
+ /// @param vault - the vault
3444
+ /// @param [vaultReservesMap] - the vault reserves map to get the reserves for; if not provided, the function will fetch the reserves
3445
+ /// @returns a map of the pending rewards token mint and amount in lamports
3446
+ async getUserPendingRewardsInVaultReservesFarms(
3447
+ user: Address,
3448
+ vault: KaminoVault,
3449
+ vaultReservesMap?: Map<Address, KaminoReserve>
3450
+ ): Promise<Map<Address, Decimal>> {
3451
+ const vaultState = await vault.getState();
3452
+
3453
+ const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
3454
+
3455
+ const vaultReserves = vaultState.vaultAllocationStrategy
3456
+ .map((allocationStrategy) => allocationStrategy.reserve)
3457
+ .filter((reserve) => reserve !== DEFAULT_PUBLIC_KEY);
3458
+ const pendingRewardsPerToken: Map<Address, Decimal> = new Map();
3459
+
3460
+ const farmClient = new Farms(this.getConnection());
3461
+ for (const reserveAddress of vaultReserves) {
3462
+ const reserveState = vaultReservesState.get(reserveAddress);
3463
+ if (!reserveState) {
3464
+ console.log(`Reserve to read farm incentives for not found: ${reserveAddress}`);
3465
+ continue;
3466
+ }
3467
+
3468
+ if (reserveState.state.farmCollateral === DEFAULT_PUBLIC_KEY) {
3469
+ continue;
3470
+ }
3471
+
3472
+ const delegatee = await this.computeUserFarmStateDelegateePDAForUserInVault(
3473
+ farmClient.getProgramID(),
3474
+ vault.address,
3475
+ reserveAddress,
3476
+ user
3477
+ );
3478
+ const userState = await getUserStatePDA(
3479
+ farmClient.getProgramID(),
3480
+ reserveState.state.farmCollateral,
3481
+ delegatee[0]
3482
+ );
3483
+ const pendingRewards = await getUserPendingRewardsInFarm(
3484
+ this.getConnection(),
3485
+ userState,
3486
+ reserveState.state.farmCollateral
3487
+ );
3488
+ pendingRewards.forEach((reward, token) => {
3489
+ const existingReward = pendingRewardsPerToken.get(token);
3490
+ if (existingReward) {
3491
+ pendingRewardsPerToken.set(token, existingReward.add(reward));
3492
+ } else {
3493
+ pendingRewardsPerToken.set(token, reward);
3494
+ }
3495
+ });
3496
+ }
3497
+
3498
+ return pendingRewardsPerToken;
3499
+ }
3500
+
3501
+ /// reads the pending rewards for a user in the vault farm, the reserves farms of the vault and the delegated vault farm
3502
+ /// @param user - the user address
3503
+ /// @param vault - the vault
3504
+ /// @param [vaultReservesMap] - the vault reserves map to get the reserves for; if not provided, the function will fetch the reserves
3505
+ /// @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
3506
+ async getAllPendingRewardsForUserInVault(
3507
+ user: Address,
3508
+ vault: KaminoVault,
3509
+ vaultReservesMap?: Map<Address, KaminoReserve>
3510
+ ): Promise<PendingRewardsForUserInVault> {
3511
+ const pendingRewardsInVaultFarm = await this.getUserPendingRewardsInVaultFarm(user, vault);
3512
+ const pendingRewardsInVaultReservesFarms = await this.getUserPendingRewardsInVaultReservesFarms(
3513
+ user,
3514
+ vault,
3515
+ vaultReservesMap
3516
+ );
3517
+ const pendingRewardsInVaultDelegatedFarm = await this.getUserPendingRewardsInVaultDelegatedFarm(
3518
+ user,
3519
+ vault.address
3520
+ );
3521
+
3522
+ const totalPendingRewards = new Map<Address, Decimal>();
3523
+ pendingRewardsInVaultFarm.forEach((reward, token) => {
3524
+ const existingReward = totalPendingRewards.get(token);
3525
+ if (existingReward) {
3526
+ totalPendingRewards.set(token, existingReward.add(reward));
3527
+ } else {
3528
+ totalPendingRewards.set(token, reward);
3529
+ }
3530
+ });
3531
+ pendingRewardsInVaultReservesFarms.forEach((reward, token) => {
3532
+ const existingReward = totalPendingRewards.get(token);
3533
+ if (existingReward) {
3534
+ totalPendingRewards.set(token, existingReward.add(reward));
3535
+ } else {
3536
+ totalPendingRewards.set(token, reward);
3537
+ }
3538
+ });
3539
+ pendingRewardsInVaultDelegatedFarm.forEach((reward, token) => {
3540
+ const existingReward = totalPendingRewards.get(token);
3541
+ if (existingReward) {
3542
+ totalPendingRewards.set(token, existingReward.add(reward));
3543
+ } else {
3544
+ totalPendingRewards.set(token, reward);
3545
+ }
3546
+ });
3547
+
3548
+ return {
3549
+ pendingRewardsInVaultFarm,
3550
+ pendingRewardsInVaultReservesFarms,
3551
+ pendingRewardsInVaultDelegatedFarm,
3552
+ totalPendingRewards,
3553
+ };
3554
+ }
3555
+
3556
+ /**
3557
+ * 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
3558
+ * @param user - the user to claim the rewards
3559
+ * @param vault - the vault
3560
+ * @param [vaultReservesMap] - the vault reserves map to get the reserves for; if not provided, the function will fetch the reserves
3561
+ * @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
3562
+ */
3563
+ async getClaimAllRewardsForVaultIxs(
3564
+ user: TransactionSigner,
3565
+ vault: KaminoVault,
3566
+ vaultReservesMap?: Map<Address, KaminoReserve>
3567
+ ): Promise<Instruction[]> {
3568
+ const [vaultFarmIxs, delegatedFarmIxs, reservesFarmsIxs] = await Promise.all([
3569
+ this.getClaimVaultFarmRewardsIxs(user, vault),
3570
+ this.getClaimVaultDelegatedFarmRewardsIxs(user, vault),
3571
+ this.getClaimVaultReservesFarmsRewardsIxs(user, vault, vaultReservesMap),
3572
+ ]);
3573
+
3574
+ return [...new Set([...vaultFarmIxs, ...delegatedFarmIxs, ...reservesFarmsIxs])];
3575
+ }
3576
+
3577
+ /**
3578
+ * This function will return the instructions to claim the rewards for the farm of a vault
3579
+ * @param user - the user to claim the rewards
3580
+ * @param vault - the vault
3581
+ * @returns the instructions to claim the rewards for the farm of the vault
3582
+ */
3583
+ async getClaimVaultFarmRewardsIxs(user: TransactionSigner, vault: KaminoVault): Promise<Instruction[]> {
3584
+ const vaultState = await vault.getState();
3585
+ const hasFarm = await vault.hasFarm();
3586
+ if (!hasFarm) {
3587
+ return [];
3588
+ }
3589
+
3590
+ const farmClient = new Farms(this.getConnection());
3591
+ const pendingRewardsInVaultFarm = await this.getUserPendingRewardsInVaultFarm(user.address, vault);
3592
+ // if there are no pending rewards of their total is 0 no ix is needed
3593
+ const totalPendingRewards = Array.from(pendingRewardsInVaultFarm.values()).reduce(
3594
+ (acc, reward) => acc.add(reward),
3595
+ new Decimal(0)
3596
+ );
3597
+ if (totalPendingRewards.eq(0)) {
3598
+ return [];
3599
+ }
3600
+ return farmClient.claimForUserForFarmAllRewardsIx(user, vaultState.vaultFarm, false);
3601
+ }
3602
+
3603
+ /**
3604
+ * This function will return the instructions to claim the rewards for the delegated farm of a vault
3605
+ * @param user - the user to claim the rewards
3606
+ * @param vault - the vault
3607
+ * @returns the instructions to claim the rewards for the delegated farm of the vault
3608
+ */
3609
+ async getClaimVaultDelegatedFarmRewardsIxs(user: TransactionSigner, vault: KaminoVault): Promise<Instruction[]> {
3610
+ const delegatedFarm = await this.getDelegatedFarmForVault(vault.address);
3611
+ if (!delegatedFarm) {
3612
+ return [];
3613
+ }
3614
+
3615
+ const farmClient = new Farms(this.getConnection());
3616
+
3617
+ const delegatee = await this.computeDelegateeForUserInDelegatedFarm(
3618
+ farmClient.getProgramID(),
3619
+ vault.address,
3620
+ delegatedFarm,
3621
+ user.address
3622
+ );
3623
+ const userState = await getUserStatePDA(farmClient.getProgramID(), delegatedFarm, delegatee);
3624
+ // check if the user state exists
3625
+ const userStateExists = await fetchEncodedAccount(this.getConnection(), userState);
3626
+ if (!userStateExists.exists) {
3627
+ return [];
3628
+ }
3629
+
3630
+ return farmClient.claimForUserForFarmAllRewardsIx(user, delegatedFarm, true, [delegatee]);
3631
+ }
3632
+
3633
+ /**
3634
+ * This function will return the instructions to claim the rewards for the reserves farms of a vault
3635
+ * @param user - the user to claim the rewards
3636
+ * @param vault - the vault
3637
+ * @param [vaultReservesMap] - the vault reserves map to get the reserves for; if not provided, the function will fetch the reserves
3638
+ * @returns the instructions to claim the rewards for the reserves farms of the vault
3639
+ */
3640
+ async getClaimVaultReservesFarmsRewardsIxs(
3641
+ user: TransactionSigner,
3642
+ vault: KaminoVault,
3643
+ vaultReservesMap?: Map<Address, KaminoReserve>
3644
+ ): Promise<Instruction[]> {
3645
+ const vaultState = await vault.getState();
3646
+
3647
+ const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
3648
+
3649
+ const vaultReserves = vaultState.vaultAllocationStrategy
3650
+ .map((allocationStrategy) => allocationStrategy.reserve)
3651
+ .filter((reserve) => reserve !== DEFAULT_PUBLIC_KEY);
3652
+
3653
+ const ixs: Instruction[] = [];
3654
+ const farmClient = new Farms(this.getConnection());
3655
+ for (const reserveAddress of vaultReserves) {
3656
+ const reserveState = vaultReservesState.get(reserveAddress);
3657
+ if (!reserveState) {
3658
+ console.log(`Reserve to read farm incentives for not found: ${reserveAddress}`);
3659
+ continue;
3660
+ }
3661
+
3662
+ if (reserveState.state.farmCollateral === DEFAULT_PUBLIC_KEY) {
3663
+ continue;
3664
+ }
3665
+
3666
+ const delegatee = await this.computeUserFarmStateDelegateePDAForUserInVault(
3667
+ farmClient.getProgramID(),
3668
+ vault.address,
3669
+ reserveAddress,
3670
+ user.address
3671
+ );
3672
+ const userState = await getUserStatePDA(
3673
+ farmClient.getProgramID(),
3674
+ reserveState.state.farmCollateral,
3675
+ delegatee[0]
3676
+ );
3677
+
3678
+ const pendingRewards = await getUserPendingRewardsInFarm(
3679
+ this.getConnection(),
3680
+ userState,
3681
+ reserveState.state.farmCollateral
3682
+ );
3683
+ const totalPendingRewards = Array.from(pendingRewards.values()).reduce(
3684
+ (acc, reward) => acc.add(reward),
3685
+ new Decimal(0)
3686
+ );
3687
+ if (totalPendingRewards.eq(0)) {
3688
+ continue;
3689
+ }
3690
+ const ix = await farmClient.claimForUserForFarmAllRewardsIx(user, reserveState.state.farmCollateral, true, [
3691
+ delegatee[0],
3692
+ ]);
3693
+ ixs.push(...ix);
3694
+ }
3695
+
3696
+ return ixs;
3697
+ }
3698
+
3285
3699
  private appendRemainingAccountsForVaultReserves(
3286
3700
  ix: Instruction,
3287
3701
  vaultReserves: Address[],
@@ -3310,16 +3724,33 @@ export class KaminoVault {
3310
3724
  readonly address: Address;
3311
3725
  state: VaultState | undefined | null;
3312
3726
  programId: Address;
3727
+ client: KaminoVaultClient;
3728
+ vaultReservesStateCache: Map<Address, KaminoReserve> | undefined;
3313
3729
 
3314
- constructor(vaultAddress: Address, state?: VaultState, programId: Address = kaminoVaultId) {
3730
+ constructor(
3731
+ rpc: Rpc<SolanaRpcApi>,
3732
+ vaultAddress: Address,
3733
+ state?: VaultState,
3734
+ programId: Address = kaminoVaultId,
3735
+ recentSlotDurationMs: number = DEFAULT_RECENT_SLOT_DURATION_MS
3736
+ ) {
3315
3737
  this.address = vaultAddress;
3316
3738
  this.state = state;
3317
3739
  this.programId = programId;
3740
+ this.client = new KaminoVaultClient(rpc, recentSlotDurationMs);
3318
3741
  }
3319
3742
 
3320
- async getState(rpc: Rpc<GetAccountInfoApi>): Promise<VaultState> {
3743
+ static loadWithClientAndState(client: KaminoVaultClient, vaultAddress: Address, state: VaultState): KaminoVault {
3744
+ const vault = new KaminoVault(client.getConnection(), vaultAddress);
3745
+ vault.state = state;
3746
+ vault.programId = client.getProgramID();
3747
+ vault.client = client;
3748
+ return vault;
3749
+ }
3750
+
3751
+ async getState(): Promise<VaultState> {
3321
3752
  if (!this.state) {
3322
- const res = await VaultState.fetch(rpc, this.address, this.programId);
3753
+ const res = await VaultState.fetch(this.client.getConnection(), this.address, this.programId);
3323
3754
  if (!res) {
3324
3755
  throw new Error('Invalid vault');
3325
3756
  }
@@ -3330,18 +3761,141 @@ export class KaminoVault {
3330
3761
  }
3331
3762
  }
3332
3763
 
3333
- async reloadState(rpc: Rpc<GetAccountInfoApi>): Promise<VaultState> {
3334
- this.state = await VaultState.fetch(rpc, this.address, this.programId);
3764
+ async reloadVaultReserves(): Promise<void> {
3765
+ this.vaultReservesStateCache = await this.client.loadVaultReserves(this.state!);
3766
+ }
3767
+
3768
+ async reloadState(): Promise<VaultState> {
3769
+ this.state = await VaultState.fetch(this.client.getConnection(), this.address, this.programId);
3335
3770
  if (!this.state) {
3336
3771
  throw new Error('Could not fetch vault');
3337
3772
  }
3338
3773
  return this.state;
3339
3774
  }
3340
3775
 
3341
- async hasFarm(rpc: Rpc<GetAccountInfoApi>): Promise<boolean> {
3342
- const state = await this.getState(rpc);
3776
+ async hasFarm(): Promise<boolean> {
3777
+ const state = await this.getState();
3343
3778
  return state.vaultFarm !== DEFAULT_PUBLIC_KEY;
3344
3779
  }
3780
+
3781
+ /**
3782
+ * 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
3783
+ * @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
3784
+ */
3785
+ async getVaultHoldings(): Promise<VaultHoldings> {
3786
+ if (!this.state || !this.vaultReservesStateCache) {
3787
+ await this.reloadState();
3788
+ await this.reloadVaultReserves();
3789
+ }
3790
+
3791
+ return await this.client.getVaultHoldings(this.state!, undefined, this.vaultReservesStateCache!, undefined);
3792
+ }
3793
+
3794
+ /**
3795
+ * This will return the a map between reserve pubkey and the allocation overview for the reserve
3796
+ * @returns a map between reserve pubkey and the allocation overview for the reserve
3797
+ */
3798
+ async getVaultAllocations(): Promise<Map<Address, ReserveAllocationOverview>> {
3799
+ if (!this.state) {
3800
+ await this.reloadState();
3801
+ }
3802
+
3803
+ return this.client.getVaultAllocations(this.state!);
3804
+ }
3805
+
3806
+ /**
3807
+ * 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
3808
+ * @returns a struct containing actualAPY and theoreticalAPY for the vault
3809
+ */
3810
+ async getAPYs(slot?: Slot): Promise<VaultAPYs> {
3811
+ if (!this.state || !this.vaultReservesStateCache) {
3812
+ await this.reloadState();
3813
+ await this.reloadVaultReserves();
3814
+ }
3815
+
3816
+ const latestSlot = slot ?? (await this.client.getConnection().getSlot({ commitment: 'confirmed' }).send());
3817
+ const actualApy = await this.client.getVaultActualAPY(this.state!, latestSlot, this.vaultReservesStateCache!);
3818
+ const theoreticalApy = await this.client.getVaultTheoreticalAPY(
3819
+ this.state!,
3820
+ latestSlot,
3821
+ this.vaultReservesStateCache!
3822
+ );
3823
+
3824
+ return {
3825
+ actualAPY: actualApy,
3826
+ theoreticalAPY: theoreticalApy,
3827
+ };
3828
+ }
3829
+
3830
+ /**
3831
+ * This method returns the exchange rate of the vault (tokens per share)
3832
+ * @returns - Decimal representing the exchange rate (tokens per share)
3833
+ */
3834
+ async getExchangeRate(slot?: Slot): Promise<Decimal> {
3835
+ if (!this.state || !this.vaultReservesStateCache) {
3836
+ await this.reloadState();
3837
+ await this.reloadVaultReserves();
3838
+ }
3839
+
3840
+ const latestSlot = slot ?? (await this.client.getConnection().getSlot({ commitment: 'confirmed' }).send());
3841
+ const tokensPerShare = await this.client.getTokensPerShareSingleVault(this.state!, latestSlot);
3842
+ return tokensPerShare;
3843
+ }
3844
+
3845
+ /**
3846
+ * This method returns the user shares balance for a given vault
3847
+ * @param user - user to calculate the shares balance for
3848
+ * @param vault - vault to calculate shares balance for
3849
+ * @returns - a struct of user share balance (staked in vault farm if the vault has a farm and unstaked) in decimal (not lamports)
3850
+ */
3851
+ async getUserShares(user: Address): Promise<UserSharesForVault> {
3852
+ return this.client.getUserSharesBalanceSingleVault(user, this);
3853
+ }
3854
+
3855
+ /**
3856
+ * 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
3857
+ * @param user - user to deposit
3858
+ * @param tokenAmount - token amount to be deposited, in decimals (will be converted in lamports)
3859
+ * @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
3860
+ * @param [farmState] - the state of the vault farm, if the vault has a farm. Optional. If not provided, it will be fetched
3861
+ * @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
3862
+ */
3863
+ async depositIxs(
3864
+ user: TransactionSigner,
3865
+ tokenAmount: Decimal,
3866
+ vaultReservesMap?: Map<Address, KaminoReserve>,
3867
+ farmState?: FarmState
3868
+ ): Promise<DepositIxs> {
3869
+ if (vaultReservesMap) {
3870
+ this.vaultReservesStateCache = vaultReservesMap;
3871
+ }
3872
+ return this.client.depositIxs(user, this, tokenAmount, this.vaultReservesStateCache, farmState);
3873
+ }
3874
+
3875
+ /**
3876
+ * 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
3877
+ * @param user - user to withdraw
3878
+ * @param shareAmount - share amount to withdraw (in tokens, not lamports), in order to withdraw everything, any value > user share amount
3879
+ * @param slot - current slot, used to estimate the interest earned in the different reserves with allocation from the vault
3880
+ * @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
3881
+ * @param [farmState] - the state of the vault farm, if the vault has a farm. Optional. If not provided, it will be fetched
3882
+ * @returns an array of instructions to create missing ATAs if needed and the withdraw instructions
3883
+ */
3884
+ async withdrawIxs(
3885
+ user: TransactionSigner,
3886
+ shareAmount: Decimal,
3887
+ slot?: Slot,
3888
+ vaultReservesMap?: Map<Address, KaminoReserve>,
3889
+ farmState?: FarmState
3890
+ ): Promise<WithdrawIxs> {
3891
+ if (vaultReservesMap) {
3892
+ this.vaultReservesStateCache = vaultReservesMap;
3893
+ }
3894
+
3895
+ const currentSlot = slot ?? (await this.client.getConnection().getSlot({ commitment: 'confirmed' }).send());
3896
+
3897
+ return this.client.withdrawIxs(user, this, shareAmount, currentSlot, this.vaultReservesStateCache, farmState);
3898
+ }
3345
3899
  }
3346
3900
 
3347
3901
  /**
@@ -3456,13 +4010,56 @@ export type VaultHolder = {
3456
4010
  amount: Decimal;
3457
4011
  };
3458
4012
 
3459
- export type VaultHoldings = {
4013
+ export type APY = {
4014
+ grossAPY: Decimal;
4015
+ netAPY: Decimal;
4016
+ };
4017
+
4018
+ export type VaultAPYs = {
4019
+ theoreticalAPY: APY;
4020
+ actualAPY: APY;
4021
+ };
4022
+
4023
+ export class VaultHoldings {
3460
4024
  available: Decimal;
3461
4025
  invested: Decimal;
3462
4026
  investedInReserves: Map<Address, Decimal>;
3463
4027
  pendingFees: Decimal;
3464
4028
  totalAUMIncludingFees: Decimal;
3465
- };
4029
+
4030
+ constructor(params: {
4031
+ available: Decimal;
4032
+ invested: Decimal;
4033
+ investedInReserves: Map<Address, Decimal>;
4034
+ pendingFees: Decimal;
4035
+ totalAUMIncludingFees: Decimal;
4036
+ }) {
4037
+ this.available = params.available;
4038
+ this.invested = params.invested;
4039
+ this.investedInReserves = params.investedInReserves;
4040
+ this.pendingFees = params.pendingFees;
4041
+ this.totalAUMIncludingFees = params.totalAUMIncludingFees;
4042
+ }
4043
+
4044
+ asJSON() {
4045
+ return {
4046
+ available: this.available.toString(),
4047
+ invested: this.invested.toString(),
4048
+ totalAUMIncludingFees: this.totalAUMIncludingFees.toString(),
4049
+ pendingFees: this.pendingFees.toString(),
4050
+ investedInReserves: pubkeyHashMapToJson(this.investedInReserves),
4051
+ };
4052
+ }
4053
+
4054
+ print() {
4055
+ console.log('Holdings:');
4056
+ console.log(' Available:', this.available.toString());
4057
+ console.log(' Invested:', this.invested.toString());
4058
+ console.log(' Total AUM including fees:', this.totalAUMIncludingFees.toString());
4059
+ console.log(' Pending fees:', this.pendingFees.toString());
4060
+ console.log(' Invested in reserves:', pubkeyHashMapToJson(this.investedInReserves));
4061
+ }
4062
+ }
3466
4063
 
3467
4064
  /**
3468
4065
  * earnedInterest represents the interest earned from now until the slot provided in the future
@@ -3518,6 +4115,7 @@ export type VaultOverview = {
3518
4115
  actualSupplyAPY: APYs;
3519
4116
  vaultFarmIncentives: FarmIncentives;
3520
4117
  reservesFarmsIncentives: VaultReservesFarmsIncentives;
4118
+ delegatedFarmIncentives: FarmIncentives;
3521
4119
  totalBorrowed: Decimal;
3522
4120
  totalBorrowedUSD: Decimal;
3523
4121
  totalSupplied: Decimal;
@@ -3545,11 +4143,9 @@ export type VaultCumulativeInterestWithTimestamp = {
3545
4143
  timestamp: number;
3546
4144
  };
3547
4145
 
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
- }
4146
+ export type PendingRewardsForUserInVault = {
4147
+ pendingRewardsInVaultFarm: Map<Address, Decimal>;
4148
+ pendingRewardsInVaultDelegatedFarm: Map<Address, Decimal>;
4149
+ pendingRewardsInVaultReservesFarms: Map<Address, Decimal>;
4150
+ totalPendingRewards: Map<Address, Decimal>;
4151
+ };