@kamino-finance/klend-sdk 5.11.0 → 5.11.2-beta.0

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 (78) hide show
  1. package/dist/classes/action.d.ts +12 -2
  2. package/dist/classes/action.d.ts.map +1 -1
  3. package/dist/classes/action.js +245 -29
  4. package/dist/classes/action.js.map +1 -1
  5. package/dist/classes/obligation.d.ts +1 -1
  6. package/dist/classes/obligation.d.ts.map +1 -1
  7. package/dist/classes/obligation.js +1 -1
  8. package/dist/classes/obligation.js.map +1 -1
  9. package/dist/classes/vault.d.ts +1 -0
  10. package/dist/classes/vault.d.ts.map +1 -1
  11. package/dist/classes/vault.js +13 -10
  12. package/dist/classes/vault.js.map +1 -1
  13. package/dist/idl_codegen_kamino_vault/accounts/VaultState.d.ts +3 -0
  14. package/dist/idl_codegen_kamino_vault/accounts/VaultState.d.ts.map +1 -1
  15. package/dist/idl_codegen_kamino_vault/accounts/VaultState.js +7 -1
  16. package/dist/idl_codegen_kamino_vault/accounts/VaultState.js.map +1 -1
  17. package/dist/idl_codegen_kamino_vault/errors/custom.d.ts +25 -1
  18. package/dist/idl_codegen_kamino_vault/errors/custom.d.ts.map +1 -1
  19. package/dist/idl_codegen_kamino_vault/errors/custom.js +43 -1
  20. package/dist/idl_codegen_kamino_vault/errors/custom.js.map +1 -1
  21. package/dist/idl_codegen_kamino_vault/instructions/initVault.d.ts +1 -0
  22. package/dist/idl_codegen_kamino_vault/instructions/initVault.d.ts.map +1 -1
  23. package/dist/idl_codegen_kamino_vault/instructions/initVault.js +1 -0
  24. package/dist/idl_codegen_kamino_vault/instructions/initVault.js.map +1 -1
  25. package/dist/idl_codegen_kamino_vault/instructions/updateReserveAllocation.d.ts +1 -1
  26. package/dist/idl_codegen_kamino_vault/instructions/updateReserveAllocation.d.ts.map +1 -1
  27. package/dist/idl_codegen_kamino_vault/instructions/updateReserveAllocation.js +1 -1
  28. package/dist/idl_codegen_kamino_vault/instructions/updateReserveAllocation.js.map +1 -1
  29. package/dist/idl_codegen_kamino_vault/types/VaultConfigField.d.ts +13 -0
  30. package/dist/idl_codegen_kamino_vault/types/VaultConfigField.d.ts.map +1 -1
  31. package/dist/idl_codegen_kamino_vault/types/VaultConfigField.js +25 -1
  32. package/dist/idl_codegen_kamino_vault/types/VaultConfigField.js.map +1 -1
  33. package/dist/idl_codegen_kamino_vault/types/index.d.ts +2 -2
  34. package/dist/idl_codegen_kamino_vault/types/index.d.ts.map +1 -1
  35. package/dist/idl_codegen_kamino_vault/types/index.js.map +1 -1
  36. package/dist/lending_operations/repay_with_collateral_calcs.d.ts.map +1 -1
  37. package/dist/lending_operations/repay_with_collateral_calcs.js +9 -5
  38. package/dist/lending_operations/repay_with_collateral_calcs.js.map +1 -1
  39. package/dist/lending_operations/repay_with_collateral_operations.d.ts +5 -0
  40. package/dist/lending_operations/repay_with_collateral_operations.d.ts.map +1 -1
  41. package/dist/lending_operations/repay_with_collateral_operations.js +26 -1
  42. package/dist/lending_operations/repay_with_collateral_operations.js.map +1 -1
  43. package/dist/utils/constants.d.ts +1 -0
  44. package/dist/utils/constants.d.ts.map +1 -1
  45. package/dist/utils/constants.js +2 -1
  46. package/dist/utils/constants.js.map +1 -1
  47. package/dist/utils/lookupTable.d.ts +27 -0
  48. package/dist/utils/lookupTable.d.ts.map +1 -1
  49. package/dist/utils/lookupTable.js +58 -0
  50. package/dist/utils/lookupTable.js.map +1 -1
  51. package/dist/utils/seeds.d.ts +11 -1
  52. package/dist/utils/seeds.d.ts.map +1 -1
  53. package/dist/utils/seeds.js +13 -3
  54. package/dist/utils/seeds.js.map +1 -1
  55. package/dist/utils/userMetadata.js +6 -6
  56. package/dist/utils/userMetadata.js.map +1 -1
  57. package/package.json +1 -1
  58. package/src/classes/action.ts +378 -49
  59. package/src/classes/obligation.ts +1 -1
  60. package/src/classes/vault.ts +10 -9
  61. package/src/idl_codegen_kamino_vault/accounts/VaultState.ts +9 -1
  62. package/src/idl_codegen_kamino_vault/errors/custom.ts +42 -0
  63. package/src/idl_codegen_kamino_vault/instructions/initVault.ts +2 -0
  64. package/src/idl_codegen_kamino_vault/instructions/updateReserveAllocation.ts +2 -2
  65. package/src/idl_codegen_kamino_vault/types/VaultConfigField.ts +30 -0
  66. package/src/idl_codegen_kamino_vault/types/index.ts +2 -0
  67. package/src/idl_kamino_vault.json +30 -3
  68. package/src/lending_operations/repay_with_collateral_calcs.ts +14 -5
  69. package/src/lending_operations/repay_with_collateral_operations.ts +63 -20
  70. package/src/utils/constants.ts +1 -0
  71. package/src/utils/lookupTable.ts +62 -0
  72. package/src/utils/seeds.ts +14 -4
  73. package/src/utils/userMetadata.ts +14 -14
  74. package/dist/classes/lut_utils.d.ts +0 -29
  75. package/dist/classes/lut_utils.d.ts.map +0 -1
  76. package/dist/classes/lut_utils.js +0 -62
  77. package/dist/classes/lut_utils.js.map +0 -1
  78. package/src/classes/lut_utils.ts +0 -63
@@ -11,7 +11,7 @@ import {
11
11
  SYSVAR_RENT_PUBKEY,
12
12
  TransactionInstruction,
13
13
  } from '@solana/web3.js';
14
- import { NATIVE_MINT, TOKEN_PROGRAM_ID, unpackAccount } from '@solana/spl-token';
14
+ import { getAssociatedTokenAddressSync, NATIVE_MINT, TOKEN_PROGRAM_ID, unpackAccount } from '@solana/spl-token';
15
15
  import {
16
16
  getAssociatedTokenAddress,
17
17
  getTransferWsolIxns,
@@ -82,7 +82,7 @@ import {
82
82
  import { batchFetch, collToLamportsDecimal, ZERO } from '@kamino-finance/kliquidity-sdk';
83
83
  import { FullBPSDecimal } from '@kamino-finance/kliquidity-sdk/dist/utils/CreationParameters';
84
84
  import { FarmState } from '@kamino-finance/farms-sdk/dist';
85
- import { getAccountsInLUT, initLookupTableIx } from './lut_utils';
85
+ import { getAccountsInLUT, initLookupTableIx } from '../utils/lookupTable';
86
86
  import {
87
87
  getFarmStakeIxs,
88
88
  getFarmUnstakeAndWithdrawIxs,
@@ -100,6 +100,8 @@ const CTOKEN_VAULT_SEED = 'ctoken_vault';
100
100
  const BASE_VAULT_AUTHORITY_SEED = 'authority';
101
101
  const SHARES_SEED = 'shares';
102
102
 
103
+ export const INITIAL_DEPOSIT_LAMPORTS = 1000;
104
+
103
105
  /**
104
106
  * KaminoVaultClient is a class that provides a high-level interface to interact with the Kamino Vault program.
105
107
  */
@@ -195,6 +197,8 @@ export class KaminoVaultClient {
195
197
  this._kaminoVaultProgramId
196
198
  )[0];
197
199
 
200
+ const adminTokenAccount = getAssociatedTokenAddressSync(vaultConfig.tokenMint, vaultConfig.admin, false);
201
+
198
202
  const initVaultAccounts: InitVaultAccounts = {
199
203
  adminAuthority: vaultConfig.admin,
200
204
  vaultState: vaultState.publicKey,
@@ -206,6 +210,7 @@ export class KaminoVaultClient {
206
210
  rent: SYSVAR_RENT_PUBKEY,
207
211
  tokenProgram: vaultConfig.tokenMintProgramId,
208
212
  sharesTokenProgram: TOKEN_PROGRAM_ID,
213
+ adminTokenAccount,
209
214
  };
210
215
  const initVaultIx = initVault(initVaultAccounts, this._kaminoVaultProgramId);
211
216
 
@@ -288,7 +293,7 @@ export class KaminoVaultClient {
288
293
  );
289
294
 
290
295
  const updateReserveAllocationAccounts: UpdateReserveAllocationAccounts = {
291
- adminAuthority: vaultState.adminAuthority,
296
+ signer: vaultState.adminAuthority,
292
297
  vaultState: vault.address,
293
298
  baseVaultAuthority: vaultState.baseVaultAuthority,
294
299
  reserveCollateralMint: reserveState.collateral.mintPubkey,
@@ -1010,20 +1015,16 @@ export class KaminoVaultClient {
1010
1015
  if (allReserves.length === 0) {
1011
1016
  throw new Error('No reserves found for the vault, please select at least one reserve for the vault');
1012
1017
  }
1013
-
1014
1018
  const [allReservesStateMap, computedReservesAllocation] = await Promise.all([
1015
1019
  this.loadVaultReserves(vaultState),
1016
1020
  this.getVaultComputedReservesAllocation(vaultState),
1017
1021
  ]);
1018
-
1019
1022
  const tokenProgram = await getAccountOwner(this.getConnection(), vaultState.tokenMint);
1020
1023
  const [{ ata: _payerTokenAta, createAtaIx }] = createAtasIdempotent(payer, [
1021
1024
  { mint: vaultState.tokenMint, tokenProgram },
1022
1025
  ]);
1023
-
1024
1026
  // compute total vault holdings and expected distribution based on weights
1025
1027
  const curentVaultAllocations = this.getVaultAllocations(vaultState);
1026
-
1027
1028
  const reservesToDisinvestFrom: PublicKey[] = [];
1028
1029
  const reservesToInvestInto: PublicKey[] = [];
1029
1030
 
@@ -1528,8 +1529,8 @@ export class KaminoVaultClient {
1528
1529
 
1529
1530
  let totalLeftToInvest = holdings.totalAUMIncludingFees.sub(holdings.pendingFees);
1530
1531
  let currentAllocationSum = totalAllocation;
1531
- const ZERO = new Decimal(0);
1532
- while (totalLeftToInvest.gt(ZERO)) {
1532
+ const ONE = new Decimal(1);
1533
+ while (totalLeftToInvest.gt(ONE)) {
1533
1534
  const totalLeftover = totalLeftToInvest;
1534
1535
  for (const reserve of allReserves) {
1535
1536
  const reserveWithWeight = initialVaultAllocations.get(reserve);
@@ -38,6 +38,7 @@ export interface VaultStateFields {
38
38
  vaultFarm: PublicKey
39
39
  creationTimestamp: BN
40
40
  padding1: BN
41
+ allocationAdmin: PublicKey
41
42
  padding2: Array<BN>
42
43
  }
43
44
 
@@ -75,6 +76,7 @@ export interface VaultStateJSON {
75
76
  vaultFarm: string
76
77
  creationTimestamp: string
77
78
  padding1: string
79
+ allocationAdmin: string
78
80
  padding2: Array<string>
79
81
  }
80
82
 
@@ -112,6 +114,7 @@ export class VaultState {
112
114
  readonly vaultFarm: PublicKey
113
115
  readonly creationTimestamp: BN
114
116
  readonly padding1: BN
117
+ readonly allocationAdmin: PublicKey
115
118
  readonly padding2: Array<BN>
116
119
 
117
120
  static readonly discriminator = Buffer.from([
@@ -152,7 +155,8 @@ export class VaultState {
152
155
  borsh.publicKey("vaultFarm"),
153
156
  borsh.u64("creationTimestamp"),
154
157
  borsh.u64("padding1"),
155
- borsh.array(borsh.u128(), 244, "padding2"),
158
+ borsh.publicKey("allocationAdmin"),
159
+ borsh.array(borsh.u128(), 242, "padding2"),
156
160
  ])
157
161
 
158
162
  constructor(fields: VaultStateFields) {
@@ -191,6 +195,7 @@ export class VaultState {
191
195
  this.vaultFarm = fields.vaultFarm
192
196
  this.creationTimestamp = fields.creationTimestamp
193
197
  this.padding1 = fields.padding1
198
+ this.allocationAdmin = fields.allocationAdmin
194
199
  this.padding2 = fields.padding2
195
200
  }
196
201
 
@@ -275,6 +280,7 @@ export class VaultState {
275
280
  vaultFarm: dec.vaultFarm,
276
281
  creationTimestamp: dec.creationTimestamp,
277
282
  padding1: dec.padding1,
283
+ allocationAdmin: dec.allocationAdmin,
278
284
  padding2: dec.padding2,
279
285
  })
280
286
  }
@@ -316,6 +322,7 @@ export class VaultState {
316
322
  vaultFarm: this.vaultFarm.toString(),
317
323
  creationTimestamp: this.creationTimestamp.toString(),
318
324
  padding1: this.padding1.toString(),
325
+ allocationAdmin: this.allocationAdmin.toString(),
319
326
  padding2: this.padding2.map((item) => item.toString()),
320
327
  }
321
328
  }
@@ -357,6 +364,7 @@ export class VaultState {
357
364
  vaultFarm: new PublicKey(obj.vaultFarm),
358
365
  creationTimestamp: new BN(obj.creationTimestamp),
359
366
  padding1: new BN(obj.padding1),
367
+ allocationAdmin: new PublicKey(obj.allocationAdmin),
360
368
  padding2: obj.padding2.map((item) => new BN(item)),
361
369
  })
362
370
  }
@@ -43,6 +43,9 @@ export type CustomError =
43
43
  | ManagementFeeGreaterThanMaxAllowed
44
44
  | VaultAUMZero
45
45
  | MissingReserveForBatchRefresh
46
+ | MinWithdrawAmountTooBig
47
+ | InvestTooSoon
48
+ | WrongAdminOrAllocationAdmin
46
49
 
47
50
  export class DepositAmountsZero extends Error {
48
51
  static readonly code = 7000
@@ -528,6 +531,39 @@ export class MissingReserveForBatchRefresh extends Error {
528
531
  }
529
532
  }
530
533
 
534
+ export class MinWithdrawAmountTooBig extends Error {
535
+ static readonly code = 7044
536
+ readonly code = 7044
537
+ readonly name = "MinWithdrawAmountTooBig"
538
+ readonly msg = "Min withdraw amount is too big"
539
+
540
+ constructor(readonly logs?: string[]) {
541
+ super("7044: Min withdraw amount is too big")
542
+ }
543
+ }
544
+
545
+ export class InvestTooSoon extends Error {
546
+ static readonly code = 7045
547
+ readonly code = 7045
548
+ readonly name = "InvestTooSoon"
549
+ readonly msg = "Invest is called too soon after last invest"
550
+
551
+ constructor(readonly logs?: string[]) {
552
+ super("7045: Invest is called too soon after last invest")
553
+ }
554
+ }
555
+
556
+ export class WrongAdminOrAllocationAdmin extends Error {
557
+ static readonly code = 7046
558
+ readonly code = 7046
559
+ readonly name = "WrongAdminOrAllocationAdmin"
560
+ readonly msg = "Wrong admin or allocation admin"
561
+
562
+ constructor(readonly logs?: string[]) {
563
+ super("7046: Wrong admin or allocation admin")
564
+ }
565
+ }
566
+
531
567
  export function fromCode(code: number, logs?: string[]): CustomError | null {
532
568
  switch (code) {
533
569
  case 7000:
@@ -618,6 +654,12 @@ export function fromCode(code: number, logs?: string[]): CustomError | null {
618
654
  return new VaultAUMZero(logs)
619
655
  case 7043:
620
656
  return new MissingReserveForBatchRefresh(logs)
657
+ case 7044:
658
+ return new MinWithdrawAmountTooBig(logs)
659
+ case 7045:
660
+ return new InvestTooSoon(logs)
661
+ case 7046:
662
+ return new WrongAdminOrAllocationAdmin(logs)
621
663
  }
622
664
 
623
665
  return null
@@ -11,6 +11,7 @@ export interface InitVaultAccounts {
11
11
  tokenVault: PublicKey
12
12
  baseTokenMint: PublicKey
13
13
  sharesMint: PublicKey
14
+ adminTokenAccount: PublicKey
14
15
  systemProgram: PublicKey
15
16
  rent: PublicKey
16
17
  tokenProgram: PublicKey
@@ -28,6 +29,7 @@ export function initVault(
28
29
  { pubkey: accounts.tokenVault, isSigner: false, isWritable: true },
29
30
  { pubkey: accounts.baseTokenMint, isSigner: false, isWritable: false },
30
31
  { pubkey: accounts.sharesMint, isSigner: false, isWritable: true },
32
+ { pubkey: accounts.adminTokenAccount, isSigner: false, isWritable: true },
31
33
  { pubkey: accounts.systemProgram, isSigner: false, isWritable: false },
32
34
  { pubkey: accounts.rent, isSigner: false, isWritable: false },
33
35
  { pubkey: accounts.tokenProgram, isSigner: false, isWritable: false },
@@ -10,7 +10,7 @@ export interface UpdateReserveAllocationArgs {
10
10
  }
11
11
 
12
12
  export interface UpdateReserveAllocationAccounts {
13
- adminAuthority: PublicKey
13
+ signer: PublicKey
14
14
  vaultState: PublicKey
15
15
  baseVaultAuthority: PublicKey
16
16
  reserveCollateralMint: PublicKey
@@ -29,7 +29,7 @@ export function updateReserveAllocation(
29
29
  programId: PublicKey = PROGRAM_ID
30
30
  ) {
31
31
  const keys: Array<AccountMeta> = [
32
- { pubkey: accounts.adminAuthority, isSigner: true, isWritable: true },
32
+ { pubkey: accounts.signer, isSigner: true, isWritable: true },
33
33
  { pubkey: accounts.vaultState, isSigner: false, isWritable: true },
34
34
  { pubkey: accounts.baseVaultAuthority, isSigner: false, isWritable: false },
35
35
  {
@@ -256,6 +256,29 @@ export class Farm {
256
256
  }
257
257
  }
258
258
 
259
+ export interface AllocationAdminJSON {
260
+ kind: "AllocationAdmin"
261
+ }
262
+
263
+ export class AllocationAdmin {
264
+ static readonly discriminator = 11
265
+ static readonly kind = "AllocationAdmin"
266
+ readonly discriminator = 11
267
+ readonly kind = "AllocationAdmin"
268
+
269
+ toJSON(): AllocationAdminJSON {
270
+ return {
271
+ kind: "AllocationAdmin",
272
+ }
273
+ }
274
+
275
+ toEncodable() {
276
+ return {
277
+ AllocationAdmin: {},
278
+ }
279
+ }
280
+ }
281
+
259
282
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
260
283
  export function fromDecoded(obj: any): types.VaultConfigFieldKind {
261
284
  if (typeof obj !== "object") {
@@ -295,6 +318,9 @@ export function fromDecoded(obj: any): types.VaultConfigFieldKind {
295
318
  if ("Farm" in obj) {
296
319
  return new Farm()
297
320
  }
321
+ if ("AllocationAdmin" in obj) {
322
+ return new AllocationAdmin()
323
+ }
298
324
 
299
325
  throw new Error("Invalid enum object")
300
326
  }
@@ -336,6 +362,9 @@ export function fromJSON(
336
362
  case "Farm": {
337
363
  return new Farm()
338
364
  }
365
+ case "AllocationAdmin": {
366
+ return new AllocationAdmin()
367
+ }
339
368
  }
340
369
  }
341
370
 
@@ -352,6 +381,7 @@ export function layout(property?: string) {
352
381
  borsh.struct([], "Name"),
353
382
  borsh.struct([], "LookupTable"),
354
383
  borsh.struct([], "Farm"),
384
+ borsh.struct([], "AllocationAdmin"),
355
385
  ])
356
386
  if (property !== undefined) {
357
387
  return ret.replicate(property)
@@ -63,6 +63,7 @@ export type VaultConfigFieldKind =
63
63
  | VaultConfigField.Name
64
64
  | VaultConfigField.LookupTable
65
65
  | VaultConfigField.Farm
66
+ | VaultConfigField.AllocationAdmin
66
67
  export type VaultConfigFieldJSON =
67
68
  | VaultConfigField.PerformanceFeeBpsJSON
68
69
  | VaultConfigField.ManagementFeeBpsJSON
@@ -75,6 +76,7 @@ export type VaultConfigFieldJSON =
75
76
  | VaultConfigField.NameJSON
76
77
  | VaultConfigField.LookupTableJSON
77
78
  | VaultConfigField.FarmJSON
79
+ | VaultConfigField.AllocationAdminJSON
78
80
 
79
81
  export { VaultAllocation } from "./VaultAllocation"
80
82
  export type {
@@ -35,6 +35,11 @@
35
35
  "isMut": true,
36
36
  "isSigner": false
37
37
  },
38
+ {
39
+ "name": "adminTokenAccount",
40
+ "isMut": true,
41
+ "isSigner": false
42
+ },
38
43
  {
39
44
  "name": "systemProgram",
40
45
  "isMut": false,
@@ -62,7 +67,7 @@
62
67
  "name": "updateReserveAllocation",
63
68
  "accounts": [
64
69
  {
65
- "name": "adminAuthority",
70
+ "name": "signer",
66
71
  "isMut": true,
67
72
  "isSigner": true
68
73
  },
@@ -996,12 +1001,16 @@
996
1001
  "name": "padding1",
997
1002
  "type": "u64"
998
1003
  },
1004
+ {
1005
+ "name": "allocationAdmin",
1006
+ "type": "publicKey"
1007
+ },
999
1008
  {
1000
1009
  "name": "padding2",
1001
1010
  "type": {
1002
1011
  "array": [
1003
1012
  "u128",
1004
- 244
1013
+ 242
1005
1014
  ]
1006
1015
  }
1007
1016
  }
@@ -1874,6 +1883,9 @@
1874
1883
  },
1875
1884
  {
1876
1885
  "name": "Farm"
1886
+ },
1887
+ {
1888
+ "name": "AllocationAdmin"
1877
1889
  }
1878
1890
  ]
1879
1891
  }
@@ -2160,6 +2172,21 @@
2160
2172
  "code": 7043,
2161
2173
  "name": "MissingReserveForBatchRefresh",
2162
2174
  "msg": "Missing reserve for batch refresh"
2175
+ },
2176
+ {
2177
+ "code": 7044,
2178
+ "name": "MinWithdrawAmountTooBig",
2179
+ "msg": "Min withdraw amount is too big"
2180
+ },
2181
+ {
2182
+ "code": 7045,
2183
+ "name": "InvestTooSoon",
2184
+ "msg": "Invest is called too soon after last invest"
2185
+ },
2186
+ {
2187
+ "code": 7046,
2188
+ "name": "WrongAdminOrAllocationAdmin",
2189
+ "msg": "Wrong admin or allocation admin"
2163
2190
  }
2164
2191
  ]
2165
- }
2192
+ }
@@ -2,6 +2,7 @@ import Decimal from 'decimal.js';
2
2
  import { KaminoMarket, KaminoObligation, KaminoReserve, numberToLamportsDecimal } from '../classes';
3
3
  import { PublicKey } from '@solana/web3.js';
4
4
  import { lamportsToDecimal } from '../classes/utils';
5
+ import { MaxWithdrawLtvCheck, getMaxWithdrawLtvCheck } from './repay_with_collateral_operations';
5
6
 
6
7
  export function calcRepayAmountWithSlippage(
7
8
  kaminoMarket: KaminoMarket,
@@ -102,6 +103,7 @@ export function calcMaxWithdrawCollateral(
102
103
  .filter((p) => !p.reserveAddress.equals(borrow.reserveAddress))
103
104
  .reduce((acc, b) => acc.add(b.marketValueRefreshed), new Decimal('0'));
104
105
  }
106
+ const maxWithdrawLtvCheck = getMaxWithdrawLtvCheck(obligation);
105
107
 
106
108
  let remainingDepositsValueWithLtv = new Decimal('0');
107
109
  if (obligation.getDeposits().length > 1) {
@@ -109,8 +111,13 @@ export function calcMaxWithdrawCollateral(
109
111
  .getDeposits()
110
112
  .filter((p) => !p.reserveAddress.equals(deposit.reserveAddress))
111
113
  .reduce((acc, d) => {
112
- const { maxLtv } = obligation.getLtvForReserve(market, market.getReserveByAddress(d.reserveAddress)!);
113
- return acc.add(d.marketValueRefreshed.mul(maxLtv));
114
+ const { maxLtv, liquidationLtv } = obligation.getLtvForReserve(
115
+ market,
116
+ market.getReserveByAddress(d.reserveAddress)!
117
+ );
118
+ const maxWithdrawLtv =
119
+ maxWithdrawLtvCheck === MaxWithdrawLtvCheck.LIQUIDATION_THRESHOLD ? liquidationLtv : maxLtv;
120
+ return acc.add(d.marketValueRefreshed.mul(maxWithdrawLtv));
114
121
  }, new Decimal('0'));
115
122
  }
116
123
 
@@ -123,16 +130,18 @@ export function calcMaxWithdrawCollateral(
123
130
  repayingAllDebt: repayAmountLamports.gte(borrow.amount),
124
131
  };
125
132
  } else {
126
- const { maxLtv: collMaxLtv } = obligation.getLtvForReserve(
133
+ const { maxLtv: collMaxLtv, liquidationLtv: collLiquidationLtv } = obligation.getLtvForReserve(
127
134
  market,
128
135
  market.getReserveByAddress(depositReserve.address)!
129
136
  );
137
+ const maxWithdrawLtv =
138
+ maxWithdrawLtvCheck === MaxWithdrawLtvCheck.LIQUIDATION_THRESHOLD ? collLiquidationLtv : collMaxLtv;
130
139
  const numerator = deposit.marketValueRefreshed
131
- .mul(collMaxLtv)
140
+ .mul(maxWithdrawLtv)
132
141
  .add(remainingDepositsValueWithLtv)
133
142
  .sub(remainingBorrowsValue);
134
143
 
135
- const denominator = depositReserve.getOracleMarketPrice().mul(collMaxLtv);
144
+ const denominator = depositReserve.getOracleMarketPrice().mul(maxWithdrawLtv);
136
145
  const maxCollWithdrawAmount = numerator.div(denominator);
137
146
  const withdrawableCollLamports = maxCollWithdrawAmount.mul(depositReserve.getMintFactor()).floor();
138
147
 
@@ -56,6 +56,11 @@ interface RepayWithCollSwapInputsProps<QuoteResponse> {
56
56
  quoter: SwapQuoteProvider<QuoteResponse>;
57
57
  }
58
58
 
59
+ export enum MaxWithdrawLtvCheck {
60
+ MAX_LTV,
61
+ LIQUIDATION_THRESHOLD,
62
+ }
63
+
59
64
  export async function getRepayWithCollSwapInputs<QuoteResponse>({
60
65
  collTokenMint,
61
66
  currentSlot,
@@ -212,8 +217,17 @@ export async function getRepayWithCollIxs<QuoteResponse>({
212
217
  const { debtRepayAmountLamports, flashRepayAmountLamports, maxCollateralWithdrawLamports, swapQuote } = initialInputs;
213
218
  const { inputAmountLamports: collSwapInLamports } = swapInputs;
214
219
 
215
- const collReserve = kaminoMarket.getReserveByMint(collTokenMint)!;
216
- const debtReserve = kaminoMarket.getReserveByMint(debtTokenMint)!;
220
+ const collReserve = kaminoMarket.getReserveByMint(collTokenMint);
221
+
222
+ if (!collReserve) {
223
+ throw new Error(`Collateral reserve with mint ${collTokenMint} not found in market ${kaminoMarket.getAddress()}`);
224
+ }
225
+
226
+ const debtReserve = kaminoMarket.getReserveByMint(debtTokenMint);
227
+
228
+ if (!debtReserve) {
229
+ throw new Error(`Debt reserve with mint ${debtTokenMint} not found in market ${kaminoMarket.getAddress()}`);
230
+ }
217
231
 
218
232
  // the client should use these values to prevent this input, but the tx may succeed, so we don't want to fail
219
233
  // there is also a chance that the tx will consume debt token from the user's ata which they would not expect
@@ -303,25 +317,48 @@ async function buildRepayWithCollateralIxs(
303
317
 
304
318
  const requestElevationGroup = !isClosingPosition && obligation.state.elevationGroup !== 0;
305
319
 
320
+ const maxWithdrawLtvCheck = getMaxWithdrawLtvCheck(obligation);
321
+
306
322
  // 3. Repay using the flash borrowed funds & withdraw collateral to swap and pay the flash loan
307
- const repayAndWithdrawAction = await KaminoAction.buildRepayAndWithdrawTxns(
308
- market,
309
- isClosingPosition ? U64_MAX : debtRepayAmountLamports.toString(),
310
- debtReserve.getLiquidityMint(),
311
- isClosingPosition ? U64_MAX : collWithdrawLamports.toString(),
312
- collReserve.getLiquidityMint(),
313
- obligation.state.owner,
314
- currentSlot,
315
- obligation,
316
- useV2Ixs,
317
- 0,
318
- false,
319
- requestElevationGroup,
320
- undefined,
321
- undefined,
322
- referrer,
323
- scopeRefresh
324
- );
323
+ let repayAndWithdrawAction;
324
+ if (maxWithdrawLtvCheck === MaxWithdrawLtvCheck.MAX_LTV) {
325
+ repayAndWithdrawAction = await KaminoAction.buildRepayAndWithdrawTxns(
326
+ market,
327
+ isClosingPosition ? U64_MAX : debtRepayAmountLamports.toString(),
328
+ debtReserve.getLiquidityMint(),
329
+ isClosingPosition ? U64_MAX : collWithdrawLamports.toString(),
330
+ collReserve.getLiquidityMint(),
331
+ obligation.state.owner,
332
+ currentSlot,
333
+ obligation,
334
+ useV2Ixs,
335
+ 0,
336
+ false,
337
+ requestElevationGroup,
338
+ undefined,
339
+ undefined,
340
+ referrer,
341
+ scopeRefresh
342
+ );
343
+ } else {
344
+ repayAndWithdrawAction = await KaminoAction.buildRepayAndWithdrawV2Txns(
345
+ market,
346
+ isClosingPosition ? U64_MAX : debtRepayAmountLamports.toString(),
347
+ debtReserve.getLiquidityMint(),
348
+ isClosingPosition ? U64_MAX : collWithdrawLamports.toString(),
349
+ collReserve.getLiquidityMint(),
350
+ obligation.state.owner,
351
+ currentSlot,
352
+ obligation,
353
+ 0,
354
+ false,
355
+ requestElevationGroup,
356
+ undefined,
357
+ undefined,
358
+ referrer,
359
+ scopeRefresh
360
+ );
361
+ }
325
362
 
326
363
  // 4. Swap collateral to debt to repay flash loan
327
364
  const { preActionIxs, swapIxs } = swapQuoteIxs;
@@ -337,3 +374,9 @@ async function buildRepayWithCollateralIxs(
337
374
  flashRepayIxn,
338
375
  ];
339
376
  }
377
+
378
+ export const getMaxWithdrawLtvCheck = (obligation: KaminoObligation) => {
379
+ return obligation.refreshedStats.userTotalBorrowBorrowFactorAdjusted.gte(obligation.refreshedStats.borrowLimit)
380
+ ? MaxWithdrawLtvCheck.LIQUIDATION_THRESHOLD
381
+ : MaxWithdrawLtvCheck.MAX_LTV;
382
+ };
@@ -85,3 +85,4 @@ export const DEFAULT_MAX_COMPUTE_UNITS = 1_400_000;
85
85
  * Minimum initial deposit required for the initialization of a reserve
86
86
  */
87
87
  export const MIN_INITIAL_DEPOSIT = 100_000;
88
+ export const MIN_VAULT_INITIAL_DEPOSIT = 1_000_000_000;
@@ -53,3 +53,65 @@ export const extendLookupTableIxs = (
53
53
 
54
54
  return extendLookupIxs;
55
55
  };
56
+
57
+ /**
58
+ * This method retuns an instruction that creates a lookup table, alongside the pubkey of the lookup table
59
+ * @param payer - the owner of the lookup table
60
+ * @param slot - the current slot
61
+ * @returns - the instruction to create the lookup table and its address
62
+ */
63
+ export function initLookupTableIx(payer: PublicKey, slot: number): [TransactionInstruction, PublicKey] {
64
+ const [ixn, address] = AddressLookupTableProgram.createLookupTable({
65
+ authority: payer,
66
+ payer,
67
+ recentSlot: slot,
68
+ });
69
+
70
+ return [ixn, address];
71
+ }
72
+
73
+ /**
74
+ * This method retuns an instruction that deactivates a lookup table, which is needed to close it
75
+ * @param payer - the owner of the lookup table
76
+ * @param lookupTable - the lookup table to deactivate
77
+ * @returns - the instruction to deactivate the lookup table
78
+ */
79
+ export function deactivateLookupTableIx(payer: PublicKey, lookupTable: PublicKey): TransactionInstruction {
80
+ const ixn = AddressLookupTableProgram.deactivateLookupTable({
81
+ authority: payer,
82
+ lookupTable: lookupTable,
83
+ });
84
+
85
+ return ixn;
86
+ }
87
+
88
+ /**
89
+ * This method returns an instruction that closes a lookup table. That lookup table needs to be disabled at least 500 blocks before closing it.
90
+ * @param payer - the owner of the lookup table
91
+ * @param lookupTable - the lookup table to close
92
+ * @returns - the instruction to close the lookup table
93
+ */
94
+ /// this require the LUT to be deactivated at least 500 blocks before
95
+ export function closeLookupTableIx(payer: PublicKey, lookupTable: PublicKey): TransactionInstruction {
96
+ const ixn = AddressLookupTableProgram.closeLookupTable({
97
+ authority: payer,
98
+ recipient: payer,
99
+ lookupTable: lookupTable,
100
+ });
101
+
102
+ return ixn;
103
+ }
104
+
105
+ /**
106
+ * Returns the accounts in a lookup table
107
+ * @param lookupTable - lookup table to get the accounts from
108
+ * @returns - an array of accounts in the lookup table
109
+ */
110
+ export async function getAccountsInLUT(connection: Connection, lookupTable: PublicKey): Promise<PublicKey[]> {
111
+ const lutState = await connection.getAddressLookupTable(lookupTable);
112
+ if (!lutState || !lutState.value) {
113
+ throw new Error(`Lookup table ${lookupTable} not found`);
114
+ }
115
+
116
+ return lutState.value.state.addresses;
117
+ }
@@ -38,6 +38,10 @@ export const BASE_SEED_REFERRER_STATE = 'ref_state';
38
38
  * Short url seed
39
39
  */
40
40
  export const BASE_SEED_SHORT_URL = 'short_url';
41
+ /**
42
+ * Farm user state seed
43
+ */
44
+ export const BASE_SEED_USER_STATE = 'user';
41
45
 
42
46
  /**
43
47
  * User farm state seed
@@ -188,9 +192,15 @@ export function shortUrlPda(shortUrl: string, programId: PublicKey = PROGRAM_ID)
188
192
  return PublicKey.findProgramAddressSync([Buffer.from(BASE_SEED_SHORT_URL), Buffer.from(shortUrl)], programId);
189
193
  }
190
194
 
191
- export function obligationFarmStatePda(obligation: PublicKey, farm: PublicKey, programId: PublicKey = farmsId) {
195
+ /**
196
+ * Returns the PDA for the obligation farm state
197
+ * @param farm
198
+ * @param obligation
199
+ * @returns pda
200
+ */
201
+ export function obligationFarmStatePda(farm: PublicKey, obligation: PublicKey) {
192
202
  return PublicKey.findProgramAddressSync(
193
- [Buffer.from(BASE_SEED_FARM_USER_STATE), farm.toBuffer(), obligation.toBuffer()],
194
- programId
195
- );
203
+ [Buffer.from(BASE_SEED_USER_STATE), farm.toBytes(), obligation.toBytes()],
204
+ farmsId
205
+ )[0];
196
206
  }