@kamino-finance/klend-sdk 5.10.9 → 5.10.11

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 (55) hide show
  1. package/README.md +26 -2
  2. package/dist/classes/manager.d.ts +9 -3
  3. package/dist/classes/manager.d.ts.map +1 -1
  4. package/dist/classes/manager.js +10 -2
  5. package/dist/classes/manager.js.map +1 -1
  6. package/dist/classes/market.d.ts.map +1 -1
  7. package/dist/classes/market.js +4 -1
  8. package/dist/classes/market.js.map +1 -1
  9. package/dist/classes/types.d.ts +7 -0
  10. package/dist/classes/types.d.ts.map +1 -1
  11. package/dist/classes/vault.d.ts +23 -11
  12. package/dist/classes/vault.d.ts.map +1 -1
  13. package/dist/classes/vault.js +141 -29
  14. package/dist/classes/vault.js.map +1 -1
  15. package/dist/client_kamino_manager.d.ts.map +1 -1
  16. package/dist/client_kamino_manager.js +5 -5
  17. package/dist/client_kamino_manager.js.map +1 -1
  18. package/dist/idl_codegen_kamino_vault/errors/custom.d.ts +17 -1
  19. package/dist/idl_codegen_kamino_vault/errors/custom.d.ts.map +1 -1
  20. package/dist/idl_codegen_kamino_vault/errors/custom.js +29 -1
  21. package/dist/idl_codegen_kamino_vault/errors/custom.js.map +1 -1
  22. package/dist/idl_codegen_kamino_vault/instructions/deposit.d.ts +0 -1
  23. package/dist/idl_codegen_kamino_vault/instructions/deposit.d.ts.map +1 -1
  24. package/dist/idl_codegen_kamino_vault/instructions/deposit.js +0 -5
  25. package/dist/idl_codegen_kamino_vault/instructions/deposit.js.map +1 -1
  26. package/dist/idl_codegen_kamino_vault/instructions/updateReserveAllocation.d.ts +1 -1
  27. package/dist/idl_codegen_kamino_vault/instructions/updateReserveAllocation.d.ts.map +1 -1
  28. package/dist/idl_codegen_kamino_vault/instructions/updateReserveAllocation.js +5 -1
  29. package/dist/idl_codegen_kamino_vault/instructions/updateReserveAllocation.js.map +1 -1
  30. package/dist/idl_codegen_kamino_vault/types/ReserveConfig.d.ts +8 -5
  31. package/dist/idl_codegen_kamino_vault/types/ReserveConfig.d.ts.map +1 -1
  32. package/dist/idl_codegen_kamino_vault/types/ReserveConfig.js +9 -8
  33. package/dist/idl_codegen_kamino_vault/types/ReserveConfig.js.map +1 -1
  34. package/dist/utils/accountListing.d.ts +4 -0
  35. package/dist/utils/accountListing.d.ts.map +1 -0
  36. package/dist/utils/accountListing.js +37 -0
  37. package/dist/utils/accountListing.js.map +1 -0
  38. package/dist/utils/index.d.ts +1 -0
  39. package/dist/utils/index.d.ts.map +1 -1
  40. package/dist/utils/index.js +1 -0
  41. package/dist/utils/index.js.map +1 -1
  42. package/package.json +2 -1
  43. package/src/classes/manager.ts +12 -2
  44. package/src/classes/market.ts +7 -1
  45. package/src/classes/types.ts +9 -1
  46. package/src/classes/vault.ts +182 -32
  47. package/src/client.ts +11 -0
  48. package/src/client_kamino_manager.ts +12 -5
  49. package/src/idl_codegen_kamino_vault/errors/custom.ts +28 -0
  50. package/src/idl_codegen_kamino_vault/instructions/deposit.ts +0 -6
  51. package/src/idl_codegen_kamino_vault/instructions/updateReserveAllocation.ts +6 -2
  52. package/src/idl_codegen_kamino_vault/types/ReserveConfig.ts +17 -14
  53. package/src/idl_kamino_vault.json +17 -9
  54. package/src/utils/accountListing.ts +32 -0
  55. package/src/utils/index.ts +1 -0
@@ -4,12 +4,14 @@ import Decimal from 'decimal.js/decimal';
4
4
  /** the populateLUTIxs should be executed in a separate transaction as we cannot create and populate a lookup table in the same tx */
5
5
  export type InitVaultIxs = {
6
6
  initVaultIxs: TransactionInstruction[];
7
+ createLUTIx: TransactionInstruction;
7
8
  populateLUTIxs: TransactionInstruction[];
8
9
  };
9
10
 
10
11
  export type AcceptVaultOwnershipIxs = {
11
12
  acceptVaultOwnershipIx: TransactionInstruction;
12
- updateLUTIxs: TransactionInstruction[];
13
+ initNewLUTIx: TransactionInstruction;
14
+ updateLUTIxs: TransactionInstruction[]; // this has to be executed in a transaction after the initNewLUTIx is executed
13
15
  };
14
16
 
15
17
  export type UpdateReserveAllocationIxs = {
@@ -46,3 +48,9 @@ export type UserSharesForVault = {
46
48
  stakedShares: Decimal;
47
49
  totalShares: Decimal;
48
50
  };
51
+
52
+ export type ReserveAllocationOverview = {
53
+ targetWeight: Decimal;
54
+ tokenAllocationCap: Decimal;
55
+ ctokenAllocation: Decimal;
56
+ };
@@ -72,6 +72,7 @@ import {
72
72
  AcceptVaultOwnershipIxs,
73
73
  DepositIxs,
74
74
  InitVaultIxs,
75
+ ReserveAllocationOverview,
75
76
  SyncVaultLUTIxs,
76
77
  UpdateReserveAllocationIxs,
77
78
  UpdateVaultConfigIxs,
@@ -234,7 +235,7 @@ export class KaminoVaultClient {
234
235
  lut.toString()
235
236
  );
236
237
 
237
- const ixns = [createVaultIx, initVaultIx, createLUTIx, setLUTIx];
238
+ const ixns = [createVaultIx, initVaultIx, setLUTIx];
238
239
 
239
240
  if (vaultConfig.getPerformanceFeeBps() > 0) {
240
241
  const setPerformanceFeeIx = this.updateUninitialisedVaultConfigIx(
@@ -264,7 +265,7 @@ export class KaminoVaultClient {
264
265
  ixns.push(setNameIx);
265
266
  }
266
267
 
267
- return { vault: vaultState, initVaultIxs: { initVaultIxs: ixns, populateLUTIxs: insertIntoLUTIxs } };
268
+ return { vault: vaultState, initVaultIxs: { initVaultIxs: ixns, createLUTIx, populateLUTIxs: insertIntoLUTIxs } };
268
269
  }
269
270
 
270
271
  /**
@@ -295,7 +296,7 @@ export class KaminoVaultClient {
295
296
  ctokenVault: cTokenVault,
296
297
  systemProgram: SystemProgram.programId,
297
298
  rent: SYSVAR_RENT_PUBKEY,
298
- tokenProgram: TOKEN_PROGRAM_ID,
299
+ reserveCollateralTokenProgram: vaultState.tokenProgram,
299
300
  };
300
301
 
301
302
  const updateReserveAllocationArgs: UpdateReserveAllocationArgs = {
@@ -337,12 +338,14 @@ export class KaminoVaultClient {
337
338
  * @param vault the vault to update
338
339
  * @param mode the field to update (based on VaultConfigFieldKind enum)
339
340
  * @param value the value to update the field with
341
+ * @param [signer] the signer of the transaction. Optional. If not provided the admin of the vault will be used. It should be used when changing the admin of the vault if we want to batch multiple ixs in the same tx
340
342
  * @returns a struct that contains the instruction to update the field and an optional list of instructions to update the lookup table
341
343
  */
342
344
  async updateVaultConfigIxs(
343
345
  vault: KaminoVault,
344
346
  mode: VaultConfigFieldKind,
345
- value: string
347
+ value: string,
348
+ signer?: PublicKey
346
349
  ): Promise<UpdateVaultConfigIxs> {
347
350
  const vaultState: VaultState = await vault.getState(this.getConnection());
348
351
 
@@ -351,6 +354,9 @@ export class KaminoVaultClient {
351
354
  vaultState: vault.address,
352
355
  klendProgram: this._kaminoLendProgramId,
353
356
  };
357
+ if (signer) {
358
+ updateVaultConfigAccs.adminAuthority = signer;
359
+ }
354
360
 
355
361
  const updateVaultConfigArgs: UpdateVaultConfigArgs = {
356
362
  entry: mode,
@@ -525,12 +531,12 @@ export class KaminoVaultClient {
525
531
 
526
532
  const LUTIxs = [];
527
533
  const [initNewLUTIx, newLUT] = initLookupTableIx(vaultState.pendingAdmin, await this.getConnection().getSlot());
528
- LUTIxs.push(initNewLUTIx);
529
534
 
530
535
  const insertIntoLUTIxs = await this.insertIntoLookupTableIxs(
531
536
  vaultState.pendingAdmin,
532
537
  newLUT,
533
- accountsInExistentLUT
538
+ accountsInExistentLUT,
539
+ []
534
540
  );
535
541
 
536
542
  LUTIxs.push(...insertIntoLUTIxs);
@@ -538,13 +544,15 @@ export class KaminoVaultClient {
538
544
  const updateVaultConfigIxs = await this.updateVaultConfigIxs(
539
545
  vault,
540
546
  new VaultConfigField.LookupTable(),
541
- newLUT.toString()
547
+ newLUT.toString(),
548
+ vaultState.pendingAdmin
542
549
  );
543
550
  LUTIxs.push(updateVaultConfigIxs.updateVaultConfigIx);
544
551
  LUTIxs.push(...updateVaultConfigIxs.updateLUTIxs);
545
552
 
546
553
  const acceptVaultOwnershipIxs: AcceptVaultOwnershipIxs = {
547
554
  acceptVaultOwnershipIx,
555
+ initNewLUTIx,
548
556
  updateLUTIxs: LUTIxs,
549
557
  };
550
558
 
@@ -720,7 +728,6 @@ export class KaminoVaultClient {
720
728
  userTokenAta: userTokenAta,
721
729
  userSharesAta: userSharesAta,
722
730
  tokenProgram: tokenProgramID,
723
- instructionSysvarAccount: SYSVAR_INSTRUCTIONS_PUBKEY,
724
731
  klendProgram: this._kaminoLendProgramId,
725
732
  sharesTokenProgram: TOKEN_PROGRAM_ID,
726
733
  };
@@ -802,7 +809,7 @@ export class KaminoVaultClient {
802
809
  * This function will return a struct with the instructions to unstake from the farm if necessary and the instructions for 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
803
810
  * @param user - user to withdraw
804
811
  * @param vault - vault to withdraw from
805
- * @param shareAmount - share amount to withdraw, in order to withdraw everything, any value > user share amount
812
+ * @param shareAmount - share amount to withdraw (in tokens, not lamports), in order to withdraw everything, any value > user share amount
806
813
  * @param slot - current slot, used to estimate the interest earned in the different reserves with allocation from the vault
807
814
  * @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
808
815
  * @param [farmState] - the state of the vault farm, if the vault has a farm. Optional. If not provided, it will be fetched
@@ -839,8 +846,8 @@ export class KaminoVaultClient {
839
846
  }
840
847
 
841
848
  // if the vault has allocations withdraw otherwise wtihdraw from available ix
842
- const vaultAllocation = vaultState.vaultAllocationStrategy.find((allocation) =>
843
- allocation.reserve.equals(PublicKey.default)
849
+ const vaultAllocation = vaultState.vaultAllocationStrategy.find(
850
+ (allocation) => !allocation.reserve.equals(PublicKey.default)
844
851
  );
845
852
 
846
853
  if (vaultAllocation) {
@@ -990,7 +997,6 @@ export class KaminoVaultClient {
990
997
  return withdrawIxns;
991
998
  }
992
999
 
993
- // todo: make sure we also check the ata of the investor for the vault token exists
994
1000
  /**
995
1001
  * This will trigger invest by balancing, based on weights, the reserve allocations of the vault. It can either withdraw or deposit into reserves to balance them. This is a function that should be cranked
996
1002
  * @param payer wallet that pays the tx
@@ -998,20 +1004,87 @@ export class KaminoVaultClient {
998
1004
  * @returns - an array of invest instructions for each invest action required for the vault reserves
999
1005
  */
1000
1006
  async investAllReservesIxs(payer: PublicKey, vault: KaminoVault): Promise<TransactionInstruction[]> {
1001
- //TODO: Order invest ixns by - invest that removes first, then invest that adds
1002
1007
  const vaultState = await vault.getState(this.getConnection());
1003
- const vaultReserves = this.getVaultReserves(vaultState);
1008
+ const allReserves = this.getAllVaultReserves(vaultState);
1009
+ if (allReserves.length === 0) {
1010
+ throw new Error('No reserves found for the vault, please select at least one reserve for the vault');
1011
+ }
1012
+
1013
+ const [allReservesStateMap, computedReservesAllocation] = await Promise.all([
1014
+ this.loadVaultReserves(vaultState),
1015
+ this.getVaultComputedReservesAllocation(vaultState),
1016
+ ]);
1017
+
1018
+ const tokenProgram = await getAccountOwner(this.getConnection(), vaultState.tokenMint);
1019
+ const [{ ata: _payerTokenAta, createAtaIx }] = createAtasIdempotent(payer, [
1020
+ { mint: vaultState.tokenMint, tokenProgram },
1021
+ ]);
1022
+
1023
+ // compute total vault holdings and expected distribution based on weights
1024
+ const curentVaultAllocations = this.getVaultAllocations(vaultState);
1025
+
1026
+ const reservesToDisinvestFrom: PublicKey[] = [];
1027
+
1028
+ for (let index = 0; index < allReserves.length; index++) {
1029
+ const reservePubkey = allReserves[index];
1030
+ const reserveState = allReservesStateMap.get(reservePubkey)!;
1031
+ const computedAllocation = computedReservesAllocation.get(reservePubkey)!;
1032
+ const currentCTokenAllocation = curentVaultAllocations.get(reservePubkey)!.ctokenAllocation;
1033
+
1034
+ const reserveCollExchangeRate = reserveState.getCollateralExchangeRate();
1035
+ const reserveAllocationLiquidityAmount = lamportsToDecimal(
1036
+ currentCTokenAllocation.div(reserveCollExchangeRate),
1037
+ vaultState.tokenMintDecimals.toNumber()
1038
+ );
1039
+
1040
+ if (computedAllocation.lt(reserveAllocationLiquidityAmount)) {
1041
+ reservesToDisinvestFrom.push(reservePubkey);
1042
+ }
1043
+ }
1044
+
1004
1045
  const investIxnsPromises: Promise<TransactionInstruction[]>[] = [];
1005
- for (const reserve of vaultReserves) {
1006
- const reserveState = await Reserve.fetch(this.getConnection(), reserve, this._kaminoLendProgramId);
1046
+ // invest first the reserves from which we disinvest, then the other ones
1047
+ for (const reserve of reservesToDisinvestFrom) {
1048
+ const reserveState = allReservesStateMap.get(reserve);
1007
1049
  if (reserveState === null) {
1008
1050
  throw new Error(`Reserve ${reserve.toBase58()} not found`);
1009
1051
  }
1010
- const investIxsPromise = this.investSingleReserveIxs(payer, vault, { address: reserve, state: reserveState });
1052
+ const investIxsPromise = this.investSingleReserveIxs(
1053
+ payer,
1054
+ vault,
1055
+ {
1056
+ address: reserve,
1057
+ state: reserveState!.state,
1058
+ },
1059
+ allReservesStateMap,
1060
+ false
1061
+ );
1011
1062
  investIxnsPromises.push(investIxsPromise);
1012
1063
  }
1013
1064
 
1014
- const investIxns = await Promise.all(investIxnsPromises).then((ixns) => ixns.flat());
1065
+ for (const reserve of allReserves) {
1066
+ if (!reservesToDisinvestFrom.includes(reserve)) {
1067
+ const reserveState = allReservesStateMap.get(reserve);
1068
+ if (reserveState === null) {
1069
+ throw new Error(`Reserve ${reserve.toBase58()} not found`);
1070
+ }
1071
+ const investIxsPromise = this.investSingleReserveIxs(
1072
+ payer,
1073
+ vault,
1074
+ {
1075
+ address: reserve,
1076
+ state: reserveState!.state,
1077
+ },
1078
+ allReservesStateMap,
1079
+ false
1080
+ );
1081
+ investIxnsPromises.push(investIxsPromise);
1082
+ }
1083
+ }
1084
+
1085
+ let investIxns: TransactionInstruction[] = [];
1086
+ investIxns.push(createAtaIx);
1087
+ investIxns = await Promise.all(investIxnsPromises).then((ixns) => ixns.flat());
1015
1088
 
1016
1089
  return investIxns;
1017
1090
  }
@@ -1029,16 +1102,23 @@ export class KaminoVaultClient {
1029
1102
  payer: PublicKey,
1030
1103
  vault: KaminoVault,
1031
1104
  reserve: ReserveWithAddress,
1032
- vaultReservesMap?: PubkeyHashMap<PublicKey, KaminoReserve>
1105
+ vaultReservesMap?: PubkeyHashMap<PublicKey, KaminoReserve>,
1106
+ createAtaIfNeeded: boolean = true
1033
1107
  ): Promise<TransactionInstruction[]> {
1108
+ console.log('create invest ix for reserve', reserve.address.toBase58());
1034
1109
  const vaultState = await vault.getState(this.getConnection());
1035
1110
  const cTokenVault = getCTokenVaultPda(vault.address, reserve.address, this._kaminoVaultProgramId);
1036
1111
  const lendingMarketAuth = lendingMarketAuthPda(reserve.state.lendingMarket, this._kaminoLendProgramId)[0];
1037
1112
 
1113
+ const ixs: TransactionInstruction[] = [];
1114
+
1038
1115
  const tokenProgram = await getAccountOwner(this.getConnection(), vaultState.tokenMint);
1039
1116
  const [{ ata: payerTokenAta, createAtaIx }] = createAtasIdempotent(payer, [
1040
1117
  { mint: vaultState.tokenMint, tokenProgram },
1041
1118
  ]);
1119
+ if (createAtaIfNeeded) {
1120
+ ixs.push(createAtaIx);
1121
+ }
1042
1122
 
1043
1123
  const investAccounts: InvestAccounts = {
1044
1124
  payer,
@@ -1400,6 +1480,49 @@ export class KaminoVaultClient {
1400
1480
  return ixns;
1401
1481
  }
1402
1482
 
1483
+ /** Read the total holdings of a vault and the reserve weights and returns a map from each reserve to how many tokens should be deposited.
1484
+ * @param vaultState - the vault state to calculate the allocation for
1485
+ * @returns - a map from each reserve to how many tokens should be invested into
1486
+ */
1487
+ async getVaultComputedReservesAllocation(vaultState: VaultState): Promise<PubkeyHashMap<PublicKey, Decimal>> {
1488
+ const holdings = await this.getVaultHoldings(vaultState);
1489
+ const initialVaultAllocations = this.getVaultAllocations(vaultState);
1490
+
1491
+ const allReserves = this.getAllVaultReserves(vaultState);
1492
+
1493
+ let totalAllocation = new Decimal(0);
1494
+ initialVaultAllocations.forEach((allocation) => {
1495
+ totalAllocation = totalAllocation.add(allocation.targetWeight);
1496
+ });
1497
+ const expectedHoldingsDistribution = new PubkeyHashMap<PublicKey, Decimal>();
1498
+
1499
+ let totalLeftToInvest = holdings.total;
1500
+ let currentAllocationSum = totalAllocation;
1501
+ const ZERO = new Decimal(0);
1502
+ while (totalLeftToInvest.gt(ZERO)) {
1503
+ const totalLeftover = totalLeftToInvest;
1504
+ for (const reserve of allReserves) {
1505
+ const reserveWithWeight = initialVaultAllocations.get(reserve);
1506
+ const targetAllocation = reserveWithWeight!.targetWeight.mul(totalLeftover).div(currentAllocationSum);
1507
+ const reserveCap = reserveWithWeight!.tokenAllocationCap;
1508
+ // todo: check if both target and reserveCap
1509
+ const amountToInvest = Decimal.min(targetAllocation, reserveCap, totalLeftToInvest);
1510
+ totalLeftToInvest = totalLeftToInvest.sub(amountToInvest);
1511
+ if (amountToInvest.eq(reserveCap)) {
1512
+ currentAllocationSum = currentAllocationSum.sub(reserveWithWeight!.targetWeight);
1513
+ }
1514
+ const reserveHasPreallocation = expectedHoldingsDistribution.has(reserve);
1515
+ if (reserveHasPreallocation) {
1516
+ expectedHoldingsDistribution.set(reserve, expectedHoldingsDistribution.get(reserve)!.add(amountToInvest));
1517
+ } else {
1518
+ expectedHoldingsDistribution.set(reserve, amountToInvest);
1519
+ }
1520
+ }
1521
+ }
1522
+
1523
+ return expectedHoldingsDistribution;
1524
+ }
1525
+
1403
1526
  /**
1404
1527
  * This method returns the user shares balance for a given vault
1405
1528
  * @param user - user to calculate the shares balance for
@@ -1652,7 +1775,7 @@ export class KaminoVaultClient {
1652
1775
  /**
1653
1776
  * This will return the a map between reserve pubkey and the pct of the vault invested amount in each reserve
1654
1777
  * @param vaultState - the kamino vault to get reserves distribution for
1655
- * @returns a ma between reserve pubkey and the allocation pct for the reserve
1778
+ * @returns a map between reserve pubkey and the allocation pct for the reserve
1656
1779
  */
1657
1780
  getAllocationsDistribuionPct(vaultState: VaultState): PubkeyHashMap<PublicKey, Decimal> {
1658
1781
  const allocationsDistributionPct = new PubkeyHashMap<PublicKey, Decimal>();
@@ -1661,7 +1784,6 @@ export class KaminoVaultClient {
1661
1784
  const filteredAllocations = vaultState.vaultAllocationStrategy.filter(
1662
1785
  (allocation) => !allocation.reserve.equals(PublicKey.default)
1663
1786
  );
1664
- console.log('filteredAllocations length', filteredAllocations.length);
1665
1787
  filteredAllocations.forEach((allocation) => {
1666
1788
  totalAllocation = totalAllocation.add(new Decimal(allocation.targetAllocationWeight.toString()));
1667
1789
  });
@@ -1676,6 +1798,30 @@ export class KaminoVaultClient {
1676
1798
  return allocationsDistributionPct;
1677
1799
  }
1678
1800
 
1801
+ /**
1802
+ * This will return the a map between reserve pubkey and the allocation overview for the reserve
1803
+ * @param vaultState - the kamino vault to get reserves allocation overview for
1804
+ * @returns a map between reserve pubkey and the allocation overview for the reserve
1805
+ */
1806
+ getVaultAllocations(vaultState: VaultState): PubkeyHashMap<PublicKey, ReserveAllocationOverview> {
1807
+ const vaultAllocations = new PubkeyHashMap<PublicKey, ReserveAllocationOverview>();
1808
+
1809
+ vaultState.vaultAllocationStrategy.map((allocation) => {
1810
+ if (allocation.reserve.equals(PublicKey.default)) {
1811
+ return;
1812
+ }
1813
+
1814
+ const allocationOverview: ReserveAllocationOverview = {
1815
+ targetWeight: new Decimal(allocation.targetAllocationWeight.toString()),
1816
+ tokenAllocationCap: new Decimal(allocation.tokenAllocationCap.toString()),
1817
+ ctokenAllocation: new Decimal(allocation.ctokenAllocation.toString()),
1818
+ };
1819
+ vaultAllocations.set(allocation.reserve, allocationOverview);
1820
+ });
1821
+
1822
+ return vaultAllocations;
1823
+ }
1824
+
1679
1825
  /**
1680
1826
  * This will return an unsorted hash map of all reserves that the given vault has allocations for, toghether with the amount that can be withdrawn from each of the reserves
1681
1827
  * @param vault - the kamino vault to get available liquidity to withdraw for
@@ -1728,7 +1874,11 @@ export class KaminoVaultClient {
1728
1874
  * @returns a hashmap from each reserve pubkey to the reserve state
1729
1875
  */
1730
1876
  getAllVaultReserves(vault: VaultState): PublicKey[] {
1731
- return vault.vaultAllocationStrategy.map((vaultAllocation) => vaultAllocation.reserve);
1877
+ return vault.vaultAllocationStrategy
1878
+ .map((vaultAllocation) => vaultAllocation.reserve)
1879
+ .filter((reserve) => {
1880
+ return !reserve.equals(PublicKey.default);
1881
+ });
1732
1882
  }
1733
1883
 
1734
1884
  /**
@@ -2283,10 +2433,10 @@ export class KaminoVaultConfig {
2283
2433
  readonly tokenMint: PublicKey;
2284
2434
  /** The token mint program id */
2285
2435
  readonly tokenMintProgramId: PublicKey;
2286
- /** The performance fee rate of the vault, expressed as a decimal */
2287
- readonly performanceFeeRate: Decimal;
2288
- /** The management fee rate of the vault, expressed as a decimal */
2289
- readonly managementFeeRate: Decimal;
2436
+ /** The performance fee rate of the vault, as percents, expressed as a decimal */
2437
+ readonly performanceFeeRatePercentage: Decimal;
2438
+ /** The management fee rate of the vault, as percents, expressed as a decimal */
2439
+ readonly managementFeeRatePercentage: Decimal;
2290
2440
  /** The name to be stored on cain for the vault (max 40 characters). */
2291
2441
  readonly name: string;
2292
2442
 
@@ -2294,24 +2444,24 @@ export class KaminoVaultConfig {
2294
2444
  admin: PublicKey;
2295
2445
  tokenMint: PublicKey;
2296
2446
  tokenMintProgramId: PublicKey;
2297
- performanceFeeRate: Decimal;
2298
- managementFeeRate: Decimal;
2447
+ performanceFeeRatePercentage: Decimal;
2448
+ managementFeeRatePercentage: Decimal;
2299
2449
  name: string;
2300
2450
  }) {
2301
2451
  this.admin = args.admin;
2302
2452
  this.tokenMint = args.tokenMint;
2303
- this.performanceFeeRate = args.performanceFeeRate;
2304
- this.managementFeeRate = args.managementFeeRate;
2453
+ this.performanceFeeRatePercentage = args.performanceFeeRatePercentage;
2454
+ this.managementFeeRatePercentage = args.managementFeeRatePercentage;
2305
2455
  this.tokenMintProgramId = args.tokenMintProgramId;
2306
2456
  this.name = args.name;
2307
2457
  }
2308
2458
 
2309
2459
  getPerformanceFeeBps(): number {
2310
- return this.performanceFeeRate.mul(100).toNumber();
2460
+ return this.performanceFeeRatePercentage.mul(100).toNumber();
2311
2461
  }
2312
2462
 
2313
2463
  getManagementFeeBps(): number {
2314
- return this.managementFeeRate.mul(100).toNumber();
2464
+ return this.managementFeeRatePercentage.mul(100).toNumber();
2315
2465
  }
2316
2466
  }
2317
2467
 
package/src/client.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  getAllUserMetadatasWithFilter,
10
10
  getProgramId,
11
11
  toJson,
12
+ getAllObligationAccounts,
12
13
  } from './lib';
13
14
  import * as fs from 'fs';
14
15
  import { Connection, GetProgramAccountsFilter, Keypair, PublicKey } from '@solana/web3.js';
@@ -65,6 +66,16 @@ async function main() {
65
66
  console.log(toJson(kaminoObligation?.refreshedStats));
66
67
  });
67
68
 
69
+ commands
70
+ .command('print-all-obligation-accounts')
71
+ .option(`--rpc <string>`, 'The RPC URL')
72
+ .action(async ({ rpc }) => {
73
+ const connection = new Connection(rpc, {});
74
+ for await (const obligationAccount of getAllObligationAccounts(connection)) {
75
+ console.log(toJson(obligationAccount.toJSON()));
76
+ }
77
+ });
78
+
68
79
  commands
69
80
  .command('print-reserve')
70
81
  .option(`--url <string>`, 'The admin keypair file')
@@ -245,14 +245,21 @@ async function main() {
245
245
  admin: mode === 'multisig' ? multisigPk : env.payer.publicKey,
246
246
  tokenMint: tokenMint,
247
247
  tokenMintProgramId: tokenProgramID,
248
- performanceFeeRate: new Decimal(0.0),
249
- managementFeeRate: new Decimal(0.0),
248
+ performanceFeeRatePercentage: new Decimal(0.0),
249
+ managementFeeRatePercentage: new Decimal(0.0),
250
250
  name,
251
251
  });
252
252
 
253
253
  const { vault: vaultKp, initVaultIxs: instructions } = await kaminoManager.createVaultIxs(kaminoVaultConfig);
254
254
 
255
- const _createVaultSig = await processTxn(env.client, env.payer, instructions.initVaultIxs, mode, 2500, [vaultKp]);
255
+ const _createVaultSig = await processTxn(
256
+ env.client,
257
+ env.payer,
258
+ [...instructions.initVaultIxs, instructions.createLUTIx],
259
+ mode,
260
+ 2500,
261
+ [vaultKp]
262
+ );
256
263
  await sleep(5000);
257
264
  const _populateLUTSig = await processTxn(env.client, env.payer, instructions.populateLUTIxs, mode, 2500, []);
258
265
 
@@ -327,7 +334,7 @@ async function main() {
327
334
  []
328
335
  );
329
336
 
330
- mode === 'execute' && console.log('Pending admin updated:', updateVaultPendingAdminSig);
337
+ mode === 'execute' && console.log('Vault updated:', updateVaultPendingAdminSig);
331
338
  });
332
339
 
333
340
  commands
@@ -777,7 +784,7 @@ async function main() {
777
784
  kaminoVault,
778
785
  reserveWithAddress
779
786
  );
780
- const investReserveSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [], 400_000);
787
+ const investReserveSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [], 800_000);
781
788
 
782
789
  mode === 'execute' && console.log('Reserve invested:', investReserveSig);
783
790
  });
@@ -41,6 +41,8 @@ export type CustomError =
41
41
  | WithdrawResultsInZeroShares
42
42
  | CannotWithdrawZeroShares
43
43
  | ManagementFeeGreaterThanMaxAllowed
44
+ | VaultAUMZero
45
+ | MissingReserveForBatchRefresh
44
46
 
45
47
  export class DepositAmountsZero extends Error {
46
48
  static readonly code = 7000
@@ -504,6 +506,28 @@ export class ManagementFeeGreaterThanMaxAllowed extends Error {
504
506
  }
505
507
  }
506
508
 
509
+ export class VaultAUMZero extends Error {
510
+ static readonly code = 7042
511
+ readonly code = 7042
512
+ readonly name = "VaultAUMZero"
513
+ readonly msg = "Vault assets under management are empty"
514
+
515
+ constructor(readonly logs?: string[]) {
516
+ super("7042: Vault assets under management are empty")
517
+ }
518
+ }
519
+
520
+ export class MissingReserveForBatchRefresh extends Error {
521
+ static readonly code = 7043
522
+ readonly code = 7043
523
+ readonly name = "MissingReserveForBatchRefresh"
524
+ readonly msg = "Missing reserve for batch refresh"
525
+
526
+ constructor(readonly logs?: string[]) {
527
+ super("7043: Missing reserve for batch refresh")
528
+ }
529
+ }
530
+
507
531
  export function fromCode(code: number, logs?: string[]): CustomError | null {
508
532
  switch (code) {
509
533
  case 7000:
@@ -590,6 +614,10 @@ export function fromCode(code: number, logs?: string[]): CustomError | null {
590
614
  return new CannotWithdrawZeroShares(logs)
591
615
  case 7041:
592
616
  return new ManagementFeeGreaterThanMaxAllowed(logs)
617
+ case 7042:
618
+ return new VaultAUMZero(logs)
619
+ case 7043:
620
+ return new MissingReserveForBatchRefresh(logs)
593
621
  }
594
622
 
595
623
  return null
@@ -20,7 +20,6 @@ export interface DepositAccounts {
20
20
  klendProgram: PublicKey
21
21
  tokenProgram: PublicKey
22
22
  sharesTokenProgram: PublicKey
23
- instructionSysvarAccount: PublicKey
24
23
  }
25
24
 
26
25
  export const layout = borsh.struct([borsh.u64("maxAmount")])
@@ -42,11 +41,6 @@ export function deposit(
42
41
  { pubkey: accounts.klendProgram, isSigner: false, isWritable: false },
43
42
  { pubkey: accounts.tokenProgram, isSigner: false, isWritable: false },
44
43
  { pubkey: accounts.sharesTokenProgram, isSigner: false, isWritable: false },
45
- {
46
- pubkey: accounts.instructionSysvarAccount,
47
- isSigner: false,
48
- isWritable: false,
49
- },
50
44
  ]
51
45
  const identifier = Buffer.from([242, 35, 198, 137, 82, 225, 242, 182])
52
46
  const buffer = Buffer.alloc(1000)
@@ -16,9 +16,9 @@ export interface UpdateReserveAllocationAccounts {
16
16
  reserveCollateralMint: PublicKey
17
17
  reserve: PublicKey
18
18
  ctokenVault: PublicKey
19
+ reserveCollateralTokenProgram: PublicKey
19
20
  systemProgram: PublicKey
20
21
  rent: PublicKey
21
- tokenProgram: PublicKey
22
22
  }
23
23
 
24
24
  export const layout = borsh.struct([borsh.u64("weight"), borsh.u64("cap")])
@@ -39,9 +39,13 @@ export function updateReserveAllocation(
39
39
  },
40
40
  { pubkey: accounts.reserve, isSigner: false, isWritable: false },
41
41
  { pubkey: accounts.ctokenVault, isSigner: false, isWritable: true },
42
+ {
43
+ pubkey: accounts.reserveCollateralTokenProgram,
44
+ isSigner: false,
45
+ isWritable: false,
46
+ },
42
47
  { pubkey: accounts.systemProgram, isSigner: false, isWritable: false },
43
48
  { pubkey: accounts.rent, isSigner: false, isWritable: false },
44
- { pubkey: accounts.tokenProgram, isSigner: false, isWritable: false },
45
49
  ]
46
50
  const identifier = Buffer.from([5, 54, 213, 112, 75, 232, 117, 37])
47
51
  const buffer = Buffer.alloc(1000)
@@ -60,7 +60,8 @@ export interface ReserveConfigFields {
60
60
  debtWithdrawalCap: types.WithdrawalCapsFields
61
61
  elevationGroups: Array<number>
62
62
  disableUsageAsCollOutsideEmode: number
63
- utilizationLimitBlockBorrowingAbove: number
63
+ /** Utilization (in percentage) above which borrowing is blocked. 0 to disable. */
64
+ utilizationLimitBlockBorrowingAbovePct: number
64
65
  /**
65
66
  * Whether this reserve should be subject to auto-deleveraging after deposit or borrow limit is
66
67
  * crossed.
@@ -147,7 +148,8 @@ export interface ReserveConfigJSON {
147
148
  debtWithdrawalCap: types.WithdrawalCapsJSON
148
149
  elevationGroups: Array<number>
149
150
  disableUsageAsCollOutsideEmode: number
150
- utilizationLimitBlockBorrowingAbove: number
151
+ /** Utilization (in percentage) above which borrowing is blocked. 0 to disable. */
152
+ utilizationLimitBlockBorrowingAbovePct: number
151
153
  /**
152
154
  * Whether this reserve should be subject to auto-deleveraging after deposit or borrow limit is
153
155
  * crossed.
@@ -235,7 +237,8 @@ export class ReserveConfig {
235
237
  readonly debtWithdrawalCap: types.WithdrawalCaps
236
238
  readonly elevationGroups: Array<number>
237
239
  readonly disableUsageAsCollOutsideEmode: number
238
- readonly utilizationLimitBlockBorrowingAbove: number
240
+ /** Utilization (in percentage) above which borrowing is blocked. 0 to disable. */
241
+ readonly utilizationLimitBlockBorrowingAbovePct: number
239
242
  /**
240
243
  * Whether this reserve should be subject to auto-deleveraging after deposit or borrow limit is
241
244
  * crossed.
@@ -297,8 +300,8 @@ export class ReserveConfig {
297
300
  })
298
301
  this.elevationGroups = fields.elevationGroups
299
302
  this.disableUsageAsCollOutsideEmode = fields.disableUsageAsCollOutsideEmode
300
- this.utilizationLimitBlockBorrowingAbove =
301
- fields.utilizationLimitBlockBorrowingAbove
303
+ this.utilizationLimitBlockBorrowingAbovePct =
304
+ fields.utilizationLimitBlockBorrowingAbovePct
302
305
  this.autodeleverageEnabled = fields.autodeleverageEnabled
303
306
  this.reserved1 = fields.reserved1
304
307
  this.borrowLimitOutsideElevationGroup =
@@ -336,7 +339,7 @@ export class ReserveConfig {
336
339
  types.WithdrawalCaps.layout("debtWithdrawalCap"),
337
340
  borsh.array(borsh.u8(), 20, "elevationGroups"),
338
341
  borsh.u8("disableUsageAsCollOutsideEmode"),
339
- borsh.u8("utilizationLimitBlockBorrowingAbove"),
342
+ borsh.u8("utilizationLimitBlockBorrowingAbovePct"),
340
343
  borsh.u8("autodeleverageEnabled"),
341
344
  borsh.array(borsh.u8(), 1, "reserved1"),
342
345
  borsh.u64("borrowLimitOutsideElevationGroup"),
@@ -383,8 +386,8 @@ export class ReserveConfig {
383
386
  ),
384
387
  elevationGroups: obj.elevationGroups,
385
388
  disableUsageAsCollOutsideEmode: obj.disableUsageAsCollOutsideEmode,
386
- utilizationLimitBlockBorrowingAbove:
387
- obj.utilizationLimitBlockBorrowingAbove,
389
+ utilizationLimitBlockBorrowingAbovePct:
390
+ obj.utilizationLimitBlockBorrowingAbovePct,
388
391
  autodeleverageEnabled: obj.autodeleverageEnabled,
389
392
  reserved1: obj.reserved1,
390
393
  borrowLimitOutsideElevationGroup: obj.borrowLimitOutsideElevationGroup,
@@ -428,8 +431,8 @@ export class ReserveConfig {
428
431
  ),
429
432
  elevationGroups: fields.elevationGroups,
430
433
  disableUsageAsCollOutsideEmode: fields.disableUsageAsCollOutsideEmode,
431
- utilizationLimitBlockBorrowingAbove:
432
- fields.utilizationLimitBlockBorrowingAbove,
434
+ utilizationLimitBlockBorrowingAbovePct:
435
+ fields.utilizationLimitBlockBorrowingAbovePct,
433
436
  autodeleverageEnabled: fields.autodeleverageEnabled,
434
437
  reserved1: fields.reserved1,
435
438
  borrowLimitOutsideElevationGroup: fields.borrowLimitOutsideElevationGroup,
@@ -468,8 +471,8 @@ export class ReserveConfig {
468
471
  debtWithdrawalCap: this.debtWithdrawalCap.toJSON(),
469
472
  elevationGroups: this.elevationGroups,
470
473
  disableUsageAsCollOutsideEmode: this.disableUsageAsCollOutsideEmode,
471
- utilizationLimitBlockBorrowingAbove:
472
- this.utilizationLimitBlockBorrowingAbove,
474
+ utilizationLimitBlockBorrowingAbovePct:
475
+ this.utilizationLimitBlockBorrowingAbovePct,
473
476
  autodeleverageEnabled: this.autodeleverageEnabled,
474
477
  reserved1: this.reserved1,
475
478
  borrowLimitOutsideElevationGroup:
@@ -515,8 +518,8 @@ export class ReserveConfig {
515
518
  debtWithdrawalCap: types.WithdrawalCaps.fromJSON(obj.debtWithdrawalCap),
516
519
  elevationGroups: obj.elevationGroups,
517
520
  disableUsageAsCollOutsideEmode: obj.disableUsageAsCollOutsideEmode,
518
- utilizationLimitBlockBorrowingAbove:
519
- obj.utilizationLimitBlockBorrowingAbove,
521
+ utilizationLimitBlockBorrowingAbovePct:
522
+ obj.utilizationLimitBlockBorrowingAbovePct,
520
523
  autodeleverageEnabled: obj.autodeleverageEnabled,
521
524
  reserved1: obj.reserved1,
522
525
  borrowLimitOutsideElevationGroup: new BN(