@kamino-finance/klend-sdk 5.12.8 → 5.13.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 (62) hide show
  1. package/dist/classes/action.d.ts +2 -2
  2. package/dist/classes/action.d.ts.map +1 -1
  3. package/dist/classes/action.js +5 -3
  4. package/dist/classes/action.js.map +1 -1
  5. package/dist/classes/manager.d.ts.map +1 -1
  6. package/dist/classes/manager.js.map +1 -1
  7. package/dist/classes/market.d.ts +10 -0
  8. package/dist/classes/market.d.ts.map +1 -1
  9. package/dist/classes/market.js +17 -10
  10. package/dist/classes/market.js.map +1 -1
  11. package/dist/classes/obligation.d.ts +2 -2
  12. package/dist/classes/obligation.js +1 -1
  13. package/dist/classes/obligationOrder.d.ts +30 -7
  14. package/dist/classes/obligationOrder.d.ts.map +1 -1
  15. package/dist/classes/obligationOrder.js +47 -12
  16. package/dist/classes/obligationOrder.js.map +1 -1
  17. package/dist/classes/utils.d.ts +0 -1
  18. package/dist/classes/utils.d.ts.map +1 -1
  19. package/dist/classes/utils.js +0 -7
  20. package/dist/classes/utils.js.map +1 -1
  21. package/dist/lib.d.ts +1 -0
  22. package/dist/lib.d.ts.map +1 -1
  23. package/dist/lib.js +1 -0
  24. package/dist/lib.js.map +1 -1
  25. package/dist/obligation_orders/common.d.ts +62 -0
  26. package/dist/obligation_orders/common.d.ts.map +1 -0
  27. package/dist/obligation_orders/common.js +20 -0
  28. package/dist/obligation_orders/common.js.map +1 -0
  29. package/dist/obligation_orders/index.d.ts +4 -0
  30. package/dist/obligation_orders/index.d.ts.map +1 -0
  31. package/dist/obligation_orders/index.js +20 -0
  32. package/dist/obligation_orders/index.js.map +1 -0
  33. package/dist/obligation_orders/internal.d.ts +6 -0
  34. package/dist/obligation_orders/internal.d.ts.map +1 -0
  35. package/dist/obligation_orders/internal.js +72 -0
  36. package/dist/obligation_orders/internal.js.map +1 -0
  37. package/dist/obligation_orders/ltv_based.d.ts +51 -0
  38. package/dist/obligation_orders/ltv_based.d.ts.map +1 -0
  39. package/dist/obligation_orders/ltv_based.js +107 -0
  40. package/dist/obligation_orders/ltv_based.js.map +1 -0
  41. package/dist/obligation_orders/price_based.d.ts +81 -0
  42. package/dist/obligation_orders/price_based.d.ts.map +1 -0
  43. package/dist/obligation_orders/price_based.js +167 -0
  44. package/dist/obligation_orders/price_based.js.map +1 -0
  45. package/dist/utils/validations.d.ts +5 -0
  46. package/dist/utils/validations.d.ts.map +1 -0
  47. package/dist/utils/validations.js +36 -0
  48. package/dist/utils/validations.js.map +1 -0
  49. package/package.json +1 -1
  50. package/src/classes/action.ts +6 -4
  51. package/src/classes/manager.ts +1 -4
  52. package/src/classes/market.ts +21 -11
  53. package/src/classes/obligation.ts +2 -2
  54. package/src/classes/obligationOrder.ts +57 -20
  55. package/src/classes/utils.ts +0 -7
  56. package/src/lib.ts +1 -0
  57. package/src/obligation_orders/common.ts +70 -0
  58. package/src/obligation_orders/index.ts +3 -0
  59. package/src/obligation_orders/internal.ts +92 -0
  60. package/src/obligation_orders/ltv_based.ts +143 -0
  61. package/src/obligation_orders/price_based.ts +256 -0
  62. package/src/utils/validations.ts +31 -0
@@ -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 { getSingleElement, orThrow, roundNearest } from './utils';
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
- private readonly minUserLtvExclusive: Decimal;
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
- private readonly maxUserLtvExclusive: Decimal;
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
- private readonly minDebtCollPriceRatioExclusive: Decimal;
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 LTV:
118
- priceRatio.mul(obligation.refreshedStats.liquidationLtv).div(obligation.refreshedStats.loanToValue)
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
- private readonly maxDebtCollPriceRatioExclusive: Decimal;
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
- private readonly amount: Decimal;
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, 'Opportunity type requires a single borrow');
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
- const diffToBadDebt = new Decimal(1).sub(userNoBfLtv);
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 !== position.mintAddress) {
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(), 'Condition type requires a single borrow');
500
- const singleDeposit = getSingleElement(obligation.getDeposits(), 'Condition type requires a single deposit');
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
 
@@ -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/lib.ts CHANGED
@@ -14,3 +14,4 @@ export * from './utils';
14
14
  export * from './leverage';
15
15
  export * from './referrals';
16
16
  export * from './lending_operations';
17
+ export * from './obligation_orders';
@@ -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,3 @@
1
+ export * from './price_based';
2
+ export * from './ltv_based';
3
+ export * from './common';
@@ -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
+ }
@@ -0,0 +1,143 @@
1
+ import Decimal from 'decimal.js';
2
+ import { ObligationOrderAtIndex, OrderCondition, UserLtvAbove, UserLtvBelow } from '../classes/obligationOrder';
3
+ import { checkThat } from '../utils/validations';
4
+ import { OrderContext, OrderSpecification, OrderType } from './common';
5
+ import { createConditionBasedOrder, readTriggerBasedOrder, toOrderIndex } from './internal';
6
+
7
+ /**
8
+ * Creates an LTV-based {@link ObligationOrderAtIndex} based on the given stop-loss or take-profit specification.
9
+ *
10
+ * The returned object can then be passed directly to {@link KaminoAction.buildSetObligationOrderIxn()} to build an
11
+ * instruction which replaces (or cancels, if the specification is `null`) the given obligation's stop-loss or
12
+ * take-profit order on-chain.
13
+ *
14
+ * The given obligation cannot use 0-LTV collaterals (see {@link checkObligationCompatible()} for rationale).
15
+ */
16
+ export function createLtvBasedOrder(
17
+ context: OrderContext,
18
+ orderType: OrderType,
19
+ specification: LtvBasedOrderSpecification | null
20
+ ): ObligationOrderAtIndex {
21
+ checkObligationCompatible(context);
22
+ const index = toOrderIndex(orderType);
23
+ if (specification === null) {
24
+ return ObligationOrderAtIndex.empty(index);
25
+ }
26
+ const condition = toOrderCondition(orderType, specification.trigger);
27
+ checkThat(
28
+ condition.threshold().gte(MIN_LTV_THRESHOLD) && condition.threshold().lte(MAX_LTV_THRESHOLD),
29
+ `LTV-based trigger outside valid range [${MIN_LTV_THRESHOLD}%; ${MAX_LTV_THRESHOLD}%]: ${condition.threshold()}%`
30
+ );
31
+ return createConditionBasedOrder(context, condition, specification).atIndex(index);
32
+ }
33
+
34
+ /**
35
+ * Parses an {@link OrderSpecification} from the selected stop-loss or take-profit order of the given obligation.
36
+ *
37
+ * The given obligation cannot use 0-LTV collaterals (see {@link checkObligationCompatible()} for rationale).
38
+ *
39
+ * The selected order is expected to be of matching type (i.e. as if it was created using the
40
+ * {@link createLtvBasedOrder()}).
41
+ */
42
+ export function readLtvBasedOrder(context: OrderContext, orderType: OrderType): LtvBasedOrderSpecification | null {
43
+ checkObligationCompatible(context);
44
+ const kaminoOrder = context.kaminoObligation.getOrders()[toOrderIndex(orderType)];
45
+ if (kaminoOrder === null) {
46
+ return null;
47
+ }
48
+ const trigger = toTrigger(kaminoOrder.condition, orderType);
49
+ return readTriggerBasedOrder(kaminoOrder, trigger);
50
+ }
51
+
52
+ /**
53
+ * A high-level specification of an LTV-based order.
54
+ */
55
+ export type LtvBasedOrderSpecification = OrderSpecification<LtvBasedOrderTrigger>;
56
+
57
+ /**
58
+ * A discriminator enum for {@link LtvBasedOrderTrigger};
59
+ */
60
+ export enum LtvBasedOrderTriggerType {
61
+ StopLoss = 'StopLoss',
62
+ TakeProfit = 'TakeProfit',
63
+ }
64
+
65
+ /**
66
+ * One of possible triggers depending on the obligation's type and the price bracket's side.
67
+ */
68
+ export type LtvBasedOrderTrigger = StopLoss | TakeProfit;
69
+
70
+ /**
71
+ * A trigger for a stop-loss on LTV.
72
+ */
73
+ export type StopLoss = {
74
+ type: LtvBasedOrderTriggerType.StopLoss;
75
+ whenLtvPctAbove: number;
76
+ };
77
+
78
+ /**
79
+ * A trigger for a take-profit on LTV.
80
+ */
81
+ export type TakeProfit = {
82
+ type: LtvBasedOrderTriggerType.TakeProfit;
83
+ whenLtvPctBelow: number;
84
+ };
85
+
86
+ // Only internals below:
87
+
88
+ const FULL_PCT = 100;
89
+ const MIN_LTV_THRESHOLD = 0.01;
90
+ const MAX_LTV_THRESHOLD = 0.99;
91
+
92
+ function checkObligationCompatible({ kaminoMarket, kaminoObligation }: OrderContext) {
93
+ for (const depositReserveAddress of kaminoObligation.deposits.keys()) {
94
+ const depositReserve = kaminoMarket.getExistingReserveByAddress(depositReserveAddress);
95
+ // Note: the seemingly over-cautious requirement below ensures that the user-facing LTV calculation gives the same
96
+ // result as on the Klend SC side (they differ in the handling of 0-LTV collaterals; see
97
+ // `KaminoObligation.loanToValue()` doc for details). We may unify the 0-LTV handling some day and remove this.
98
+ checkThat(
99
+ depositReserve.state.config.loanToValuePct !== 0,
100
+ `LTV-based orders cannot be used with a 0-LTV collateral: ${depositReserve.symbol}`
101
+ );
102
+ }
103
+ }
104
+
105
+ function toOrderCondition(orderType: OrderType, trigger: LtvBasedOrderTrigger): OrderCondition {
106
+ switch (orderType) {
107
+ case OrderType.StopLoss:
108
+ if (trigger.type === LtvBasedOrderTriggerType.StopLoss) {
109
+ return new UserLtvAbove(new Decimal(trigger.whenLtvPctAbove).div(FULL_PCT));
110
+ }
111
+ break;
112
+ case OrderType.TakeProfit:
113
+ if (trigger.type === LtvBasedOrderTriggerType.TakeProfit) {
114
+ return new UserLtvBelow(new Decimal(trigger.whenLtvPctBelow).div(FULL_PCT));
115
+ }
116
+ break;
117
+ }
118
+ throw new Error(`an LTV-based ${orderType} order cannot use ${trigger.type} condition`);
119
+ }
120
+
121
+ function toTrigger(condition: OrderCondition, orderType: OrderType): LtvBasedOrderTrigger {
122
+ switch (orderType) {
123
+ case OrderType.StopLoss:
124
+ if (condition instanceof UserLtvAbove) {
125
+ return {
126
+ type: LtvBasedOrderTriggerType.StopLoss,
127
+ whenLtvPctAbove: condition.minUserLtvExclusive.mul(FULL_PCT).toNumber(),
128
+ };
129
+ }
130
+ break;
131
+ case OrderType.TakeProfit:
132
+ if (condition instanceof UserLtvBelow) {
133
+ return {
134
+ type: LtvBasedOrderTriggerType.TakeProfit,
135
+ whenLtvPctBelow: condition.maxUserLtvExclusive.mul(FULL_PCT).toNumber(),
136
+ };
137
+ }
138
+ break;
139
+ }
140
+ throw new Error(
141
+ `an LTV-based ${orderType} order has an incompatible on-chain condition ${condition.constructor.name}`
142
+ );
143
+ }