@kamino-finance/klend-sdk 4.0.2 → 5.0.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 (67) hide show
  1. package/README.md +2 -2
  2. package/dist/classes/action.d.ts +3 -1
  3. package/dist/classes/action.d.ts.map +1 -1
  4. package/dist/classes/action.js +62 -61
  5. package/dist/classes/action.js.map +1 -1
  6. package/dist/classes/obligation.d.ts +9 -0
  7. package/dist/classes/obligation.d.ts.map +1 -1
  8. package/dist/classes/obligation.js +8 -0
  9. package/dist/classes/obligation.js.map +1 -1
  10. package/dist/classes/reserve.d.ts +3 -1
  11. package/dist/classes/reserve.d.ts.map +1 -1
  12. package/dist/classes/reserve.js +32 -0
  13. package/dist/classes/reserve.js.map +1 -1
  14. package/dist/classes/shared.d.ts +8 -0
  15. package/dist/classes/shared.d.ts.map +1 -1
  16. package/dist/classes/shared.js +6 -1
  17. package/dist/classes/shared.js.map +1 -1
  18. package/dist/classes/vault.d.ts.map +1 -1
  19. package/dist/classes/vault.js +19 -5
  20. package/dist/classes/vault.js.map +1 -1
  21. package/dist/lending_operations/repay_with_collateral_calcs.d.ts +18 -1
  22. package/dist/lending_operations/repay_with_collateral_calcs.d.ts.map +1 -1
  23. package/dist/lending_operations/repay_with_collateral_calcs.js +62 -0
  24. package/dist/lending_operations/repay_with_collateral_calcs.js.map +1 -1
  25. package/dist/lending_operations/repay_with_collateral_operations.d.ts +27 -43
  26. package/dist/lending_operations/repay_with_collateral_operations.d.ts.map +1 -1
  27. package/dist/lending_operations/repay_with_collateral_operations.js +99 -152
  28. package/dist/lending_operations/repay_with_collateral_operations.js.map +1 -1
  29. package/dist/leverage/operations.d.ts +14 -3
  30. package/dist/leverage/operations.d.ts.map +1 -1
  31. package/dist/leverage/operations.js +184 -128
  32. package/dist/leverage/operations.js.map +1 -1
  33. package/dist/utils/ata.d.ts +12 -3
  34. package/dist/utils/ata.d.ts.map +1 -1
  35. package/dist/utils/ata.js +26 -39
  36. package/dist/utils/ata.js.map +1 -1
  37. package/dist/utils/index.d.ts +0 -2
  38. package/dist/utils/index.d.ts.map +1 -1
  39. package/dist/utils/index.js +0 -2
  40. package/dist/utils/index.js.map +1 -1
  41. package/dist/utils/instruction.d.ts +1 -0
  42. package/dist/utils/instruction.d.ts.map +1 -1
  43. package/dist/utils/instruction.js +18 -0
  44. package/dist/utils/instruction.js.map +1 -1
  45. package/package.json +3 -3
  46. package/src/classes/action.ts +92 -98
  47. package/src/classes/obligation.ts +15 -6
  48. package/src/classes/reserve.ts +48 -1
  49. package/src/classes/shared.ts +10 -0
  50. package/src/classes/vault.ts +23 -19
  51. package/src/lending_operations/repay_with_collateral_calcs.ts +98 -1
  52. package/src/lending_operations/repay_with_collateral_operations.ts +233 -253
  53. package/src/leverage/operations.ts +227 -140
  54. package/src/utils/ata.ts +33 -56
  55. package/src/utils/index.ts +0 -2
  56. package/src/utils/instruction.ts +22 -0
  57. package/dist/utils/layout.d.ts +0 -14
  58. package/dist/utils/layout.d.ts.map +0 -1
  59. package/dist/utils/layout.js +0 -123
  60. package/dist/utils/layout.js.map +0 -1
  61. package/dist/utils/syncNative.d.ts +0 -11
  62. package/dist/utils/syncNative.d.ts.map +0 -1
  63. package/dist/utils/syncNative.js +0 -45
  64. package/dist/utils/syncNative.js.map +0 -1
  65. package/src/global.d.ts +0 -1
  66. package/src/utils/layout.ts +0 -118
  67. package/src/utils/syncNative.ts +0 -22
@@ -15,6 +15,7 @@ import {
15
15
  NATIVE_MINT,
16
16
  TOKEN_PROGRAM_ID,
17
17
  createCloseAccountInstruction,
18
+ createSyncNativeInstruction,
18
19
  } from '@solana/spl-token';
19
20
  import BN from 'bn.js';
20
21
  import Decimal from 'decimal.js';
@@ -47,7 +48,6 @@ import {
47
48
  buildComputeBudgetIx,
48
49
  createAssociatedTokenAccountIdempotentInstruction,
49
50
  ObligationType,
50
- syncNative,
51
51
  U64_MAX,
52
52
  referrerTokenStatePda,
53
53
  userMetadataPda,
@@ -1043,14 +1043,14 @@ export class KaminoAction {
1043
1043
  kaminoMarket: KaminoMarket,
1044
1044
  currentSlot: number = 0
1045
1045
  ) {
1046
- const { axn, createAtasIxns } = await KaminoAction.initializeWithdrawReferrerFees(
1046
+ const { axn, createAtaIxs } = await KaminoAction.initializeWithdrawReferrerFees(
1047
1047
  tokenMint,
1048
1048
  owner,
1049
1049
  kaminoMarket,
1050
1050
  currentSlot
1051
1051
  );
1052
1052
 
1053
- axn.preTxnIxs.push(...createAtasIxns);
1053
+ axn.preTxnIxs.push(...createAtaIxs);
1054
1054
  axn.preTxnIxsLabels.push(`createAtasIxs[${axn.userTokenAccountAddress.toString()}]`);
1055
1055
 
1056
1056
  axn.addRefreshReserveIxs([axn.reserve.address]);
@@ -2445,70 +2445,57 @@ export class KaminoAction {
2445
2445
  }
2446
2446
 
2447
2447
  if ((action === 'withdraw' || action === 'borrow' || action === 'redeem') && !this.mint.equals(WRAPPED_SOL_MINT)) {
2448
- const userTokenAccountInfo = await this.kaminoMarket.getConnection().getAccountInfo(this.userTokenAccountAddress);
2449
-
2450
- if (!userTokenAccountInfo) {
2451
- const [, createUserTokenAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2452
- this.owner,
2453
- this.reserve.getLiquidityMint(),
2454
- this.owner,
2455
- this.reserve.getLiquidityTokenProgram(),
2456
- this.userTokenAccountAddress
2457
- );
2448
+ const [, createUserTokenAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2449
+ this.owner,
2450
+ this.reserve.getLiquidityMint(),
2451
+ this.owner,
2452
+ this.reserve.getLiquidityTokenProgram(),
2453
+ this.userTokenAccountAddress
2454
+ );
2458
2455
 
2459
- if (this.positions === POSITION_LIMIT) {
2460
- this.preTxnIxs.push(createUserTokenAccountIx);
2461
- this.preTxnIxsLabels.push(`CreateLiquidityUserAta[${this.owner}]`);
2462
- } else {
2463
- this.setupIxs.unshift(createUserTokenAccountIx);
2464
- this.setupIxsLabels.unshift(`CreateLiquidityUserAta[${this.owner}]`);
2465
- }
2456
+ if (this.positions === POSITION_LIMIT) {
2457
+ this.preTxnIxs.push(createUserTokenAccountIx);
2458
+ this.preTxnIxsLabels.push(`CreateLiquidityUserAta[${this.owner}]`);
2459
+ } else {
2460
+ this.setupIxs.unshift(createUserTokenAccountIx);
2461
+ this.setupIxsLabels.unshift(`CreateLiquidityUserAta[${this.owner}]`);
2466
2462
  }
2467
2463
  }
2468
2464
 
2469
2465
  if (action === 'liquidate') {
2470
- const userTokenAccountInfo = await this.kaminoMarket.getConnection().getAccountInfo(this.userTokenAccountAddress);
2471
-
2472
2466
  if (!this.outflowReserve) {
2473
2467
  throw new Error(`Outflow reserve state not found ${this.mint}`);
2474
2468
  }
2475
2469
 
2476
- if (!userTokenAccountInfo) {
2477
- const [, createUserTokenAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2478
- this.owner,
2479
- this.outflowReserve.getLiquidityMint(),
2480
- this.owner,
2481
- this.outflowReserve.getLiquidityTokenProgram(),
2482
- this.userTokenAccountAddress
2483
- );
2484
- if (this.positions === POSITION_LIMIT && this.mint.equals(WRAPPED_SOL_MINT)) {
2485
- this.preTxnIxs.push(createUserTokenAccountIx);
2486
- this.preTxnIxsLabels.push(`CreateUserAta[${this.userTokenAccountAddress.toBase58()}]`);
2487
- } else {
2488
- this.setupIxs.unshift(createUserTokenAccountIx);
2489
- this.setupIxsLabels.unshift(`CreateUserAta[${this.userTokenAccountAddress.toBase58()}]`);
2490
- }
2470
+ const [, createUserTokenAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2471
+ this.owner,
2472
+ this.outflowReserve.getLiquidityMint(),
2473
+ this.owner,
2474
+ this.outflowReserve.getLiquidityTokenProgram(),
2475
+ this.userTokenAccountAddress
2476
+ );
2477
+ if (this.positions === POSITION_LIMIT && this.mint.equals(WRAPPED_SOL_MINT)) {
2478
+ this.preTxnIxs.push(createUserTokenAccountIx);
2479
+ this.preTxnIxsLabels.push(`CreateUserAta[${this.userTokenAccountAddress.toBase58()}]`);
2480
+ } else {
2481
+ this.setupIxs.unshift(createUserTokenAccountIx);
2482
+ this.setupIxsLabels.unshift(`CreateUserAta[${this.userTokenAccountAddress.toBase58()}]`);
2491
2483
  }
2492
2484
 
2493
- const userCollateralAccountInfo = await this.kaminoMarket
2494
- .getConnection()
2495
- .getAccountInfo(this.userCollateralAccountAddress);
2496
- if (!userCollateralAccountInfo) {
2497
- const [, createUserCollateralAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2498
- this.owner,
2499
- this.outflowReserve.getCTokenMint(),
2500
- this.owner,
2501
- TOKEN_PROGRAM_ID,
2502
- this.userCollateralAccountAddress
2503
- );
2485
+ const [, createUserCollateralAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2486
+ this.owner,
2487
+ this.outflowReserve.getCTokenMint(),
2488
+ this.owner,
2489
+ TOKEN_PROGRAM_ID,
2490
+ this.userCollateralAccountAddress
2491
+ );
2504
2492
 
2505
- if (this.positions === POSITION_LIMIT && this.mint.equals(WRAPPED_SOL_MINT)) {
2506
- this.preTxnIxs.push(createUserCollateralAccountIx);
2507
- this.preTxnIxsLabels.push(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2508
- } else {
2509
- this.setupIxs.unshift(createUserCollateralAccountIx);
2510
- this.setupIxsLabels.unshift(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2511
- }
2493
+ if (this.positions === POSITION_LIMIT && this.mint.equals(WRAPPED_SOL_MINT)) {
2494
+ this.preTxnIxs.push(createUserCollateralAccountIx);
2495
+ this.preTxnIxsLabels.push(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2496
+ } else {
2497
+ this.setupIxs.unshift(createUserCollateralAccountIx);
2498
+ this.setupIxsLabels.unshift(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2512
2499
  }
2513
2500
 
2514
2501
  if (!this.additionalTokenAccountAddress) {
@@ -2547,43 +2534,32 @@ export class KaminoAction {
2547
2534
  }
2548
2535
 
2549
2536
  if (action === 'withdraw' || action === 'mint' || action === 'deposit' || action === 'repayAndWithdraw') {
2550
- const userTokenAccountInfo = await this.kaminoMarket.getConnection().getAccountInfo(this.userTokenAccountAddress);
2551
-
2552
- // TODO: Might need to remove this
2553
- if (!userTokenAccountInfo) {
2554
- const [, createUserTokenAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2555
- this.owner,
2556
- this.reserve.getLiquidityMint(),
2557
- this.owner,
2558
- this.reserve.getLiquidityTokenProgram(),
2559
- this.userTokenAccountAddress
2560
- );
2561
- this.preTxnIxs.push(createUserTokenAccountIx);
2562
- this.preTxnIxsLabels.push(`CreateUserAta[${this.userTokenAccountAddress.toBase58()}]`);
2563
- }
2537
+ const [, createUserTokenAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2538
+ this.owner,
2539
+ this.reserve.getLiquidityMint(),
2540
+ this.owner,
2541
+ this.reserve.getLiquidityTokenProgram(),
2542
+ this.userTokenAccountAddress
2543
+ );
2544
+ this.preTxnIxs.push(createUserTokenAccountIx);
2545
+ this.preTxnIxsLabels.push(`CreateUserAta[${this.userTokenAccountAddress.toBase58()}]`);
2564
2546
  }
2565
2547
  if (action === 'mint') {
2566
- const userCollateralAccountInfo = await this.kaminoMarket
2567
- .getConnection()
2568
- .getAccountInfo(this.userCollateralAccountAddress);
2569
-
2570
- if (!userCollateralAccountInfo) {
2571
- const collateralMintPubkey = this.reserve.getCTokenMint();
2572
- const [, createUserCollateralAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2573
- this.owner,
2574
- collateralMintPubkey,
2575
- this.owner,
2576
- TOKEN_PROGRAM_ID,
2577
- this.userCollateralAccountAddress
2578
- );
2548
+ const collateralMintPubkey = this.reserve.getCTokenMint();
2549
+ const [, createUserCollateralAccountIx] = createAssociatedTokenAccountIdempotentInstruction(
2550
+ this.owner,
2551
+ collateralMintPubkey,
2552
+ this.owner,
2553
+ TOKEN_PROGRAM_ID,
2554
+ this.userCollateralAccountAddress
2555
+ );
2579
2556
 
2580
- if (this.positions === POSITION_LIMIT && this.mint.equals(WRAPPED_SOL_MINT)) {
2581
- this.preTxnIxs.push(createUserCollateralAccountIx);
2582
- this.preTxnIxsLabels.push(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2583
- } else {
2584
- this.setupIxs.unshift(createUserCollateralAccountIx);
2585
- this.setupIxsLabels.unshift(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2586
- }
2557
+ if (this.positions === POSITION_LIMIT && this.mint.equals(WRAPPED_SOL_MINT)) {
2558
+ this.preTxnIxs.push(createUserCollateralAccountIx);
2559
+ this.preTxnIxsLabels.push(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2560
+ } else {
2561
+ this.setupIxs.unshift(createUserCollateralAccountIx);
2562
+ this.setupIxsLabels.unshift(`CreateCollateralUserAta[${this.userCollateralAccountAddress.toString()}]`);
2587
2563
  }
2588
2564
  }
2589
2565
  }
@@ -2661,7 +2637,7 @@ export class KaminoAction {
2661
2637
  TOKEN_PROGRAM_ID
2662
2638
  );
2663
2639
 
2664
- const syncIx = syncNative(userTokenAccountAddress);
2640
+ const syncIx = createSyncNativeInstruction(userTokenAccountAddress);
2665
2641
  if (userWSOLAccountInfo) {
2666
2642
  if (sendAction) {
2667
2643
  preIxs.push(syncIx);
@@ -2801,12 +2777,12 @@ export class KaminoAction {
2801
2777
  throw new Error(`Reserve ${mint} not found in market ${kaminoMarket.getAddress().toBase58()}`);
2802
2778
  }
2803
2779
 
2804
- const { atas, createAtasIxns } = await getAtasWithCreateIxnsIfMissing(
2805
- kaminoMarket.getConnection(),
2806
- owner,
2807
- [reserve.getLiquidityMint()],
2808
- [reserve.getLiquidityTokenProgram()]
2809
- );
2780
+ const { atas, createAtaIxs } = await getAtasWithCreateIxnsIfMissing(kaminoMarket.getConnection(), owner, [
2781
+ {
2782
+ mint: reserve.getLiquidityMint(),
2783
+ tokenProgram: reserve.getLiquidityTokenProgram(),
2784
+ },
2785
+ ]);
2810
2786
 
2811
2787
  const userTokenAccountAddress = atas[0];
2812
2788
 
@@ -2830,7 +2806,7 @@ export class KaminoAction {
2830
2806
  undefined,
2831
2807
  undefined
2832
2808
  ),
2833
- createAtasIxns,
2809
+ createAtaIxs,
2834
2810
  };
2835
2811
  }
2836
2812
 
@@ -2867,7 +2843,7 @@ export class KaminoAction {
2867
2843
  owner: PublicKey,
2868
2844
  kaminoObligation: KaminoObligation | null,
2869
2845
  referrer: PublicKey
2870
- ) {
2846
+ ): Promise<PublicKey> {
2871
2847
  let referrerKey = referrer;
2872
2848
  if (!referrer || referrer.equals(PublicKey.default)) {
2873
2849
  if (kaminoObligation === null) {
@@ -2881,4 +2857,22 @@ export class KaminoAction {
2881
2857
  }
2882
2858
  return referrerKey;
2883
2859
  }
2860
+
2861
+ public static actionToIxs(action: KaminoAction): Array<TransactionInstruction> {
2862
+ const ixs: TransactionInstruction[] = [...action.setupIxs];
2863
+ ixs.push(...KaminoAction.actionToLendingIxs(action));
2864
+ ixs.push(...action.cleanupIxs);
2865
+ return ixs;
2866
+ }
2867
+
2868
+ public static actionToLendingIxs(action: KaminoAction): Array<TransactionInstruction> {
2869
+ const ixs: TransactionInstruction[] = [];
2870
+ for (let i = 0; i < action.lendingIxs.length; i++) {
2871
+ ixs.push(action.lendingIxs[i]);
2872
+ if (i !== action.lendingIxs.length - 1) {
2873
+ ixs.push(...action.inBetweenIxs);
2874
+ }
2875
+ }
2876
+ return ixs;
2877
+ }
2884
2878
  }
@@ -305,6 +305,15 @@ export class KaminoObligation {
305
305
  return this.refreshedStats.netAccountValue;
306
306
  }
307
307
 
308
+ /**
309
+ * Get the loan to value and liquidation loan to value for a collateral token reserve as ratios, accounting for the obligation elevation group if it is active
310
+ * @param market
311
+ * @param reserve
312
+ */
313
+ public getLtvForReserve(market: KaminoMarket, reserve: KaminoReserve): { maxLtv: Decimal; liquidationLtv: Decimal } {
314
+ return KaminoObligation.getLtvForReserve(market, reserve, this.state.elevationGroup);
315
+ }
316
+
308
317
  /**
309
318
  * @returns the potential elevation groups the obligation qualifies for
310
319
  */
@@ -807,12 +816,12 @@ export class KaminoObligation {
807
816
  return borrowLimit.div(userTotalDeposit);
808
817
  }
809
818
 
810
- /*
811
- How much of a given token can a user borrow extra given an elevation group,
819
+ /*
820
+ How much of a given token can a user borrow extra given an elevation group,
812
821
  regardless of caps and liquidity or assuming infinite liquidity and infinite caps,
813
822
  until it hits max LTV.
814
823
 
815
- This is purely a function about the borrow power of an obligation,
824
+ This is purely a function about the borrow power of an obligation,
816
825
  not a reserve-specific, caps-specific, liquidity-specific function.
817
826
 
818
827
  * @param market - The KaminoMarket instance.
@@ -890,7 +899,7 @@ export class KaminoObligation {
890
899
  return Decimal.max(new Decimal(0), maxBorrowAmount);
891
900
  }
892
901
 
893
- /*
902
+ /*
894
903
  How much of a given token can a user borrow extra given an elevation group,
895
904
  and a specific reserve, until it hits max LTV and given available liquidity and caps.
896
905
 
@@ -924,7 +933,7 @@ export class KaminoObligation {
924
933
  }
925
934
  }
926
935
 
927
- /*
936
+ /*
928
937
  Returns true if the loan is eligible for the elevation group, including for the default one.
929
938
  * @param market - The KaminoMarket object representing the market.
930
939
  * @param slot - The slot number of the loan.
@@ -979,7 +988,7 @@ export class KaminoObligation {
979
988
  return isEligibleBasedOnLtv;
980
989
  }
981
990
 
982
- /*
991
+ /*
983
992
  Returns all elevation groups for a given obligation, except the default one
984
993
  * @param market - The KaminoMarket instance.
985
994
  * @returns An array of ElevationGroupDescription objects representing the elevation groups for the obligation.
@@ -20,7 +20,7 @@ import {
20
20
  TokenOracleData,
21
21
  U64_MAX,
22
22
  } from '../utils';
23
- import { ReserveDataType, ReserveFarmInfo, ReserveRewardYield, ReserveStatus } from './shared';
23
+ import { FeeCalculation, Fees, ReserveDataType, ReserveFarmInfo, ReserveRewardYield, ReserveStatus } from './shared';
24
24
  import { Reserve, ReserveFields } from '../idl_codegen/accounts';
25
25
  import { BorrowRateCurve, CurvePointFields, ReserveConfig, UpdateConfigMode } from '../idl_codegen/types';
26
26
  import { calculateAPYFromAPR, getBorrowRate, lamportsToNumberDecimal, parseTokenSymbol, positiveOrZero } from './utils';
@@ -642,6 +642,53 @@ export class KaminoReserve {
642
642
  return this.state.collateral.mintPubkey;
643
643
  }
644
644
 
645
+ calculateFees(
646
+ amountLamports: Decimal,
647
+ borrowFeeRate: Decimal,
648
+ feeCalculation: FeeCalculation,
649
+ referralFeeBps: number,
650
+ hasReferrer: boolean
651
+ ): Fees {
652
+ const referralFeeRate = new Decimal(referralFeeBps).div(ONE_HUNDRED_PCT_IN_BPS);
653
+ if (borrowFeeRate.gt('0') && amountLamports.gt('0')) {
654
+ const needToAssessReferralFee = referralFeeRate.gt('0') && hasReferrer;
655
+ const minimumFee = new Decimal('1'); // 1 token to market owner, nothing to referrer
656
+
657
+ let borrowFeeAmount: Decimal;
658
+ if (feeCalculation === FeeCalculation.Exclusive) {
659
+ borrowFeeAmount = amountLamports.mul(borrowFeeRate);
660
+ } else {
661
+ const borrowFeeFactor = borrowFeeRate.div(borrowFeeRate.add('1'));
662
+ borrowFeeAmount = amountLamports.mul(borrowFeeFactor);
663
+ }
664
+ const borrowFee = Decimal.max(borrowFeeAmount, minimumFee);
665
+ if (borrowFee.gte(amountLamports)) {
666
+ throw Error('Borrow amount is too small to receive liquidity after fees');
667
+ }
668
+ const referralFee = needToAssessReferralFee
669
+ ? referralFeeRate.eq(1)
670
+ ? borrowFee
671
+ : borrowFee.mul(referralFeeRate).floor()
672
+ : new Decimal(0);
673
+
674
+ const protocolFee = borrowFee.sub(referralFee);
675
+
676
+ return { protocolFees: protocolFee, referrerFees: referralFee };
677
+ } else {
678
+ return { protocolFees: new Decimal(0), referrerFees: new Decimal(0) };
679
+ }
680
+ }
681
+
682
+ calculateFlashLoanFees(flashLoanAmountLamports: Decimal, referralFeeBps: number, hasReferrer: boolean): Fees {
683
+ return this.calculateFees(
684
+ flashLoanAmountLamports,
685
+ this.getFlashLoanFee(),
686
+ FeeCalculation.Exclusive,
687
+ referralFeeBps,
688
+ hasReferrer
689
+ );
690
+ }
691
+
645
692
  setBuffer(buffer: AccountInfo<Buffer> | null) {
646
693
  this.buffer = buffer;
647
694
  }
@@ -55,3 +55,13 @@ export type ReserveFarmInfo = {
55
55
  fetched: boolean;
56
56
  farmStates: FarmState[];
57
57
  };
58
+
59
+ export enum FeeCalculation {
60
+ Inclusive = 'Inclusive',
61
+ Exclusive = 'Exclusive',
62
+ }
63
+
64
+ export type Fees = {
65
+ protocolFees: Decimal;
66
+ referrerFees: Decimal;
67
+ };
@@ -185,11 +185,16 @@ export class KaminoVaultClient {
185
185
  const createAtasIxns: TransactionInstruction[] = [];
186
186
  const closeAtasIxns: TransactionInstruction[] = [];
187
187
  if (vaultState.tokenMint.equals(WRAPPED_SOL_MINT)) {
188
- const {
189
- atas: wsolAta,
190
- createAtasIxns: createWsolAtaIxns,
191
- closeAtasIxns: closeWsolAtaIxns,
192
- } = await getAtasWithCreateIxnsIfMissing(this._connection, user, [WRAPPED_SOL_MINT], [TOKEN_PROGRAM_ID]);
188
+ const { atas: wsolAta, createAtaIxs: createWsolAtaIxns } = await getAtasWithCreateIxnsIfMissing(
189
+ this._connection,
190
+ user,
191
+ [
192
+ {
193
+ mint: WRAPPED_SOL_MINT,
194
+ tokenProgram: TOKEN_PROGRAM_ID,
195
+ },
196
+ ]
197
+ );
193
198
  createAtasIxns.push(...createWsolAtaIxns);
194
199
  const depositWsolixn = getDepositWsolIxns(
195
200
  user,
@@ -197,15 +202,14 @@ export class KaminoVaultClient {
197
202
  numberToLamportsDecimal(tokenAmount, vaultState.tokenMintDecimals.toNumber()).ceil()
198
203
  );
199
204
  createAtasIxns.push(...depositWsolixn);
200
- closeAtasIxns.push(...closeWsolAtaIxns);
201
205
  }
202
206
 
203
- const { atas, createAtasIxns: createSharesAtaIxns } = await getAtasWithCreateIxnsIfMissing(
204
- this._connection,
205
- user,
206
- [vaultState.sharesMint],
207
- [TOKEN_PROGRAM_ID]
208
- );
207
+ const { atas, createAtaIxs: createSharesAtaIxns } = await getAtasWithCreateIxnsIfMissing(this._connection, user, [
208
+ {
209
+ mint: vaultState.sharesMint,
210
+ tokenProgram: TOKEN_PROGRAM_ID,
211
+ },
212
+ ]);
209
213
  createAtasIxns.push(...createSharesAtaIxns);
210
214
 
211
215
  const userSharesAta = atas[0];
@@ -256,12 +260,12 @@ export class KaminoVaultClient {
256
260
  const vaultState = await vault.getState(this._connection);
257
261
 
258
262
  const userSharesAta = getAssociatedTokenAddress(vaultState.sharesMint, user);
259
- const { atas, createAtasIxns } = await getAtasWithCreateIxnsIfMissing(
260
- this._connection,
261
- user,
262
- [vaultState.tokenMint],
263
- [TOKEN_PROGRAM_ID]
264
- );
263
+ const { atas, createAtaIxs } = await getAtasWithCreateIxnsIfMissing(this._connection, user, [
264
+ {
265
+ mint: vaultState.tokenMint,
266
+ tokenProgram: TOKEN_PROGRAM_ID,
267
+ },
268
+ ]);
265
269
  const userTokenAta = atas[0];
266
270
 
267
271
  const tokensToWithdraw = shareAmount.div(await this.getTokensPerShareSingleVault(vault, slot));
@@ -329,7 +333,7 @@ export class KaminoVaultClient {
329
333
  })
330
334
  );
331
335
 
332
- return [...createAtasIxns, ...withdrawIxns];
336
+ return [...createAtaIxs, ...withdrawIxns];
333
337
  }
334
338
 
335
339
  /**
@@ -1,7 +1,104 @@
1
1
  import Decimal from 'decimal.js';
2
- import { KaminoMarket, KaminoObligation } from '../classes';
2
+ import { KaminoMarket, KaminoObligation, KaminoReserve, numberToLamportsDecimal } from '../classes';
3
3
  import { PublicKey } from '@solana/web3.js';
4
4
 
5
+ export function calcRepayAmountWithSlippage(
6
+ kaminoMarket: KaminoMarket,
7
+ debtReserve: KaminoReserve,
8
+ currentSlot: number,
9
+ obligation: KaminoObligation,
10
+ amount: Decimal,
11
+ referrer: PublicKey
12
+ ): {
13
+ repayAmount: Decimal;
14
+ repayAmountLamports: Decimal;
15
+ flashRepayAmountLamports: Decimal;
16
+ } {
17
+ const irSlippageBpsForDebt = obligation
18
+ .estimateObligationInterestRate(
19
+ kaminoMarket,
20
+ debtReserve,
21
+ obligation.state.borrows.find((borrow) => borrow.borrowReserve.equals(debtReserve.address))!,
22
+ currentSlot
23
+ )
24
+ .toDecimalPlaces(debtReserve.state.liquidity.mintDecimals.toNumber(), Decimal.ROUND_CEIL);
25
+ // add 0.1% to irSlippageBpsForDebt because we don't want to estimate slightly less than SC and end up not reapying enough
26
+ const repayAmount = amount
27
+ .mul(irSlippageBpsForDebt.mul(new Decimal('1.001')))
28
+ .toDecimalPlaces(debtReserve.state.liquidity.mintDecimals.toNumber(), Decimal.ROUND_CEIL);
29
+ const repayAmountLamports = numberToLamportsDecimal(repayAmount, debtReserve.stats.decimals);
30
+
31
+ const { flashRepayAmountLamports } = calcFlashRepayAmount({
32
+ reserve: debtReserve,
33
+ referralFeeBps: kaminoMarket.state.referralFeeBps,
34
+ hasReferral: !referrer.equals(PublicKey.default),
35
+ flashBorrowAmountLamports: repayAmountLamports,
36
+ });
37
+ return { repayAmount, repayAmountLamports, flashRepayAmountLamports };
38
+ }
39
+
40
+ export const calcFlashRepayAmount = (props: {
41
+ reserve: KaminoReserve;
42
+ referralFeeBps: number;
43
+ hasReferral: boolean;
44
+ flashBorrowAmountLamports: Decimal;
45
+ }): {
46
+ flashRepayAmountLamports: Decimal;
47
+ } => {
48
+ const { reserve, referralFeeBps, hasReferral, flashBorrowAmountLamports } = props;
49
+ const { referrerFees, protocolFees } = reserve.calculateFlashLoanFees(
50
+ flashBorrowAmountLamports,
51
+ referralFeeBps,
52
+ hasReferral
53
+ );
54
+ const flashRepayAmountLamports = flashBorrowAmountLamports.add(referrerFees).add(protocolFees);
55
+
56
+ return {
57
+ flashRepayAmountLamports,
58
+ };
59
+ };
60
+
61
+ export function calcMaxWithdrawCollateral(
62
+ kaminoMarket: KaminoMarket,
63
+ collReserveAddr: PublicKey,
64
+ debtReserveAddr: PublicKey,
65
+ obligation: KaminoObligation,
66
+ repayAmountLamports: Decimal
67
+ ) {
68
+ const collReserve = kaminoMarket.getReserveByAddress(collReserveAddr)!;
69
+ const debtReserve = kaminoMarket.getReserveByAddress(debtReserveAddr)!;
70
+
71
+ const debtOraclePx = debtReserve.getOracleMarketPrice();
72
+ const collOraclePx = collReserve.getOracleMarketPrice();
73
+ const { maxLtv: collMaxLtv } = obligation.getLtvForReserve(kaminoMarket, collReserve);
74
+ const debtBorrowFactor = debtReserve.getBorrowFactor();
75
+
76
+ const debtPosition = obligation.getBorrowByReserve(debtReserve.address)!;
77
+ const collPosition = obligation.getDepositByReserve(collReserve.address)!;
78
+ const initialCollValue = collPosition.amount.floor().div(collReserve.getMintFactor()).mul(collOraclePx);
79
+ const remainingDebtAmountLamports = debtPosition.amount.sub(repayAmountLamports);
80
+ const remainingDebtBfWeightedValue = remainingDebtAmountLamports.ceil().div(debtReserve.getMintFactor()).mul(debtBorrowFactor).mul(debtOraclePx);
81
+
82
+ let isClosingPosition = false;
83
+ if (remainingDebtAmountLamports.lte(new Decimal(0)) && obligation.getBorrows().length === 1) {
84
+ isClosingPosition = true;
85
+ }
86
+ const numerator = initialCollValue.mul(collMaxLtv).sub(remainingDebtBfWeightedValue);
87
+ const denominator = collOraclePx.mul(collMaxLtv);
88
+ const maxCollWithdrawAmount = numerator.div(denominator);
89
+ const maxCollateralWithdrawalAmountLamports = maxCollWithdrawAmount.mul(collReserve.getMintFactor()).floor();
90
+
91
+ let withdrawableCollLamports: Decimal;
92
+ if (isClosingPosition) {
93
+ // sanity check: we have extra collateral to swap, but we want to ensure we don't quote for way more than needed and get a bad px
94
+ const maxSwapCollLamportsWithBuffer = maxCollateralWithdrawalAmountLamports.mul('1.1');
95
+ withdrawableCollLamports = Decimal.min(maxSwapCollLamportsWithBuffer, collPosition.amount).floor();
96
+ } else {
97
+ withdrawableCollLamports = Decimal.max(new Decimal(0), maxCollateralWithdrawalAmountLamports);
98
+ }
99
+ return { isClosingPosition, withdrawableCollLamports };
100
+ }
101
+
5
102
  export function estimateDebtRepaymentWithColl(props: {
6
103
  collAmount: Decimal; // in decimals
7
104
  priceDebtToColl: Decimal;