@kamino-finance/klend-sdk 5.2.12 → 5.2.14

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 (36) hide show
  1. package/dist/classes/manager.d.ts +36 -7
  2. package/dist/classes/manager.d.ts.map +1 -1
  3. package/dist/classes/manager.js +41 -7
  4. package/dist/classes/manager.js.map +1 -1
  5. package/dist/classes/obligation.d.ts +2 -0
  6. package/dist/classes/obligation.d.ts.map +1 -1
  7. package/dist/classes/obligation.js +12 -6
  8. package/dist/classes/obligation.js.map +1 -1
  9. package/dist/classes/types.d.ts +23 -0
  10. package/dist/classes/types.d.ts.map +1 -0
  11. package/dist/classes/types.js +3 -0
  12. package/dist/classes/types.js.map +1 -0
  13. package/dist/classes/vault.d.ts +45 -9
  14. package/dist/classes/vault.d.ts.map +1 -1
  15. package/dist/classes/vault.js +349 -20
  16. package/dist/classes/vault.js.map +1 -1
  17. package/dist/client_kamino_manager.d.ts.map +1 -1
  18. package/dist/client_kamino_manager.js +100 -16
  19. package/dist/client_kamino_manager.js.map +1 -1
  20. package/dist/lending_operations/repay_with_collateral_calcs.d.ts +4 -2
  21. package/dist/lending_operations/repay_with_collateral_calcs.d.ts.map +1 -1
  22. package/dist/lending_operations/repay_with_collateral_calcs.js +45 -52
  23. package/dist/lending_operations/repay_with_collateral_calcs.js.map +1 -1
  24. package/dist/lending_operations/repay_with_collateral_operations.d.ts +7 -0
  25. package/dist/lending_operations/repay_with_collateral_operations.d.ts.map +1 -1
  26. package/dist/lending_operations/repay_with_collateral_operations.js +13 -3
  27. package/dist/lending_operations/repay_with_collateral_operations.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/classes/manager.ts +57 -10
  30. package/src/classes/obligation.ts +15 -6
  31. package/src/classes/types.ts +27 -0
  32. package/src/classes/vault.ts +511 -26
  33. package/src/client_kamino_manager.ts +174 -19
  34. package/src/lending_operations/repay_with_collateral_calcs.ts +55 -61
  35. package/src/lending_operations/repay_with_collateral_operations.ts +24 -4
  36. package/src/leverage/operations.ts +1 -1
@@ -26,6 +26,7 @@ import {
26
26
  ReserveAllocationConfig,
27
27
  ReserveWithAddress,
28
28
  signSendAndConfirmRawTransactionWithRetry,
29
+ sleep,
29
30
  Web3Client,
30
31
  } from './lib';
31
32
  import * as anchor from '@coral-xyz/anchor';
@@ -52,6 +53,7 @@ import {
52
53
  PerformanceFeeBps,
53
54
  } from './idl_codegen_kamino_vault/types/VaultConfigField';
54
55
  import { getAccountOwner } from './utils/rpc';
56
+ import { Farms, FarmState, getUserStatePDA } from '@kamino-finance/farms-sdk';
55
57
 
56
58
  dotenv.config({
57
59
  path: `.env${process.env.ENV ? '.' + process.env.ENV : ''}`,
@@ -224,9 +226,10 @@ async function main() {
224
226
  `--mode <string>`,
225
227
  'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage'
226
228
  )
229
+ .option(`--name`, 'The onchain name of the strat')
227
230
  .option(`--staging`, 'If true, will use the staging programs')
228
231
  .option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
229
- .action(async ({ mint, mode, staging, multisig }) => {
232
+ .action(async ({ mint, mode, name, staging, multisig }) => {
230
233
  const env = initializeClient(mode === 'multisig', staging);
231
234
  const tokenMint = new PublicKey(mint);
232
235
 
@@ -244,11 +247,14 @@ async function main() {
244
247
  tokenMintProgramId: tokenProgramID,
245
248
  performanceFeeRate: new Decimal(0.0),
246
249
  managementFeeRate: new Decimal(0.0),
250
+ name,
247
251
  });
248
252
 
249
- const { vault: vaultKp, ixns: instructions } = await kaminoManager.createVaultIxs(kaminoVaultConfig);
253
+ const { vault: vaultKp, initVaultIxs: instructions } = await kaminoManager.createVaultIxs(kaminoVaultConfig);
250
254
 
251
- const _createVaultSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [vaultKp]);
255
+ const _createVaultSig = await processTxn(env.client, env.payer, instructions.initVaultIxs, mode, 2500, [vaultKp]);
256
+ await sleep(5000);
257
+ const _populateLUTSig = await processTxn(env.client, env.payer, instructions.populateLUTIxs, mode, 2500, []);
252
258
 
253
259
  mode === 'execute' && console.log('Vault created:', vaultKp.publicKey.toBase58());
254
260
  });
@@ -274,9 +280,16 @@ async function main() {
274
280
  const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
275
281
 
276
282
  const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
277
- const instruction = await kaminoManager.updateVaultConfigIx(kaminoVault, new PendingVaultAdmin(), newAdmin);
278
-
279
- const updateVaultPendingAdminSig = await processTxn(env.client, env.payer, [instruction], mode, 2500, []);
283
+ const instructions = await kaminoManager.updateVaultConfigIxs(kaminoVault, new PendingVaultAdmin(), newAdmin);
284
+
285
+ const updateVaultPendingAdminSig = await processTxn(
286
+ env.client,
287
+ env.payer,
288
+ [instructions.updateVaultConfigIx, ...instructions.updateLUTIxs],
289
+ mode,
290
+ 2500,
291
+ []
292
+ );
280
293
 
281
294
  mode === 'execute' && console.log('Pending admin updated:', updateVaultPendingAdminSig);
282
295
  });
@@ -302,13 +315,84 @@ async function main() {
302
315
  const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
303
316
 
304
317
  const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
305
- const instruction = await kaminoManager.updateVaultConfigIx(kaminoVault, new ManagementFeeBps(), feeBps);
318
+ const instructions = await kaminoManager.updateVaultConfigIxs(kaminoVault, new ManagementFeeBps(), feeBps);
319
+
320
+ const updateVaultConfigSig = await processTxn(
321
+ env.client,
322
+ env.payer,
323
+ [instructions.updateVaultConfigIx, ...instructions.updateLUTIxs],
324
+ mode,
325
+ 2500,
326
+ []
327
+ );
328
+
329
+ mode === 'execute' && console.log('Management fee updated:', updateVaultConfigSig);
330
+ });
331
+
332
+ commands
333
+ .command('insert-into-lut')
334
+ .requiredOption('--lut <string>', 'Lookup table address')
335
+ .requiredOption('--addresses <string>', 'The addresses to insert into the LUT, space separated')
336
+ .requiredOption(
337
+ `--mode <string>`,
338
+ 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage'
339
+ )
340
+ .option(`--staging`, 'If true, will use the staging programs')
341
+ .option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
342
+ .action(async ({ lut, addresses, mode, staging, multisig }) => {
343
+ const env = initializeClient(mode === 'multisig', staging);
344
+ const lutAddress = new PublicKey(lut);
345
+
346
+ const addressesArr = addresses.split(' ').map((address: string) => new PublicKey(address));
347
+
348
+ if (mode === 'multisig' && !multisig) {
349
+ throw new Error('If using multisig mode, multisig is required');
350
+ }
306
351
 
307
- const updateVaultConfigSig = await processTxn(env.client, env.payer, [instruction], mode, 2500, []);
352
+ const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
353
+
354
+ const instructions = await kaminoManager.insertIntoLUT(env.payer.publicKey, lutAddress, addressesArr);
355
+
356
+ const updateVaultConfigSig = await processTxn(env.client, env.payer, instructions, mode, 2500, []);
308
357
 
309
358
  mode === 'execute' && console.log('Management fee updated:', updateVaultConfigSig);
310
359
  });
311
360
 
361
+ commands
362
+ .command('sync-vault-lut')
363
+ .requiredOption('--vault <string>', 'The vault address to sync')
364
+ .requiredOption(
365
+ `--mode <string>`,
366
+ 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage'
367
+ )
368
+ .option(`--staging`, 'If true, will use the staging programs')
369
+ .option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
370
+ .action(async ({ vault, mode, staging, multisig }) => {
371
+ const env = initializeClient(mode === 'multisig', staging);
372
+ const vaultAddress = new PublicKey(vault);
373
+
374
+ if (mode === 'multisig' && !multisig) {
375
+ throw new Error('If using multisig mode, multisig is required');
376
+ }
377
+
378
+ const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
379
+
380
+ const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
381
+ const syncLUTIxs = await kaminoManager.syncVaultLUT(kaminoVault);
382
+
383
+ // if we need to create the LUT we have to do that in a separate tx and wait a little bit after
384
+ if (syncLUTIxs.setupLUTIfNeededIxs.length > 0) {
385
+ const setupLUTSig = await processTxn(env.client, env.payer, syncLUTIxs.setupLUTIfNeededIxs, mode, 2500, []);
386
+ await sleep(5000);
387
+ mode === 'execute' && console.log('LUT created and set to the vault:', setupLUTSig);
388
+ }
389
+ // if there are accounts to be added to the LUT we have to do that in a separate tx
390
+ for (const ix of syncLUTIxs.syncLUTIxs) {
391
+ const insertIntoLUTSig = await processTxn(env.client, env.payer, [ix], mode, 2500, []);
392
+ mode === 'execute' && console.log('Accounts added to the LUT:', insertIntoLUTSig);
393
+ }
394
+ });
395
+
312
396
  commands
313
397
  .command('update-vault-perf-fee')
314
398
  .requiredOption('--vault <string>', 'Vault address')
@@ -330,9 +414,16 @@ async function main() {
330
414
  const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
331
415
 
332
416
  const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
333
- const instruction = await kaminoManager.updateVaultConfigIx(kaminoVault, new PerformanceFeeBps(), feeBps);
334
-
335
- const updateVaultPerfFeeSig = await processTxn(env.client, env.payer, [instruction], mode, 2500, []);
417
+ const instructions = await kaminoManager.updateVaultConfigIxs(kaminoVault, new PerformanceFeeBps(), feeBps);
418
+
419
+ const updateVaultPerfFeeSig = await processTxn(
420
+ env.client,
421
+ env.payer,
422
+ [instructions.updateVaultConfigIx, ...instructions.updateLUTIxs],
423
+ mode,
424
+ 2500,
425
+ []
426
+ );
336
427
 
337
428
  mode === 'execute' && console.log('Performance fee updated:', updateVaultPerfFeeSig);
338
429
  });
@@ -357,9 +448,16 @@ async function main() {
357
448
  const kaminoManager = new KaminoManager(env.connection, env.kLendProgramId, env.kVaultProgramId);
358
449
 
359
450
  const kaminoVault = new KaminoVault(vaultAddress, undefined, env.kVaultProgramId);
360
- const instruction = await kaminoManager.acceptVaultOwnershipIx(kaminoVault);
361
-
362
- const acceptVaultOwnershipSig = await processTxn(env.client, env.payer, [instruction], mode, 2500, []);
451
+ const instructions = await kaminoManager.acceptVaultOwnershipIxs(kaminoVault);
452
+
453
+ const acceptVaultOwnershipSig = await processTxn(
454
+ env.client,
455
+ env.payer,
456
+ [instructions.acceptVaultOwnershipIx, ...instructions.updateLUTIxs],
457
+ mode,
458
+ 2500,
459
+ []
460
+ );
363
461
 
364
462
  mode === 'execute' && console.log('Vault ownership accepted:', acceptVaultOwnershipSig);
365
463
  });
@@ -422,6 +520,56 @@ async function main() {
422
520
  mode === 'execute' && console.log('Pending fees withdrawn:', withdrawPendingFeesSig);
423
521
  });
424
522
 
523
+ commands
524
+ .command('stake')
525
+ .requiredOption('--vault <string>', 'Vault address')
526
+ .requiredOption('--farm <string>', 'Farm address')
527
+ .requiredOption('--amount <string>', 'The number of kTokens to stake')
528
+ .requiredOption(
529
+ `--mode <string>`,
530
+ 'simulate - to print txn simulation, inspect - to get txn simulation in explorer, execute - execute txn, multisig - to get bs58 txn for multisig usage'
531
+ )
532
+ .option(`--staging`, 'If true, will use the staging programs')
533
+ .option(`--multisig <string>`, 'If using multisig mode this is required, otherwise will be ignored')
534
+ .action(async ({ vault, farm, amount, mode, staging, multisig }) => {
535
+ const env = initializeClient(mode === 'multisig', staging);
536
+ const vaultAddress = new PublicKey(vault);
537
+ const farmAddress = new PublicKey(farm);
538
+
539
+ const farmClient = new Farms(env.connection);
540
+
541
+ const farmState = await FarmState.fetch(env.connection, farmAddress);
542
+ const vaultState = await new KaminoVault(vaultAddress, undefined, env.kVaultProgramId).getState(env.connection);
543
+
544
+ const scopePricesArg = farmState!.scopePrices.equals(PublicKey.default)
545
+ ? farmClient.getProgramID()
546
+ : farmState!.scopePrices;
547
+
548
+ const ixns: TransactionInstruction[] = [];
549
+ const userState = getUserStatePDA(farmClient.getProgramID(), farmAddress, env.provider.publicKey);
550
+ if (!userState) {
551
+ const createUserIx = await farmClient.createNewUserIx(env.provider.publicKey, farmAddress);
552
+ ixns.push(createUserIx);
553
+ }
554
+
555
+ // todo: fix in farms sdk to make this not async
556
+ const stakeIx = await farmClient.stakeIx(
557
+ env.payer.publicKey,
558
+ farmAddress,
559
+ new Decimal(amount),
560
+ vaultState.sharesMint,
561
+ scopePricesArg
562
+ );
563
+ ixns.push(stakeIx);
564
+
565
+ if (mode === 'multisig' && !multisig) {
566
+ throw new Error('If using multisig mode, multisig is required');
567
+ }
568
+ const withdrawPendingFeesSig = await processTxn(env.client, env.payer, ixns, mode, 2500, []);
569
+
570
+ mode === 'execute' && console.log('Pending fees withdrawn:', withdrawPendingFeesSig);
571
+ });
572
+
425
573
  commands
426
574
  .command('update-vault-reserve-allocation')
427
575
  .requiredOption('--vault <string>', 'Vault address')
@@ -466,7 +614,14 @@ async function main() {
466
614
  firstReserveAllocationConfig
467
615
  );
468
616
 
469
- const updateVaultAllocationSig = await processTxn(env.client, env.payer, [instructions], mode, 2500, []);
617
+ const updateVaultAllocationSig = await processTxn(
618
+ env.client,
619
+ env.payer,
620
+ [instructions.updateReserveAllocationIx, ...instructions.updateLUTIxs],
621
+ mode,
622
+ 2500,
623
+ []
624
+ );
470
625
 
471
626
  mode === 'execute' && console.log('Vault allocation updated:', updateVaultAllocationSig);
472
627
  });
@@ -557,7 +712,7 @@ async function main() {
557
712
  for (let i = 0; i < instructions.length; i++) {
558
713
  const txInstructions: TransactionInstruction[] = [];
559
714
  txInstructions.push(instructions[i]);
560
- const investReserveSig = await processTxn(env.client, env.payer, txInstructions, mode, 2500, [], 400000);
715
+ const investReserveSig = await processTxn(env.client, env.payer, txInstructions, mode, 2500, [], 800000);
561
716
 
562
717
  mode === 'execute' && console.log('Reserve invested:', investReserveSig);
563
718
  }
@@ -599,7 +754,7 @@ async function main() {
599
754
  kaminoVault,
600
755
  reserveWithAddress
601
756
  );
602
- const investReserveSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [], 400_000);
757
+ const investReserveSig = await processTxn(env.client, env.payer, instructions, mode, 2500, [], 800_000);
603
758
 
604
759
  mode === 'execute' && console.log('Reserve invested:', investReserveSig);
605
760
  });
@@ -1059,7 +1214,7 @@ async function processTxn(
1059
1214
  if (simulation.value.logs && simulation.value.logs.length > 0) {
1060
1215
  console.log('Simulation: \n' + simulation.value.logs);
1061
1216
  } else {
1062
- console.log('Simulation failed: \n' + simulation);
1217
+ console.log('Simulation failed: \n' + simulation.value.err);
1063
1218
  }
1064
1219
  } else if (mode === 'inspect') {
1065
1220
  console.log(
@@ -1076,7 +1231,7 @@ async function processTxn(
1076
1231
  function createAddExtraComputeUnitFeeTransaction(units: number, microLamports: number): TransactionInstruction[] {
1077
1232
  const ixns: TransactionInstruction[] = [];
1078
1233
  ixns.push(ComputeBudgetProgram.setComputeUnitLimit({ units }));
1079
- ixns.push(ComputeBudgetProgram.setComputeUnitPrice({ microLamports }));
1234
+ ixns.push(ComputeBudgetProgram.setComputeUnitPrice({ microLamports: new Decimal(microLamports).floor().toNumber() }));
1080
1235
  return ixns;
1081
1236
  }
1082
1237
 
@@ -76,79 +76,73 @@ export const calcFlashRepayAmount = (props: {
76
76
  };
77
77
 
78
78
  export function calcMaxWithdrawCollateral(
79
- kaminoMarket: KaminoMarket,
79
+ market: KaminoMarket,
80
+ obligation: KaminoObligation,
80
81
  collReserveAddr: PublicKey,
81
82
  debtReserveAddr: PublicKey,
82
- obligation: KaminoObligation,
83
83
  repayAmountLamports: Decimal
84
84
  ): {
85
- canWithdrawRemainingColl: boolean;
85
+ repayAmountLamports: Decimal;
86
86
  withdrawableCollLamports: Decimal;
87
+ canWithdrawAllColl: boolean;
88
+ repayingAllDebt: boolean;
87
89
  } {
88
- const collReserve = kaminoMarket.getReserveByAddress(collReserveAddr)!;
89
- const collOraclePx = collReserve.getOracleMarketPrice();
90
- const { maxLtv: collMaxLtv } = obligation.getLtvForReserve(kaminoMarket, collReserve);
91
-
92
- const collPosition = obligation.getDepositByReserve(collReserve.address)!;
93
- const initialCollValue = collPosition.amount.floor().div(collReserve.getMintFactor()).mul(collOraclePx);
94
-
95
- let totalRemainingDebtValue = new Decimal(0);
96
- const borrows = obligation.getBorrows();
97
- for (const debtPosition of borrows) {
98
- const debtReserve = kaminoMarket.getReserveByAddress(debtPosition.reserveAddress)!;
99
- const debtOraclePx = debtReserve.getOracleMarketPrice();
100
- const debtBorrowFactor = debtReserve.getBorrowFactor();
101
- let remainingDebtAmountLamports = debtPosition.amount;
102
- if (debtPosition.reserveAddress.equals(debtReserveAddr)) {
103
- remainingDebtAmountLamports = remainingDebtAmountLamports.sub(repayAmountLamports);
104
- }
105
- const remainingDebtBfWeightedValue = remainingDebtAmountLamports
106
- .ceil()
107
- .div(debtReserve.getMintFactor())
108
- .mul(debtBorrowFactor)
109
- .mul(debtOraclePx);
110
- totalRemainingDebtValue = totalRemainingDebtValue.add(remainingDebtBfWeightedValue);
111
- }
112
-
113
- let canWithdrawRemainingColl = false;
114
- if (totalRemainingDebtValue.lte(new Decimal(0)) && borrows.length === 1) {
115
- canWithdrawRemainingColl = true;
90
+ const deposit = obligation.getDepositByReserve(collReserveAddr)!;
91
+ const borrow = obligation.getBorrowByReserve(debtReserveAddr)!;
92
+ const depositReserve = market.getReserveByAddress(deposit.reserveAddress)!;
93
+ const debtReserve = market.getReserveByAddress(borrow.reserveAddress)!;
94
+ const depositTotalLamports = deposit.amount.floor();
95
+
96
+ const remainingBorrowLamports = borrow.amount.sub(repayAmountLamports).ceil();
97
+ const remainingBorrowAmount = remainingBorrowLamports.div(debtReserve.getMintFactor());
98
+ let remainingBorrowsValue = remainingBorrowAmount.mul(debtReserve.getOracleMarketPrice());
99
+ if (obligation.getBorrows().length > 1) {
100
+ remainingBorrowsValue = obligation
101
+ .getBorrows()
102
+ .filter((p) => !p.reserveAddress.equals(borrow.reserveAddress))
103
+ .reduce((acc, b) => acc.add(b.marketValueRefreshed), new Decimal('0'));
116
104
  }
117
105
 
118
- const deposits = obligation.getDeposits();
119
- const otherCollDeposits = deposits.filter((deposit) => !deposit.reserveAddress.equals(collReserve.address));
120
-
121
- let totalOtherCollateralValue = new Decimal(0);
122
- for (const d of otherCollDeposits) {
123
- const otherCollReserve = kaminoMarket.getReserveByAddress(d.reserveAddress)!;
124
- const otherCollOraclePx = otherCollReserve.getOracleMarketPrice();
125
- const otherCollMaxLtv = obligation.getLtvForReserve(kaminoMarket, otherCollReserve).maxLtv;
126
- const otherCollValue = d.amount
127
- .floor()
128
- .div(otherCollReserve.getMintFactor())
129
- .mul(otherCollOraclePx)
130
- .mul(otherCollMaxLtv);
131
- totalOtherCollateralValue = totalOtherCollateralValue.add(otherCollValue);
106
+ let remainingDepositsValueWithLtv = new Decimal('0');
107
+ if (obligation.getDeposits().length > 1) {
108
+ remainingDepositsValueWithLtv = obligation
109
+ .getDeposits()
110
+ .filter((p) => !p.reserveAddress.equals(deposit.reserveAddress))
111
+ .reduce((acc, d) => {
112
+ const { maxLtv } = obligation.getLtvForReserve(market, market.getReserveByAddress(d.reserveAddress)!);
113
+ return acc.add(d.marketValueRefreshed.mul(maxLtv));
114
+ }, new Decimal('0'));
132
115
  }
133
116
 
134
- const numerator = initialCollValue.mul(collMaxLtv).add(totalOtherCollateralValue).sub(totalRemainingDebtValue);
135
-
136
- // If all collateral cannot cover the remaining debt
137
- if (numerator.lte('0')) {
138
- return { canWithdrawRemainingColl: false, withdrawableCollLamports: new Decimal(0) };
139
- }
140
-
141
- const denominator = collOraclePx.mul(collMaxLtv);
142
- const maxCollWithdrawAmount = numerator.div(denominator);
143
- const maxCollateralWithdrawalAmountLamports = maxCollWithdrawAmount.mul(collReserve.getMintFactor()).floor();
144
-
145
- let withdrawableCollLamports: Decimal;
146
- if (canWithdrawRemainingColl) {
147
- withdrawableCollLamports = Decimal.min(maxCollateralWithdrawalAmountLamports, collPosition.amount).floor();
117
+ // can withdraw all coll
118
+ if (remainingDepositsValueWithLtv.gte(remainingBorrowsValue)) {
119
+ return {
120
+ repayAmountLamports: repayAmountLamports,
121
+ withdrawableCollLamports: depositTotalLamports,
122
+ canWithdrawAllColl: true,
123
+ repayingAllDebt: repayAmountLamports.gte(borrow.amount),
124
+ };
148
125
  } else {
149
- withdrawableCollLamports = Decimal.max(new Decimal(0), maxCollateralWithdrawalAmountLamports);
126
+ const { maxLtv: collMaxLtv } = obligation.getLtvForReserve(
127
+ market,
128
+ market.getReserveByAddress(depositReserve.address)!
129
+ );
130
+ const numerator = deposit.marketValueRefreshed
131
+ .mul(collMaxLtv)
132
+ .add(remainingDepositsValueWithLtv)
133
+ .sub(remainingBorrowsValue);
134
+
135
+ const denominator = depositReserve.getOracleMarketPrice().mul(collMaxLtv);
136
+ const maxCollWithdrawAmount = numerator.div(denominator);
137
+ const withdrawableCollLamports = maxCollWithdrawAmount.mul(depositReserve.getMintFactor()).floor();
138
+
139
+ return {
140
+ repayAmountLamports: repayAmountLamports,
141
+ withdrawableCollLamports,
142
+ canWithdrawAllColl: false,
143
+ repayingAllDebt: repayAmountLamports.gte(borrow.amount),
144
+ };
150
145
  }
151
- return { canWithdrawRemainingColl, withdrawableCollLamports };
152
146
  }
153
147
 
154
148
  export function estimateDebtRepaymentWithColl(props: {
@@ -29,6 +29,13 @@ export type RepayWithCollIxsResponse<QuoteResponse> = {
29
29
  export type InitialInputs<QuoteResponse> = {
30
30
  debtRepayAmountLamports: Decimal;
31
31
  flashRepayAmountLamports: Decimal;
32
+ /**
33
+ * The amount of collateral available to withdraw, if this is less than the swap input amount, then the swap may fail due to slippage, or tokens may be debited from the user's ATA, so the caller needs to check this
34
+ */
35
+ maxCollateralWithdrawLamports: Decimal;
36
+ /**
37
+ * The quote from the provided quoter
38
+ */
32
39
  swapQuote: SwapQuote<QuoteResponse>;
33
40
  currentSlot: number;
34
41
  klendAccounts: Array<PublicKey>;
@@ -93,9 +100,9 @@ export async function getRepayWithCollSwapInputs<QuoteResponse>({
93
100
  }
94
101
  const { withdrawableCollLamports } = calcMaxWithdrawCollateral(
95
102
  kaminoMarket,
103
+ obligation,
96
104
  collReserve.address,
97
105
  debtReserve.address,
98
- obligation,
99
106
  repayAmountLamports
100
107
  );
101
108
 
@@ -156,6 +163,7 @@ export async function getRepayWithCollSwapInputs<QuoteResponse>({
156
163
  initialInputs: {
157
164
  debtRepayAmountLamports: repayAmountLamports,
158
165
  flashRepayAmountLamports,
166
+ maxCollateralWithdrawLamports: withdrawableCollLamports,
159
167
  swapQuote,
160
168
  currentSlot,
161
169
  klendAccounts: uniqueKlendAccounts,
@@ -196,18 +204,30 @@ export async function getRepayWithCollIxs<QuoteResponse>({
196
204
  budgetAndPriorityFeeIxs,
197
205
  scopeRefresh,
198
206
  });
199
- const { debtRepayAmountLamports, flashRepayAmountLamports, swapQuote } = initialInputs;
207
+ const { debtRepayAmountLamports, flashRepayAmountLamports, maxCollateralWithdrawLamports, swapQuote } = initialInputs;
200
208
  const { inputAmountLamports: collSwapInLamports } = swapInputs;
201
209
 
202
210
  const collReserve = kaminoMarket.getReserveByMint(collTokenMint)!;
203
211
  const debtReserve = kaminoMarket.getReserveByMint(debtTokenMint)!;
204
212
 
213
+ // the client should use these values to prevent this input, but the tx may succeed, so we don't want to fail
214
+ // there is also a chance that the tx will consume debt token from the user's ata which they would not expect
215
+ if (collSwapInLamports.greaterThan(maxCollateralWithdrawLamports)) {
216
+ logger(
217
+ `Collateral swap in amount ${collSwapInLamports} exceeds max withdrawable collateral ${maxCollateralWithdrawLamports}, tx may fail with slippage`
218
+ );
219
+ swapInputs.inputAmountLamports = maxCollateralWithdrawLamports;
220
+ }
221
+
222
+ const actualSwapInLamports = Decimal.min(collSwapInLamports, maxCollateralWithdrawLamports);
205
223
  logger(
206
- `Expected to swap in: ${collSwapInLamports.div(collReserve.getMintFactor())} ${
224
+ `Expected to swap in: ${actualSwapInLamports.div(collReserve.getMintFactor())} ${
207
225
  collReserve.symbol
208
226
  }, for: ${flashRepayAmountLamports.div(debtReserve.getMintFactor())} ${debtReserve.symbol}, quoter px: ${
209
227
  swapQuote.priceAInB
210
- } ${debtReserve.symbol}/${collReserve.symbol}`
228
+ } ${debtReserve.symbol}/${collReserve.symbol}, required px: ${flashRepayAmountLamports
229
+ .div(debtReserve.getMintFactor())
230
+ .div(actualSwapInLamports.div(collReserve.getMintFactor()))} ${debtReserve.symbol}/${collReserve.symbol}`
211
231
  );
212
232
 
213
233
  const swapResponse = await swapper(swapInputs, initialInputs.klendAccounts, swapQuote);
@@ -1045,7 +1045,7 @@ export async function getAdjustLeverageSwapInputs<QuoteResponse>({
1045
1045
 
1046
1046
  const swapInputAmount = toLamports(
1047
1047
  !collIsKtoken ? calcs.borrowAmount : calcs.amountToFlashBorrowDebt,
1048
- debtReserve.stats.decimals,
1048
+ debtReserve.stats.decimals
1049
1049
  ).ceil();
1050
1050
 
1051
1051
  const swapInputsForQuote: SwapInputs = {