@kamino-finance/klend-sdk 5.4.0 → 5.4.2

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.
Files changed (40) hide show
  1. package/dist/classes/action.d.ts +0 -3
  2. package/dist/classes/action.d.ts.map +1 -1
  3. package/dist/classes/action.js +7 -10
  4. package/dist/classes/action.js.map +1 -1
  5. package/dist/classes/manager.d.ts +15 -1
  6. package/dist/classes/manager.d.ts.map +1 -1
  7. package/dist/classes/manager.js +18 -0
  8. package/dist/classes/manager.js.map +1 -1
  9. package/dist/classes/market.d.ts +1 -0
  10. package/dist/classes/market.d.ts.map +1 -1
  11. package/dist/classes/market.js +45 -0
  12. package/dist/classes/market.js.map +1 -1
  13. package/dist/classes/vault.d.ts +22 -0
  14. package/dist/classes/vault.d.ts.map +1 -1
  15. package/dist/classes/vault.js +99 -22
  16. package/dist/classes/vault.js.map +1 -1
  17. package/dist/client_kamino_manager.d.ts.map +1 -1
  18. package/dist/client_kamino_manager.js +23 -4
  19. package/dist/client_kamino_manager.js.map +1 -1
  20. package/dist/idl_codegen_kamino_vault/accounts/VaultState.d.ts +6 -0
  21. package/dist/idl_codegen_kamino_vault/accounts/VaultState.d.ts.map +1 -1
  22. package/dist/idl_codegen_kamino_vault/accounts/VaultState.js +14 -2
  23. package/dist/idl_codegen_kamino_vault/accounts/VaultState.js.map +1 -1
  24. package/dist/idl_codegen_kamino_vault/types/VaultAllocation.d.ts +5 -0
  25. package/dist/idl_codegen_kamino_vault/types/VaultAllocation.d.ts.map +1 -1
  26. package/dist/idl_codegen_kamino_vault/types/VaultAllocation.js +8 -1
  27. package/dist/idl_codegen_kamino_vault/types/VaultAllocation.js.map +1 -1
  28. package/dist/utils/constants.d.ts +4 -0
  29. package/dist/utils/constants.d.ts.map +1 -1
  30. package/dist/utils/constants.js +5 -1
  31. package/dist/utils/constants.js.map +1 -1
  32. package/package.json +7 -2
  33. package/src/classes/action.ts +1 -4
  34. package/src/classes/manager.ts +28 -0
  35. package/src/classes/market.ts +65 -0
  36. package/src/classes/vault.ts +132 -29
  37. package/src/client_kamino_manager.ts +35 -4
  38. package/src/idl_codegen_kamino_vault/accounts/VaultState.ts +18 -2
  39. package/src/idl_codegen_kamino_vault/types/VaultAllocation.ts +10 -1
  40. package/src/utils/constants.ts +6 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kamino-finance/klend-sdk",
3
- "version": "5.4.0",
3
+ "version": "5.4.2",
4
4
  "description": "Typescript SDK for interacting with the Kamino Lending (klend) protocol",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,6 +29,7 @@
29
29
  "docs": "typedoc",
30
30
  "build": "rm -rf dist/; tsc",
31
31
  "build:test": "yarn tsc --project ./tests/tsconfig.json",
32
+ "build:watch": "yarn tsc --watch",
32
33
  "test": "npx ts-mocha tests/**/*.test.ts --exit",
33
34
  "watch": "tsc --watch",
34
35
  "coverage": "jest --coverage",
@@ -43,7 +44,9 @@
43
44
  "start-validator": "solana-test-validator $(./deps/test-validator-params.sh)",
44
45
  "start-validator-and-test": "yarn start-server-and-test 'yarn start-validator' http://127.0.0.1:8899/health test",
45
46
  "dump-programs": "./deps/dump-from-mainnet.sh",
46
- "kamino-manager": "tsx src/client_kamino_manager.ts"
47
+ "kamino-manager": "tsx src/client_kamino_manager.ts",
48
+ "dev": "concurrently \"yarn build:watch\" \"nodemon --watch dist -e js,ts --exec 'yarn dev:push-and-update'\"",
49
+ "dev:push-and-update": "yalc publish && cd examples && yalc update"
47
50
  },
48
51
  "dependencies": {
49
52
  "@coral-xyz/anchor": "^0.28.0",
@@ -81,6 +84,7 @@
81
84
  "anchor-client-gen": "^0.28.1",
82
85
  "chai": "^4.3.7",
83
86
  "chai-bignumber": "^3.1.0",
87
+ "concurrently": "^9.1.0",
84
88
  "eslint": "^8.4.0",
85
89
  "eslint-config-airbnb-base": "^15.0.0",
86
90
  "eslint-config-airbnb-typescript": "^16.1.0",
@@ -90,6 +94,7 @@
90
94
  "jest": "^27.4.3",
91
95
  "jest-fetch-mock": "^3.0.3",
92
96
  "mocha": "^10.2.0",
97
+ "nodemon": "^3.1.7",
93
98
  "prettier": "^2.8.8",
94
99
  "start-server-and-test": "^2.0.0",
95
100
  "ts-jest": "^28.0.7",
@@ -58,6 +58,7 @@ import {
58
58
  getAssociatedTokenAddress,
59
59
  ScopeRefresh,
60
60
  createAtasIdempotent,
61
+ POSITION_LIMIT,
61
62
  } from '../utils';
62
63
  import { KaminoMarket } from './market';
63
64
  import { KaminoObligation } from './obligation';
@@ -69,10 +70,6 @@ import { VanillaObligation } from '../utils/ObligationType';
69
70
  import { PROGRAM_ID } from '../lib';
70
71
  import { U16_MAX } from '@kamino-finance/scope-sdk';
71
72
 
72
- export const POSITION_LIMIT = 10;
73
- export const BORROWS_LIMIT = 5;
74
- export const DEPOSITS_LIMIT = 8;
75
-
76
73
  const SOL_PADDING_FOR_INTEREST = new BN('1000000');
77
74
 
78
75
  export type ActionType =
@@ -17,6 +17,8 @@ import {
17
17
  MarketOverview,
18
18
  ReserveAllocationConfig,
19
19
  ReserveOverview,
20
+ SimulatedVaultHoldingsWithEarnedInterest,
21
+ VaultFees,
20
22
  VaultFeesPct,
21
23
  VaultHolder,
22
24
  VaultHoldings,
@@ -701,6 +703,32 @@ export class KaminoManager {
701
703
  return this._vaultClient.getVaultCumulativeInterest(vaultState);
702
704
  }
703
705
 
706
+ /**
707
+ * Simulate the current holdings of the vault and the earned interest
708
+ * @param vaultState the kamino vault state to get simulated holdings and earnings for
709
+ * @param vaultReserves optional; the state of the reserves in the vault allocation
710
+ * @returns a struct of simulated vault holdings and earned interest
711
+ */
712
+ async calculateSimulatedHoldingsWithInterest(
713
+ vaultState: VaultState,
714
+ vaultReserves?: PubkeyHashMap<PublicKey, KaminoReserve>
715
+ ): Promise<SimulatedVaultHoldingsWithEarnedInterest> {
716
+ return this._vaultClient.calculateSimulatedHoldingsWithInterest(vaultState, vaultReserves);
717
+ }
718
+
719
+ /**
720
+ * Simulate the current holdings and compute the fees that would be charged
721
+ * @param vaultState the kamino vault state to get simulated fees for
722
+ * @param simulatedCurrentHoldingsWithInterest optional; the simulated holdings and interest earned by the vault
723
+ * @returns a struct of simulated management and interest fees
724
+ */
725
+ async calculateSimulatedFees(
726
+ vaultState: VaultState,
727
+ simulatedCurrentHoldingsWithInterest?: SimulatedVaultHoldingsWithEarnedInterest
728
+ ): Promise<VaultFees> {
729
+ return this._vaultClient.calculateSimulatedFees(vaultState, simulatedCurrentHoldingsWithInterest);
730
+ }
731
+
704
732
  /**
705
733
  * This will load the onchain state for all the reserves that the vault has allocations for
706
734
  * @param vaultState - the vault state to load reserves for
@@ -21,6 +21,7 @@ import {
21
21
  PubkeyHashMap,
22
22
  CandidatePrice,
23
23
  PublicKeySet,
24
+ DEPOSITS_LIMIT,
24
25
  } from '../utils';
25
26
  import base58 from 'bs58';
26
27
  import { BN } from '@coral-xyz/anchor';
@@ -727,6 +728,70 @@ export class KaminoMarket {
727
728
  });
728
729
  }
729
730
 
731
+ async getAllObligationsByDepositedReserve(reserve: PublicKey) {
732
+ const finalObligations: KaminoObligation[] = [];
733
+ for (let i = 0; i < DEPOSITS_LIMIT; i++) {
734
+ const [slot, obligations] = await Promise.all([
735
+ this.connection.getSlot(),
736
+ this.connection.getProgramAccounts(this.programId, {
737
+ filters: [
738
+ {
739
+ dataSize: Obligation.layout.span + 8,
740
+ },
741
+ {
742
+ memcmp: {
743
+ offset: 96 + 136 * i,
744
+ bytes: reserve.toBase58(),
745
+ },
746
+ },
747
+ {
748
+ memcmp: {
749
+ offset: 32,
750
+ bytes: this.address,
751
+ },
752
+ },
753
+ ],
754
+ }),
755
+ ]);
756
+
757
+ const collateralExchangeRates = new PubkeyHashMap<PublicKey, Decimal>();
758
+ const cumulativeBorrowRates = new PubkeyHashMap<PublicKey, Decimal>();
759
+
760
+ const obligationsBatch = obligations.map((obligation) => {
761
+ if (obligation.account === null) {
762
+ throw new Error('Invalid account');
763
+ }
764
+ if (!obligation.account.owner.equals(this.programId)) {
765
+ throw new Error("account doesn't belong to this program");
766
+ }
767
+
768
+ const obligationAccount = Obligation.decode(obligation.account.data);
769
+
770
+ if (!obligationAccount) {
771
+ throw Error('Could not parse obligation.');
772
+ }
773
+
774
+ KaminoObligation.addRatesForObligation(
775
+ this,
776
+ obligationAccount,
777
+ collateralExchangeRates,
778
+ cumulativeBorrowRates,
779
+ slot
780
+ );
781
+
782
+ return new KaminoObligation(
783
+ this,
784
+ obligation.pubkey,
785
+ obligationAccount,
786
+ collateralExchangeRates,
787
+ cumulativeBorrowRates
788
+ );
789
+ });
790
+ finalObligations.push(...obligationsBatch);
791
+ }
792
+ return finalObligations;
793
+ }
794
+
730
795
  async getAllUserObligations(user: PublicKey) {
731
796
  const [currentSlot, obligations] = await Promise.all([
732
797
  this.connection.getSlot(),
@@ -58,7 +58,7 @@ import { withdraw } from '../idl_codegen_kamino_vault/instructions';
58
58
  import { PROGRAM_ID } from '../idl_codegen/programId';
59
59
  import { DEFAULT_RECENT_SLOT_DURATION_MS, ReserveWithAddress } from './reserve';
60
60
  import { Fraction } from './fraction';
61
- import { createAtasIdempotent, lendingMarketAuthPda, PublicKeySet } from '../utils';
61
+ import { createAtasIdempotent, lendingMarketAuthPda, PublicKeySet, SECONDS_PER_YEAR } from '../utils';
62
62
  import bs58 from 'bs58';
63
63
  import { getAccountOwner, getProgramAccounts } from '../utils/rpc';
64
64
  import {
@@ -69,6 +69,7 @@ import {
69
69
  UpdateVaultConfigIxs,
70
70
  } from './types';
71
71
  import { collToLamportsDecimal } from '@kamino-finance/kliquidity-sdk';
72
+ import { FullBPSDecimal } from '@kamino-finance/kliquidity-sdk/dist/utils/CreationParameters';
72
73
 
73
74
  export const kaminoVaultId = new PublicKey('kvauTFR8qm1dhniz6pYuBZkuene3Hfrs1VQhVRgCNrr');
74
75
  export const kaminoVaultStagingId = new PublicKey('STkvh7ostar39Fwr4uZKASs1RNNuYMFMTsE77FiRsL2');
@@ -675,8 +676,8 @@ export class KaminoVaultClient {
675
676
  const kaminoVault = new KaminoVault(vault.address, vaultState, vault.programId);
676
677
 
677
678
  // if the vault has allocations withdraw otherwise wtihdraw from available ix
678
- const vaultAllocation = vaultState.vaultAllocationStrategy.find(
679
- (allocation) => ~allocation.reserve.equals(PublicKey.default)
679
+ const vaultAllocation = vaultState.vaultAllocationStrategy.find((allocation) =>
680
+ allocation.reserve.equals(PublicKey.default)
680
681
  );
681
682
 
682
683
  if (vaultAllocation) {
@@ -734,18 +735,25 @@ export class KaminoVaultClient {
734
735
  },
735
736
  ]);
736
737
 
737
- const tokensToWithdraw = shareAmount.mul(await this.getTokensPerShareSingleVault(vault, slot));
738
+ const shareLamportsToWithdraw = collToLamportsDecimal(shareAmount, vaultState.sharesMintDecimals.toNumber());
739
+ const tokensPerShare = await this.getTokensPerShareSingleVault(vault, slot);
740
+ const sharesPerToken = new Decimal(1).div(tokensPerShare);
741
+ const tokensToWithdraw = shareLamportsToWithdraw.mul(tokensPerShare);
738
742
  let tokenLeftToWithdraw = tokensToWithdraw;
743
+ const availableTokens = new Decimal(vaultState.tokenAvailable.toString());
744
+ tokenLeftToWithdraw = tokenLeftToWithdraw.sub(availableTokens);
739
745
 
740
- tokenLeftToWithdraw = tokenLeftToWithdraw.sub(new Decimal(vaultState.tokenAvailable.toString()));
746
+ type ReserveWithTokensToWithdraw = { reserve: PublicKey; shares: Decimal };
741
747
 
742
- const reservesToWithdraw: PublicKey[] = [];
743
- const amountToWithdraw: Decimal[] = [];
744
- amountToWithdraw.push(new Decimal(vaultState.tokenAvailable.toString()));
748
+ const reserveWithSharesAmountToWithdraw: ReserveWithTokensToWithdraw[] = [];
749
+ let isFirstWithdraw = true;
745
750
 
746
751
  if (tokenLeftToWithdraw.lte(0)) {
747
752
  // Availabe enough to withdraw all - using first reserve as it does not matter
748
- reservesToWithdraw.push(vaultState.vaultAllocationStrategy[0].reserve);
753
+ reserveWithSharesAmountToWithdraw.push({
754
+ reserve: vaultState.vaultAllocationStrategy[0].reserve,
755
+ shares: shareLamportsToWithdraw,
756
+ });
749
757
  } else {
750
758
  // Get decreasing order sorted available liquidity to withdraw from each reserve allocated to
751
759
  const reserveAllocationAvailableLiquidityToWithdraw = await this.getReserveAllocationAvailableLiquidityToWithdraw(
@@ -754,15 +762,22 @@ export class KaminoVaultClient {
754
762
  vaultReservesState
755
763
  );
756
764
  // sort
757
- const reserveAllocationAvailableLiquidityToWithdrawSorted = new PubkeyHashMap(
758
- [...reserveAllocationAvailableLiquidityToWithdraw.entries()].sort((a, b) => b[1].sub(a[1]).toNumber())
759
- );
765
+ const reserveAllocationAvailableLiquidityToWithdrawSorted = [
766
+ ...reserveAllocationAvailableLiquidityToWithdraw.entries(),
767
+ ].sort((a, b) => b[1].sub(a[1]).toNumber());
760
768
 
761
- reserveAllocationAvailableLiquidityToWithdrawSorted.forEach((availableLiquidityToWithdraw, key) => {
769
+ reserveAllocationAvailableLiquidityToWithdrawSorted.forEach(([key, availableLiquidityToWithdraw], _) => {
762
770
  if (tokenLeftToWithdraw.gt(0)) {
763
- reservesToWithdraw.push(key);
764
- tokenLeftToWithdraw = tokenLeftToWithdraw.sub(availableLiquidityToWithdraw);
765
- amountToWithdraw.push(Decimal.min(tokenLeftToWithdraw, availableLiquidityToWithdraw));
771
+ let tokensToWithdrawFromReserve = Decimal.min(tokenLeftToWithdraw, availableLiquidityToWithdraw);
772
+ if (isFirstWithdraw) {
773
+ tokensToWithdrawFromReserve = tokensToWithdrawFromReserve.add(availableTokens);
774
+ isFirstWithdraw = false;
775
+ }
776
+ // round up to the nearest integer the shares to withdraw
777
+ const sharesToWithdrawFromReserve = tokensToWithdrawFromReserve.mul(sharesPerToken).ceil();
778
+ reserveWithSharesAmountToWithdraw.push({ reserve: key, shares: sharesToWithdrawFromReserve });
779
+
780
+ tokenLeftToWithdraw = tokenLeftToWithdraw.sub(tokensToWithdrawFromReserve);
766
781
  }
767
782
  });
768
783
  }
@@ -770,26 +785,35 @@ export class KaminoVaultClient {
770
785
  const withdrawIxns: TransactionInstruction[] = [];
771
786
  withdrawIxns.push(createAtaIx);
772
787
 
773
- reservesToWithdraw.forEach((reserve, index) => {
774
- const reserveState = vaultReservesState.get(reserve);
788
+ // let sharesLeftToWithdraw = shareAmount;
789
+ for (let reserveIndex = 0; reserveIndex < reserveWithSharesAmountToWithdraw.length; reserveIndex++) {
790
+ const reserveWithTokens = reserveWithSharesAmountToWithdraw[reserveIndex];
791
+ const reserveState = vaultReservesState.get(reserveWithTokens.reserve);
775
792
  if (reserveState === undefined) {
776
- throw new Error(`Reserve ${reserve.toBase58()} not found in vault reserves map`);
793
+ throw new Error(`Reserve ${reserveWithTokens.reserve.toBase58()} not found in vault reserves map`);
777
794
  }
778
-
779
795
  const marketAddress = reserveState.state.lendingMarket;
796
+
797
+ const isLastWithdraw = reserveIndex === reserveWithSharesAmountToWithdraw.length - 1;
798
+ // if it is not last withdraw it means that we can pass all shares as we are withdrawing everything from that reserve
799
+ let sharesToWithdraw = shareAmount;
800
+ if (isLastWithdraw) {
801
+ sharesToWithdraw = reserveWithTokens.shares;
802
+ }
803
+
780
804
  const withdrawFromReserveIx = this.withdrawIxn(
781
805
  user,
782
806
  vault,
783
807
  vaultState,
784
808
  marketAddress,
785
- { address: reserve, state: reserveState.state },
809
+ { address: reserveWithTokens.reserve, state: reserveState.state },
786
810
  userSharesAta,
787
811
  userTokenAta,
788
- amountToWithdraw[index],
812
+ sharesToWithdraw,
789
813
  vaultReservesState
790
814
  );
791
815
  withdrawIxns.push(withdrawFromReserveIx);
792
- });
816
+ }
793
817
 
794
818
  return withdrawIxns;
795
819
  }
@@ -1483,18 +1507,23 @@ export class KaminoVaultClient {
1483
1507
 
1484
1508
  const reserveAllocationAvailableLiquidityToWithdraw = new PubkeyHashMap<PublicKey, Decimal>();
1485
1509
  vaultState.vaultAllocationStrategy.forEach((allocationStrategy) => {
1510
+ if (allocationStrategy.reserve.equals(PublicKey.default)) {
1511
+ return;
1512
+ }
1486
1513
  const reserve = reserves.get(allocationStrategy.reserve);
1487
1514
  if (reserve === undefined) {
1488
1515
  throw new Error(`Reserve ${allocationStrategy.reserve.toBase58()} not found`);
1489
1516
  }
1490
- const reserveCollExchangeRate = reserve.getEstimatedCollateralExchangeRate(
1491
- slot,
1492
- new Fraction(reserve.state.liquidity.absoluteReferralRateSf)
1517
+ let referralFeeBps = 0;
1518
+ const denominator = reserve.state.config.protocolTakeRatePct / 100;
1519
+ if (denominator > 0) {
1520
+ referralFeeBps = new Fraction(reserve.state.liquidity.absoluteReferralRateSf)
1493
1521
  .toDecimal()
1494
- .div(reserve.state.config.protocolTakeRatePct / 100)
1522
+ .div(denominator)
1495
1523
  .floor()
1496
- .toNumber()
1497
- );
1524
+ .toNumber();
1525
+ }
1526
+ const reserveCollExchangeRate = reserve.getEstimatedCollateralExchangeRate(slot, referralFeeBps);
1498
1527
  const reserveAllocationLiquidityAmount = new Decimal(allocationStrategy.ctokenAllocation.toString()).div(
1499
1528
  reserveCollExchangeRate
1500
1529
  );
@@ -1922,6 +1951,70 @@ export class KaminoVaultClient {
1922
1951
  const netYieldLamports = new Fraction(vaultState.cumulativeEarnedInterestSf).toDecimal();
1923
1952
  return lamportsToDecimal(netYieldLamports, vaultState.tokenMintDecimals.toString());
1924
1953
  }
1954
+
1955
+ /**
1956
+ * Simulate the current holdings of the vault and the earned interest
1957
+ * @param vaultState the kamino vault state to get simulated holdings and earnings for
1958
+ * @param vaultReserves optional; the state of the reserves in the vault allocation
1959
+ * @returns a struct of simulated vault holdings and earned interest
1960
+ */
1961
+ async calculateSimulatedHoldingsWithInterest(
1962
+ vaultState: VaultState,
1963
+ vaultReserves?: PubkeyHashMap<PublicKey, KaminoReserve>
1964
+ ): Promise<SimulatedVaultHoldingsWithEarnedInterest> {
1965
+ const latestUpdateTs = vaultState.lastFeeChargeTimestamp.toNumber();
1966
+ const lastUpdateSlot = latestUpdateTs / this.recentSlotDurationMs;
1967
+
1968
+ const currentSlot = await this._connection.getSlot('confirmed');
1969
+
1970
+ const lastUpdateHoldingsPromise = this.getVaultHoldings(vaultState, lastUpdateSlot, vaultReserves);
1971
+ const currentHoldingsPromise = this.getVaultHoldings(vaultState, currentSlot, vaultReserves);
1972
+ const [lastUpdateHoldings, currentHoldings] = await Promise.all([
1973
+ lastUpdateHoldingsPromise,
1974
+ currentHoldingsPromise,
1975
+ ]);
1976
+
1977
+ const earnedInterest = currentHoldings.total.sub(lastUpdateHoldings.total);
1978
+
1979
+ return {
1980
+ holdings: currentHoldings,
1981
+ earnedInterest: earnedInterest,
1982
+ };
1983
+ }
1984
+
1985
+ /**
1986
+ * Simulate the current holdings and compute the fees that would be charged
1987
+ * @param vaultState the kamino vault state to get simulated fees for
1988
+ * @param simulatedCurrentHoldingsWithInterest optional; the simulated holdings and interest earned by the vault
1989
+ * @returns a struct of simulated management and interest fees
1990
+ */
1991
+ async calculateSimulatedFees(
1992
+ vaultState: VaultState,
1993
+ simulatedCurrentHoldingsWithInterest?: SimulatedVaultHoldingsWithEarnedInterest
1994
+ ): Promise<VaultFees> {
1995
+ const timestampNow = new Date().getTime();
1996
+ const timestampLastUpdate = vaultState.lastFeeChargeTimestamp.toNumber();
1997
+ const timeElapsed = timestampNow - timestampLastUpdate;
1998
+
1999
+ const simulatedCurrentHoldings = simulatedCurrentHoldingsWithInterest
2000
+ ? simulatedCurrentHoldingsWithInterest
2001
+ : await this.calculateSimulatedHoldingsWithInterest(vaultState);
2002
+
2003
+ const performanceFee = simulatedCurrentHoldings.earnedInterest.mul(
2004
+ new Decimal(vaultState.performanceFeeBps.toString()).div(FullBPSDecimal)
2005
+ );
2006
+
2007
+ const managementFeeFactor = new Decimal(timeElapsed)
2008
+ .mul(new Decimal(vaultState.managementFeeBps.toString()))
2009
+ .div(new Decimal(SECONDS_PER_YEAR));
2010
+ const prevAUM = new Fraction(vaultState.prevAumSf).toDecimal();
2011
+ const mgmtFee = prevAUM.mul(managementFeeFactor);
2012
+
2013
+ return {
2014
+ managementFee: mgmtFee,
2015
+ performanceFee: performanceFee,
2016
+ };
2017
+ }
1925
2018
  } // KaminoVaultClient
1926
2019
 
1927
2020
  export class KaminoVault {
@@ -2042,6 +2135,11 @@ export type VaultHoldings = {
2042
2135
  total: Decimal;
2043
2136
  };
2044
2137
 
2138
+ export type SimulatedVaultHoldingsWithEarnedInterest = {
2139
+ holdings: VaultHoldings;
2140
+ earnedInterest: Decimal;
2141
+ };
2142
+
2045
2143
  export type VaultHoldingsWithUSDValue = {
2046
2144
  holdings: VaultHoldings;
2047
2145
  availableUSD: Decimal;
@@ -2091,3 +2189,8 @@ export type VaultFeesPct = {
2091
2189
  managementFeePct: Decimal;
2092
2190
  performanceFeePct: Decimal;
2093
2191
  };
2192
+
2193
+ export type VaultFees = {
2194
+ managementFee: Decimal;
2195
+ performanceFee: Decimal;
2196
+ };
@@ -649,7 +649,7 @@ async function main() {
649
649
  const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
650
650
  const instructions = await kaminoManager.depositToVaultIxs(env.payer.publicKey, kaminoVault, amount);
651
651
 
652
- const depositSig = await processTxn(env.client, env.payer, instructions, mode, 2500, []);
652
+ const depositSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [], 800_000);
653
653
 
654
654
  mode === 'execute' && console.log('User deposit:', depositSig);
655
655
  });
@@ -675,14 +675,14 @@ async function main() {
675
675
  const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
676
676
 
677
677
  const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
678
- const instructions = await kaminoManager.withdrawFromVaultIxs(
678
+ const withdrawIxs = await kaminoManager.withdrawFromVaultIxs(
679
679
  env.payer.publicKey,
680
680
  kaminoVault,
681
681
  new Decimal(amount),
682
682
  await env.connection.getSlot('confirmed')
683
683
  );
684
684
 
685
- const depositSig = await processTxn(env.client, env.payer, instructions, mode, 2500, []);
685
+ const depositSig = await processTxn(env.client, env.payer, withdrawIxs, mode, 2500, [], 800_000);
686
686
 
687
687
  mode === 'execute' && console.log('User withdraw:', depositSig);
688
688
  });
@@ -754,7 +754,7 @@ async function main() {
754
754
  kaminoVault,
755
755
  reserveWithAddress
756
756
  );
757
- const investReserveSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [], 800_000);
757
+ const investReserveSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [], 400_000);
758
758
 
759
759
  mode === 'execute' && console.log('Reserve invested:', investReserveSig);
760
760
  });
@@ -926,6 +926,37 @@ async function main() {
926
926
  console.log('all vaults', allVaults);
927
927
  });
928
928
 
929
+ commands.command('get-all-vaults-pks').action(async () => {
930
+ const env = initializeClient(false, false);
931
+ const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
932
+
933
+ const allVaults = await kaminoManager.getAllVaults();
934
+ console.log(
935
+ 'all vaults',
936
+ allVaults.map((vault) => vault.address.toBase58())
937
+ );
938
+ });
939
+
940
+ commands
941
+ .command('get-simulated-interest-and-fees')
942
+ .requiredOption('--vault <string>', 'Vault address')
943
+ .action(async ({ vault }) => {
944
+ const env = initializeClient(false, false);
945
+
946
+ const vaultAddress = new PublicKey(vault);
947
+
948
+ const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
949
+ const vaultState = await new KaminoVault(vaultAddress, undefined, env.kVaultProgramId).getState(env.connection);
950
+
951
+ const simulatedHoldings = await kaminoManager.calculateSimulatedHoldingsWithInterest(vaultState);
952
+
953
+ console.log('Simulated holdings with interest', simulatedHoldings);
954
+
955
+ const simulatedFees = await kaminoManager.calculateSimulatedFees(vaultState, simulatedHoldings);
956
+
957
+ console.log('Simulated fees', simulatedFees);
958
+ });
959
+
929
960
  commands
930
961
  .command('download-lending-market-config')
931
962
  .requiredOption('--lending-market <string>', 'Lending Market Address')
@@ -36,6 +36,8 @@ export interface VaultStateFields {
36
36
  name: Array<number>
37
37
  vaultLookupTable: PublicKey
38
38
  vaultFarm: PublicKey
39
+ creationTimestamp: BN
40
+ padding1: BN
39
41
  padding2: Array<BN>
40
42
  }
41
43
 
@@ -71,6 +73,8 @@ export interface VaultStateJSON {
71
73
  name: Array<number>
72
74
  vaultLookupTable: string
73
75
  vaultFarm: string
76
+ creationTimestamp: string
77
+ padding1: string
74
78
  padding2: Array<string>
75
79
  }
76
80
 
@@ -106,6 +110,8 @@ export class VaultState {
106
110
  readonly name: Array<number>
107
111
  readonly vaultLookupTable: PublicKey
108
112
  readonly vaultFarm: PublicKey
113
+ readonly creationTimestamp: BN
114
+ readonly padding1: BN
109
115
  readonly padding2: Array<BN>
110
116
 
111
117
  static readonly discriminator = Buffer.from([
@@ -144,7 +150,9 @@ export class VaultState {
144
150
  borsh.array(borsh.u8(), 40, "name"),
145
151
  borsh.publicKey("vaultLookupTable"),
146
152
  borsh.publicKey("vaultFarm"),
147
- borsh.array(borsh.u128(), 245, "padding2"),
153
+ borsh.u64("creationTimestamp"),
154
+ borsh.u64("padding1"),
155
+ borsh.array(borsh.u128(), 244, "padding2"),
148
156
  ])
149
157
 
150
158
  constructor(fields: VaultStateFields) {
@@ -181,6 +189,8 @@ export class VaultState {
181
189
  this.name = fields.name
182
190
  this.vaultLookupTable = fields.vaultLookupTable
183
191
  this.vaultFarm = fields.vaultFarm
192
+ this.creationTimestamp = fields.creationTimestamp
193
+ this.padding1 = fields.padding1
184
194
  this.padding2 = fields.padding2
185
195
  }
186
196
 
@@ -221,7 +231,7 @@ export class VaultState {
221
231
  }
222
232
 
223
233
  static decode(data: Buffer): VaultState {
224
- if (!data.slice(0, 8).equals(VaultState.discriminator)) {
234
+ if (!VaultState.discriminator.equals(data.slice(0, 8))) {
225
235
  throw new Error("invalid account discriminator")
226
236
  }
227
237
 
@@ -263,6 +273,8 @@ export class VaultState {
263
273
  name: dec.name,
264
274
  vaultLookupTable: dec.vaultLookupTable,
265
275
  vaultFarm: dec.vaultFarm,
276
+ creationTimestamp: dec.creationTimestamp,
277
+ padding1: dec.padding1,
266
278
  padding2: dec.padding2,
267
279
  })
268
280
  }
@@ -302,6 +314,8 @@ export class VaultState {
302
314
  name: this.name,
303
315
  vaultLookupTable: this.vaultLookupTable.toString(),
304
316
  vaultFarm: this.vaultFarm.toString(),
317
+ creationTimestamp: this.creationTimestamp.toString(),
318
+ padding1: this.padding1.toString(),
305
319
  padding2: this.padding2.map((item) => item.toString()),
306
320
  }
307
321
  }
@@ -341,6 +355,8 @@ export class VaultState {
341
355
  name: obj.name,
342
356
  vaultLookupTable: new PublicKey(obj.vaultLookupTable),
343
357
  vaultFarm: new PublicKey(obj.vaultFarm),
358
+ creationTimestamp: new BN(obj.creationTimestamp),
359
+ padding1: new BN(obj.padding1),
344
360
  padding2: obj.padding2.map((item) => new BN(item)),
345
361
  })
346
362
  }
@@ -9,6 +9,7 @@ export interface VaultAllocationFields {
9
9
  targetAllocationWeight: BN
10
10
  /** Maximum token invested in this reserve */
11
11
  tokenAllocationCap: BN
12
+ ctokenVaultBump: BN
12
13
  configPadding: Array<BN>
13
14
  ctokenAllocation: BN
14
15
  lastInvestSlot: BN
@@ -22,6 +23,7 @@ export interface VaultAllocationJSON {
22
23
  targetAllocationWeight: string
23
24
  /** Maximum token invested in this reserve */
24
25
  tokenAllocationCap: string
26
+ ctokenVaultBump: string
25
27
  configPadding: Array<string>
26
28
  ctokenAllocation: string
27
29
  lastInvestSlot: string
@@ -35,6 +37,7 @@ export class VaultAllocation {
35
37
  readonly targetAllocationWeight: BN
36
38
  /** Maximum token invested in this reserve */
37
39
  readonly tokenAllocationCap: BN
40
+ readonly ctokenVaultBump: BN
38
41
  readonly configPadding: Array<BN>
39
42
  readonly ctokenAllocation: BN
40
43
  readonly lastInvestSlot: BN
@@ -46,6 +49,7 @@ export class VaultAllocation {
46
49
  this.ctokenVault = fields.ctokenVault
47
50
  this.targetAllocationWeight = fields.targetAllocationWeight
48
51
  this.tokenAllocationCap = fields.tokenAllocationCap
52
+ this.ctokenVaultBump = fields.ctokenVaultBump
49
53
  this.configPadding = fields.configPadding
50
54
  this.ctokenAllocation = fields.ctokenAllocation
51
55
  this.lastInvestSlot = fields.lastInvestSlot
@@ -60,7 +64,8 @@ export class VaultAllocation {
60
64
  borsh.publicKey("ctokenVault"),
61
65
  borsh.u64("targetAllocationWeight"),
62
66
  borsh.u64("tokenAllocationCap"),
63
- borsh.array(borsh.u64(), 128, "configPadding"),
67
+ borsh.u64("ctokenVaultBump"),
68
+ borsh.array(borsh.u64(), 127, "configPadding"),
64
69
  borsh.u64("ctokenAllocation"),
65
70
  borsh.u64("lastInvestSlot"),
66
71
  borsh.u128("tokenTargetAllocationSf"),
@@ -77,6 +82,7 @@ export class VaultAllocation {
77
82
  ctokenVault: obj.ctokenVault,
78
83
  targetAllocationWeight: obj.targetAllocationWeight,
79
84
  tokenAllocationCap: obj.tokenAllocationCap,
85
+ ctokenVaultBump: obj.ctokenVaultBump,
80
86
  configPadding: obj.configPadding,
81
87
  ctokenAllocation: obj.ctokenAllocation,
82
88
  lastInvestSlot: obj.lastInvestSlot,
@@ -91,6 +97,7 @@ export class VaultAllocation {
91
97
  ctokenVault: fields.ctokenVault,
92
98
  targetAllocationWeight: fields.targetAllocationWeight,
93
99
  tokenAllocationCap: fields.tokenAllocationCap,
100
+ ctokenVaultBump: fields.ctokenVaultBump,
94
101
  configPadding: fields.configPadding,
95
102
  ctokenAllocation: fields.ctokenAllocation,
96
103
  lastInvestSlot: fields.lastInvestSlot,
@@ -105,6 +112,7 @@ export class VaultAllocation {
105
112
  ctokenVault: this.ctokenVault.toString(),
106
113
  targetAllocationWeight: this.targetAllocationWeight.toString(),
107
114
  tokenAllocationCap: this.tokenAllocationCap.toString(),
115
+ ctokenVaultBump: this.ctokenVaultBump.toString(),
108
116
  configPadding: this.configPadding.map((item) => item.toString()),
109
117
  ctokenAllocation: this.ctokenAllocation.toString(),
110
118
  lastInvestSlot: this.lastInvestSlot.toString(),
@@ -119,6 +127,7 @@ export class VaultAllocation {
119
127
  ctokenVault: new PublicKey(obj.ctokenVault),
120
128
  targetAllocationWeight: new BN(obj.targetAllocationWeight),
121
129
  tokenAllocationCap: new BN(obj.tokenAllocationCap),
130
+ ctokenVaultBump: new BN(obj.ctokenVaultBump),
122
131
  configPadding: obj.configPadding.map((item) => new BN(item)),
123
132
  ctokenAllocation: new BN(obj.ctokenAllocation),
124
133
  lastInvestSlot: new BN(obj.lastInvestSlot),
@@ -8,6 +8,8 @@ export const U64_MAX = '18446744073709551615';
8
8
  const INITIAL_COLLATERAL_RATIO = 1;
9
9
  export const INITIAL_COLLATERAL_RATE = new Decimal(INITIAL_COLLATERAL_RATIO);
10
10
 
11
+ export const SECONDS_PER_YEAR = 365.242_199 * 24.0 * 60.0 * 60.0;
12
+
11
13
  export type ENV = 'mainnet-beta' | 'devnet' | 'localnet';
12
14
 
13
15
  export function isENV(value: any): value is ENV {
@@ -81,3 +83,7 @@ export const SOL_DECIMALS = 9;
81
83
  export const USDC_MAINNET_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
82
84
 
83
85
  export const MAINNET_BETA_CHAIN_ID = 101;
86
+
87
+ export const POSITION_LIMIT = 10;
88
+ export const BORROWS_LIMIT = 5;
89
+ export const DEPOSITS_LIMIT = 8;