@strkfarm/sdk 1.1.39 → 1.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.global.js +723 -179
- package/dist/index.browser.mjs +722 -176
- package/dist/index.d.ts +126 -13
- package/dist/index.js +723 -176
- package/dist/index.mjs +722 -176
- package/package.json +1 -1
- package/src/global.ts +18 -0
- package/src/modules/avnu.ts +5 -4
- package/src/modules/harvests.ts +16 -15
- package/src/strategies/ekubo-cl-vault.tsx +255 -79
- package/src/strategies/universal-adapters/baseAdapter.ts +184 -2
- package/src/strategies/universal-adapters/vesu-adapter.ts +34 -17
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +322 -0
- package/src/strategies/universal-lst-muliplier-strategy.tsx +226 -67
- package/src/strategies/universal-strategy.tsx +5 -5
- package/src/utils/health-factor-math.ts +83 -0
- package/src/utils/math-utils.ts +150 -0
|
@@ -11,9 +11,12 @@ import { assert, logger } from "@/utils";
|
|
|
11
11
|
import { SingleTokenInfo } from "./base-strategy";
|
|
12
12
|
import { Call, Contract, uint256 } from "starknet";
|
|
13
13
|
import ERC4626Abi from "@/data/erc4626.abi.json";
|
|
14
|
+
import { HealthFactorMath } from "@/utils/health-factor-math";
|
|
15
|
+
import { findMaxInputWithSlippage } from "@/utils/math-utils";
|
|
14
16
|
|
|
15
17
|
export interface HyperLSTStrategySettings extends UniversalStrategySettings {
|
|
16
18
|
borrowable_assets: TokenInfo[];
|
|
19
|
+
underlyingToken: TokenInfo;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTStrategySettings> {
|
|
@@ -34,8 +37,7 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
asset() {
|
|
37
|
-
|
|
38
|
-
return vesuAdapter1.config.collateral;
|
|
40
|
+
return this.getVesuSameTokenAdapter().config.collateral;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
getTag() {
|
|
@@ -44,7 +46,7 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
44
46
|
|
|
45
47
|
// Vesu adapter with LST and base token match
|
|
46
48
|
getVesuSameTokenAdapter() {
|
|
47
|
-
const baseAdapter = this.getAdapter(
|
|
49
|
+
const baseAdapter = this.getAdapter(getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, this.metadata.additionalInfo.underlyingToken.symbol)) as VesuAdapter;
|
|
48
50
|
baseAdapter.networkConfig = this.config;
|
|
49
51
|
baseAdapter.pricer = this.pricer;
|
|
50
52
|
return baseAdapter;
|
|
@@ -102,18 +104,55 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
102
104
|
isDeposit: boolean,
|
|
103
105
|
leg1DepositAmount: Web3Number
|
|
104
106
|
}) {
|
|
107
|
+
assert(params.isDeposit, 'Only deposit is supported in getAvnuSwapMultiplyCall')
|
|
105
108
|
// TODO use a varibale for 1.02
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
const maxBorrowableAmounts = await this.getMaxBorrowableAmount({ isAPYComputation: false });
|
|
110
|
+
const allVesuAdapters = this.getVesuAdapters();
|
|
111
|
+
let remainingAmount = params.leg1DepositAmount;
|
|
112
|
+
const lstExRate = await this.getLSTExchangeRate();
|
|
113
|
+
const baseAssetPrice = await this.pricer.getPrice(this.getLSTUnderlyingTokenInfo().symbol);
|
|
114
|
+
const lstPrice = baseAssetPrice.price * lstExRate;
|
|
115
|
+
for (let i = 0; i < maxBorrowableAmounts.maxBorrowables.length; i++) {
|
|
116
|
+
const maxBorrowable = maxBorrowableAmounts.maxBorrowables[i];
|
|
117
|
+
const vesuAdapter = allVesuAdapters.find(adapter => adapter.config.debt.address.eq(maxBorrowable.borrowableAsset.address));
|
|
118
|
+
if (!vesuAdapter) {
|
|
119
|
+
throw new Error(`${this.getTag()}::getAvnuSwapMultiplyCall: vesuAdapter not found for borrowable asset: ${maxBorrowable.borrowableAsset.symbol}`);
|
|
120
|
+
}
|
|
121
|
+
const maxLTV = await vesuAdapter.getLTVConfig(this.config);
|
|
122
|
+
const debtPrice = await this.pricer.getPrice(maxBorrowable.borrowableAsset.symbol);
|
|
123
|
+
const maxAmountToDeposit = HealthFactorMath.getMinCollateralRequiredOnLooping(
|
|
124
|
+
maxBorrowable.amount,
|
|
125
|
+
debtPrice.price,
|
|
126
|
+
this.metadata.additionalInfo.targetHealthFactor,
|
|
127
|
+
maxLTV,
|
|
128
|
+
lstPrice,
|
|
129
|
+
this.asset()
|
|
130
|
+
)
|
|
131
|
+
const amountToDeposit = remainingAmount.minimum(maxAmountToDeposit);
|
|
132
|
+
logger.verbose(`${this.getTag()}::getAvnuSwapMultiplyCall::${vesuAdapter.config.debt.symbol}:: remainingAmount: ${remainingAmount}, amountToDeposit: ${amountToDeposit}, depositAmount: ${amountToDeposit}, maxBorrowable: ${maxBorrowable.amount}`);
|
|
133
|
+
const call = await this._getAvnuDepositSwapLegCall({
|
|
134
|
+
isDeposit: params.isDeposit,
|
|
135
|
+
// adjust decimals of debt asset
|
|
136
|
+
leg1DepositAmount: amountToDeposit,
|
|
137
|
+
minHF: 1.1, // undo
|
|
138
|
+
vesuAdapter: vesuAdapter
|
|
139
|
+
});
|
|
140
|
+
remainingAmount = remainingAmount.minus(amountToDeposit);
|
|
141
|
+
|
|
142
|
+
// return the first possible call because computing all calls at a time
|
|
143
|
+
// is not efficinet for swaps
|
|
144
|
+
return {call, vesuAdapter};
|
|
145
|
+
}
|
|
146
|
+
throw new Error(`${this.getTag()}::getAvnuSwapMultiplyCall: no calls found`);
|
|
110
147
|
}
|
|
111
148
|
|
|
112
|
-
|
|
149
|
+
async _getAvnuDepositSwapLegCall(params: {
|
|
113
150
|
isDeposit: boolean,
|
|
114
151
|
leg1DepositAmount: Web3Number,
|
|
115
|
-
minHF: number // e.g. 1.01
|
|
152
|
+
minHF: number, // e.g. 1.01
|
|
153
|
+
vesuAdapter: VesuAdapter
|
|
116
154
|
}) {
|
|
155
|
+
const { vesuAdapter } = params;
|
|
117
156
|
logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall params: ${JSON.stringify(params)}`);
|
|
118
157
|
assert(params.isDeposit, 'Only deposit is supported in _getAvnuDepositSwapLegCall')
|
|
119
158
|
// add collateral
|
|
@@ -121,11 +160,11 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
121
160
|
// approve and swap strk
|
|
122
161
|
// add collateral again
|
|
123
162
|
|
|
124
|
-
|
|
125
|
-
const legLTV = await
|
|
163
|
+
|
|
164
|
+
const legLTV = await vesuAdapter.getLTVConfig(this.config);
|
|
126
165
|
logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall legLTV: ${legLTV}`);
|
|
127
|
-
const existingPositions = await
|
|
128
|
-
const collateralisation = await
|
|
166
|
+
const existingPositions = await vesuAdapter.getPositions(this.config);
|
|
167
|
+
const collateralisation = await vesuAdapter.getCollateralization(this.config);
|
|
129
168
|
const existingCollateralInfo = existingPositions[0];
|
|
130
169
|
const existingDebtInfo = existingPositions[1];
|
|
131
170
|
logger.debug(`${this.getTag()}::_getAvnuDepositSwapLegCall existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
|
|
@@ -139,11 +178,44 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
139
178
|
logger.debug(`${this.getTag()}::_getAvnuDepositSwapLegCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`);
|
|
140
179
|
|
|
141
180
|
|
|
181
|
+
const debtTokenInfo = vesuAdapter.config.debt;
|
|
182
|
+
let newDepositAmount = params.leg1DepositAmount;
|
|
142
183
|
const totalCollateral = existingCollateralInfo.amount.plus(params.leg1DepositAmount);
|
|
143
184
|
logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall totalCollateral: ${totalCollateral}`);
|
|
144
|
-
const totalDebtAmount =
|
|
145
|
-
|
|
146
|
-
|
|
185
|
+
const totalDebtAmount = new Web3Number(
|
|
186
|
+
totalCollateral.multipliedBy(collateralPrice).multipliedBy(legLTV).dividedBy(debtPrice).dividedBy(params.minHF).toString(),
|
|
187
|
+
debtTokenInfo.decimals
|
|
188
|
+
);
|
|
189
|
+
let debtAmount = totalDebtAmount.minus(existingDebtInfo.amount);
|
|
190
|
+
logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall totalDebtAmount: ${totalDebtAmount}, initial computed debt: ${debtAmount}`);
|
|
191
|
+
const maxBorrowable = await this.getMaxBorrowableAmountByVesuAdapter(vesuAdapter, false);
|
|
192
|
+
|
|
193
|
+
// if the debt amount is greater than 0 and the max borrowable amount is 0, skip
|
|
194
|
+
if (debtAmount.gt(0) && maxBorrowable.amount.eq(0)) {
|
|
195
|
+
logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall maxBorrowable is 0, skipping`);
|
|
196
|
+
return undefined;
|
|
197
|
+
} else if (debtAmount.gt(0) && maxBorrowable.amount.gt(0)) {
|
|
198
|
+
debtAmount = maxBorrowable.amount.minimum(debtAmount);
|
|
199
|
+
const newDebtUSDValue = debtAmount.multipliedBy(debtPrice);
|
|
200
|
+
const totalCollateralRequired = HealthFactorMath.getCollateralRequired(
|
|
201
|
+
debtAmount.plus(existingDebtInfo.amount),
|
|
202
|
+
debtPrice,
|
|
203
|
+
params.minHF,
|
|
204
|
+
legLTV,
|
|
205
|
+
collateralPrice,
|
|
206
|
+
this.asset()
|
|
207
|
+
);
|
|
208
|
+
newDepositAmount = totalCollateralRequired.minus(existingCollateralInfo.amount);
|
|
209
|
+
if (newDepositAmount.lt(0)) {
|
|
210
|
+
throw new Error(`${this.getTag()}::_getAvnuDepositSwapLegCall newDepositAmount is less than 0, newDepositAmount: ${newDepositAmount}, totalCollateralRequired: ${totalCollateralRequired}, existingCollateralInfo.amount: ${existingCollateralInfo.amount}`);
|
|
211
|
+
}
|
|
212
|
+
if (newDebtUSDValue.toNumber() < 100) {
|
|
213
|
+
// too less debt, skip
|
|
214
|
+
logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall newDebtUSDValue is less than 100, skipping`);
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
147
219
|
logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall debtAmount: ${debtAmount}`);
|
|
148
220
|
if (debtAmount.lt(0)) {
|
|
149
221
|
// this is to unwind the position to optimal HF.
|
|
@@ -156,33 +228,35 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
156
228
|
assert(calls.length == 1, `Expected 1 call for unwind, got ${calls.length}`);
|
|
157
229
|
return calls[0];
|
|
158
230
|
}
|
|
231
|
+
console.log(`debtAmount`, debtAmount.toWei(), params.leg1DepositAmount.toWei());
|
|
159
232
|
const STEP0 = UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1;
|
|
160
233
|
const manage0Info = this.getProofs<ApproveCallParams>(STEP0);
|
|
161
234
|
const manageCall0 = manage0Info.callConstructor({
|
|
162
|
-
amount:
|
|
235
|
+
amount: newDepositAmount
|
|
163
236
|
});
|
|
164
|
-
const STEP1 = UNIVERSAL_MANAGE_IDS.VESU_LEG1;
|
|
237
|
+
const STEP1 = getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, vesuAdapter.config.debt.symbol);
|
|
165
238
|
const manage1Info = this.getProofs<VesuModifyPositionCallParams>(STEP1);
|
|
166
239
|
const manageCall1 = manage1Info.callConstructor(VesuAdapter.getDefaultModifyPositionCallParams({
|
|
167
|
-
collateralAmount:
|
|
240
|
+
collateralAmount: newDepositAmount,
|
|
168
241
|
isAddCollateral: params.isDeposit,
|
|
169
242
|
debtAmount: debtAmount,
|
|
170
243
|
isBorrow: params.isDeposit
|
|
171
244
|
}));
|
|
245
|
+
|
|
246
|
+
console.log(`manageCall1`, manageCall1.call, debtAmount.toWei(), newDepositAmount.toWei());
|
|
172
247
|
|
|
173
248
|
const proofIds: string[] = [STEP0, STEP1];
|
|
174
249
|
const manageCalls: ManageCall[] = [manageCall0, manageCall1];
|
|
175
250
|
|
|
176
251
|
// approve and swap to LST using avnu
|
|
177
|
-
// todo add non-zero check
|
|
178
252
|
if (debtAmount.gt(0)) {
|
|
179
|
-
const STEP2 = LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_DEPOSIT;
|
|
253
|
+
const STEP2 = getAvnuManageIDs(LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_DEPOSIT, vesuAdapter.config.debt.symbol);
|
|
180
254
|
const manage2Info = this.getProofs<ApproveCallParams>(STEP2);
|
|
181
255
|
const manageCall2 = manage2Info.callConstructor({
|
|
182
256
|
amount: debtAmount
|
|
183
257
|
});
|
|
184
258
|
|
|
185
|
-
const debtTokenInfo =
|
|
259
|
+
const debtTokenInfo = vesuAdapter.config.debt;
|
|
186
260
|
const lstTokenInfo = this.asset();
|
|
187
261
|
const avnuModule = new AvnuWrapper();
|
|
188
262
|
const quote = await avnuModule.getQuotes(
|
|
@@ -202,7 +276,7 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
202
276
|
minAmountWei
|
|
203
277
|
);
|
|
204
278
|
logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall swapInfo: ${JSON.stringify(swapInfo)}`);
|
|
205
|
-
const STEP3 = LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_DEPOSIT;
|
|
279
|
+
const STEP3 = getAvnuManageIDs(LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_DEPOSIT, vesuAdapter.config.debt.symbol);
|
|
206
280
|
const manage3Info = this.getProofs<AvnuSwapCallParams>(STEP3);
|
|
207
281
|
const manageCall3 = manage3Info.callConstructor({
|
|
208
282
|
props: swapInfo
|
|
@@ -226,7 +300,7 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
226
300
|
amount: minAmount
|
|
227
301
|
});
|
|
228
302
|
|
|
229
|
-
const STEP5 = UNIVERSAL_MANAGE_IDS.VESU_LEG1;
|
|
303
|
+
const STEP5 = getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, vesuAdapter.config.debt.symbol);
|
|
230
304
|
const manage5Info = this.getProofs<VesuModifyPositionCallParams>(STEP5);
|
|
231
305
|
const manageCall5 = manage5Info.callConstructor(VesuAdapter.getDefaultModifyPositionCallParams({
|
|
232
306
|
collateralAmount: minAmount,
|
|
@@ -245,18 +319,31 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
245
319
|
|
|
246
320
|
// todo unwind or not deposit when the yield is bad.
|
|
247
321
|
|
|
248
|
-
async getLSTMultiplierRebalanceCall(): Promise<{ shouldRebalance: boolean,
|
|
249
|
-
|
|
250
|
-
|
|
322
|
+
async getLSTMultiplierRebalanceCall(): Promise<{ shouldRebalance: boolean, manageCalls: {vesuAdapter: VesuAdapter, manageCall: Call}[] }> {
|
|
323
|
+
let shouldRebalance = false;
|
|
324
|
+
const calls: {vesuAdapter: VesuAdapter, manageCall: Call}[] = [];
|
|
325
|
+
// todo undo
|
|
326
|
+
const allVesuAdapters = this.getVesuAdapters().filter(vesuAdapter => vesuAdapter.config.debt.symbol === 'LBTC');
|
|
327
|
+
for (const vesuAdapter of allVesuAdapters) {
|
|
328
|
+
const call = await this._getLSTMultiplierRebalanceCall(vesuAdapter);
|
|
329
|
+
if (call.shouldRebalance && call.manageCall) {
|
|
330
|
+
shouldRebalance = true;
|
|
331
|
+
calls.push({vesuAdapter, manageCall: call.manageCall});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return { shouldRebalance, manageCalls: calls };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async _getLSTMultiplierRebalanceCall(vesuAdapter: VesuAdapter): Promise<{ shouldRebalance: boolean, manageCall: Call | undefined }> {
|
|
338
|
+
const positions = await vesuAdapter.getPositions(this.config);
|
|
339
|
+
assert(positions.length == 2, 'Rebalance call is only supported for 2 positions');
|
|
251
340
|
const existingCollateralInfo = positions[0];
|
|
252
341
|
const existingDebtInfo = positions[1];
|
|
253
|
-
const unusedBalance =
|
|
254
|
-
const
|
|
342
|
+
const unusedBalance = await this.getUnusedBalance();
|
|
343
|
+
const healthFactor = await vesuAdapter.getHealthFactor();
|
|
255
344
|
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
const collateralisation = await vesuAdapter1.getCollateralization(this.config);
|
|
259
|
-
logger.debug(`${this.getTag()}::getVesuMultiplyCall existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
|
|
345
|
+
const collateralisation = await vesuAdapter.getCollateralization(this.config);
|
|
346
|
+
logger.debug(`${this.getTag()}::getVesuMultiplyCall::${vesuAdapter.config.debt.symbol} existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
|
|
260
347
|
existingDebtInfo: ${JSON.stringify(existingDebtInfo)}, collateralisation: ${JSON.stringify(collateralisation)}`);
|
|
261
348
|
|
|
262
349
|
// - Prices as seen by Vesu contracts, ideal for HF math
|
|
@@ -265,15 +352,17 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
265
352
|
const collateralPrice = collateralisation[0].usdValue > 0 ? collateralisation[0].usdValue / existingCollateralInfo.amount.toNumber() : 1;
|
|
266
353
|
const debtPrice = collateralisation[1].usdValue > 0 ? collateralisation[1].usdValue / existingDebtInfo.amount.toNumber() : 1;
|
|
267
354
|
logger.debug(`${this.getTag()}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`);
|
|
355
|
+
logger.debug(`${this.getTag()}::getVesuMultiplyCall healthFactor: ${healthFactor}`);
|
|
268
356
|
|
|
269
357
|
const isHFTooLow = healthFactor < this.metadata.additionalInfo.minHealthFactor;
|
|
270
358
|
const isHFTooHigh = healthFactor > this.metadata.additionalInfo.targetHealthFactor + 0.05;
|
|
271
|
-
if (isHFTooLow || isHFTooHigh) {
|
|
359
|
+
if (isHFTooLow || isHFTooHigh || 1) {
|
|
272
360
|
// use unused collateral to target more.
|
|
273
361
|
const manageCall = await this._getAvnuDepositSwapLegCall({
|
|
274
362
|
isDeposit: true,
|
|
275
363
|
leg1DepositAmount: unusedBalance.amount,
|
|
276
|
-
minHF: 1.02 // todo, shouldnt use this 1.02 HF, if there isn;t more looping left.
|
|
364
|
+
minHF: 1.02, // todo, shouldnt use this 1.02 HF, if there isn;t more looping left.
|
|
365
|
+
vesuAdapter
|
|
277
366
|
})
|
|
278
367
|
return { shouldRebalance: true, manageCall };
|
|
279
368
|
} else {
|
|
@@ -318,7 +407,7 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
318
407
|
const lstTruePrice = await this.getLSTExchangeRate();
|
|
319
408
|
// during buy, the purchase should always be <= true LST price.
|
|
320
409
|
const minOutputAmount = amountInUnderlying.dividedBy(lstTruePrice).multipliedBy(0.99979); // minus 0.021% to account for avnu fees
|
|
321
|
-
return minOutputAmount;
|
|
410
|
+
return new Web3Number(minOutputAmount.toString(), this.asset().decimals);
|
|
322
411
|
}
|
|
323
412
|
|
|
324
413
|
private async _getMinOutputAmountLSTSell(amountInLST: Web3Number) {
|
|
@@ -418,22 +507,58 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
418
507
|
return vesuAdapter1.config.debt;
|
|
419
508
|
}
|
|
420
509
|
|
|
421
|
-
async getMaxBorrowableAmount() {
|
|
510
|
+
async getMaxBorrowableAmount(params: { isAPYComputation: boolean } = { isAPYComputation: false }) {
|
|
422
511
|
const vesuAdapters = this.getVesuAdapters();
|
|
423
512
|
let netMaxBorrowableAmount = Web3Number.fromWei("0", this.getLSTUnderlyingTokenInfo().decimals);
|
|
424
|
-
const maxBorrowables: {amount: Web3Number, borrowableAsset: TokenInfo}[] = [];
|
|
425
|
-
const lstAPY = await this.getLSTAPR(this.getLSTUnderlyingTokenInfo().address);
|
|
426
|
-
const maxInterestRate = lstAPY * 0.8;
|
|
513
|
+
const maxBorrowables: {amount: Web3Number, dexSwappableAmount: Web3Number, maxBorrowableAmount: Web3Number, borrowableAsset: TokenInfo}[] = [];
|
|
427
514
|
for (const vesuAdapter of vesuAdapters) {
|
|
428
|
-
|
|
429
|
-
const debtCap = await vesuAdapter.getDebtCap(this.config);
|
|
430
|
-
maxBorrowables.push({amount: maxBorrowableAmount.minimum(debtCap), borrowableAsset: vesuAdapter.config.debt});
|
|
515
|
+
maxBorrowables.push(await this.getMaxBorrowableAmountByVesuAdapter(vesuAdapter, params.isAPYComputation));
|
|
431
516
|
}
|
|
432
517
|
maxBorrowables.sort((a, b) => b.amount.toNumber() - a.amount.toNumber());
|
|
433
518
|
netMaxBorrowableAmount = maxBorrowables.reduce((acc, curr) => acc.plus(curr.amount), Web3Number.fromWei("0", this.getLSTUnderlyingTokenInfo().decimals));
|
|
434
519
|
return {netMaxBorrowableAmount, maxBorrowables};
|
|
435
520
|
}
|
|
436
521
|
|
|
522
|
+
// recursively, using binary search computes max swappable.
|
|
523
|
+
// @dev assumes 1 token of from == 1 token of to
|
|
524
|
+
async getMaxSwappableWithMaxSlippage(fromToken: TokenInfo, toToken: TokenInfo, maxSlippage: number, maxAmount: Web3Number) {
|
|
525
|
+
const output = await findMaxInputWithSlippage({
|
|
526
|
+
apiGetOutput: async (inputAmount: number): Promise<number> => {
|
|
527
|
+
const ekuboQuoter = new EkuboQuoter(this.config);
|
|
528
|
+
await new Promise(resolve => setTimeout(resolve, 1000)); // artificial delay, to avoid rate limit
|
|
529
|
+
const quote = await ekuboQuoter.getQuote(fromToken.address.address, toToken.address.address, new Web3Number(inputAmount.toFixed(9), fromToken.decimals));
|
|
530
|
+
return Web3Number.fromWei(quote.total_calculated.toString(), toToken.decimals).toNumber();
|
|
531
|
+
},
|
|
532
|
+
maxInput: maxAmount.toNumber(),
|
|
533
|
+
maxSlippagePercent: maxSlippage,
|
|
534
|
+
tolerance: 0.001,
|
|
535
|
+
referenceRate: 1,
|
|
536
|
+
});
|
|
537
|
+
return new Web3Number(output.optimalInput, fromToken.decimals);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async getMaxBorrowableAmountByVesuAdapter(vesuAdapter: VesuAdapter, isAPYComputation: boolean) {
|
|
541
|
+
const lstAPY = await this.getLSTAPR(this.getLSTUnderlyingTokenInfo().address);
|
|
542
|
+
const maxInterestRate = lstAPY * 0.8;
|
|
543
|
+
const maxBorrowableAmount = await vesuAdapter.getMaxBorrowableByInterestRate(this.config, vesuAdapter.config.debt, maxInterestRate);
|
|
544
|
+
const debtCap = await vesuAdapter.getDebtCap(this.config);
|
|
545
|
+
|
|
546
|
+
const maxBorrowable = maxBorrowableAmount.minimum(debtCap).multipliedBy(0.999);
|
|
547
|
+
// Dont compute precise max swappable for APY computation
|
|
548
|
+
if (vesuAdapter.config.debt.address.eq(this.getLSTUnderlyingTokenInfo().address) || isAPYComputation) {
|
|
549
|
+
return {amount: maxBorrowable, dexSwappableAmount: maxBorrowable, maxBorrowableAmount: maxBorrowable, borrowableAsset: vesuAdapter.config.debt};
|
|
550
|
+
}
|
|
551
|
+
// Want < 0.02% slippage
|
|
552
|
+
try {
|
|
553
|
+
const maxSwappable = await this.getMaxSwappableWithMaxSlippage(vesuAdapter.config.debt, this.getLSTUnderlyingTokenInfo(), 0.0002, maxBorrowable);
|
|
554
|
+
return {amount: maxBorrowable.minimum(maxSwappable), dexSwappableAmount: maxSwappable, maxBorrowableAmount: maxBorrowable, borrowableAsset: vesuAdapter.config.debt};
|
|
555
|
+
} catch (error) {
|
|
556
|
+
logger.warn(`${this.getTag()}: Failed to get max swappable: ${error}`);
|
|
557
|
+
const maxSwappable = Web3Number.fromWei("0", vesuAdapter.config.debt.decimals);
|
|
558
|
+
return {amount: maxBorrowable.minimum(maxSwappable), dexSwappableAmount: maxSwappable, maxBorrowableAmount: maxBorrowable, borrowableAsset: vesuAdapter.config.debt};
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
437
562
|
// todo how much to unwind to get back healthy APY zone again
|
|
438
563
|
// if net APY < LST APR + 0.5%, we need to unwind to get back to LST APR + 1% atleast or 0 vesu position
|
|
439
564
|
// For xSTRK, simply deposit in Vesu if looping is not viable
|
|
@@ -459,7 +584,7 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
459
584
|
// todo undo this
|
|
460
585
|
async netAPY(): Promise<{ net: number; splits: { apy: number; id: string; }[]; }> {
|
|
461
586
|
const unusedBalance = await this.getUnusedBalance();
|
|
462
|
-
const maxNewDeposits = await this.maxNewDeposits();
|
|
587
|
+
const maxNewDeposits = await this.maxNewDeposits({ isAPYComputation: true });
|
|
463
588
|
const lstAPY = await this.getLSTAPR(this.getLSTUnderlyingTokenInfo().address);
|
|
464
589
|
|
|
465
590
|
// if unused balance is > max servicable from loan, we are limited by the max borrowing we can do
|
|
@@ -482,8 +607,8 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
482
607
|
}
|
|
483
608
|
}
|
|
484
609
|
|
|
485
|
-
async maxNewDeposits() {
|
|
486
|
-
const maxBorrowableAmounts = await this.getMaxBorrowableAmount();
|
|
610
|
+
async maxNewDeposits(params: { isAPYComputation: boolean } = { isAPYComputation: false }) {
|
|
611
|
+
const maxBorrowableAmounts = await this.getMaxBorrowableAmount(params);
|
|
487
612
|
|
|
488
613
|
let ltv: number | undefined = undefined;
|
|
489
614
|
for (let adapter of this.getVesuAdapters()) {
|
|
@@ -617,7 +742,7 @@ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTSt
|
|
|
617
742
|
});
|
|
618
743
|
|
|
619
744
|
// deposit and borrow or repay and withdraw
|
|
620
|
-
const STEP3_ID = LST_MULTIPLIER_MANAGE_IDS.MULTIPLY_VESU;
|
|
745
|
+
const STEP3_ID = getVesuLegId(LST_MULTIPLIER_MANAGE_IDS.MULTIPLY_VESU, vesuAdapter1.config.debt.symbol);
|
|
621
746
|
const manage3Info = this.getProofs<VesuMultiplyCallParams>(STEP3_ID);
|
|
622
747
|
const multiplyParams: VesuMultiplyCallParams = params.isIncrease ? {
|
|
623
748
|
isIncrease: true,
|
|
@@ -710,15 +835,24 @@ function getDescription(tokenSymbol: string, underlyingSymbol: string) {
|
|
|
710
835
|
return VaultDescription(tokenSymbol, underlyingSymbol);
|
|
711
836
|
}
|
|
712
837
|
|
|
838
|
+
|
|
713
839
|
enum LST_MULTIPLIER_MANAGE_IDS {
|
|
714
840
|
MULTIPLE_APPROVE = 'multiple_approve',
|
|
715
841
|
MULTIPLY_VESU = 'multiply_vesu',
|
|
716
842
|
SWITCH_DELEGATION_ON = 'switch_delegation_on',
|
|
717
843
|
SWITCH_DELEGATION_OFF = 'switch_delegation_off',
|
|
718
|
-
AVNU_MULTIPLY_APPROVE_DEPOSIT = '
|
|
719
|
-
AVNU_MULTIPLY_SWAP_DEPOSIT = '
|
|
720
|
-
AVNU_MULTIPLY_APPROVE_WITHDRAW = '
|
|
721
|
-
AVNU_MULTIPLY_SWAP_WITHDRAW = '
|
|
844
|
+
AVNU_MULTIPLY_APPROVE_DEPOSIT = 'avnu_mul_approve_dep',
|
|
845
|
+
AVNU_MULTIPLY_SWAP_DEPOSIT = 'avnu_mul_swap_dep',
|
|
846
|
+
AVNU_MULTIPLY_APPROVE_WITHDRAW = 'avnu_mul_approve_withdr',
|
|
847
|
+
AVNU_MULTIPLY_SWAP_WITHDRAW = 'avnu_mul_swap_withdr',
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function getAvnuManageIDs(baseID: LST_MULTIPLIER_MANAGE_IDS, debtTokenSymbol: string) {
|
|
851
|
+
return `${baseID}_${debtTokenSymbol.toLowerCase()}`;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function getVesuLegId(baseID: string, debtTokenSymbol: string) {
|
|
855
|
+
return `${baseID}_${debtTokenSymbol.toLowerCase()}`;
|
|
722
856
|
}
|
|
723
857
|
|
|
724
858
|
function getLooperSettings(
|
|
@@ -737,7 +871,7 @@ function getLooperSettings(
|
|
|
737
871
|
collateral: lstToken,
|
|
738
872
|
debt: underlyingToken,
|
|
739
873
|
vaultAllocator: vaultSettings.vaultAllocator,
|
|
740
|
-
id: UNIVERSAL_MANAGE_IDS.VESU_LEG1
|
|
874
|
+
id: getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, underlyingToken.symbol)
|
|
741
875
|
})
|
|
742
876
|
|
|
743
877
|
const commonAdapter = new CommonAdapter({
|
|
@@ -748,18 +882,10 @@ function getLooperSettings(
|
|
|
748
882
|
vaultAllocator: vaultSettings.vaultAllocator,
|
|
749
883
|
})
|
|
750
884
|
|
|
751
|
-
// vesu looping
|
|
752
|
-
const { isV2, addr:poolAddr } = getVesuSingletonAddress(pool1);
|
|
753
|
-
const VESU_MULTIPLY = isV2 ? vesuAdapterLST.VESU_MULTIPLY : vesuAdapterLST.VESU_MULTIPLY_V1;
|
|
754
|
-
vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(lstToken.address, VESU_MULTIPLY, LST_MULTIPLIER_MANAGE_IDS.MULTIPLE_APPROVE).bind(commonAdapter));
|
|
755
|
-
vaultSettings.leafAdapters.push(vesuAdapterLST.getMultiplyAdapter(LST_MULTIPLIER_MANAGE_IDS.MULTIPLY_VESU).bind(vesuAdapterLST));
|
|
756
|
-
vaultSettings.leafAdapters.push(vesuAdapterLST.getVesuModifyDelegationAdapter(LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_ON).bind(vesuAdapterLST));
|
|
757
|
-
vaultSettings.leafAdapters.push(vesuAdapterLST.getVesuModifyDelegationAdapter(LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_OFF).bind(vesuAdapterLST));
|
|
758
|
-
|
|
759
885
|
// Useful for returning adapter class objects that can compute
|
|
760
886
|
// certain things for us (e.g. positions, hfs)
|
|
761
887
|
vaultSettings.adapters.push(...[{
|
|
762
|
-
id:
|
|
888
|
+
id: getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, underlyingToken.symbol),
|
|
763
889
|
adapter: vesuAdapterLST
|
|
764
890
|
},{
|
|
765
891
|
id: UNIVERSAL_ADAPTERS.COMMON,
|
|
@@ -767,14 +893,42 @@ function getLooperSettings(
|
|
|
767
893
|
}])
|
|
768
894
|
|
|
769
895
|
// avnu multiply
|
|
770
|
-
|
|
771
|
-
|
|
896
|
+
const { isV2, addr:poolAddr } = getVesuSingletonAddress(pool1);
|
|
897
|
+
// vesu multiply looping
|
|
898
|
+
const VESU_MULTIPLY = isV2 ? vesuAdapterLST.VESU_MULTIPLY : vesuAdapterLST.VESU_MULTIPLY_V1;
|
|
899
|
+
vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(lstToken.address, VESU_MULTIPLY, LST_MULTIPLIER_MANAGE_IDS.MULTIPLE_APPROVE).bind(commonAdapter));
|
|
900
|
+
vaultSettings.leafAdapters.push(vesuAdapterLST.getVesuModifyDelegationAdapter(LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_ON).bind(vesuAdapterLST));
|
|
901
|
+
vaultSettings.leafAdapters.push(vesuAdapterLST.getVesuModifyDelegationAdapter(LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_OFF).bind(vesuAdapterLST));
|
|
902
|
+
|
|
903
|
+
// approve lst once to avnu
|
|
772
904
|
vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(lstToken.address, AVNU_EXCHANGE, LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_WITHDRAW).bind(commonAdapter));
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
905
|
+
for (let borrowableAsset of vaultSettings.borrowable_assets) {
|
|
906
|
+
// in-efficient avnu swap looping (but good with endur integration)
|
|
907
|
+
const debtAsset = borrowableAsset;
|
|
908
|
+
const approve_debt_token_id = getAvnuManageIDs(LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_DEPOSIT, debtAsset.symbol);
|
|
909
|
+
const swap_debt_token_id = getAvnuManageIDs(LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_DEPOSIT, debtAsset.symbol);
|
|
910
|
+
const swap_lst_token_id = getAvnuManageIDs(LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_WITHDRAW, debtAsset.symbol);
|
|
911
|
+
vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(debtAsset.address, AVNU_EXCHANGE, approve_debt_token_id).bind(commonAdapter));
|
|
912
|
+
vaultSettings.leafAdapters.push(commonAdapter.getAvnuAdapter(debtAsset.address, lstToken.address, swap_debt_token_id, false).bind(commonAdapter));
|
|
913
|
+
vaultSettings.leafAdapters.push(commonAdapter.getAvnuAdapter(lstToken.address, debtAsset.address, swap_lst_token_id, false).bind(commonAdapter));
|
|
914
|
+
|
|
915
|
+
// approve LST to add collateral
|
|
916
|
+
const vesuAdapter = new VesuAdapter({
|
|
917
|
+
poolId: pool1,
|
|
918
|
+
collateral: lstToken,
|
|
919
|
+
debt: debtAsset,
|
|
920
|
+
vaultAllocator: vaultSettings.vaultAllocator,
|
|
921
|
+
id: getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, debtAsset.symbol)
|
|
922
|
+
});
|
|
923
|
+
vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(lstToken.address, poolAddr, UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1).bind(commonAdapter));
|
|
924
|
+
vaultSettings.leafAdapters.push(vesuAdapter.getModifyPosition.bind(vesuAdapter));
|
|
777
925
|
|
|
926
|
+
// Vesu multiply
|
|
927
|
+
const multiplID = getVesuLegId(LST_MULTIPLIER_MANAGE_IDS.MULTIPLY_VESU, debtAsset.symbol);
|
|
928
|
+
vaultSettings.leafAdapters.push(vesuAdapter.getMultiplyAdapter(multiplID).bind(vesuAdapter));
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
|
|
778
932
|
// to bridge liquidity back to vault (used by bring_liquidity)
|
|
779
933
|
vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(lstToken.address, vaultSettings.vaultAddress, UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY).bind(commonAdapter));
|
|
780
934
|
vaultSettings.leafAdapters.push(commonAdapter.getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY).bind(commonAdapter));
|
|
@@ -865,6 +1019,7 @@ const hyperxSTRK: HyperLSTStrategySettings = {
|
|
|
865
1019
|
targetHealthFactor: 1.1,
|
|
866
1020
|
minHealthFactor: 1.05,
|
|
867
1021
|
borrowable_assets: Global.getDefaultTokens().filter(token => token.symbol === 'STRK'),
|
|
1022
|
+
underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'STRK')!,
|
|
868
1023
|
}
|
|
869
1024
|
|
|
870
1025
|
const hyperxWBTC: HyperLSTStrategySettings = {
|
|
@@ -878,6 +1033,7 @@ const hyperxWBTC: HyperLSTStrategySettings = {
|
|
|
878
1033
|
targetHealthFactor: 1.1,
|
|
879
1034
|
minHealthFactor: 1.05,
|
|
880
1035
|
borrowable_assets: borrowableAssets.map(asset => Global.getDefaultTokens().find(token => token.symbol === asset)!),
|
|
1036
|
+
underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!,
|
|
881
1037
|
}
|
|
882
1038
|
|
|
883
1039
|
const hyperxtBTC: HyperLSTStrategySettings = {
|
|
@@ -891,6 +1047,7 @@ const hyperxtBTC: HyperLSTStrategySettings = {
|
|
|
891
1047
|
targetHealthFactor: 1.1,
|
|
892
1048
|
minHealthFactor: 1.05,
|
|
893
1049
|
borrowable_assets: borrowableAssets.map(asset => Global.getDefaultTokens().find(token => token.symbol === asset)!),
|
|
1050
|
+
underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'tBTC')!,
|
|
894
1051
|
}
|
|
895
1052
|
|
|
896
1053
|
const hyperxsBTC: HyperLSTStrategySettings = {
|
|
@@ -904,6 +1061,7 @@ const hyperxsBTC: HyperLSTStrategySettings = {
|
|
|
904
1061
|
targetHealthFactor: 1.1,
|
|
905
1062
|
minHealthFactor: 1.05,
|
|
906
1063
|
borrowable_assets: borrowableAssets.map(asset => Global.getDefaultTokens().find(token => token.symbol === asset)!),
|
|
1064
|
+
underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'solvBTC')!,
|
|
907
1065
|
}
|
|
908
1066
|
|
|
909
1067
|
const hyperxLBTC: HyperLSTStrategySettings = {
|
|
@@ -917,6 +1075,7 @@ const hyperxLBTC: HyperLSTStrategySettings = {
|
|
|
917
1075
|
targetHealthFactor: 1.1,
|
|
918
1076
|
minHealthFactor: 1.05,
|
|
919
1077
|
borrowable_assets: borrowableAssets.map(asset => Global.getDefaultTokens().find(token => token.symbol === asset)!),
|
|
1078
|
+
underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'LBTC')!,
|
|
920
1079
|
}
|
|
921
1080
|
|
|
922
1081
|
function getInvestmentSteps(lstSymbol: string, underlyingSymbol: string) {
|
|
@@ -2,7 +2,7 @@ import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
|
2
2
|
import { BaseStrategy, SingleActionAmount, SingleTokenInfo } from "./base-strategy";
|
|
3
3
|
import { PricerBase } from "@/modules/pricerBase";
|
|
4
4
|
import { FAQ, getNoRiskTags, IConfig, IStrategyMetadata, Protocols, RiskFactor, RiskType, VaultPosition } from "@/interfaces";
|
|
5
|
-
import { Call, CallData, Contract, num, uint256 } from "starknet";
|
|
5
|
+
import { BlockIdentifier, Call, CallData, Contract, num, uint256 } from "starknet";
|
|
6
6
|
import { VesuRebalanceSettings } from "./vesu-rebalance";
|
|
7
7
|
import { assert, LeafData, logger, StandardMerkleTree } from "@/utils";
|
|
8
8
|
import UniversalVaultAbi from '../data/universal-vault.abi.json';
|
|
@@ -452,11 +452,11 @@ export class UniversalStrategy<
|
|
|
452
452
|
return [vesuAdapter1, vesuAdapter2];
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
-
async getVesuPositions(): Promise<VaultPosition[]> {
|
|
455
|
+
async getVesuPositions(blockNumber: BlockIdentifier = 'latest'): Promise<VaultPosition[]> {
|
|
456
456
|
const adapters = this.getVesuAdapters();
|
|
457
457
|
const positions: VaultPosition[] = [];
|
|
458
458
|
for (const adapter of adapters) {
|
|
459
|
-
positions.push(...await adapter.getPositions(this.config));
|
|
459
|
+
positions.push(...await adapter.getPositions(this.config, blockNumber));
|
|
460
460
|
}
|
|
461
461
|
return positions;
|
|
462
462
|
}
|
|
@@ -540,8 +540,8 @@ export class UniversalStrategy<
|
|
|
540
540
|
return 0;
|
|
541
541
|
}
|
|
542
542
|
|
|
543
|
-
async getVesuHealthFactors() {
|
|
544
|
-
return await Promise.all(this.getVesuAdapters().map(v => v.getHealthFactor()));
|
|
543
|
+
async getVesuHealthFactors(blockNumber: BlockIdentifier = 'latest') {
|
|
544
|
+
return await Promise.all(this.getVesuAdapters().map(v => v.getHealthFactor(blockNumber)));
|
|
545
545
|
}
|
|
546
546
|
|
|
547
547
|
async computeRebalanceConditionAndReturnCalls(): Promise<Call[]> {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Web3Number } from "@/dataTypes";
|
|
2
|
+
import { TokenInfo } from "@/interfaces";
|
|
3
|
+
|
|
4
|
+
export class HealthFactorMath {
|
|
5
|
+
static getCollateralRequired(
|
|
6
|
+
debtAmount: Web3Number,
|
|
7
|
+
debtPrice: number,
|
|
8
|
+
targetHF: number,
|
|
9
|
+
maxLTV: number,
|
|
10
|
+
collateralPrice: number,
|
|
11
|
+
collateralTokenInfo: TokenInfo
|
|
12
|
+
) {
|
|
13
|
+
const numerator = debtAmount.multipliedBy(debtPrice).multipliedBy(targetHF);
|
|
14
|
+
const denominator = collateralPrice * maxLTV;
|
|
15
|
+
const collateralAmount = numerator.dividedBy(denominator);
|
|
16
|
+
const netCollateral = new Web3Number(collateralAmount.toString(), collateralTokenInfo.decimals);
|
|
17
|
+
return netCollateral;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static getMinCollateralRequiredOnLooping(
|
|
21
|
+
debtAmount: Web3Number,
|
|
22
|
+
debtPrice: number,
|
|
23
|
+
targetHF: number,
|
|
24
|
+
maxLTV: number,
|
|
25
|
+
collateralPrice: number,
|
|
26
|
+
collateralTokenInfo: TokenInfo
|
|
27
|
+
) {
|
|
28
|
+
const netCollateral = this.getCollateralRequired(debtAmount, debtPrice, targetHF, maxLTV, collateralPrice, collateralTokenInfo);
|
|
29
|
+
const collateralFromDebt = new Web3Number(debtAmount.multipliedBy(debtPrice).dividedBy(collateralPrice).toString(), collateralTokenInfo.decimals);
|
|
30
|
+
return netCollateral.minus(collateralFromDebt);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static getHealthFactor(
|
|
34
|
+
collateralAmount: Web3Number,
|
|
35
|
+
collateralPrice: number,
|
|
36
|
+
maxLTV: number,
|
|
37
|
+
debtAmount: Web3Number,
|
|
38
|
+
debtPrice: number,
|
|
39
|
+
) {
|
|
40
|
+
const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
|
|
41
|
+
const denominator = debtAmount.multipliedBy(debtPrice);
|
|
42
|
+
const healthFactor = numerator.dividedBy(denominator);
|
|
43
|
+
return healthFactor.toNumber();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static getMaxDebtAmountOnLooping(
|
|
47
|
+
collateralAmount: Web3Number,
|
|
48
|
+
collateralPrice: number,
|
|
49
|
+
maxLTV: number,
|
|
50
|
+
targetHF: number,
|
|
51
|
+
debtPrice: number,
|
|
52
|
+
debtTokenInfo: TokenInfo
|
|
53
|
+
) {
|
|
54
|
+
// lets say debt usd value is X
|
|
55
|
+
// HF = ((1 * cp) + X) * maxLTV / (X)
|
|
56
|
+
// => X * HF = ((1 * cp) + X) * maxLTV
|
|
57
|
+
// => X * (HF - maxLTV) = 1 * cp * maxLTV
|
|
58
|
+
// => X = 1 * cp * maxLTV / (HF - maxLTV)
|
|
59
|
+
const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
|
|
60
|
+
const denominator = targetHF - maxLTV;
|
|
61
|
+
const debtAmount = numerator.dividedBy(denominator);
|
|
62
|
+
return new Web3Number(debtAmount.toString(), debtTokenInfo.decimals);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static getMaxDebtAmount(
|
|
66
|
+
collateralAmount: Web3Number,
|
|
67
|
+
collateralPrice: number,
|
|
68
|
+
maxLTV: number,
|
|
69
|
+
targetHF: number,
|
|
70
|
+
debtPrice: number,
|
|
71
|
+
debtTokenInfo: TokenInfo
|
|
72
|
+
) {
|
|
73
|
+
// lets say debt usd value is X
|
|
74
|
+
// HF = ((1 * cp) + X) * maxLTV / (X)
|
|
75
|
+
// => X * HF = ((1 * cp) + X) * maxLTV
|
|
76
|
+
// => X * (HF - maxLTV) = 1 * cp * maxLTV
|
|
77
|
+
// => X = 1 * cp * maxLTV / (HF - maxLTV)
|
|
78
|
+
const numerator = collateralAmount.multipliedBy(collateralPrice).multipliedBy(maxLTV);
|
|
79
|
+
const denominator = targetHF * debtPrice;
|
|
80
|
+
const debtAmount = numerator.dividedBy(denominator);
|
|
81
|
+
return new Web3Number(debtAmount.toString(), debtTokenInfo.decimals);
|
|
82
|
+
}
|
|
83
|
+
}
|