@kamino-finance/klend-sdk 5.2.1 → 5.2.3

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.
@@ -18,8 +18,6 @@ import {
18
18
  KaminoMarket,
19
19
  KaminoReserve,
20
20
  lamportsToDecimal,
21
- LendingMarket,
22
- MarketWithAddress,
23
21
  PubkeyHashMap,
24
22
  Reserve,
25
23
  WRAPPED_SOL_MINT,
@@ -50,7 +48,7 @@ import {
50
48
  import { VaultConfigFieldKind } from '../idl_codegen_kamino_vault/types';
51
49
  import { VaultState } from '../idl_codegen_kamino_vault/accounts';
52
50
  import Decimal from 'decimal.js';
53
- import { getTokenBalanceFromAccountInfoLamports, numberToLamportsDecimal, parseTokenSymbol } from './utils';
51
+ import { bpsToPct, getTokenBalanceFromAccountInfoLamports, numberToLamportsDecimal, parseTokenSymbol } from './utils';
54
52
  import { deposit } from '../idl_codegen_kamino_vault/instructions';
55
53
  import { withdraw } from '../idl_codegen_kamino_vault/instructions';
56
54
  import { PROGRAM_ID } from '../idl_codegen/programId';
@@ -348,22 +346,12 @@ export class KaminoVaultClient {
348
346
  }
349
347
 
350
348
  const reserveState = reserveStates[index]!;
351
-
352
- const market = reserveState.lendingMarket;
353
- const marketState = await LendingMarket.fetch(this._connection, market, this._kaminoLendProgramId);
354
- if (marketState === null) {
355
- throw new Error(`Market ${market.toBase58()} not found`);
356
- }
357
-
358
- const marketWithAddress = {
359
- address: market,
360
- state: marketState,
361
- };
349
+ const marketAddress = reserveState.lendingMarket;
362
350
 
363
351
  return this.withdrawPendingFeesIxn(
364
352
  vault,
365
353
  vaultState,
366
- marketWithAddress,
354
+ marketAddress,
367
355
  { address: reserve, state: reserveState },
368
356
  adminTokenAta
369
357
  );
@@ -389,7 +377,6 @@ export class KaminoVaultClient {
389
377
  * @param user - user to deposit
390
378
  * @param vault - vault to deposit into
391
379
  * @param tokenAmount - token amount to be deposited, in decimals (will be converted in lamports)
392
- * @param tokenProgramIDOverride - optional param; should be passed if token to be deposited is token22
393
380
  * @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
394
381
  * @returns - an array of instructions to be used to be executed
395
382
  */
@@ -397,12 +384,11 @@ export class KaminoVaultClient {
397
384
  user: PublicKey,
398
385
  vault: KaminoVault,
399
386
  tokenAmount: Decimal,
400
- tokenProgramIDOverride?: PublicKey,
401
387
  vaultReservesMap?: PubkeyHashMap<PublicKey, KaminoReserve>
402
388
  ): Promise<TransactionInstruction[]> {
403
389
  const vaultState = await vault.getState(this._connection);
404
390
 
405
- const tokenProgramID = tokenProgramIDOverride ? tokenProgramIDOverride : TOKEN_PROGRAM_ID;
391
+ const tokenProgramID = vaultState.tokenProgram;
406
392
  const userTokenAta = getAssociatedTokenAddress(vaultState.tokenMint, user, true, tokenProgramID);
407
393
  const createAtasIxns: TransactionInstruction[] = [];
408
394
  const closeAtasIxns: TransactionInstruction[] = [];
@@ -487,15 +473,18 @@ export class KaminoVaultClient {
487
473
  user: PublicKey,
488
474
  vault: KaminoVault,
489
475
  shareAmount: Decimal,
490
- slot: number
476
+ slot: number,
477
+ vaultReservesMap?: PubkeyHashMap<PublicKey, KaminoReserve>
491
478
  ): Promise<TransactionInstruction[]> {
492
479
  const vaultState = await vault.getState(this._connection);
493
480
 
481
+ const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
482
+
494
483
  const userSharesAta = getAssociatedTokenAddress(vaultState.sharesMint, user);
495
484
  const [{ ata: userTokenAta, createAtaIx }] = createAtasIdempotent(user, [
496
485
  {
497
486
  mint: vaultState.tokenMint,
498
- tokenProgram: TOKEN_PROGRAM_ID,
487
+ tokenProgram: vaultState.tokenProgram,
499
488
  },
500
489
  ]);
501
490
 
@@ -531,40 +520,31 @@ export class KaminoVaultClient {
531
520
  });
532
521
  }
533
522
 
534
- const reserveStates = await Reserve.fetchMultiple(this._connection, reservesToWithdraw, this._kaminoLendProgramId);
535
- const withdrawIxns: TransactionInstruction[] = await Promise.all(
536
- reservesToWithdraw.map(async (reserve, index) => {
537
- if (reserveStates[index] === null) {
538
- throw new Error(`Reserve ${reserve.toBase58()} not found`);
539
- }
540
-
541
- const reserveState = reserveStates[index]!;
523
+ const withdrawIxns: TransactionInstruction[] = [];
524
+ withdrawIxns.push(createAtaIx);
542
525
 
543
- const market = reserveState.lendingMarket;
544
- const marketState = await LendingMarket.fetch(this._connection, market, this._kaminoLendProgramId);
545
- if (marketState === null) {
546
- throw new Error(`Market ${market.toBase58()} not found`);
547
- }
548
-
549
- const marketWithAddress = {
550
- address: market,
551
- state: marketState,
552
- };
526
+ reservesToWithdraw.forEach((reserve, index) => {
527
+ const reserveState = vaultReservesState.get(reserve);
528
+ if (reserveState === undefined) {
529
+ throw new Error(`Reserve ${reserve.toBase58()} not found in vault reserves map`);
530
+ }
553
531
 
554
- return this.withdrawIxn(
555
- user,
556
- vault,
557
- vaultState,
558
- marketWithAddress,
559
- { address: reserve, state: reserveState },
560
- userSharesAta,
561
- userTokenAta,
562
- amountToWithdraw[index]
563
- );
564
- })
565
- );
532
+ const marketAddress = reserveState.state.lendingMarket;
533
+ const withdrawFromReserveIx = this.withdrawIxn(
534
+ user,
535
+ vault,
536
+ vaultState,
537
+ marketAddress,
538
+ { address: reserve, state: reserveState.state },
539
+ userSharesAta,
540
+ userTokenAta,
541
+ amountToWithdraw[index],
542
+ vaultReservesState
543
+ );
544
+ withdrawIxns.push(withdrawFromReserveIx);
545
+ });
566
546
 
567
- return [createAtaIx, ...withdrawIxns];
547
+ return withdrawIxns;
568
548
  }
569
549
 
570
550
  /**
@@ -646,17 +626,18 @@ export class KaminoVaultClient {
646
626
  return [createAtaIx, investIx];
647
627
  }
648
628
 
649
- private async withdrawIxn(
629
+ private withdrawIxn(
650
630
  user: PublicKey,
651
631
  vault: KaminoVault,
652
632
  vaultState: VaultState,
653
- marketWithAddress: MarketWithAddress,
633
+ marketAddress: PublicKey,
654
634
  reserve: ReserveWithAddress,
655
635
  userSharesAta: PublicKey,
656
636
  userTokenAta: PublicKey,
657
- shareAmountLamports: Decimal
658
- ): Promise<TransactionInstruction> {
659
- const lendingMarketAuth = lendingMarketAuthPda(marketWithAddress.address, this._kaminoLendProgramId)[0];
637
+ shareAmountLamports: Decimal,
638
+ vaultReservesState: PubkeyHashMap<PublicKey, KaminoReserve>
639
+ ): TransactionInstruction {
640
+ const lendingMarketAuth = lendingMarketAuthPda(marketAddress, this._kaminoLendProgramId)[0];
660
641
 
661
642
  const withdrawAccounts: WithdrawAccounts = {
662
643
  user: user,
@@ -672,7 +653,7 @@ export class KaminoVaultClient {
672
653
  reserve: reserve.address,
673
654
  ctokenVault: getCTokenVaultPda(vault.address, reserve.address, this._kaminoVaultProgramId),
674
655
  /** CPI accounts */
675
- lendingMarket: marketWithAddress.address,
656
+ lendingMarket: marketAddress,
676
657
  lendingMarketAuthority: lendingMarketAuth,
677
658
  reserveLiquiditySupply: reserve.state.liquidity.supplyVault,
678
659
  reserveCollateralMint: reserve.state.collateral.mintPubkey,
@@ -688,7 +669,6 @@ export class KaminoVaultClient {
688
669
  const withdrawIxn = withdraw(withdrawArgs, withdrawAccounts, this._kaminoVaultProgramId);
689
670
 
690
671
  const vaultReserves = this.getVaultReserves(vaultState);
691
- const vaultReservesState = await this.loadVaultReserves(vaultState);
692
672
 
693
673
  let vaultReservesAccountMetas: AccountMeta[] = [];
694
674
  let vaultReservesLendingMarkets: AccountMeta[] = [];
@@ -715,11 +695,11 @@ export class KaminoVaultClient {
715
695
  private async withdrawPendingFeesIxn(
716
696
  vault: KaminoVault,
717
697
  vaultState: VaultState,
718
- marketWithAddress: MarketWithAddress,
698
+ marketAddress: PublicKey,
719
699
  reserve: ReserveWithAddress,
720
700
  adminTokenAta: PublicKey
721
701
  ): Promise<TransactionInstruction> {
722
- const lendingMarketAuth = lendingMarketAuthPda(marketWithAddress.address, this._kaminoLendProgramId)[0];
702
+ const lendingMarketAuth = lendingMarketAuthPda(marketAddress, this._kaminoLendProgramId)[0];
723
703
 
724
704
  const withdrawPendingFeesAccounts: WithdrawPendingFeesAccounts = {
725
705
  adminAuthority: vaultState.adminAuthority,
@@ -732,7 +712,7 @@ export class KaminoVaultClient {
732
712
  tokenMint: vaultState.tokenMint,
733
713
  tokenProgram: TOKEN_PROGRAM_ID,
734
714
  /** CPI accounts */
735
- lendingMarket: marketWithAddress.address,
715
+ lendingMarket: marketAddress,
736
716
  lendingMarketAuthority: lendingMarketAuth,
737
717
  reserveLiquiditySupply: reserve.state.liquidity.supplyVault,
738
718
  reserveCollateralMint: reserve.state.collateral.mintPubkey,
@@ -829,6 +809,18 @@ export class KaminoVaultClient {
829
809
  return vaultUserShareBalance;
830
810
  }
831
811
 
812
+ /**
813
+ * This method returns the management and performance fee percentages
814
+ * @param vaultState - vault to retrieve the fees percentages from
815
+ * @returns - VaultFeesPct containing management and performance fee percentages
816
+ */
817
+ getVaultFeesPct(vaultState: VaultState): VaultFeesPct {
818
+ return {
819
+ managementFeePct: bpsToPct(new Decimal(vaultState.managementFeeBps.toString())),
820
+ performanceFeePct: bpsToPct(new Decimal(vaultState.performanceFeeBps.toString())),
821
+ };
822
+ }
823
+
832
824
  /**
833
825
  * This method calculates the token per shar value. This will always change based on interest earned from the vault, but calculating it requires a bunch of rpc requests. Caching this for a short duration would be optimal
834
826
  * @param vault - vault to calculate tokensPerShare for
@@ -1038,15 +1030,32 @@ export class KaminoVaultClient {
1038
1030
  *
1039
1031
  * @returns a hashmap from each reserve pubkey to the market overview of the collaterals that can be used and the min and max loan to value ratio in that market
1040
1032
  */
1041
- async getVaultCollaterals(vaultState: VaultState, slot: number): Promise<PubkeyHashMap<PublicKey, MarketOverview>> {
1042
- const vaultReservesState = Array.from((await this.loadVaultReserves(vaultState)).values());
1033
+ async getVaultCollaterals(
1034
+ vaultState: VaultState,
1035
+ slot: number,
1036
+ vaultReserves?: PubkeyHashMap<PublicKey, KaminoReserve>,
1037
+ kaminoMarkets?: KaminoMarket[]
1038
+ ): Promise<PubkeyHashMap<PublicKey, MarketOverview>> {
1039
+ const vaultReservesStateMap = vaultReserves ? vaultReserves : await this.loadVaultReserves(vaultState);
1040
+ const vaultReservesState = Array.from(vaultReservesStateMap.values());
1043
1041
 
1044
1042
  const vaultCollateralsPerReserve: PubkeyHashMap<PublicKey, MarketOverview> = new PubkeyHashMap();
1045
1043
 
1046
1044
  for (const reserve of vaultReservesState) {
1047
- const lendingMarket = await KaminoMarket.load(this._connection, reserve.state.lendingMarket, slot);
1045
+ // try to read the market from the provided list, if it doesn't exist fetch it
1046
+ let lendingMarket: KaminoMarket | undefined = undefined;
1047
+ if (kaminoMarkets) {
1048
+ lendingMarket = kaminoMarkets?.find((market) =>
1049
+ reserve.state.lendingMarket.equals(new PublicKey(market.address))
1050
+ );
1051
+ }
1052
+
1048
1053
  if (!lendingMarket) {
1049
- throw Error(`Could not fetch lending market ${reserve.state.lendingMarket.toBase58()}`);
1054
+ const fetchedLendingMarket = await KaminoMarket.load(this._connection, reserve.state.lendingMarket, slot);
1055
+ if (!fetchedLendingMarket) {
1056
+ throw Error(`Could not fetch lending market ${reserve.state.lendingMarket.toBase58()}`);
1057
+ }
1058
+ lendingMarket = fetchedLendingMarket;
1050
1059
  }
1051
1060
 
1052
1061
  const marketReserves = lendingMarket.getReserves();
@@ -1060,7 +1069,9 @@ export class KaminoVaultClient {
1060
1069
  marketReserves
1061
1070
  .filter((marketReserve) => {
1062
1071
  return (
1063
- marketReserve.state.config.liquidationThresholdPct > 0 && !marketReserve.address.equals(reserve.address)
1072
+ marketReserve.state.config.liquidationThresholdPct > 0 &&
1073
+ !marketReserve.address.equals(reserve.address) &&
1074
+ marketReserve.state.config.status === 0
1064
1075
  );
1065
1076
  })
1066
1077
  .map((filteredReserve) => {
@@ -1162,6 +1173,109 @@ export class KaminoVaultClient {
1162
1173
  availableUSD: holdings.available.mul(price),
1163
1174
  investedUSD: holdings.invested.mul(price),
1164
1175
  investedInReservesUSD: investedInReservesUSD,
1176
+ totalUSD: holdings.total.mul(price),
1177
+ };
1178
+ }
1179
+
1180
+ /**
1181
+ * This will return an VaultOverview object that encapsulates all the information about the vault, including the holdings, reserves details, theoretical APY, utilization ratio and total borrowed amount
1182
+ * @param vault - the kamino vault to get available liquidity to withdraw for
1183
+ * @param slot - current slot
1184
+ * @param price - the price of the token in the vault (e.g. USDC)
1185
+ * @param vaultReserves - 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
1186
+ * @param kaminoMarkets - optional parameter; a list of all kamino markets. If provided the function will be significantly faster as it will not have to fetch the markets
1187
+ * @returns an VaultHoldingsWithUSDValue object with details about the tokens available and invested in the vault, denominated in tokens and USD
1188
+ */
1189
+ async getVaultOverview(
1190
+ vault: VaultState,
1191
+ slot: number,
1192
+ price: Decimal,
1193
+ vaultReserves?: PubkeyHashMap<PublicKey, KaminoReserve>,
1194
+ kaminoMarkets?: KaminoMarket[]
1195
+ ): Promise<VaultOverview> {
1196
+ const vaultReservesState = vaultReserves ? vaultReserves : await this.loadVaultReserves(vault);
1197
+
1198
+ const vaultHoldingsWithUSDValuePromise = await this.getVaultHoldingsWithPrice(
1199
+ vault,
1200
+ slot,
1201
+ price,
1202
+ vaultReservesState
1203
+ );
1204
+ const vaultTheoreticalAPYPromise = await this.getVaultTheoreticalAPY(vault, slot, vaultReservesState);
1205
+ const totalInvestedAndBorrowedPromise = await this.getTotalBorrowedAndInvested(vault, slot, vaultReservesState);
1206
+ const vaultCollateralsPromise = await this.getVaultCollaterals(vault, slot, vaultReservesState, kaminoMarkets);
1207
+ const reservesOverviewPromise = await this.getVaultReservesDetails(vault, slot, vaultReservesState);
1208
+
1209
+ // 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
1210
+ const [
1211
+ vaultHoldingsWithUSDValue,
1212
+ vaultTheoreticalAPY,
1213
+ totalInvestedAndBorrowed,
1214
+ vaultCollaterals,
1215
+ reservesOverview,
1216
+ ] = await Promise.all([
1217
+ vaultHoldingsWithUSDValuePromise,
1218
+ vaultTheoreticalAPYPromise,
1219
+ totalInvestedAndBorrowedPromise,
1220
+ vaultCollateralsPromise,
1221
+ reservesOverviewPromise,
1222
+ ]);
1223
+
1224
+ return {
1225
+ holdingsUSD: vaultHoldingsWithUSDValue,
1226
+ reservesOverview: reservesOverview,
1227
+ vaultCollaterals: vaultCollaterals,
1228
+ theoreticalSupplyAPY: vaultTheoreticalAPY,
1229
+ totalBorrowed: totalInvestedAndBorrowed.totalBorrowed,
1230
+ utilizationRatio: totalInvestedAndBorrowed.utilizationRatio,
1231
+ };
1232
+ }
1233
+
1234
+ /**
1235
+ * This will return an aggregation of the current state of the vault with all the invested amounts and the utilization ratio of the vault
1236
+ * @param vault - the kamino vault to get available liquidity to withdraw for
1237
+ * @param slot - current slot
1238
+ * @param vaultReserves - 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
1239
+ * @returns an VaultReserveTotalBorrowedAndInvested object with the total invested amount, total borrowed amount and the utilization ratio of the vault
1240
+ */
1241
+ async getTotalBorrowedAndInvested(
1242
+ vault: VaultState,
1243
+ slot: number,
1244
+ vaultReserves?: PubkeyHashMap<PublicKey, KaminoReserve>
1245
+ ): Promise<VaultReserveTotalBorrowedAndInvested> {
1246
+ const vaultReservesState = vaultReserves ? vaultReserves : await this.loadVaultReserves(vault);
1247
+
1248
+ let totalInvested = new Decimal(0);
1249
+ let totalBorrowed = new Decimal(0);
1250
+
1251
+ vault.vaultAllocationStrategy.forEach((allocationStrategy) => {
1252
+ if (allocationStrategy.reserve.equals(PublicKey.default)) {
1253
+ return;
1254
+ }
1255
+
1256
+ const reserve = vaultReservesState.get(allocationStrategy.reserve);
1257
+ if (reserve === undefined) {
1258
+ throw new Error(`Reserve ${allocationStrategy.reserve.toBase58()} not found`);
1259
+ }
1260
+
1261
+ const reserveCollExchangeRate = reserve.getEstimatedCollateralExchangeRate(slot, 0);
1262
+ const reserveAllocationLiquidityAmount = new Decimal(allocationStrategy.cTokenAllocation.toString()).div(
1263
+ reserveCollExchangeRate
1264
+ );
1265
+
1266
+ const utilizationRatio = reserve.getEstimatedUtilizationRatio(slot, 0);
1267
+ totalInvested = totalInvested.add(reserveAllocationLiquidityAmount);
1268
+ totalBorrowed = totalBorrowed.add(reserveAllocationLiquidityAmount.mul(utilizationRatio));
1269
+ });
1270
+
1271
+ let utilizationRatio = new Decimal(0);
1272
+ if (!totalInvested.isZero()) {
1273
+ utilizationRatio = totalBorrowed.div(totalInvested);
1274
+ }
1275
+ return {
1276
+ totalInvested: totalInvested,
1277
+ totalBorrowed: totalBorrowed,
1278
+ utilizationRatio: utilizationRatio,
1165
1279
  };
1166
1280
  }
1167
1281
 
@@ -1193,9 +1307,10 @@ export class KaminoVaultClient {
1193
1307
  reserve.getBorrowedAmount();
1194
1308
  const reserveOverview: ReserveOverview = {
1195
1309
  supplyAPY: new Decimal(reserve.totalSupplyAPY(slot)),
1196
- uUtilizationRatio: new Decimal(reserve.getEstimatedUtilizationRatio(slot, 0)),
1310
+ utilizationRatio: new Decimal(reserve.getEstimatedUtilizationRatio(slot, 0)),
1197
1311
  liquidationThresholdPct: new Decimal(reserve.state.config.liquidationThresholdPct),
1198
1312
  borrowedAmount: reserve.getBorrowedAmount(),
1313
+ market: reserve.state.lendingMarket,
1199
1314
  };
1200
1315
  reservesDetails.set(allocationStrategy.reserve, reserveOverview);
1201
1316
  });
@@ -1371,13 +1486,21 @@ export type VaultHoldingsWithUSDValue = {
1371
1486
  availableUSD: Decimal;
1372
1487
  investedUSD: Decimal;
1373
1488
  investedInReservesUSD: PubkeyHashMap<PublicKey, Decimal>;
1489
+ totalUSD: Decimal;
1374
1490
  };
1375
1491
 
1376
1492
  export type ReserveOverview = {
1377
1493
  supplyAPY: Decimal;
1378
- uUtilizationRatio: Decimal;
1494
+ utilizationRatio: Decimal;
1379
1495
  liquidationThresholdPct: Decimal;
1380
1496
  borrowedAmount: Decimal;
1497
+ market: PublicKey;
1498
+ };
1499
+
1500
+ export type VaultReserveTotalBorrowedAndInvested = {
1501
+ totalInvested: Decimal;
1502
+ totalBorrowed: Decimal;
1503
+ utilizationRatio: Decimal;
1381
1504
  };
1382
1505
 
1383
1506
  export type MarketOverview = {
@@ -1391,3 +1514,17 @@ export type ReserveAsCollateral = {
1391
1514
  mint: PublicKey;
1392
1515
  liquidationLTVPct: Decimal;
1393
1516
  };
1517
+
1518
+ export type VaultOverview = {
1519
+ holdingsUSD: VaultHoldingsWithUSDValue;
1520
+ reservesOverview: PubkeyHashMap<PublicKey, ReserveOverview>;
1521
+ vaultCollaterals: PubkeyHashMap<PublicKey, MarketOverview>;
1522
+ theoreticalSupplyAPY: Decimal;
1523
+ totalBorrowed: Decimal;
1524
+ utilizationRatio: Decimal;
1525
+ };
1526
+
1527
+ export type VaultFeesPct = {
1528
+ managementFeePct: Decimal;
1529
+ performanceFeePct: Decimal;
1530
+ };
@@ -643,6 +643,25 @@ async function main() {
643
643
  });
644
644
  });
645
645
 
646
+ commands
647
+ .command('get-vault-overview')
648
+ .requiredOption('--vault <string>', 'Vault address')
649
+ .action(async ({ vault }) => {
650
+ const env = initializeClient(false, false);
651
+
652
+ const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
653
+
654
+ const vaultAddress = new PublicKey(vault);
655
+ const vaultState = await new KaminoVault(vaultAddress, undefined, env.kVaultProgramId).getState(env.connection);
656
+ const vaultOverview = await kaminoManager.getVaultOverview(
657
+ vaultState,
658
+ await env.connection.getSlot('confirmed'),
659
+ new Decimal(1.0)
660
+ );
661
+
662
+ console.log('vaultOverview', vaultOverview);
663
+ });
664
+
646
665
  commands
647
666
  .command('get-user-shares-for-vault')
648
667
  .requiredOption('--vault <string>', 'Vault address')