@kamino-finance/klend-sdk 5.12.8 → 5.13.1
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/dist/classes/action.d.ts +46 -22
- package/dist/classes/action.d.ts.map +1 -1
- package/dist/classes/action.js +60 -37
- package/dist/classes/action.js.map +1 -1
- package/dist/classes/fraction.d.ts +2 -0
- package/dist/classes/fraction.d.ts.map +1 -1
- package/dist/classes/fraction.js +6 -0
- package/dist/classes/fraction.js.map +1 -1
- package/dist/classes/manager.d.ts.map +1 -1
- package/dist/classes/manager.js.map +1 -1
- package/dist/classes/market.d.ts +10 -0
- package/dist/classes/market.d.ts.map +1 -1
- package/dist/classes/market.js +17 -10
- package/dist/classes/market.js.map +1 -1
- package/dist/classes/obligation.d.ts +2 -2
- package/dist/classes/obligation.d.ts.map +1 -1
- package/dist/classes/obligation.js +2 -7
- package/dist/classes/obligation.js.map +1 -1
- package/dist/classes/obligationOrder.d.ts +30 -7
- package/dist/classes/obligationOrder.d.ts.map +1 -1
- package/dist/classes/obligationOrder.js +47 -12
- package/dist/classes/obligationOrder.js.map +1 -1
- package/dist/classes/reserve.d.ts.map +1 -1
- package/dist/classes/reserve.js +1 -4
- package/dist/classes/reserve.js.map +1 -1
- package/dist/classes/utils.d.ts +0 -1
- package/dist/classes/utils.d.ts.map +1 -1
- package/dist/classes/utils.js +0 -7
- package/dist/classes/utils.js.map +1 -1
- package/dist/classes/vault.d.ts.map +1 -1
- package/dist/classes/vault.js +3 -0
- package/dist/classes/vault.js.map +1 -1
- package/dist/lending_operations/repay_with_collateral_operations.d.ts.map +1 -1
- package/dist/lending_operations/repay_with_collateral_operations.js +2 -2
- package/dist/lending_operations/repay_with_collateral_operations.js.map +1 -1
- package/dist/lending_operations/swap_collateral_operations.js +2 -4
- package/dist/lending_operations/swap_collateral_operations.js.map +1 -1
- package/dist/leverage/operations.d.ts.map +1 -1
- package/dist/leverage/operations.js +7 -14
- package/dist/leverage/operations.js.map +1 -1
- package/dist/lib.d.ts +1 -0
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +1 -0
- package/dist/lib.js.map +1 -1
- package/dist/obligation_orders/common.d.ts +62 -0
- package/dist/obligation_orders/common.d.ts.map +1 -0
- package/dist/obligation_orders/common.js +20 -0
- package/dist/obligation_orders/common.js.map +1 -0
- package/dist/obligation_orders/index.d.ts +4 -0
- package/dist/obligation_orders/index.d.ts.map +1 -0
- package/dist/obligation_orders/index.js +20 -0
- package/dist/obligation_orders/index.js.map +1 -0
- package/dist/obligation_orders/internal.d.ts +6 -0
- package/dist/obligation_orders/internal.d.ts.map +1 -0
- package/dist/obligation_orders/internal.js +72 -0
- package/dist/obligation_orders/internal.js.map +1 -0
- package/dist/obligation_orders/ltv_based.d.ts +51 -0
- package/dist/obligation_orders/ltv_based.d.ts.map +1 -0
- package/dist/obligation_orders/ltv_based.js +107 -0
- package/dist/obligation_orders/ltv_based.js.map +1 -0
- package/dist/obligation_orders/price_based.d.ts +81 -0
- package/dist/obligation_orders/price_based.d.ts.map +1 -0
- package/dist/obligation_orders/price_based.js +167 -0
- package/dist/obligation_orders/price_based.js.map +1 -0
- package/dist/utils/validations.d.ts +5 -0
- package/dist/utils/validations.d.ts.map +1 -0
- package/dist/utils/validations.js +36 -0
- package/dist/utils/validations.js.map +1 -0
- package/package.json +1 -1
- package/src/classes/action.ts +61 -52
- package/src/classes/fraction.ts +7 -0
- package/src/classes/manager.ts +1 -4
- package/src/classes/market.ts +21 -11
- package/src/classes/obligation.ts +4 -9
- package/src/classes/obligationOrder.ts +57 -20
- package/src/classes/reserve.ts +2 -5
- package/src/classes/utils.ts +0 -7
- package/src/classes/vault.ts +3 -0
- package/src/lending_operations/repay_with_collateral_operations.ts +0 -2
- package/src/lending_operations/swap_collateral_operations.ts +2 -4
- package/src/leverage/operations.ts +7 -13
- package/src/lib.ts +1 -0
- package/src/obligation_orders/common.ts +70 -0
- package/src/obligation_orders/index.ts +3 -0
- package/src/obligation_orders/internal.ts +92 -0
- package/src/obligation_orders/ltv_based.ts +143 -0
- package/src/obligation_orders/price_based.ts +256 -0
- package/src/utils/validations.ts +31 -0
package/src/classes/fraction.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import BN from 'bn.js';
|
|
2
2
|
import Decimal from 'decimal.js';
|
|
3
3
|
import { roundNearest } from './utils';
|
|
4
|
+
import { BigFractionBytes } from '../lib';
|
|
4
5
|
|
|
5
6
|
export class Fraction {
|
|
6
7
|
static MAX_SIZE_F = 128;
|
|
@@ -72,3 +73,9 @@ export class Fraction {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
export const ZERO_FRACTION = new Fraction(new BN(0));
|
|
76
|
+
|
|
77
|
+
export function bfToDecimal(x: BigFractionBytes): Decimal {
|
|
78
|
+
const bsf = x.value;
|
|
79
|
+
const accSf = bsf.reduce((acc, curr, i) => acc.add(curr.shln(i * 64)), new BN(0));
|
|
80
|
+
return new Fraction(accSf).toDecimal();
|
|
81
|
+
}
|
package/src/classes/manager.ts
CHANGED
|
@@ -629,10 +629,7 @@ export class KaminoManager {
|
|
|
629
629
|
const switchboardV2 = await SwitchboardProgram.loadMainnet(this.getConnection());
|
|
630
630
|
|
|
631
631
|
// Group reserves by market
|
|
632
|
-
const marketToReserve = new PubkeyHashMap<
|
|
633
|
-
PublicKey,
|
|
634
|
-
[PublicKey, Reserve, AccountInfo<Buffer>][]
|
|
635
|
-
>();
|
|
632
|
+
const marketToReserve = new PubkeyHashMap<PublicKey, [PublicKey, Reserve, AccountInfo<Buffer>][]>();
|
|
636
633
|
for (const [reserveAddress, reserveState, buffer] of reservePairs) {
|
|
637
634
|
const marketAddress = reserveState.lendingMarket;
|
|
638
635
|
if (!marketToReserve.has(marketAddress)) {
|
package/src/classes/market.ts
CHANGED
|
@@ -36,6 +36,7 @@ import { parseTokenSymbol, parseZeroPaddedUtf8 } from './utils';
|
|
|
36
36
|
import SwitchboardProgram from '@switchboard-xyz/sbv2-lite';
|
|
37
37
|
import { ObligationZP } from '../idl_codegen/zero_padding';
|
|
38
38
|
import { getProgramAccounts } from '../utils';
|
|
39
|
+
import { checkDefined } from '../utils/validations';
|
|
39
40
|
|
|
40
41
|
export interface ReserveRewardInfo {
|
|
41
42
|
rewardsPerSecond: Decimal; // not lamport
|
|
@@ -175,13 +176,10 @@ export class KaminoMarket {
|
|
|
175
176
|
if (elevationGroupId === 0) {
|
|
176
177
|
return null;
|
|
177
178
|
}
|
|
178
|
-
|
|
179
|
-
(candidate) => candidate.elevationGroup === elevationGroupId
|
|
179
|
+
return checkDefined(
|
|
180
|
+
this.getMarketElevationGroupDescriptions().find((candidate) => candidate.elevationGroup === elevationGroupId),
|
|
181
|
+
`${description} elevation group ${elevationGroupId} not found`
|
|
180
182
|
);
|
|
181
|
-
if (elevationGroup === undefined) {
|
|
182
|
-
throw new Error(`${description} elevation group ${elevationGroupId} not found in market ${this.getAddress()}`);
|
|
183
|
-
}
|
|
184
|
-
return elevationGroup;
|
|
185
183
|
}
|
|
186
184
|
|
|
187
185
|
getMinNetValueObligation(): Decimal {
|
|
@@ -420,6 +418,14 @@ export class KaminoMarket {
|
|
|
420
418
|
return this.reserves.get(address);
|
|
421
419
|
}
|
|
422
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Returns this market's reserve of the given address, or throws an error (including the given description) if such
|
|
423
|
+
* reserve does not exist.
|
|
424
|
+
*/
|
|
425
|
+
getExistingReserveByAddress(address: PublicKey, description: string = 'Requested'): KaminoReserve {
|
|
426
|
+
return checkDefined(this.getReserveByAddress(address), `${description} reserve ${address} not found`);
|
|
427
|
+
}
|
|
428
|
+
|
|
423
429
|
getReserveByMint(address: PublicKey): KaminoReserve | undefined {
|
|
424
430
|
for (const reserve of this.reserves.values()) {
|
|
425
431
|
if (reserve.getLiquidityMint().equals(address)) {
|
|
@@ -434,11 +440,7 @@ export class KaminoMarket {
|
|
|
434
440
|
* such reserve does not exist.
|
|
435
441
|
*/
|
|
436
442
|
getExistingReserveByMint(address: PublicKey, description: string = 'Requested'): KaminoReserve {
|
|
437
|
-
|
|
438
|
-
if (!reserve) {
|
|
439
|
-
throw new Error(`${description} reserve with mint ${address} not found in market ${this.getAddress()}`);
|
|
440
|
-
}
|
|
441
|
-
return reserve;
|
|
443
|
+
return checkDefined(this.getReserveByMint(address), `${description} reserve with mint ${address} not found`);
|
|
442
444
|
}
|
|
443
445
|
|
|
444
446
|
getReserveBySymbol(symbol: string) {
|
|
@@ -450,6 +452,14 @@ export class KaminoMarket {
|
|
|
450
452
|
return undefined;
|
|
451
453
|
}
|
|
452
454
|
|
|
455
|
+
/**
|
|
456
|
+
* Returns this market's reserve of the given symbol, or throws an error (including the given description) if
|
|
457
|
+
* such reserve does not exist.
|
|
458
|
+
*/
|
|
459
|
+
getExistingReserveBySymbol(symbol: string, description: string = 'Requested'): KaminoReserve {
|
|
460
|
+
return checkDefined(this.getReserveBySymbol(symbol), `${description} reserve with symbol ${symbol} not found`);
|
|
461
|
+
}
|
|
462
|
+
|
|
453
463
|
getReserveMintBySymbol(symbol: string) {
|
|
454
464
|
return this.getReserveBySymbol(symbol)?.getLiquidityMint();
|
|
455
465
|
}
|
|
@@ -5,7 +5,7 @@ import { KaminoReserve } from './reserve';
|
|
|
5
5
|
import { Obligation } from '../idl_codegen/accounts';
|
|
6
6
|
import { ElevationGroupDescription, KaminoMarket } from './market';
|
|
7
7
|
import BN from 'bn.js';
|
|
8
|
-
import { Fraction } from './fraction';
|
|
8
|
+
import { bfToDecimal, Fraction } from './fraction';
|
|
9
9
|
import {
|
|
10
10
|
ObligationCollateral,
|
|
11
11
|
ObligationCollateralFields,
|
|
@@ -67,7 +67,7 @@ export type ObligationStats = {
|
|
|
67
67
|
* The LTV at which the obligation becomes subject to liquidation, *suitable for UI display*.
|
|
68
68
|
*
|
|
69
69
|
* Technically, this is a ratio:
|
|
70
|
-
* - of a sum of all
|
|
70
|
+
* - of a sum of values of all deposits multiplied by reserves' liquidationLtv (i.e. `borrowLiquidationLimit`)
|
|
71
71
|
* - to a sum of values of all deposits having reserve's liquidationLtv > 0 (i.e. `userTotalLiquidatableDeposit`)
|
|
72
72
|
*
|
|
73
73
|
* Please note that this is different from the smart contract's definition of liquidation LTV (which divides by a sum
|
|
@@ -271,7 +271,7 @@ export class KaminoObligation {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
/**
|
|
274
|
-
* Returns obligation orders (including the null ones, i.e. non-active
|
|
274
|
+
* Returns obligation orders (including the null ones, i.e. non-active positions in the orders' array).
|
|
275
275
|
*/
|
|
276
276
|
getOrders(): Array<KaminoObligationOrder | null> {
|
|
277
277
|
return this.state.orders.map((order) => KaminoObligationOrder.fromState(order));
|
|
@@ -1475,12 +1475,7 @@ export class KaminoObligation {
|
|
|
1475
1475
|
* @returns Cumulative borrow rate for the specified obligation liquidity/borrow asset
|
|
1476
1476
|
*/
|
|
1477
1477
|
static getCumulativeBorrowRate(borrow: ObligationLiquidity): Decimal {
|
|
1478
|
-
|
|
1479
|
-
for (const value of borrow.cumulativeBorrowRateBsf.value.reverse()) {
|
|
1480
|
-
accSf = accSf.add(value);
|
|
1481
|
-
accSf.shrn(64);
|
|
1482
|
-
}
|
|
1483
|
-
return new Fraction(accSf).toDecimal();
|
|
1478
|
+
return bfToDecimal(borrow.cumulativeBorrowRateBsf);
|
|
1484
1479
|
}
|
|
1485
1480
|
|
|
1486
1481
|
public static getRatesForObligation(
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/* eslint-disable max-classes-per-file */
|
|
2
2
|
import { Fraction } from './fraction';
|
|
3
3
|
import { ObligationOrder } from '../idl_codegen/types';
|
|
4
|
-
import {
|
|
4
|
+
import { orThrow, roundNearest } from './utils';
|
|
5
5
|
import Decimal from 'decimal.js';
|
|
6
6
|
import BN from 'bn.js';
|
|
7
7
|
import { KaminoObligation, Position } from './obligation';
|
|
8
8
|
import { TokenAmount } from './types';
|
|
9
9
|
import { ONE_HUNDRED_PCT_IN_BPS } from '../utils';
|
|
10
|
+
import { getSingleElement } from '../utils/validations';
|
|
10
11
|
|
|
11
12
|
// Polymorphic parts of an order:
|
|
12
13
|
|
|
@@ -16,7 +17,7 @@ import { ONE_HUNDRED_PCT_IN_BPS } from '../utils';
|
|
|
16
17
|
* When a {@link KaminoObligationOrder.condition} is met by an obligation, the corresponding
|
|
17
18
|
* {@link KaminoObligationOrder.opportunity} becomes available to liquidators.
|
|
18
19
|
*/
|
|
19
|
-
interface OrderCondition {
|
|
20
|
+
export interface OrderCondition {
|
|
20
21
|
/**
|
|
21
22
|
* An abstract parameter of the condition, meaningful in context of the condition's type.
|
|
22
23
|
*/
|
|
@@ -50,7 +51,7 @@ export interface OrderOpportunity {
|
|
|
50
51
|
* A condition met when obligation's overall "User LTV" is strictly higher than the given threshold.
|
|
51
52
|
*/
|
|
52
53
|
export class UserLtvAbove implements OrderCondition {
|
|
53
|
-
|
|
54
|
+
readonly minUserLtvExclusive: Decimal;
|
|
54
55
|
|
|
55
56
|
constructor(minUserLtvExclusive: Decimal.Value) {
|
|
56
57
|
this.minUserLtvExclusive = new Decimal(minUserLtvExclusive);
|
|
@@ -72,7 +73,7 @@ export class UserLtvAbove implements OrderCondition {
|
|
|
72
73
|
* A condition met when obligation's overall "User LTV" is strictly lower than the given threshold.
|
|
73
74
|
*/
|
|
74
75
|
export class UserLtvBelow implements OrderCondition {
|
|
75
|
-
|
|
76
|
+
readonly maxUserLtvExclusive: Decimal;
|
|
76
77
|
|
|
77
78
|
constructor(maxUserLtvExclusive: Decimal.Value) {
|
|
78
79
|
this.maxUserLtvExclusive = new Decimal(maxUserLtvExclusive);
|
|
@@ -97,7 +98,7 @@ export class UserLtvBelow implements OrderCondition {
|
|
|
97
98
|
* May only be applied to single-collateral, single-debt obligations.
|
|
98
99
|
*/
|
|
99
100
|
export class DebtCollPriceRatioAbove implements OrderCondition {
|
|
100
|
-
|
|
101
|
+
readonly minDebtCollPriceRatioExclusive: Decimal;
|
|
101
102
|
|
|
102
103
|
constructor(minDebtCollPriceRatioExclusive: Decimal.Value) {
|
|
103
104
|
this.minDebtCollPriceRatioExclusive = new Decimal(minDebtCollPriceRatioExclusive);
|
|
@@ -114,8 +115,10 @@ export class DebtCollPriceRatioAbove implements OrderCondition {
|
|
|
114
115
|
this.minDebtCollPriceRatioExclusive,
|
|
115
116
|
// For single-debt-single-coll obligations, the price ratio is directly proportional
|
|
116
117
|
// to LTV - so we can calculate the "liquidation price ratio" simply by scaling the
|
|
117
|
-
// current value by the ratio of unhealthy/current
|
|
118
|
-
priceRatio
|
|
118
|
+
// current value by the ratio of unhealthy/current borrow value:
|
|
119
|
+
priceRatio
|
|
120
|
+
.mul(obligation.refreshedStats.borrowLiquidationLimit)
|
|
121
|
+
.div(obligation.refreshedStats.userTotalBorrowBorrowFactorAdjusted)
|
|
119
122
|
);
|
|
120
123
|
}
|
|
121
124
|
}
|
|
@@ -127,7 +130,7 @@ export class DebtCollPriceRatioAbove implements OrderCondition {
|
|
|
127
130
|
* May only be applied to single-collateral, single-debt obligations.
|
|
128
131
|
*/
|
|
129
132
|
export class DebtCollPriceRatioBelow implements OrderCondition {
|
|
130
|
-
|
|
133
|
+
readonly maxDebtCollPriceRatioExclusive: Decimal;
|
|
131
134
|
|
|
132
135
|
constructor(maxDebtCollPriceRatioExclusive: Decimal.Value) {
|
|
133
136
|
this.maxDebtCollPriceRatioExclusive = new Decimal(maxDebtCollPriceRatioExclusive);
|
|
@@ -150,7 +153,7 @@ export class DebtCollPriceRatioBelow implements OrderCondition {
|
|
|
150
153
|
* May only be applied to single-debt obligations.
|
|
151
154
|
*/
|
|
152
155
|
export class DeleverageDebtAmount implements OrderOpportunity {
|
|
153
|
-
|
|
156
|
+
readonly amount: Decimal;
|
|
154
157
|
|
|
155
158
|
constructor(amount: Decimal.Value) {
|
|
156
159
|
this.amount = new Decimal(amount);
|
|
@@ -161,7 +164,7 @@ export class DeleverageDebtAmount implements OrderOpportunity {
|
|
|
161
164
|
}
|
|
162
165
|
|
|
163
166
|
getMaxRepay(borrows: Array<Position>): TokenAmount {
|
|
164
|
-
const singleBorrow = getSingleElement(borrows, '
|
|
167
|
+
const singleBorrow = getSingleElement(borrows, 'borrow');
|
|
165
168
|
return {
|
|
166
169
|
mint: singleBorrow.mintAddress,
|
|
167
170
|
amount: Decimal.min(singleBorrow.amount, this.amount),
|
|
@@ -372,6 +375,15 @@ export class KaminoObligationOrder {
|
|
|
372
375
|
});
|
|
373
376
|
}
|
|
374
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Binds this order to the given slot.
|
|
380
|
+
*
|
|
381
|
+
* This is just a convenience method for easier interaction with {@link KaminoAction#buildSetObligationOrderIxn()}.
|
|
382
|
+
*/
|
|
383
|
+
atIndex(index: number): ObligationOrderAtIndex {
|
|
384
|
+
return new ObligationOrderAtIndex(index, this);
|
|
385
|
+
}
|
|
386
|
+
|
|
375
387
|
/**
|
|
376
388
|
* Calculates the given order's actual execution bonus rate.
|
|
377
389
|
*
|
|
@@ -389,18 +401,43 @@ export class KaminoObligationOrder {
|
|
|
389
401
|
this.minExecutionBonusRate,
|
|
390
402
|
this.maxExecutionBonusRate
|
|
391
403
|
);
|
|
392
|
-
// Note: instead of the existing `obligation.noBfLoanToValue()`, here we use a formula consistent with the
|
|
393
|
-
// `obligation.refreshedStats.loanToValue` (that we use in other parts of obligation orders' SDK logic):
|
|
394
|
-
const userNoBfLtv = obligation.refreshedStats.userTotalBorrow.div(
|
|
395
|
-
obligation.refreshedStats.userTotalCollateralDeposit
|
|
396
|
-
);
|
|
397
404
|
// In order to ensure that LTV improves on order execution, we apply the same heuristic formula as for the regular
|
|
398
|
-
// liquidations
|
|
399
|
-
|
|
405
|
+
// liquidations. Please note that we deliberately use the `obligation.noBfLoanToValue()`, which is consistent with
|
|
406
|
+
// the smart contract's calculation:
|
|
407
|
+
const diffToBadDebt = new Decimal(1).sub(obligation.noBfLoanToValue());
|
|
400
408
|
return Decimal.min(interpolatedBonusRate, diffToBadDebt);
|
|
401
409
|
}
|
|
402
410
|
}
|
|
403
411
|
|
|
412
|
+
/**
|
|
413
|
+
* A single slot within {@link Obligation.orders} (which may contain an order or not).
|
|
414
|
+
*
|
|
415
|
+
* This is used as an argument to {@link KaminoAction.buildSetObligationOrderIxn()} to easily set or cancel an order.
|
|
416
|
+
*/
|
|
417
|
+
export class ObligationOrderAtIndex {
|
|
418
|
+
index: number;
|
|
419
|
+
order: KaminoObligationOrder | null;
|
|
420
|
+
|
|
421
|
+
constructor(index: number, order: KaminoObligationOrder | null) {
|
|
422
|
+
this.index = index;
|
|
423
|
+
this.order = order;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Creates an empty slot representation (suitable for cancelling an order).
|
|
428
|
+
*/
|
|
429
|
+
static empty(index: number): ObligationOrderAtIndex {
|
|
430
|
+
return new ObligationOrderAtIndex(index, null);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Returns the on-chain state of the order (potentially a zeroed account data, if the order is not set).
|
|
435
|
+
*/
|
|
436
|
+
orderState(): ObligationOrder {
|
|
437
|
+
return this.order !== null ? this.order.toState() : KaminoObligationOrder.NULL_STATE;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
404
441
|
/**
|
|
405
442
|
* Numeric details on why an order's condition was met.
|
|
406
443
|
*/
|
|
@@ -449,7 +486,7 @@ export type AvailableOrderExecution = {
|
|
|
449
486
|
// Internal calculation functions:
|
|
450
487
|
|
|
451
488
|
function tokenAmountToValue(tokenAmount: TokenAmount, position: Position): Decimal {
|
|
452
|
-
if (tokenAmount.mint
|
|
489
|
+
if (!tokenAmount.mint.equals(position.mintAddress)) {
|
|
453
490
|
throw new Error(`Value of token amount ${tokenAmount} cannot be computed using data from ${position}`);
|
|
454
491
|
}
|
|
455
492
|
return tokenAmount.amount.mul(position.marketValueRefreshed).div(position.amount);
|
|
@@ -496,8 +533,8 @@ function evaluateTakeProfit(currentValue: Decimal, conditionThreshold: Decimal):
|
|
|
496
533
|
}
|
|
497
534
|
|
|
498
535
|
function calculateDebtCollPriceRatio(obligation: KaminoObligation): Decimal {
|
|
499
|
-
const singleBorrow = getSingleElement(obligation.getBorrows(), '
|
|
500
|
-
const singleDeposit = getSingleElement(obligation.getDeposits(), '
|
|
536
|
+
const singleBorrow = getSingleElement(obligation.getBorrows(), 'borrow');
|
|
537
|
+
const singleDeposit = getSingleElement(obligation.getDeposits(), 'deposit');
|
|
501
538
|
return calculateTokenPrice(singleBorrow).div(calculateTokenPrice(singleDeposit));
|
|
502
539
|
}
|
|
503
540
|
|
package/src/classes/reserve.ts
CHANGED
|
@@ -39,8 +39,7 @@ import {
|
|
|
39
39
|
positiveOrZero,
|
|
40
40
|
sameLengthArrayEquals,
|
|
41
41
|
} from './utils';
|
|
42
|
-
import { Fraction } from './fraction';
|
|
43
|
-
import BN from 'bn.js';
|
|
42
|
+
import { bfToDecimal, Fraction } from './fraction';
|
|
44
43
|
import { ActionType } from './action';
|
|
45
44
|
import { BorrowCapsAndCounters, ElevationGroupDescription, KaminoMarket } from './market';
|
|
46
45
|
import {
|
|
@@ -212,9 +211,7 @@ export class KaminoReserve {
|
|
|
212
211
|
* @returns the stale cumulative borrow rate of the reserve from the last refresh
|
|
213
212
|
*/
|
|
214
213
|
getCumulativeBorrowRate(): Decimal {
|
|
215
|
-
|
|
216
|
-
const accSf = cumulativeBorrowRateBsf.reduce((prev, curr, i) => prev.add(curr.shln(i * 64)), new BN(0));
|
|
217
|
-
return new Fraction(accSf).toDecimal();
|
|
214
|
+
return bfToDecimal(this.state.liquidity.cumulativeBorrowRateBsf);
|
|
218
215
|
}
|
|
219
216
|
|
|
220
217
|
/**
|
package/src/classes/utils.ts
CHANGED
|
@@ -336,13 +336,6 @@ export function orThrow(message: string): never {
|
|
|
336
336
|
throw new Error(message);
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
export function getSingleElement<T>(array: T[], message: string): T {
|
|
340
|
-
if (array.length !== 1) {
|
|
341
|
-
throw new Error(`${message} (found ${array.length})`);
|
|
342
|
-
}
|
|
343
|
-
return array[0];
|
|
344
|
-
}
|
|
345
|
-
|
|
346
339
|
/**
|
|
347
340
|
* Returns an integer {@link Decimal} nearest to the given one.
|
|
348
341
|
*
|
package/src/classes/vault.ts
CHANGED
|
@@ -1616,6 +1616,9 @@ export class KaminoVaultClient {
|
|
|
1616
1616
|
totalAllocation = totalAllocation.add(allocation.targetWeight);
|
|
1617
1617
|
});
|
|
1618
1618
|
const expectedHoldingsDistribution = new PubkeyHashMap<PublicKey, Decimal>();
|
|
1619
|
+
allReserves.forEach((reserve) => {
|
|
1620
|
+
expectedHoldingsDistribution.set(reserve, new Decimal(0));
|
|
1621
|
+
});
|
|
1619
1622
|
|
|
1620
1623
|
let totalLeftToInvest = holdings.totalAUMIncludingFees.sub(holdings.pendingFees);
|
|
1621
1624
|
let currentAllocationSum = totalAllocation;
|
|
@@ -346,7 +346,6 @@ async function buildRepayWithCollateralIxs(
|
|
|
346
346
|
false,
|
|
347
347
|
requestElevationGroup,
|
|
348
348
|
undefined,
|
|
349
|
-
undefined,
|
|
350
349
|
referrer
|
|
351
350
|
);
|
|
352
351
|
} else {
|
|
@@ -364,7 +363,6 @@ async function buildRepayWithCollateralIxs(
|
|
|
364
363
|
false,
|
|
365
364
|
requestElevationGroup,
|
|
366
365
|
undefined,
|
|
367
|
-
undefined,
|
|
368
366
|
referrer
|
|
369
367
|
);
|
|
370
368
|
}
|
|
@@ -407,8 +407,7 @@ async function getDepositTargetCollIxns(
|
|
|
407
407
|
0, // no extra compute budget
|
|
408
408
|
false, // we do not need ATA ixns here (we construct and close them ourselves)
|
|
409
409
|
removesElevationGroup, // we may need to (temporarily) remove the elevation group; the same or a different one will be set on withdraw, if requested
|
|
410
|
-
|
|
411
|
-
false, // we do not need to create a lookup table, dealing with an existing obligation
|
|
410
|
+
{ skipInitialization: true, skipLutCreation: true }, // we are dealing with an existing obligation, no need to create user metadata
|
|
412
411
|
context.referrer,
|
|
413
412
|
context.currentSlot,
|
|
414
413
|
removesElevationGroup ? 0 : undefined // only applicable when removing the group
|
|
@@ -457,8 +456,7 @@ async function getWithdrawSourceCollIxns(
|
|
|
457
456
|
0, // no extra compute budget
|
|
458
457
|
false, // we do not need ATA ixns here (we construct and close them ourselves)
|
|
459
458
|
requestedElevationGroup !== undefined, // the `elevationGroupIdToRequestAfterWithdraw()` has already decided on this
|
|
460
|
-
|
|
461
|
-
false, // we do not need to create a lookup table, dealing with an existing obligation
|
|
459
|
+
{ skipInitialization: true, skipLutCreation: true }, // we are dealing with an existing obligation, no need to create user metadata
|
|
462
460
|
context.referrer,
|
|
463
461
|
context.currentSlot,
|
|
464
462
|
requestedElevationGroup,
|
|
@@ -552,8 +552,7 @@ async function buildDepositWithLeverageIxns(
|
|
|
552
552
|
0,
|
|
553
553
|
false,
|
|
554
554
|
elevationGroupOverride === 0 ? false : true, // emode
|
|
555
|
-
|
|
556
|
-
false, // to be checked and created in a setup tx in the UI
|
|
555
|
+
{ skipInitialization: true, skipLutCreation: true }, // to be checked and created in a setup tx in the UI
|
|
557
556
|
referrer,
|
|
558
557
|
currentSlot
|
|
559
558
|
);
|
|
@@ -986,9 +985,8 @@ export async function buildWithdrawWithLeverageIxns(
|
|
|
986
985
|
undefined,
|
|
987
986
|
0,
|
|
988
987
|
false,
|
|
989
|
-
false,
|
|
990
|
-
|
|
991
|
-
isClosingPosition,
|
|
988
|
+
false,
|
|
989
|
+
{ skipInitialization: true, skipLutCreation: true }, // to be checked and created in a setup tx in the UI (won't be the case for withdraw anyway as this would be created in deposit)
|
|
992
990
|
referrer
|
|
993
991
|
);
|
|
994
992
|
|
|
@@ -1547,8 +1545,7 @@ async function buildIncreaseLeverageIxns(
|
|
|
1547
1545
|
0,
|
|
1548
1546
|
false,
|
|
1549
1547
|
false,
|
|
1550
|
-
|
|
1551
|
-
false, // to be checked and create in a setup tx in the UI (won't be the case for adjust anyway as this would be created in deposit)
|
|
1548
|
+
{ skipInitialization: true, skipLutCreation: true },
|
|
1552
1549
|
referrer,
|
|
1553
1550
|
currentSlot
|
|
1554
1551
|
);
|
|
@@ -1565,8 +1562,7 @@ async function buildIncreaseLeverageIxns(
|
|
|
1565
1562
|
0,
|
|
1566
1563
|
false,
|
|
1567
1564
|
false,
|
|
1568
|
-
|
|
1569
|
-
false, // to be checked and create in a setup tx in the UI (won't be the case for adjust anyway as this would be created in deposit)
|
|
1565
|
+
{ skipInitialization: true, skipLutCreation: true }, // to be checked and create in a setup tx in the UI (won't be the case for adjust anyway as this would be created in deposit)
|
|
1570
1566
|
referrer,
|
|
1571
1567
|
currentSlot
|
|
1572
1568
|
);
|
|
@@ -1739,8 +1735,7 @@ async function buildDecreaseLeverageIxns(
|
|
|
1739
1735
|
0,
|
|
1740
1736
|
false,
|
|
1741
1737
|
false,
|
|
1742
|
-
|
|
1743
|
-
false, // to be checked and create in a setup tx in the UI (won't be the case for adjust anyway as this would be created in deposit)
|
|
1738
|
+
{ skipInitialization: true, skipLutCreation: true }, // to be checked and create in a setup tx in the UI (won't be the case for adjust anyway as this would be created in deposit)
|
|
1744
1739
|
referrer
|
|
1745
1740
|
);
|
|
1746
1741
|
|
|
@@ -1756,8 +1751,7 @@ async function buildDecreaseLeverageIxns(
|
|
|
1756
1751
|
0,
|
|
1757
1752
|
false,
|
|
1758
1753
|
false,
|
|
1759
|
-
|
|
1760
|
-
false, // to be checked and create in a setup tx in the UI (won't be the case for adjust anyway as this would be created in deposit)
|
|
1754
|
+
{ skipInitialization: true, skipLutCreation: true }, // to be checked and create in a setup tx in the UI (won't be the case for adjust anyway as this would be created in deposit)
|
|
1761
1755
|
referrer,
|
|
1762
1756
|
currentSlot
|
|
1763
1757
|
);
|
package/src/lib.ts
CHANGED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { KaminoMarket, KaminoObligation } from '../classes';
|
|
2
|
+
import Decimal from 'decimal.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A basic context needed to interpret orders.
|
|
6
|
+
*/
|
|
7
|
+
export type OrderContext = {
|
|
8
|
+
kaminoMarket: KaminoMarket;
|
|
9
|
+
kaminoObligation: KaminoObligation;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A type of order for obligations that follow the "one optional stop-loss and one optional take-profit" convention.
|
|
14
|
+
*/
|
|
15
|
+
export enum OrderType {
|
|
16
|
+
StopLoss = 'StopLoss',
|
|
17
|
+
TakeProfit = 'TakeProfit',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A discriminator enum for {@link OrderAction};
|
|
22
|
+
*/
|
|
23
|
+
export enum OrderActionType {
|
|
24
|
+
FullRepay = 'FullRepay',
|
|
25
|
+
PartialRepay = 'PartialRepay',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* One of possible actions to take on a price-based order.
|
|
30
|
+
*/
|
|
31
|
+
export type OrderAction = FullRepay | PartialRepay;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* An action repaying entire obligation debt.
|
|
35
|
+
*/
|
|
36
|
+
export type FullRepay = {
|
|
37
|
+
type: OrderActionType.FullRepay;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* An action repaying the given amount of the debt.
|
|
42
|
+
*/
|
|
43
|
+
export type PartialRepay = {
|
|
44
|
+
type: OrderActionType.PartialRepay;
|
|
45
|
+
repayDebtAmountLamports: Decimal;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A high-level specification of an order.
|
|
50
|
+
* The trigger type `T` depends on the order's flavour (e.g. price-based or LTV-based).
|
|
51
|
+
*/
|
|
52
|
+
export type OrderSpecification<T> = {
|
|
53
|
+
/**
|
|
54
|
+
* The condition that makes the {@link action} available to be executed.
|
|
55
|
+
*/
|
|
56
|
+
trigger: T;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The action that may be executed.
|
|
60
|
+
*/
|
|
61
|
+
action: OrderAction;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The minimum and maximum bonus for an executor of the action, in bps.
|
|
65
|
+
*
|
|
66
|
+
* The minimum is paid when the order condition is "barely met", and then the bonus grows towards maximum as the
|
|
67
|
+
* condition gets exceeded more and more.
|
|
68
|
+
*/
|
|
69
|
+
executionBonusBpsRange: [number, number];
|
|
70
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import Decimal from 'decimal.js';
|
|
2
|
+
import {
|
|
3
|
+
DeleverageAllDebt,
|
|
4
|
+
DeleverageDebtAmount,
|
|
5
|
+
KaminoObligationOrder,
|
|
6
|
+
OrderCondition,
|
|
7
|
+
OrderOpportunity,
|
|
8
|
+
} from '../classes/obligationOrder';
|
|
9
|
+
import { checkThat, getSingleElement } from '../utils/validations';
|
|
10
|
+
import { ONE_HUNDRED_PCT_IN_BPS } from '../utils';
|
|
11
|
+
import { OrderAction, OrderActionType, OrderContext, OrderSpecification, OrderType } from './common';
|
|
12
|
+
|
|
13
|
+
// These methods are exported, buy only used internally within the obligation orders utils:
|
|
14
|
+
|
|
15
|
+
export function toOrderIndex(orderType: OrderType): number {
|
|
16
|
+
switch (orderType) {
|
|
17
|
+
case OrderType.StopLoss:
|
|
18
|
+
return 0;
|
|
19
|
+
case OrderType.TakeProfit:
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createConditionBasedOrder<C>(
|
|
25
|
+
context: OrderContext,
|
|
26
|
+
condition: OrderCondition,
|
|
27
|
+
specification: OrderSpecification<C>
|
|
28
|
+
): KaminoObligationOrder {
|
|
29
|
+
checkThat(condition.evaluate(context.kaminoObligation) === null, `cannot create an immediately-triggered order`);
|
|
30
|
+
const opportunity = toOrderOpportunity(context, specification.action);
|
|
31
|
+
const [minExecutionBonusRate, maxExecutionBonusRate] = toExecutionBonusRates(specification.executionBonusBpsRange);
|
|
32
|
+
return new KaminoObligationOrder(condition, opportunity, minExecutionBonusRate, maxExecutionBonusRate);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function readTriggerBasedOrder<T>(kaminoOrder: KaminoObligationOrder, trigger: T): OrderSpecification<T> {
|
|
36
|
+
return {
|
|
37
|
+
trigger,
|
|
38
|
+
action: toAction(kaminoOrder.opportunity),
|
|
39
|
+
executionBonusBpsRange: toExecutionBonusBps(kaminoOrder.minExecutionBonusRate, kaminoOrder.maxExecutionBonusRate),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Only internals below:
|
|
44
|
+
|
|
45
|
+
function toOrderOpportunity(context: OrderContext, action: OrderAction): OrderOpportunity {
|
|
46
|
+
switch (action.type) {
|
|
47
|
+
case OrderActionType.FullRepay:
|
|
48
|
+
return new DeleverageAllDebt();
|
|
49
|
+
case OrderActionType.PartialRepay:
|
|
50
|
+
const { repayDebtAmountLamports } = action;
|
|
51
|
+
checkThat(repayDebtAmountLamports.gt(0), `repay amount must be positive; got ${repayDebtAmountLamports}`);
|
|
52
|
+
const availableDebtAmountLamports = getSingleElement(context.kaminoObligation.getBorrows(), 'borrow').amount;
|
|
53
|
+
checkThat(
|
|
54
|
+
repayDebtAmountLamports.lte(availableDebtAmountLamports),
|
|
55
|
+
`partial repay amount ${repayDebtAmountLamports} cannot exceed the borrowed amount ${availableDebtAmountLamports}`
|
|
56
|
+
);
|
|
57
|
+
return new DeleverageDebtAmount(action.repayDebtAmountLamports);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function toExecutionBonusRates(executionBonusBpsRange: [number, number]): [Decimal, Decimal] {
|
|
62
|
+
const [minExecutionBonusRate, maxExecutionBonusRate] = executionBonusBpsRange.map((bps) =>
|
|
63
|
+
new Decimal(bps).div(ONE_HUNDRED_PCT_IN_BPS)
|
|
64
|
+
);
|
|
65
|
+
checkThat(minExecutionBonusRate.gte(0), `execution bonus rate cannot be negative: ${minExecutionBonusRate}`);
|
|
66
|
+
checkThat(
|
|
67
|
+
maxExecutionBonusRate.gte(minExecutionBonusRate),
|
|
68
|
+
`max execution bonus rate ${maxExecutionBonusRate} cannot be lower than min ${minExecutionBonusRate}`
|
|
69
|
+
);
|
|
70
|
+
return [minExecutionBonusRate, maxExecutionBonusRate];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function toAction(opportunity: OrderOpportunity): OrderAction {
|
|
74
|
+
if (opportunity instanceof DeleverageAllDebt) {
|
|
75
|
+
return {
|
|
76
|
+
type: OrderActionType.FullRepay,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (opportunity instanceof DeleverageDebtAmount) {
|
|
80
|
+
return {
|
|
81
|
+
type: OrderActionType.PartialRepay,
|
|
82
|
+
repayDebtAmountLamports: opportunity.amount,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
throw new Error(`incompatible on-chain opportunity ${opportunity.constructor.name}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function toExecutionBonusBps(minExecutionBonusRate: Decimal, maxExecutionBonusRate: Decimal): [number, number] {
|
|
89
|
+
return [minExecutionBonusRate, maxExecutionBonusRate].map((rate) =>
|
|
90
|
+
new Decimal(rate).mul(ONE_HUNDRED_PCT_IN_BPS).toNumber()
|
|
91
|
+
) as [number, number];
|
|
92
|
+
}
|