@kamino-finance/klend-sdk 5.10.10 → 5.10.12

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.
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getAllObligationAccounts = getAllObligationAccounts;
7
+ const accounts_1 = require("../idl_codegen/accounts");
8
+ const programId_1 = require("../idl_codegen/programId");
9
+ const bs58_1 = __importDefault(require("bs58"));
10
+ async function* getAllObligationAccounts(connection) {
11
+ // Poor-man's paging...
12
+ for (let i = 0; i < 256; i++) {
13
+ const obligations = await connection.getProgramAccounts(programId_1.PROGRAM_ID, {
14
+ filters: [
15
+ {
16
+ dataSize: accounts_1.Obligation.layout.span + 8,
17
+ },
18
+ {
19
+ memcmp: {
20
+ offset: 0,
21
+ bytes: bs58_1.default.encode(accounts_1.Obligation.discriminator),
22
+ },
23
+ },
24
+ {
25
+ memcmp: {
26
+ offset: 64,
27
+ bytes: bs58_1.default.encode([i]), // ...via sharding by userId's first byte (just as a source of randomness)
28
+ },
29
+ },
30
+ ],
31
+ });
32
+ for (const obligation of obligations) {
33
+ yield accounts_1.Obligation.decode(obligation.account.data);
34
+ }
35
+ }
36
+ }
37
+ //# sourceMappingURL=accountListing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accountListing.js","sourceRoot":"","sources":["../../src/utils/accountListing.ts"],"names":[],"mappings":";;;;;AAKA,4DA0BC;AA9BD,sDAAqD;AACrD,wDAAsD;AACtD,gDAAwB;AAEjB,KAAK,SAAS,CAAC,CAAC,wBAAwB,CAAC,UAAsB;IACpE,uBAAuB;IACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7B,MAAM,WAAW,GAAG,MAAM,UAAU,CAAC,kBAAkB,CAAC,sBAAU,EAAE;YAClE,OAAO,EAAE;gBACP;oBACE,QAAQ,EAAE,qBAAU,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC;iBACrC;gBACD;oBACE,MAAM,EAAE;wBACN,MAAM,EAAE,CAAC;wBACT,KAAK,EAAE,cAAI,CAAC,MAAM,CAAC,qBAAU,CAAC,aAAa,CAAC;qBAC7C;iBACF;gBACD;oBACE,MAAM,EAAE;wBACN,MAAM,EAAE,EAAE;wBACV,KAAK,EAAE,cAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,0EAA0E;qBACpG;iBACF;aACF;SACF,CAAC,CAAC;QACH,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;YACrC,MAAM,qBAAU,CAAC,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -1,3 +1,4 @@
1
+ export * from './accountListing';
1
2
  export * from './api';
2
3
  export * from './ata';
3
4
  export * from './constants';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAC;AACtB,cAAc,OAAO,CAAC;AACtB,cAAc,aAAa,CAAC;AAC5B,cAAc,OAAO,CAAC;AACtB,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,QAAQ,CAAC;AACvB,cAAc,OAAO,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,OAAO,CAAC;AACtB,cAAc,OAAO,CAAC;AACtB,cAAc,aAAa,CAAC;AAC5B,cAAc,OAAO,CAAC;AACtB,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,QAAQ,CAAC;AACvB,cAAc,OAAO,CAAC"}
@@ -14,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./accountListing"), exports);
17
18
  __exportStar(require("./api"), exports);
18
19
  __exportStar(require("./ata"), exports);
19
20
  __exportStar(require("./constants"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,wCAAsB;AACtB,wCAAsB;AACtB,8CAA4B;AAC5B,wCAAsB;AACtB,gDAA8B;AAC9B,mDAAiC;AACjC,0CAAwB;AACxB,0CAAwB;AACxB,iDAA+B;AAC/B,2CAAyB;AACzB,2CAAyB;AACzB,gDAA8B;AAC9B,0DAAwC;AACxC,iDAA+B;AAC/B,yCAAuB;AACvB,wCAAsB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,mDAAiC;AACjC,wCAAsB;AACtB,wCAAsB;AACtB,8CAA4B;AAC5B,wCAAsB;AACtB,gDAA8B;AAC9B,mDAAiC;AACjC,0CAAwB;AACxB,0CAAwB;AACxB,iDAA+B;AAC/B,2CAAyB;AACzB,2CAAyB;AACzB,gDAA8B;AAC9B,0DAAwC;AACxC,iDAA+B;AAC/B,yCAAuB;AACvB,wCAAsB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kamino-finance/klend-sdk",
3
- "version": "5.10.10",
3
+ "version": "5.10.12",
4
4
  "description": "Typescript SDK for interacting with the Kamino Lending (klend) protocol",
5
5
  "repository": {
6
6
  "type": "git",
@@ -45,6 +45,7 @@
45
45
  "start-validator": "solana-test-validator $(./deps/test-validator-params.sh)",
46
46
  "start-validator-and-test": "yarn start-server-and-test 'yarn start-validator' http://127.0.0.1:8899/health test",
47
47
  "dump-programs": "./deps/dump-from-mainnet.sh",
48
+ "cli": "tsx --no-deprecation src/client.ts",
48
49
  "kamino-manager": "tsx src/client_kamino_manager.ts",
49
50
  "dev": "concurrently \"yarn build:watch\" \"nodemon --watch dist -e js,ts --exec 'yarn dev:push-and-update'\"",
50
51
  "dev:push-and-update": "yalc publish && cd examples && yalc update"
@@ -71,6 +71,7 @@ import {
71
71
  AcceptVaultOwnershipIxs,
72
72
  DepositIxs,
73
73
  InitVaultIxs,
74
+ ReserveAllocationOverview,
74
75
  SyncVaultLUTIxs,
75
76
  UpdateReserveAllocationIxs,
76
77
  UpdateVaultConfigIxs,
@@ -416,7 +417,7 @@ export class KaminoManager {
416
417
  * 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
417
418
  * @param user - user to withdraw
418
419
  * @param vault - vault to withdraw from
419
- * @param shareAmount - share amount to withdraw, in order to withdraw everything, any value > user share amount
420
+ * @param shareAmount - share amount to withdraw (in tokens, not lamports), in order to withdraw everything, any value > user share amount
420
421
  * @param slot - current slot, used to estimate the interest earned in the different reserves with allocation from the vault
421
422
  * @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
422
423
  * @returns an array of instructions to create missing ATAs if needed and the withdraw instructions
@@ -853,12 +854,21 @@ export class KaminoManager {
853
854
  /**
854
855
  * This will return the a map between reserve pubkey and the pct of the vault invested amount in each reserve
855
856
  * @param vaultState - the kamino vault to get reserves distribution for
856
- * @returns a ma between reserve pubkey and the allocation pct for the reserve
857
+ * @returns a map between reserve pubkey and the allocation pct for the reserve
857
858
  */
858
859
  getAllocationsDistribuionPct(vaultState: VaultState): PubkeyHashMap<PublicKey, Decimal> {
859
860
  return this._vaultClient.getAllocationsDistribuionPct(vaultState);
860
861
  }
861
862
 
863
+ /**
864
+ * This will return the a map between reserve pubkey and the allocation overview for the reserve
865
+ * @param vaultState - the kamino vault to get reserves allocation overview for
866
+ * @returns a map between reserve pubkey and the allocation overview for the reserve
867
+ */
868
+ getVaultAllocations(vaultState: VaultState): PubkeyHashMap<PublicKey, ReserveAllocationOverview> {
869
+ return this._vaultClient.getVaultAllocations(vaultState);
870
+ }
871
+
862
872
  /**
863
873
  * This will return the amount of token invested from the vault into the given reserve
864
874
  * @param vault - the kamino vault to get invested amount in reserve for
@@ -265,7 +265,13 @@ export class KaminoMarket {
265
265
 
266
266
  const groupsColl = new Set(collReserve.state.config.elevationGroups);
267
267
  const groupsDebt = new Set(debtReserve.state.config.elevationGroups);
268
- const commonElevationGroups = [...groupsColl].filter((item) => groupsDebt.has(item) && item !== 0);
268
+ const commonElevationGroups = [...groupsColl].filter(
269
+ (item) =>
270
+ groupsDebt.has(item) &&
271
+ item !== 0 &&
272
+ this.state.elevationGroups[item - 1].allowNewLoans !== 0 &&
273
+ collReserve.state.config.borrowLimitAgainstThisCollateralInElevationGroup[item - 1].gt(new BN(0))
274
+ );
269
275
 
270
276
  // Ltv factor for coll token
271
277
  const maxCollateralLtv =
@@ -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
- reserveCollateralTokenProgram: vaultState.tokenProgram
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
 
@@ -801,7 +809,7 @@ export class KaminoVaultClient {
801
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
802
810
  * @param user - user to withdraw
803
811
  * @param vault - vault to withdraw from
804
- * @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
805
813
  * @param slot - current slot, used to estimate the interest earned in the different reserves with allocation from the vault
806
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
807
815
  * @param [farmState] - the state of the vault farm, if the vault has a farm. Optional. If not provided, it will be fetched
@@ -838,8 +846,8 @@ export class KaminoVaultClient {
838
846
  }
839
847
 
840
848
  // if the vault has allocations withdraw otherwise wtihdraw from available ix
841
- const vaultAllocation = vaultState.vaultAllocationStrategy.find((allocation) =>
842
- allocation.reserve.equals(PublicKey.default)
849
+ const vaultAllocation = vaultState.vaultAllocationStrategy.find(
850
+ (allocation) => !allocation.reserve.equals(PublicKey.default)
843
851
  );
844
852
 
845
853
  if (vaultAllocation) {
@@ -989,7 +997,6 @@ export class KaminoVaultClient {
989
997
  return withdrawIxns;
990
998
  }
991
999
 
992
- // todo: make sure we also check the ata of the investor for the vault token exists
993
1000
  /**
994
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
995
1002
  * @param payer wallet that pays the tx
@@ -997,20 +1004,87 @@ export class KaminoVaultClient {
997
1004
  * @returns - an array of invest instructions for each invest action required for the vault reserves
998
1005
  */
999
1006
  async investAllReservesIxs(payer: PublicKey, vault: KaminoVault): Promise<TransactionInstruction[]> {
1000
- //TODO: Order invest ixns by - invest that removes first, then invest that adds
1001
1007
  const vaultState = await vault.getState(this.getConnection());
1002
- 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
+
1003
1045
  const investIxnsPromises: Promise<TransactionInstruction[]>[] = [];
1004
- for (const reserve of vaultReserves) {
1005
- 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);
1006
1049
  if (reserveState === null) {
1007
1050
  throw new Error(`Reserve ${reserve.toBase58()} not found`);
1008
1051
  }
1009
- 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
+ );
1010
1062
  investIxnsPromises.push(investIxsPromise);
1011
1063
  }
1012
1064
 
1013
- 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());
1014
1088
 
1015
1089
  return investIxns;
1016
1090
  }
@@ -1028,16 +1102,23 @@ export class KaminoVaultClient {
1028
1102
  payer: PublicKey,
1029
1103
  vault: KaminoVault,
1030
1104
  reserve: ReserveWithAddress,
1031
- vaultReservesMap?: PubkeyHashMap<PublicKey, KaminoReserve>
1105
+ vaultReservesMap?: PubkeyHashMap<PublicKey, KaminoReserve>,
1106
+ createAtaIfNeeded: boolean = true
1032
1107
  ): Promise<TransactionInstruction[]> {
1108
+ console.log('create invest ix for reserve', reserve.address.toBase58());
1033
1109
  const vaultState = await vault.getState(this.getConnection());
1034
1110
  const cTokenVault = getCTokenVaultPda(vault.address, reserve.address, this._kaminoVaultProgramId);
1035
1111
  const lendingMarketAuth = lendingMarketAuthPda(reserve.state.lendingMarket, this._kaminoLendProgramId)[0];
1036
1112
 
1113
+ const ixs: TransactionInstruction[] = [];
1114
+
1037
1115
  const tokenProgram = await getAccountOwner(this.getConnection(), vaultState.tokenMint);
1038
1116
  const [{ ata: payerTokenAta, createAtaIx }] = createAtasIdempotent(payer, [
1039
1117
  { mint: vaultState.tokenMint, tokenProgram },
1040
1118
  ]);
1119
+ if (createAtaIfNeeded) {
1120
+ ixs.push(createAtaIx);
1121
+ }
1041
1122
 
1042
1123
  const investAccounts: InvestAccounts = {
1043
1124
  payer,
@@ -1399,6 +1480,49 @@ export class KaminoVaultClient {
1399
1480
  return ixns;
1400
1481
  }
1401
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
+
1402
1526
  /**
1403
1527
  * This method returns the user shares balance for a given vault
1404
1528
  * @param user - user to calculate the shares balance for
@@ -1651,7 +1775,7 @@ export class KaminoVaultClient {
1651
1775
  /**
1652
1776
  * This will return the a map between reserve pubkey and the pct of the vault invested amount in each reserve
1653
1777
  * @param vaultState - the kamino vault to get reserves distribution for
1654
- * @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
1655
1779
  */
1656
1780
  getAllocationsDistribuionPct(vaultState: VaultState): PubkeyHashMap<PublicKey, Decimal> {
1657
1781
  const allocationsDistributionPct = new PubkeyHashMap<PublicKey, Decimal>();
@@ -1660,7 +1784,6 @@ export class KaminoVaultClient {
1660
1784
  const filteredAllocations = vaultState.vaultAllocationStrategy.filter(
1661
1785
  (allocation) => !allocation.reserve.equals(PublicKey.default)
1662
1786
  );
1663
- console.log('filteredAllocations length', filteredAllocations.length);
1664
1787
  filteredAllocations.forEach((allocation) => {
1665
1788
  totalAllocation = totalAllocation.add(new Decimal(allocation.targetAllocationWeight.toString()));
1666
1789
  });
@@ -1675,6 +1798,30 @@ export class KaminoVaultClient {
1675
1798
  return allocationsDistributionPct;
1676
1799
  }
1677
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
+
1678
1825
  /**
1679
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
1680
1827
  * @param vault - the kamino vault to get available liquidity to withdraw for
@@ -1727,7 +1874,11 @@ export class KaminoVaultClient {
1727
1874
  * @returns a hashmap from each reserve pubkey to the reserve state
1728
1875
  */
1729
1876
  getAllVaultReserves(vault: VaultState): PublicKey[] {
1730
- 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
+ });
1731
1882
  }
1732
1883
 
1733
1884
  /**
@@ -2282,10 +2433,10 @@ export class KaminoVaultConfig {
2282
2433
  readonly tokenMint: PublicKey;
2283
2434
  /** The token mint program id */
2284
2435
  readonly tokenMintProgramId: PublicKey;
2285
- /** The performance fee rate of the vault, expressed as a decimal */
2286
- readonly performanceFeeRate: Decimal;
2287
- /** The management fee rate of the vault, expressed as a decimal */
2288
- 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;
2289
2440
  /** The name to be stored on cain for the vault (max 40 characters). */
2290
2441
  readonly name: string;
2291
2442
 
@@ -2293,24 +2444,24 @@ export class KaminoVaultConfig {
2293
2444
  admin: PublicKey;
2294
2445
  tokenMint: PublicKey;
2295
2446
  tokenMintProgramId: PublicKey;
2296
- performanceFeeRate: Decimal;
2297
- managementFeeRate: Decimal;
2447
+ performanceFeeRatePercentage: Decimal;
2448
+ managementFeeRatePercentage: Decimal;
2298
2449
  name: string;
2299
2450
  }) {
2300
2451
  this.admin = args.admin;
2301
2452
  this.tokenMint = args.tokenMint;
2302
- this.performanceFeeRate = args.performanceFeeRate;
2303
- this.managementFeeRate = args.managementFeeRate;
2453
+ this.performanceFeeRatePercentage = args.performanceFeeRatePercentage;
2454
+ this.managementFeeRatePercentage = args.managementFeeRatePercentage;
2304
2455
  this.tokenMintProgramId = args.tokenMintProgramId;
2305
2456
  this.name = args.name;
2306
2457
  }
2307
2458
 
2308
2459
  getPerformanceFeeBps(): number {
2309
- return this.performanceFeeRate.mul(100).toNumber();
2460
+ return this.performanceFeeRatePercentage.mul(100).toNumber();
2310
2461
  }
2311
2462
 
2312
2463
  getManagementFeeBps(): number {
2313
- return this.managementFeeRate.mul(100).toNumber();
2464
+ return this.managementFeeRatePercentage.mul(100).toNumber();
2314
2465
  }
2315
2466
  }
2316
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
  });
@@ -0,0 +1,32 @@
1
+ import { Connection } from '@solana/web3.js';
2
+ import { Obligation } from '../idl_codegen/accounts';
3
+ import { PROGRAM_ID } from '../idl_codegen/programId';
4
+ import bs58 from 'bs58';
5
+
6
+ export async function* getAllObligationAccounts(connection: Connection): AsyncGenerator<Obligation, void, unknown> {
7
+ // Poor-man's paging...
8
+ for (let i = 0; i < 256; i++) {
9
+ const obligations = await connection.getProgramAccounts(PROGRAM_ID, {
10
+ filters: [
11
+ {
12
+ dataSize: Obligation.layout.span + 8,
13
+ },
14
+ {
15
+ memcmp: {
16
+ offset: 0,
17
+ bytes: bs58.encode(Obligation.discriminator),
18
+ },
19
+ },
20
+ {
21
+ memcmp: {
22
+ offset: 64,
23
+ bytes: bs58.encode([i]), // ...via sharding by userId's first byte (just as a source of randomness)
24
+ },
25
+ },
26
+ ],
27
+ });
28
+ for (const obligation of obligations) {
29
+ yield Obligation.decode(obligation.account.data);
30
+ }
31
+ }
32
+ }
@@ -1,3 +1,4 @@
1
+ export * from './accountListing';
1
2
  export * from './api';
2
3
  export * from './ata';
3
4
  export * from './constants';