@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.
Files changed (88) hide show
  1. package/dist/classes/action.d.ts +46 -22
  2. package/dist/classes/action.d.ts.map +1 -1
  3. package/dist/classes/action.js +60 -37
  4. package/dist/classes/action.js.map +1 -1
  5. package/dist/classes/fraction.d.ts +2 -0
  6. package/dist/classes/fraction.d.ts.map +1 -1
  7. package/dist/classes/fraction.js +6 -0
  8. package/dist/classes/fraction.js.map +1 -1
  9. package/dist/classes/manager.d.ts.map +1 -1
  10. package/dist/classes/manager.js.map +1 -1
  11. package/dist/classes/market.d.ts +10 -0
  12. package/dist/classes/market.d.ts.map +1 -1
  13. package/dist/classes/market.js +17 -10
  14. package/dist/classes/market.js.map +1 -1
  15. package/dist/classes/obligation.d.ts +2 -2
  16. package/dist/classes/obligation.d.ts.map +1 -1
  17. package/dist/classes/obligation.js +2 -7
  18. package/dist/classes/obligation.js.map +1 -1
  19. package/dist/classes/obligationOrder.d.ts +30 -7
  20. package/dist/classes/obligationOrder.d.ts.map +1 -1
  21. package/dist/classes/obligationOrder.js +47 -12
  22. package/dist/classes/obligationOrder.js.map +1 -1
  23. package/dist/classes/reserve.d.ts.map +1 -1
  24. package/dist/classes/reserve.js +1 -4
  25. package/dist/classes/reserve.js.map +1 -1
  26. package/dist/classes/utils.d.ts +0 -1
  27. package/dist/classes/utils.d.ts.map +1 -1
  28. package/dist/classes/utils.js +0 -7
  29. package/dist/classes/utils.js.map +1 -1
  30. package/dist/classes/vault.d.ts.map +1 -1
  31. package/dist/classes/vault.js +3 -0
  32. package/dist/classes/vault.js.map +1 -1
  33. package/dist/lending_operations/repay_with_collateral_operations.d.ts.map +1 -1
  34. package/dist/lending_operations/repay_with_collateral_operations.js +2 -2
  35. package/dist/lending_operations/repay_with_collateral_operations.js.map +1 -1
  36. package/dist/lending_operations/swap_collateral_operations.js +2 -4
  37. package/dist/lending_operations/swap_collateral_operations.js.map +1 -1
  38. package/dist/leverage/operations.d.ts.map +1 -1
  39. package/dist/leverage/operations.js +7 -14
  40. package/dist/leverage/operations.js.map +1 -1
  41. package/dist/lib.d.ts +1 -0
  42. package/dist/lib.d.ts.map +1 -1
  43. package/dist/lib.js +1 -0
  44. package/dist/lib.js.map +1 -1
  45. package/dist/obligation_orders/common.d.ts +62 -0
  46. package/dist/obligation_orders/common.d.ts.map +1 -0
  47. package/dist/obligation_orders/common.js +20 -0
  48. package/dist/obligation_orders/common.js.map +1 -0
  49. package/dist/obligation_orders/index.d.ts +4 -0
  50. package/dist/obligation_orders/index.d.ts.map +1 -0
  51. package/dist/obligation_orders/index.js +20 -0
  52. package/dist/obligation_orders/index.js.map +1 -0
  53. package/dist/obligation_orders/internal.d.ts +6 -0
  54. package/dist/obligation_orders/internal.d.ts.map +1 -0
  55. package/dist/obligation_orders/internal.js +72 -0
  56. package/dist/obligation_orders/internal.js.map +1 -0
  57. package/dist/obligation_orders/ltv_based.d.ts +51 -0
  58. package/dist/obligation_orders/ltv_based.d.ts.map +1 -0
  59. package/dist/obligation_orders/ltv_based.js +107 -0
  60. package/dist/obligation_orders/ltv_based.js.map +1 -0
  61. package/dist/obligation_orders/price_based.d.ts +81 -0
  62. package/dist/obligation_orders/price_based.d.ts.map +1 -0
  63. package/dist/obligation_orders/price_based.js +167 -0
  64. package/dist/obligation_orders/price_based.js.map +1 -0
  65. package/dist/utils/validations.d.ts +5 -0
  66. package/dist/utils/validations.d.ts.map +1 -0
  67. package/dist/utils/validations.js +36 -0
  68. package/dist/utils/validations.js.map +1 -0
  69. package/package.json +1 -1
  70. package/src/classes/action.ts +61 -52
  71. package/src/classes/fraction.ts +7 -0
  72. package/src/classes/manager.ts +1 -4
  73. package/src/classes/market.ts +21 -11
  74. package/src/classes/obligation.ts +4 -9
  75. package/src/classes/obligationOrder.ts +57 -20
  76. package/src/classes/reserve.ts +2 -5
  77. package/src/classes/utils.ts +0 -7
  78. package/src/classes/vault.ts +3 -0
  79. package/src/lending_operations/repay_with_collateral_operations.ts +0 -2
  80. package/src/lending_operations/swap_collateral_operations.ts +2 -4
  81. package/src/leverage/operations.ts +7 -13
  82. package/src/lib.ts +1 -0
  83. package/src/obligation_orders/common.ts +70 -0
  84. package/src/obligation_orders/index.ts +3 -0
  85. package/src/obligation_orders/internal.ts +92 -0
  86. package/src/obligation_orders/ltv_based.ts +143 -0
  87. package/src/obligation_orders/price_based.ts +256 -0
  88. package/src/utils/validations.ts +31 -0
@@ -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
+ }
@@ -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)) {
@@ -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
- const elevationGroup = this.getMarketElevationGroupDescriptions().find(
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
- const reserve = this.getReserveByMint(address);
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 borrows' values multiplied by reserves' borrowFactor (i.e. `userTotalBorrowBorrowFactorAdjusted`)
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 order slots).
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
- let accSf = new BN(0);
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 { 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
 
@@ -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
- const cumulativeBorrowRateBsf = this.state.liquidity.cumulativeBorrowRateBsf.value;
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
  /**
@@ -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
  *
@@ -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
- false, // we are dealing with an existing obligation, no need to create user metadata
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
- false, // we are dealing with an existing obligation, no need to create user metadata
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
- false, // to be checked and created in a setup tx in the UI
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, // 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)
990
- false, // 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)
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
- 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)
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
- 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)
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
- 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)
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
- 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)
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
@@ -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
+ }