@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.
- package/README.md +2 -2
- package/dist/classes/action.d.ts +3 -1
- package/dist/classes/action.d.ts.map +1 -1
- package/dist/classes/action.js +62 -61
- package/dist/classes/action.js.map +1 -1
- package/dist/classes/obligation.d.ts +9 -0
- package/dist/classes/obligation.d.ts.map +1 -1
- package/dist/classes/obligation.js +8 -0
- package/dist/classes/obligation.js.map +1 -1
- package/dist/classes/reserve.d.ts +3 -1
- package/dist/classes/reserve.d.ts.map +1 -1
- package/dist/classes/reserve.js +32 -0
- package/dist/classes/reserve.js.map +1 -1
- package/dist/classes/shared.d.ts +8 -0
- package/dist/classes/shared.d.ts.map +1 -1
- package/dist/classes/shared.js +6 -1
- package/dist/classes/shared.js.map +1 -1
- package/dist/classes/vault.d.ts.map +1 -1
- package/dist/classes/vault.js +19 -5
- package/dist/classes/vault.js.map +1 -1
- package/dist/lending_operations/repay_with_collateral_calcs.d.ts +18 -1
- package/dist/lending_operations/repay_with_collateral_calcs.d.ts.map +1 -1
- package/dist/lending_operations/repay_with_collateral_calcs.js +62 -0
- package/dist/lending_operations/repay_with_collateral_calcs.js.map +1 -1
- package/dist/lending_operations/repay_with_collateral_operations.d.ts +27 -43
- package/dist/lending_operations/repay_with_collateral_operations.d.ts.map +1 -1
- package/dist/lending_operations/repay_with_collateral_operations.js +99 -152
- package/dist/lending_operations/repay_with_collateral_operations.js.map +1 -1
- package/dist/leverage/operations.d.ts +14 -3
- package/dist/leverage/operations.d.ts.map +1 -1
- package/dist/leverage/operations.js +184 -128
- package/dist/leverage/operations.js.map +1 -1
- package/dist/utils/ata.d.ts +12 -3
- package/dist/utils/ata.d.ts.map +1 -1
- package/dist/utils/ata.js +26 -39
- package/dist/utils/ata.js.map +1 -1
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +0 -2
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/instruction.d.ts +1 -0
- package/dist/utils/instruction.d.ts.map +1 -1
- package/dist/utils/instruction.js +18 -0
- package/dist/utils/instruction.js.map +1 -1
- package/package.json +3 -3
- package/src/classes/action.ts +92 -98
- package/src/classes/obligation.ts +15 -6
- package/src/classes/reserve.ts +48 -1
- package/src/classes/shared.ts +10 -0
- package/src/classes/vault.ts +23 -19
- package/src/lending_operations/repay_with_collateral_calcs.ts +98 -1
- package/src/lending_operations/repay_with_collateral_operations.ts +233 -253
- package/src/leverage/operations.ts +227 -140
- package/src/utils/ata.ts +33 -56
- package/src/utils/index.ts +0 -2
- package/src/utils/instruction.ts +22 -0
- package/dist/utils/layout.d.ts +0 -14
- package/dist/utils/layout.d.ts.map +0 -1
- package/dist/utils/layout.js +0 -123
- package/dist/utils/layout.js.map +0 -1
- package/dist/utils/syncNative.d.ts +0 -11
- package/dist/utils/syncNative.d.ts.map +0 -1
- package/dist/utils/syncNative.js +0 -45
- package/dist/utils/syncNative.js.map +0 -1
- package/src/global.d.ts +0 -1
- package/src/utils/layout.ts +0 -118
- package/src/utils/syncNative.ts +0 -22
package/src/classes/action.ts
CHANGED
|
@@ -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,
|
|
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(...
|
|
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
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
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
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
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
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
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
|
|
2494
|
-
.
|
|
2495
|
-
.
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
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
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
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
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
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
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
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
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
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 =
|
|
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,
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
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
|
-
|
|
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.
|
package/src/classes/reserve.ts
CHANGED
|
@@ -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
|
}
|
package/src/classes/shared.ts
CHANGED
|
@@ -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
|
+
};
|
package/src/classes/vault.ts
CHANGED
|
@@ -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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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 [...
|
|
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;
|