@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/.prettierignore +1 -0
- package/makefile +12 -0
- package/package.json +32 -0
- package/src/addresses.ts +206 -0
- package/src/idl/clend.ts +7509 -0
- package/src/index.ts +31 -0
- package/src/instructions.ts +466 -0
- package/src/jupUtils.ts +347 -0
- package/src/jupiterUtils.ts +288 -0
- package/src/logger.ts +21 -0
- package/src/math.ts +684 -0
- package/src/mockJupiterUtils.ts +109 -0
- package/src/rpc.ts +1296 -0
- package/src/state.ts +512 -0
- package/src/utils.ts +249 -0
- package/test/bank.test.ts +95 -0
- package/test/interest-rate.test.ts +114 -0
- package/test/leverage.test.ts +867 -0
- package/test/token-amounts.test.ts +73 -0
- package/tsconfig.json +17 -0
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
|
+
}
|