@carrot-protocol/clend-rpc 0.0.1-mrgn-fork1-dev-7be6ef2

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/src/math.ts ADDED
@@ -0,0 +1,684 @@
1
+ import { BN } from "@coral-xyz/anchor";
2
+ import { ClendAccount, WrappedI80F48 } from "./state";
3
+ import { wrappedI80F48toBigNumber } from "./utils";
4
+ import { Decimal } from "decimal.js";
5
+
6
+ /**
7
+ * Convert a UI number to a token amount BN
8
+ * @param uiAmount Amount in UI format (e.g., 1.5 USDC)
9
+ * @param decimals Token decimals
10
+ * @returns BN representing the token amount in smallest units (lamports)
11
+ */
12
+ export function uiToAmount(uiAmount: number, decimals: number): BN {
13
+ return new BN(Math.floor(uiAmount * 10 ** decimals));
14
+ }
15
+
16
+ /**
17
+ * Convert a token amount BN to a UI number
18
+ * @param tokenAmount BN representing the token amount in smallest units (lamports)
19
+ * @param decimals Token decimals
20
+ * @returns Number in UI format (e.g., 1.5 USDC)
21
+ */
22
+ export function amountToUi(amount: BN | number, decimals: number): number {
23
+ if (typeof amount === "number") {
24
+ return amount / 10 ** decimals;
25
+ }
26
+ return amount.toNumber() / 10 ** decimals;
27
+ }
28
+
29
+ /**
30
+ * Calculate the utilization rate of a bank
31
+ * @param totalSupply Total supply of tokens
32
+ * @param totalBorrow Total borrowed tokens
33
+ * @returns Utilization rate as a decimal (0-1)
34
+ */
35
+ export function calculateUtilizationRate(
36
+ totalSupply: number,
37
+ totalBorrow: number,
38
+ ): number {
39
+ if (totalSupply === 0 || totalBorrow === 0) return 0;
40
+ return Math.min(totalBorrow / totalSupply, 1);
41
+ }
42
+
43
+ /**
44
+ * Calculate asset quantity from asset shares
45
+ * @param assetShares Asset shares amount
46
+ * @param assetShareValue Value of each asset share
47
+ * @returns Asset quantity
48
+ */
49
+ export function calculateAssetQuantity(
50
+ assetShares: number | BN,
51
+ assetShareValue: WrappedI80F48,
52
+ ): number {
53
+ const shares =
54
+ typeof assetShares === "number"
55
+ ? assetShares
56
+ : Number(assetShares.toString());
57
+ const shareValue = Number(
58
+ wrappedI80F48toBigNumber(assetShareValue).toString(),
59
+ );
60
+ return shares * shareValue;
61
+ }
62
+
63
+ /**
64
+ * Calculate liability quantity from liability shares
65
+ * @param liabilityShares Liability shares amount
66
+ * @param liabilityShareValue Value of each liability share
67
+ * @returns Liability quantity
68
+ */
69
+ export function calculateLiabilityQuantity(
70
+ liabilityShares: number | BN,
71
+ liabilityShareValue: WrappedI80F48,
72
+ ): number {
73
+ const shares =
74
+ typeof liabilityShares === "number"
75
+ ? liabilityShares
76
+ : Number(liabilityShares.toString());
77
+ const shareValue = Number(
78
+ wrappedI80F48toBigNumber(liabilityShareValue).toString(),
79
+ );
80
+ return shares * shareValue;
81
+ }
82
+
83
+ /**
84
+ * Calculate total asset quantity
85
+ * @param totalAssetShares Total asset shares in the bank
86
+ * @param assetShareValue Value of each asset share
87
+ * @returns Total asset quantity
88
+ *
89
+ * Note: You can get these values from a Bank object:
90
+ * - totalAssetShares = bank.totalAssetShares
91
+ * - assetShareValue = bank.assetShareValue
92
+ */
93
+ export function calculateTotalAssetQuantity(
94
+ totalAssetShares: WrappedI80F48,
95
+ assetShareValue: WrappedI80F48,
96
+ ): number {
97
+ return calculateAssetQuantity(
98
+ wrappedI80F48toBigNumber(totalAssetShares),
99
+ assetShareValue,
100
+ );
101
+ }
102
+
103
+ /**
104
+ * Calculate total liability quantity
105
+ * @param totalLiabilityShares Total liability shares in the bank
106
+ * @param liabilityShareValue Value of each liability share
107
+ * @returns Total liability quantity
108
+ *
109
+ * Note: You can get these values from a Bank object:
110
+ * - totalLiabilityShares = bank.totalLiabilityShares
111
+ * - liabilityShareValue = bank.liabilityShareValue
112
+ */
113
+ export function calculateTotalLiabilityQuantity(
114
+ totalLiabilityShares: WrappedI80F48,
115
+ liabilityShareValue: WrappedI80F48,
116
+ ): number {
117
+ return calculateLiabilityQuantity(
118
+ wrappedI80F48toBigNumber(totalLiabilityShares),
119
+ liabilityShareValue,
120
+ );
121
+ }
122
+
123
+ /**
124
+ * Calculate the bank's utilization rate
125
+ * @param totalAssetShares Total asset shares in the bank
126
+ * @param assetShareValue Value of each asset share
127
+ * @param totalLiabilityShares Total liability shares in the bank
128
+ * @param liabilityShareValue Value of each liability share
129
+ * @returns Utilization rate as a decimal (0-1)
130
+ *
131
+ * Note: You can get these values from a Bank object:
132
+ * - totalAssetShares = bank.totalAssetShares
133
+ * - assetShareValue = bank.assetShareValue
134
+ * - totalLiabilityShares = bank.totalLiabilityShares
135
+ * - liabilityShareValue = bank.liabilityShareValue
136
+ */
137
+ export function calculateBankUtilizationRate(
138
+ totalAssetShares: WrappedI80F48,
139
+ assetShareValue: WrappedI80F48,
140
+ totalLiabilityShares: WrappedI80F48,
141
+ liabilityShareValue: WrappedI80F48,
142
+ ): number {
143
+ const totalAssets = calculateTotalAssetQuantity(
144
+ totalAssetShares,
145
+ assetShareValue,
146
+ );
147
+ const totalLiabilities = calculateTotalLiabilityQuantity(
148
+ totalLiabilityShares,
149
+ liabilityShareValue,
150
+ );
151
+
152
+ return calculateUtilizationRate(totalAssets, totalLiabilities);
153
+ }
154
+
155
+ /**
156
+ * Calculate the base interest rate based on utilization rate
157
+ * @param utilizationRate Current utilization rate (0-1)
158
+ * @param optimalUtilizationRate Optimal utilization rate target
159
+ * @param plateauInterestRate Interest rate at optimal utilization
160
+ * @param maxInterestRate Maximum interest rate at 100% utilization
161
+ * @returns Base interest rate as a decimal
162
+ *
163
+ * Note: You can get these values from a Bank object:
164
+ * - optimalUtilizationRate = bank.config.interestRateConfig.optimalUtilizationRate
165
+ * - plateauInterestRate = bank.config.interestRateConfig.plateauInterestRate
166
+ * - maxInterestRate = bank.config.interestRateConfig.maxInterestRate
167
+ */
168
+ export function calculateBaseInterestRate(
169
+ utilizationRate: number,
170
+ optimalUtilizationRate: WrappedI80F48,
171
+ plateauInterestRate: WrappedI80F48,
172
+ maxInterestRate: WrappedI80F48,
173
+ ): number {
174
+ const optimalRate = Number(
175
+ wrappedI80F48toBigNumber(optimalUtilizationRate).toString(),
176
+ );
177
+ const plateauRate = Number(
178
+ wrappedI80F48toBigNumber(plateauInterestRate).toString(),
179
+ );
180
+ const maxRate = Number(wrappedI80F48toBigNumber(maxInterestRate).toString());
181
+
182
+ if (utilizationRate <= optimalRate) {
183
+ // Below optimal: linear increase from 0 to plateau rate
184
+ return (utilizationRate / optimalRate) * plateauRate;
185
+ } else {
186
+ // Above optimal: linear increase from plateau to max rate
187
+ const excessUtilization = utilizationRate - optimalRate;
188
+ const remainingUtilization = 1 - optimalRate;
189
+ const excessRate = maxRate - plateauRate;
190
+
191
+ return (
192
+ plateauRate + (excessUtilization / remainingUtilization) * excessRate
193
+ );
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Calculate the supply APY
199
+ * @param utilizationRate Current utilization rate (0-1)
200
+ * @param baseInterestRate Base interest rate calculated from utilization
201
+ * @param protocolIrFee Protocol interest rate fee
202
+ * @param insuranceIrFee Insurance interest rate fee
203
+ * @returns Supply APY as a decimal (e.g., 0.05 for 5%)
204
+ *
205
+ * Note: You can get these values from a Bank object:
206
+ * - utilizationRate = calculateBankUtilizationRate(...)
207
+ * - baseInterestRate = calculateBaseInterestRate(...)
208
+ * - protocolIrFee = bank.config.interestRateConfig.protocolIrFee
209
+ * - insuranceIrFee = bank.config.interestRateConfig.insuranceIrFee
210
+ */
211
+ export function calculateSupplyApy(
212
+ utilizationRate: number,
213
+ baseInterestRate: number,
214
+ protocolIrFee: WrappedI80F48,
215
+ insuranceIrFee: WrappedI80F48,
216
+ ): number {
217
+ // Supply rate = base rate * utilization rate * (1 - protocol fees)
218
+ const protocolFee = Number(
219
+ wrappedI80F48toBigNumber(protocolIrFee).toString(),
220
+ );
221
+ const insuranceFee = Number(
222
+ wrappedI80F48toBigNumber(insuranceIrFee).toString(),
223
+ );
224
+ const totalFeeRate = protocolFee + insuranceFee;
225
+
226
+ return baseInterestRate * utilizationRate * (1 - totalFeeRate);
227
+ }
228
+
229
+ /**
230
+ * Calculate the borrow APY
231
+ * @param baseInterestRate Base interest rate calculated from utilization
232
+ * @param protocolFixedFeeApr Protocol fixed fee APR
233
+ * @param insuranceFeeFixedApr Insurance fixed fee APR
234
+ * @returns Borrow APY as a decimal (e.g., 0.08 for 8%)
235
+ *
236
+ * Note: You can get these values from a Bank object:
237
+ * - baseInterestRate = calculateBaseInterestRate(...)
238
+ * - protocolFixedFeeApr = bank.config.interestRateConfig.protocolFixedFeeApr
239
+ * - insuranceFeeFixedApr = bank.config.interestRateConfig.insuranceFeeFixedApr
240
+ */
241
+ export function calculateBorrowApy(
242
+ baseInterestRate: number,
243
+ protocolFixedFeeApr: WrappedI80F48,
244
+ insuranceFeeFixedApr: WrappedI80F48,
245
+ ): number {
246
+ // Borrow rate = base rate + fixed protocol fee + fixed insurance fee
247
+ const protocolFee = Number(
248
+ wrappedI80F48toBigNumber(protocolFixedFeeApr).toString(),
249
+ );
250
+ const insuranceFee = Number(
251
+ wrappedI80F48toBigNumber(insuranceFeeFixedApr).toString(),
252
+ );
253
+
254
+ return baseInterestRate + protocolFee + insuranceFee;
255
+ }
256
+
257
+ /**
258
+ * Computes amounts needed to adjust leverage
259
+ * @param currentCollateral - Current collateral amount (e.g., JLP)
260
+ * @param currentDebt - Current debt amount (e.g., USDC)
261
+ * @param collateralPrice - Current collateral price in USD
262
+ * @param debtPrice - Current debt token price in USD
263
+ * @param currentLeverage - Current leverage ratio
264
+ * @param targetLeverage - Desired leverage ratio
265
+ */
266
+ export function computeAdjustLeverageAmounts(
267
+ currentCollateral: number,
268
+ currentDebt: number,
269
+ collateralPrice: number,
270
+ debtPrice: number,
271
+ currentLeverage: number,
272
+ targetLeverage: number,
273
+ ): {
274
+ isIncrease: boolean;
275
+ collateralDelta: number;
276
+ debtDelta: number;
277
+ } {
278
+ // Convert everything to USD value
279
+ const currentPositionValue = currentCollateral * collateralPrice;
280
+ const currentDebtValue = currentDebt * debtPrice;
281
+ const currentEquity = currentPositionValue - currentDebtValue;
282
+
283
+ // Target position size based on desired leverage
284
+ const targetPositionValue = currentEquity * targetLeverage;
285
+ const targetCollateral = targetPositionValue / collateralPrice;
286
+ const targetDebtValue = targetPositionValue - currentEquity;
287
+ const targetDebt = targetDebtValue / debtPrice;
288
+
289
+ // Calculate changes needed
290
+ const collateralDelta = targetCollateral - currentCollateral;
291
+ const debtDelta = targetDebt - currentDebt;
292
+ const isIncrease = targetLeverage > currentLeverage;
293
+
294
+ return {
295
+ isIncrease,
296
+ collateralDelta: Math.abs(collateralDelta),
297
+ debtDelta: Math.abs(debtDelta),
298
+ };
299
+ }
300
+
301
+ /**
302
+ * Computes the maximum leverage possible given deposit and borrow asset weights
303
+ * @param depositAssetWeight - Initial risk weight of deposit asset (0-1). Higher value = more trusted as collateral
304
+ * @param borrowLiabilityWeight - Initial risk weight of borrow asset (0-2). Higher value = more conservative borrowing
305
+ * @returns Object containing:
306
+ * - maxLeverage: Maximum leverage possible through recursive borrow-deposit
307
+ * - ltv: Loan-to-Value ratio, representing what portion of collateral can be borrowed
308
+ */
309
+ export function computeMaxLeverage(
310
+ depositAssetWeight: number,
311
+ borrowLiabilityWeight: number,
312
+ ): { maxLeverage: number; ltv: number } {
313
+ // Handle edge cases
314
+ if (borrowLiabilityWeight <= 0) {
315
+ return { maxLeverage: -1, ltv: -1 };
316
+ }
317
+
318
+ // LTV represents what portion of collateral value can be borrowed
319
+ const ltv = depositAssetWeight / borrowLiabilityWeight;
320
+
321
+ // Maximum leverage possible through recursive borrow-deposit cycles
322
+ // If LTV > 1, leverage is infinite
323
+ if (ltv > 1) {
324
+ return { maxLeverage: -1, ltv };
325
+ }
326
+
327
+ if (ltv === 1) {
328
+ return { maxLeverage: -1, ltv };
329
+ }
330
+
331
+ return {
332
+ maxLeverage: 1 / (1 - ltv),
333
+ ltv,
334
+ };
335
+ }
336
+
337
+ /**
338
+ * Computes the liquidation threshold (LT) given deposit and borrow asset maintenance weights.
339
+ * The LT represents the Loan-to-Value ratio at which liquidation can occur.
340
+ * @param depositAssetWeightMaint - Maintenance risk weight of deposit asset (0-1).
341
+ * @param borrowLiabilityWeightMaint - Maintenance risk weight of borrow asset (0-2).
342
+ * @returns Object containing:
343
+ * - liquidationThreshold: The LTV ratio at which liquidation occurs. Returns -1 if parameters are invalid.
344
+ */
345
+ export function computeLiquidationThreshold(
346
+ depositAssetWeightMaint: number,
347
+ borrowLiabilityWeightMaint: number,
348
+ ): { liquidationThreshold: number } {
349
+ // Handle edge cases/invalid inputs
350
+ if (borrowLiabilityWeightMaint <= 0 || depositAssetWeightMaint < 0) {
351
+ // LT cannot be calculated if liability weight is non-positive
352
+ // or asset weight is negative.
353
+ return { liquidationThreshold: -1 };
354
+ }
355
+
356
+ // Liquidation Threshold (LT) is the LTV calculated using maintenance weights.
357
+ // It represents the point where weighted assets no longer cover weighted liabilities.
358
+ const liquidationThreshold =
359
+ depositAssetWeightMaint / borrowLiabilityWeightMaint;
360
+
361
+ // LT can theoretically be >= 1 if weights are set permissively,
362
+ // although this implies liquidation is impossible or happens immediately
363
+ // depending on initial LTV. We return the calculated value.
364
+ return {
365
+ liquidationThreshold,
366
+ };
367
+ }
368
+
369
+ /**
370
+ * Computes the borrow and deposit amounts needed to achieve target leverage, accounting for weights
371
+ * @param principal - Initial deposit amount
372
+ * @param targetLeverage - Desired leverage multiplier
373
+ * @param depositPrice - Price of deposit/collateral asset
374
+ * @param borrowPrice - Price of borrow/debt asset
375
+ * @param collateralWeight - Weight of collateral asset (0-1)
376
+ * @param debtWeight - Weight of debt asset (1+)
377
+ * @returns Object containing:
378
+ * - borrowAmount: Amount to borrow in borrow asset units
379
+ * - totalDepositAmount: Total final position size in deposit asset units
380
+ */
381
+ export function computeLoopingAmounts(
382
+ principal: number,
383
+ targetLeverage: number,
384
+ depositPrice: number,
385
+ borrowPrice: number,
386
+ collateralWeight: number,
387
+ debtWeight: number,
388
+ ): {
389
+ borrowAmountUi: number;
390
+ totalDepositAmountUi: number;
391
+ } {
392
+ // Validate inputs
393
+ if (principal <= 0) throw new Error("Principal must be positive");
394
+ if (targetLeverage <= 1)
395
+ throw new Error("Target leverage must be greater than 1");
396
+ if (depositPrice <= 0 || borrowPrice <= 0)
397
+ throw new Error("Prices must be positive");
398
+ if (collateralWeight <= 0 || collateralWeight > 1)
399
+ throw new Error("Collateral weight must be between 0 and 1");
400
+ if (debtWeight < 1)
401
+ throw new Error("Debt weight must be greater than or equal to 1");
402
+
403
+ // With weights, we need to solve for the borrow amount that achieves the target weighted leverage
404
+ // Given:
405
+ // - P = principal (initial deposit)
406
+ // - B = borrow amount
407
+ // - Tp = total position (P + additional from swap)
408
+ // - Dp = deposit price
409
+ // - Bp = borrow price
410
+ // - Cw = collateral weight
411
+ // - Dw = debt weight
412
+ // - L = target leverage
413
+ //
414
+ // We have:
415
+ // L = (Tp * Dp * Cw) / ((Tp * Dp * Cw) - (B * Bp * Dw))
416
+ //
417
+ // We know that Tp = P + B*(Bp/Dp) (the additional amount from swapping the borrowed amount)
418
+ //
419
+ // Substituting:
420
+ // L = ((P + B*(Bp/Dp)) * Dp * Cw) / (((P + B*(Bp/Dp)) * Dp * Cw) - (B * Bp * Dw))
421
+ // L = (P*Dp*Cw + B*Bp*Cw) / (P*Dp*Cw + B*Bp*Cw - B*Bp*Dw)
422
+ // L = (P*Dp*Cw + B*Bp*Cw) / (P*Dp*Cw + B*Bp*(Cw - Dw))
423
+ //
424
+ // Solving for B:
425
+ // L * (P*Dp*Cw + B*Bp*(Cw - Dw)) = P*Dp*Cw + B*Bp*Cw
426
+ // L*P*Dp*Cw + L*B*Bp*(Cw - Dw) = P*Dp*Cw + B*Bp*Cw
427
+ // L*B*Bp*(Cw - Dw) = P*Dp*Cw + B*Bp*Cw - L*P*Dp*Cw
428
+ // L*B*Bp*(Cw - Dw) = P*Dp*Cw*(1 - L) + B*Bp*Cw
429
+ // L*B*Bp*(Cw - Dw) - B*Bp*Cw = P*Dp*Cw*(1 - L)
430
+ // B*Bp*(L*(Cw - Dw) - Cw) = P*Dp*Cw*(1 - L)
431
+ // B = (P*Dp*Cw*(1 - L)) / (Bp*(L*(Cw - Dw) - Cw))
432
+ // B = (P*Dp*Cw*(L - 1)) / (Bp*(Cw - L*(Cw - Dw)))
433
+
434
+ // This is a more general solution that handles weights
435
+ const numerator =
436
+ principal * depositPrice * collateralWeight * (targetLeverage - 1);
437
+ const denominator =
438
+ borrowPrice *
439
+ (collateralWeight - targetLeverage * (collateralWeight - debtWeight));
440
+
441
+ // If denominator is close to zero or negative, we need to cap the leverage
442
+ if (denominator <= 0.000001) {
443
+ throw new Error("Target leverage is too high for the given weights");
444
+ }
445
+
446
+ const borrowAmount = numerator / denominator;
447
+
448
+ // Calculate total deposit amount
449
+ const totalDepositAmount =
450
+ principal + borrowAmount * (borrowPrice / depositPrice);
451
+
452
+ return {
453
+ borrowAmountUi: borrowAmount,
454
+ totalDepositAmountUi: totalDepositAmount,
455
+ };
456
+ }
457
+
458
+ /**
459
+ * Calculates the weighted value of an amount based on its price and weight
460
+ * @param amount - Raw amount of the token
461
+ * @param price - Price of the token
462
+ * @param weight - Weight to apply (0-1 for assets, 1+ for liabilities)
463
+ * @returns Weighted value in USD terms
464
+ */
465
+ export function calculateWeightedValue(
466
+ amount: number,
467
+ price: number,
468
+ weight: number,
469
+ ): number {
470
+ return amount * price * weight;
471
+ }
472
+
473
+ /**
474
+ * Calculates leverage using weighted values
475
+ * @param collateralAmount - Amount of collateral
476
+ * @param collateralPrice - Price of collateral
477
+ * @param collateralWeight - Weight of collateral (0-1)
478
+ * @param debtAmount - Amount of debt
479
+ * @param debtPrice - Price of debt
480
+ * @param debtWeight - Weight of debt (1+)
481
+ * @returns Leverage ratio
482
+ */
483
+ export function calculateWeightedLeverage(
484
+ collateralAmount: number,
485
+ collateralPrice: number,
486
+ collateralWeight: number,
487
+ debtAmount: number,
488
+ debtPrice: number,
489
+ debtWeight: number,
490
+ ): number {
491
+ const weightedCollateralValue = calculateWeightedValue(
492
+ collateralAmount,
493
+ collateralPrice,
494
+ collateralWeight,
495
+ );
496
+ const weightedDebtValue = calculateWeightedValue(
497
+ debtAmount,
498
+ debtPrice,
499
+ debtWeight,
500
+ );
501
+
502
+ const netValue = weightedCollateralValue - weightedDebtValue;
503
+
504
+ if (netValue <= 0) {
505
+ throw new Error("Net value must be positive");
506
+ }
507
+
508
+ return weightedCollateralValue / netValue;
509
+ }
510
+
511
+ /**
512
+ * Calculates the required borrow amount to achieve target leverage
513
+ * @param collateralAmount - Initial collateral amount
514
+ * @param collateralPrice - Price of collateral
515
+ * @param collateralWeight - Weight of collateral (0-1)
516
+ * @param debtPrice - Price of debt
517
+ * @param debtWeight - Weight of debt (1+)
518
+ * @param targetLeverage - Desired leverage ratio
519
+ * @returns Required borrow amount in debt token units
520
+ */
521
+ export function calculateRequiredBorrowAmount(
522
+ collateralAmount: number,
523
+ collateralPrice: number,
524
+ collateralWeight: number,
525
+ debtPrice: number,
526
+ debtWeight: number,
527
+ targetLeverage: number,
528
+ ): number {
529
+ const weightedCollateralValue = calculateWeightedValue(
530
+ collateralAmount,
531
+ collateralPrice,
532
+ collateralWeight,
533
+ );
534
+
535
+ // Solve for debt value in the leverage equation:
536
+ // leverage = weightedCollateralValue / (weightedCollateralValue - weightedDebtValue)
537
+ // => weightedDebtValue = weightedCollateralValue * (1 - 1/leverage)
538
+ const weightedDebtValue = weightedCollateralValue * (1 - 1 / targetLeverage);
539
+
540
+ // Convert weighted debt value to actual debt amount
541
+ return weightedDebtValue / (debtPrice * debtWeight);
542
+ }
543
+
544
+ /**
545
+ * Calculates the debt to repay when withdrawing collateral to maintain the same leverage ratio
546
+ * @param currentCollateral - Current collateral amount
547
+ * @param currentDebt - Current debt amount
548
+ * @param withdrawAmount - Amount of collateral to withdraw
549
+ * @param collateralPrice - Price of collateral token
550
+ * @param debtPrice - Price of debt token
551
+ * @param collateralWeight - Weight of collateral (0-1)
552
+ * @param debtWeight - Weight of debt (1+)
553
+ * @returns Object containing the debt to repay and new leverage values
554
+ */
555
+ export function computeWithdrawLeverageAmounts(
556
+ currentCollateral: number,
557
+ currentDebt: number,
558
+ withdrawAmount: number,
559
+ collateralPrice: number,
560
+ debtPrice: number,
561
+ collateralWeight: number,
562
+ debtWeight: number,
563
+ ): {
564
+ debtToRepay: number;
565
+ newCollateral: number;
566
+ newDebt: number;
567
+ currentLeverage: number;
568
+ newLeverage: number;
569
+ } {
570
+ // Calculate current leverage
571
+ const currentLeverage = calculateWeightedLeverage(
572
+ currentCollateral,
573
+ collateralPrice,
574
+ collateralWeight,
575
+ currentDebt,
576
+ debtPrice,
577
+ debtWeight,
578
+ );
579
+
580
+ // Ensure we don't withdraw more than available
581
+ if (withdrawAmount > currentCollateral) {
582
+ throw new Error("Withdrawal amount exceeds available collateral");
583
+ }
584
+
585
+ // Calculate new collateral amount
586
+ const newCollateral = currentCollateral - withdrawAmount;
587
+
588
+ // Calculate weighted values for current position
589
+ const currentWeightedCollateral =
590
+ currentCollateral * collateralPrice * collateralWeight;
591
+ const currentWeightedDebt = currentDebt * debtPrice * debtWeight;
592
+
593
+ // Calculate weighted collateral after withdrawal
594
+ const newWeightedCollateral =
595
+ newCollateral * collateralPrice * collateralWeight;
596
+
597
+ // To maintain the same leverage, we need:
598
+ // currentLeverage = newWeightedCollateral / (newWeightedCollateral - newWeightedDebt)
599
+ //
600
+ // Solving for newWeightedDebt:
601
+ // currentLeverage * (newWeightedCollateral - newWeightedDebt) = newWeightedCollateral
602
+ // currentLeverage * newWeightedCollateral - currentLeverage * newWeightedDebt = newWeightedCollateral
603
+ // -currentLeverage * newWeightedDebt = newWeightedCollateral - currentLeverage * newWeightedCollateral
604
+ // -currentLeverage * newWeightedDebt = newWeightedCollateral * (1 - currentLeverage)
605
+
606
+ const newWeightedDebt = newWeightedCollateral * (1 - 1 / currentLeverage);
607
+
608
+ // Convert weighted debt to actual debt amount
609
+ const newDebt = newWeightedDebt / (debtPrice * debtWeight);
610
+
611
+ // Calculate debt to repay
612
+ const debtToRepay = currentDebt - newDebt;
613
+
614
+ // Calculate what the new leverage will be (should be very close to currentLeverage)
615
+ const newLeverage = calculateWeightedLeverage(
616
+ newCollateral,
617
+ collateralPrice,
618
+ collateralWeight,
619
+ newDebt,
620
+ debtPrice,
621
+ debtWeight,
622
+ );
623
+
624
+ return {
625
+ debtToRepay,
626
+ newCollateral,
627
+ newDebt,
628
+ currentLeverage,
629
+ newLeverage,
630
+ };
631
+ }
632
+
633
+ /**
634
+ * Calculates the health factor of a Marginfi account.
635
+ * Health Factor = Total Weighted Assets (Maintenance) / Total Weighted Liabilities (Maintenance)
636
+ * A health factor > 1 indicates solvency. Liquidation occurs when it approaches 1.
637
+ *
638
+ * @param assets - An array of the user's deposited assets with their values and maintenance weights.
639
+ * @param liabilities - An array of the user's borrowed liabilities with their values and maintenance weights.
640
+ * @returns The health factor. Returns Infinity if there are no liabilities.
641
+ */
642
+ export function calculateHealthFactor(
643
+ assets: {
644
+ assetAmountUi: number;
645
+ assetPrice: number;
646
+ maintAssetWeight: number;
647
+ }[],
648
+ liabilities: {
649
+ liabilityAmountUi: number;
650
+ liabilityPrice: number;
651
+ maintLiabilityWeight: number;
652
+ }[],
653
+ ): number {
654
+ let totalWeightedAssets = 0;
655
+ for (const asset of assets) {
656
+ totalWeightedAssets +=
657
+ asset.assetAmountUi * asset.assetPrice * asset.maintAssetWeight;
658
+ }
659
+
660
+ let totalWeightedLiabilities = 0;
661
+ for (const liability of liabilities) {
662
+ totalWeightedLiabilities +=
663
+ liability.liabilityAmountUi *
664
+ liability.liabilityPrice *
665
+ liability.maintLiabilityWeight;
666
+ }
667
+
668
+ // if no borrows, 100% health
669
+ if (totalWeightedLiabilities === 0) {
670
+ return 1;
671
+ }
672
+
673
+ return totalWeightedAssets / totalWeightedLiabilities;
674
+ }
675
+
676
+ /**
677
+ * Calculate USD value from UI amount and price
678
+ * @param uiAmount Amount in UI format (e.g., 1.5 USDC)
679
+ * @param price Price of the token in USD
680
+ * @returns USD value
681
+ */
682
+ export function calculateUsdValue(uiAmount: number, price: number): number {
683
+ return uiAmount * price;
684
+ }