@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.
@@ -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
- const vesuAdapter1 = this.getAdapter(UNIVERSAL_ADAPTERS.VESU_LEG1) as VesuAdapter;
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(UNIVERSAL_ADAPTERS.VESU_LEG1) as VesuAdapter;
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
- return this._getAvnuDepositSwapLegCall({
107
- ...params,
108
- minHF: 1.1 // undo
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
- private async _getAvnuDepositSwapLegCall(params: {
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
- const [vesuAdapter1] = this.getVesuAdapters();
125
- const legLTV = await vesuAdapter1.getLTVConfig(this.config);
163
+
164
+ const legLTV = await vesuAdapter.getLTVConfig(this.config);
126
165
  logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall legLTV: ${legLTV}`);
127
- const existingPositions = await vesuAdapter1.getPositions(this.config);
128
- const collateralisation = await vesuAdapter1.getCollateralization(this.config);
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 = totalCollateral.multipliedBy(collateralPrice).multipliedBy(legLTV).dividedBy(debtPrice).dividedBy(params.minHF);
145
- logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall totalDebtAmount: ${totalDebtAmount}`);
146
- const debtAmount = totalDebtAmount.minus(existingDebtInfo.amount);
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: params.leg1DepositAmount
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: params.leg1DepositAmount,
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 = vesuAdapter1.config.debt;
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, manageCall: Call | undefined }> {
249
- const positions = await this.getVaultPositions();
250
- assert(positions.length == 3, 'Rebalance call is only supported for 3 positions');
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 = positions[2];
254
- const [healthFactor] = await this.getVesuHealthFactors();
342
+ const unusedBalance = await this.getUnusedBalance();
343
+ const healthFactor = await vesuAdapter.getHealthFactor();
255
344
 
256
- const [vesuAdapter1] = this.getVesuAdapters();
257
- const legLTV = await vesuAdapter1.getLTVConfig(this.config);
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
- const maxBorrowableAmount = await vesuAdapter.getMaxBorrowableByInterestRate(this.config, vesuAdapter.config.debt, maxInterestRate);
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 = 'avnu_multiply_approve_deposit',
719
- AVNU_MULTIPLY_SWAP_DEPOSIT = 'avnu_multiply_swap_deposit',
720
- AVNU_MULTIPLY_APPROVE_WITHDRAW = 'avnu_multiply_approve_withdraw',
721
- AVNU_MULTIPLY_SWAP_WITHDRAW = '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: UNIVERSAL_ADAPTERS.VESU_LEG1,
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
- vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(underlyingToken.address, AVNU_EXCHANGE, LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_DEPOSIT).bind(commonAdapter));
771
- vaultSettings.leafAdapters.push(commonAdapter.getAvnuAdapter(underlyingToken.address, lstToken.address, LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_DEPOSIT, false).bind(commonAdapter));
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
- vaultSettings.leafAdapters.push(commonAdapter.getAvnuAdapter(lstToken.address, underlyingToken.address, LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_WITHDRAW, false).bind(commonAdapter));
774
- // approve LST to add collateral
775
- vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(lstToken.address, poolAddr, UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1).bind(commonAdapter));
776
- vaultSettings.leafAdapters.push(vesuAdapterLST.getModifyPosition.bind(vesuAdapterLST));
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
+ }