@strkfarm/sdk 2.0.0-dev.9 → 2.0.0-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/index.browser.global.js +111371 -93151
  2. package/dist/index.browser.mjs +27815 -32690
  3. package/dist/index.d.ts +1095 -2011
  4. package/dist/index.js +27425 -32309
  5. package/dist/index.mjs +27590 -32452
  6. package/package.json +6 -5
  7. package/src/data/ekubo-price-fethcer.abi.json +265 -0
  8. package/src/data/universal-vault.abi.json +20 -135
  9. package/src/dataTypes/address.ts +0 -7
  10. package/src/dataTypes/index.ts +3 -2
  11. package/src/dataTypes/mynumber.ts +141 -0
  12. package/src/global.ts +296 -288
  13. package/src/index.browser.ts +6 -5
  14. package/src/interfaces/common.tsx +324 -184
  15. package/src/modules/apollo-client-config.ts +28 -0
  16. package/src/modules/avnu.ts +4 -17
  17. package/src/modules/ekubo-pricer.ts +79 -0
  18. package/src/modules/ekubo-quoter.ts +11 -88
  19. package/src/modules/erc20.ts +21 -67
  20. package/src/modules/harvests.ts +26 -15
  21. package/src/modules/index.ts +11 -13
  22. package/src/modules/lst-apr.ts +0 -36
  23. package/src/modules/pragma.ts +23 -8
  24. package/src/modules/pricer-from-api.ts +150 -14
  25. package/src/modules/pricer.ts +2 -1
  26. package/src/modules/pricerBase.ts +2 -1
  27. package/src/node/deployer.ts +36 -1
  28. package/src/node/pricer-redis.ts +2 -1
  29. package/src/strategies/autoCompounderStrk.ts +1 -1
  30. package/src/strategies/base-strategy.ts +5 -22
  31. package/src/strategies/ekubo-cl-vault.tsx +2904 -2175
  32. package/src/strategies/factory.ts +165 -0
  33. package/src/strategies/index.ts +10 -11
  34. package/src/strategies/registry.ts +268 -0
  35. package/src/strategies/sensei.ts +416 -292
  36. package/src/strategies/universal-adapters/adapter-utils.ts +1 -5
  37. package/src/strategies/universal-adapters/baseAdapter.ts +153 -181
  38. package/src/strategies/universal-adapters/common-adapter.ts +77 -98
  39. package/src/strategies/universal-adapters/index.ts +1 -5
  40. package/src/strategies/universal-adapters/vesu-adapter.ts +218 -220
  41. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +51 -58
  42. package/src/strategies/universal-lst-muliplier-strategy.tsx +1952 -992
  43. package/src/strategies/universal-strategy.tsx +1713 -1150
  44. package/src/strategies/vesu-rebalance.tsx +1189 -986
  45. package/src/utils/health-factor-math.ts +5 -11
  46. package/src/utils/index.ts +8 -9
  47. package/src/utils/strategy-utils.ts +57 -0
  48. package/src/data/extended-deposit.abi.json +0 -3613
  49. package/src/modules/ExtendedWrapperSDk/index.ts +0 -62
  50. package/src/modules/ExtendedWrapperSDk/types.ts +0 -311
  51. package/src/modules/ExtendedWrapperSDk/wrapper.ts +0 -395
  52. package/src/modules/midas.ts +0 -159
  53. package/src/modules/token-market-data.ts +0 -202
  54. package/src/strategies/svk-strategy.ts +0 -247
  55. package/src/strategies/universal-adapters/adapter-optimizer.ts +0 -65
  56. package/src/strategies/universal-adapters/avnu-adapter.ts +0 -413
  57. package/src/strategies/universal-adapters/extended-adapter.ts +0 -972
  58. package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
  59. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +0 -1306
  60. package/src/strategies/vesu-extended-strategy/services/operationService.ts +0 -34
  61. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +0 -77
  62. package/src/strategies/vesu-extended-strategy/utils/constants.ts +0 -49
  63. package/src/strategies/vesu-extended-strategy/utils/helper.ts +0 -370
  64. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +0 -1379
@@ -1,1072 +1,2032 @@
1
- import { FAQ, getMainnetConfig, getNoRiskTags, highlightTextWithLinks, IConfig, IStrategyMetadata, Protocols, RiskFactor, RiskType, TokenInfo, VaultPosition } from "@/interfaces";
2
- import { AUMTypes, getContractDetails, UNIVERSAL_MANAGE_IDS, UniversalManageCall, UniversalStrategySettings } from "./universal-strategy";
1
+ import {
2
+ FAQ,
3
+ getNoRiskTags,
4
+ highlightTextWithLinks,
5
+ IConfig,
6
+ IStrategyMetadata,
7
+ Protocols,
8
+ RiskFactor,
9
+ RiskType,
10
+ StrategyCategory,
11
+ StrategyTag,
12
+ StrategyLiveStatus,
13
+ StrategySettings,
14
+ TokenInfo,
15
+ AuditStatus,
16
+ SourceCodeType,
17
+ AccessControlType,
18
+ InstantWithdrawalVault
19
+ } from "@/interfaces";
20
+ import {
21
+ AUMTypes,
22
+ getContractDetails,
23
+ UNIVERSAL_ADAPTERS,
24
+ UNIVERSAL_MANAGE_IDS,
25
+ UniversalManageCall,
26
+ UniversalStrategy,
27
+ UniversalStrategySettings
28
+ } from "./universal-strategy";
3
29
  import { PricerBase } from "@/modules/pricerBase";
4
30
  import { ContractAddr, Web3Number } from "@/dataTypes";
5
31
  import { Global } from "@/global";
6
- import { ApproveCallParams, APYType, AvnuSwapCallParams, BaseAdapterConfig, CommonAdapter, ManageCall, PositionInfo, UnusedBalanceAdapter, VesuMultiplyAdapter, VesuPools, VesuSupplyOnlyAdapter } from "./universal-adapters";
32
+ import {
33
+ ApproveCallParams,
34
+ AvnuSwapCallParams,
35
+ CommonAdapter,
36
+ getVesuSingletonAddress,
37
+ ManageCall,
38
+ Swap,
39
+ VesuAdapter,
40
+ VesuModifyDelegationCallParams,
41
+ VesuModifyPositionCallParams,
42
+ VesuMultiplyCallParams,
43
+ VesuPools
44
+ } from "./universal-adapters";
7
45
  import { AVNU_EXCHANGE } from "./universal-adapters/adapter-utils";
8
- import { DepegRiskLevel, LiquidationRiskLevel, SmartContractRiskLevel, TechnicalRiskLevel } from "@/interfaces/risks";
9
- import { AvnuWrapper, EkuboQuoter, ERC20, PricerFromApi, PricerLST } from "@/modules";
46
+ import {
47
+ DepegRiskLevel,
48
+ LiquidationRiskLevel,
49
+ SmartContractRiskLevel,
50
+ TechnicalRiskLevel
51
+ } from "@/interfaces/risks";
52
+ import {
53
+ AvnuWrapper,
54
+ EkuboQuoter,
55
+ ERC20,
56
+ LSTAPRService,
57
+ PricerLST
58
+ } from "@/modules";
10
59
  import { assert, logger } from "@/utils";
11
- import { SingleActionAmount, SingleTokenInfo } from "./base-strategy";
12
- import { SVKStrategy } from "./svk-strategy";
13
- import { Call, uint256 } from "starknet";
60
+ import { SingleTokenInfo } from "./base-strategy";
61
+ import { VaultPosition } from "@/interfaces";
62
+ import { Call, Contract, uint256 } from "starknet";
14
63
  import ERC4626Abi from "@/data/erc4626.abi.json";
15
- import { AdapterOptimizer } from "./universal-adapters/adapter-optimizer";
16
- import { MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP } from "./vesu-extended-strategy/utils/constants";
17
- import { VesuExtendedStrategySettings } from "./vesu-extended-strategy/vesu-extended-strategy";
64
+ import { HealthFactorMath } from "@/utils/health-factor-math";
65
+ import { findMaxInputWithSlippage } from "@/utils/math-utils";
18
66
 
19
67
  export interface HyperLSTStrategySettings extends UniversalStrategySettings {
20
- borrowable_assets: TokenInfo[];
21
- underlyingToken: TokenInfo;
22
- quoteAmountToFetchPrice: Web3Number;
23
- targetHealthFactor: number;
24
- minHealthFactor: number;
25
- aumOracle: ContractAddr;
68
+ borrowable_assets: TokenInfo[];
69
+ underlyingToken: TokenInfo;
26
70
  }
27
71
 
28
- export class UniversalLstMultiplierStrategy<S extends HyperLSTStrategySettings> extends SVKStrategy<S> {
29
-
30
- private quoteAmountToFetchPrice = new Web3Number(1, 18);
31
-
32
- constructor(config: IConfig, pricer: PricerBase, metadata: IStrategyMetadata<S>) {
33
- super(config, pricer, metadata);
34
- const STRKToken = Global.getDefaultTokens().find(token => token.symbol === 'STRK')!;
35
- const underlyingToken = this.getLSTUnderlyingTokenInfo();
36
- if (underlyingToken.address.eq(STRKToken.address)) {
37
- this.quoteAmountToFetchPrice = new Web3Number(100, 18);
38
- } else {
39
- // else this BTC
40
- this.quoteAmountToFetchPrice = new Web3Number(0.01, this.asset().decimals);
72
+ export class UniversalLstMultiplierStrategy extends UniversalStrategy<HyperLSTStrategySettings> {
73
+ private quoteAmountToFetchPrice = new Web3Number(1, 18);
74
+
75
+ constructor(
76
+ config: IConfig,
77
+ pricer: PricerBase,
78
+ metadata: IStrategyMetadata<HyperLSTStrategySettings>
79
+ ) {
80
+ super(config, pricer, metadata);
81
+
82
+ const STRKToken = Global.getDefaultTokens().find(
83
+ (token) => token.symbol === "STRK"
84
+ )!;
85
+ const underlyingToken = this.getLSTUnderlyingTokenInfo();
86
+ if (underlyingToken.address.eq(STRKToken.address)) {
87
+ this.quoteAmountToFetchPrice = new Web3Number(100, 18);
88
+ } else {
89
+ // else this BTC
90
+ this.quoteAmountToFetchPrice = new Web3Number(
91
+ 0.01,
92
+ this.asset().decimals
93
+ );
94
+ }
41
95
  }
42
96
 
43
- this.metadata.additionalInfo.adapters.forEach(adapter => {
44
- adapter.adapter.config.networkConfig = this.config;
45
- adapter.adapter.config.pricer = this.pricer;
46
- if ((adapter.adapter as VesuMultiplyAdapter).vesuAdapter) {
47
- (adapter.adapter as VesuMultiplyAdapter).vesuAdapter.networkConfig = this.config;
48
- (adapter.adapter as VesuMultiplyAdapter).vesuAdapter.pricer = this.pricer;
49
- }
50
- });
51
- }
52
-
53
- getTag() {
54
- return `${UniversalLstMultiplierStrategy.name}:${this.metadata.name}`;
55
- }
56
-
57
- // Vesu adapter with LST and base token match
58
- getVesuSameTokenAdapter() {
59
- const vesuMultipleAdapters = this.getVesuAdapters();
60
- console.log(vesuMultipleAdapters.map(adapter => adapter.config.debt.symbol));
61
- const baseAdapter = vesuMultipleAdapters.find((adapter) => {
62
- return adapter.config.debt.address.eq(this.metadata.additionalInfo.underlyingToken.address);
63
- })
64
- if (!baseAdapter) {
65
- throw new Error(`${this.getTag()}::getVesuSameTokenAdapter: base adapter not found`);
97
+ asset() {
98
+ return this.getVesuSameTokenAdapter().config.collateral;
99
+ }
100
+
101
+ getTag() {
102
+ return `${UniversalLstMultiplierStrategy.name}:${this.metadata.name}`;
103
+ }
104
+
105
+ // Vesu adapter with LST and base token match
106
+ getVesuSameTokenAdapter() {
107
+ const baseAdapter = this.getAdapter(
108
+ getVesuLegId(
109
+ UNIVERSAL_MANAGE_IDS.VESU_LEG1,
110
+ this.metadata.additionalInfo.underlyingToken.symbol
111
+ )
112
+ ) as VesuAdapter;
113
+ baseAdapter.networkConfig = this.config;
114
+ baseAdapter.pricer = this.pricer;
115
+ return baseAdapter;
116
+ }
117
+
118
+ // only one leg is used
119
+ // todo support lending assets of underlying as well (like if xSTRK looping is not viable, simply supply STRK)
120
+ getVesuAdapters() {
121
+ const adapters: VesuAdapter[] = [];
122
+ const baseAdapter = this.getVesuSameTokenAdapter();
123
+ for (const asset of this.metadata.additionalInfo.borrowable_assets) {
124
+ const vesuAdapter1 = new VesuAdapter({
125
+ poolId: baseAdapter.config.poolId,
126
+ collateral: this.asset(),
127
+ debt: asset,
128
+ vaultAllocator: this.metadata.additionalInfo.vaultAllocator,
129
+ id: ""
130
+ });
131
+ vesuAdapter1.pricer = this.pricer;
132
+ vesuAdapter1.networkConfig = this.config;
133
+ adapters.push(vesuAdapter1);
134
+ }
135
+ return adapters;
136
+ }
137
+
138
+ // not applicable for this strategy
139
+ // No rewards on collateral or borrowing of LST assets
140
+ protected async getRewardsAUM(prevAum: Web3Number): Promise<Web3Number> {
141
+ const lstToken = this.asset();
142
+ if (lstToken.symbol === "xSTRK") {
143
+ return super.getRewardsAUM(prevAum);
144
+ }
145
+ return Web3Number.fromWei("0", lstToken.decimals);
146
+ }
147
+
148
+ getLSTUnderlyingTokenInfo() {
149
+ const vesuAdapter1 = this.getVesuSameTokenAdapter();
150
+ return vesuAdapter1.config.debt;
66
151
  }
67
- baseAdapter.config.networkConfig = this.config;
68
- baseAdapter.config.pricer = this.pricer;
69
- return baseAdapter;
70
- }
71
-
72
- // only one leg is used
73
- // todo support lending assets of underlying as well (like if xSTRK looping is not viable, simply supply STRK)
74
- getVesuAdapters() {
75
- const vesuMultipleAdapters = this.metadata.additionalInfo.adapters
76
- .filter(adapter => adapter.adapter.name === VesuMultiplyAdapter.name)
77
- .map(adapter => adapter.adapter) as VesuMultiplyAdapter[]
78
-
79
- for (const vesuAdapter of vesuMultipleAdapters) {
80
- vesuAdapter.config.pricer = this.pricer;
81
- vesuAdapter.config.networkConfig = this.config;
152
+
153
+ async getLSTDexPrice() {
154
+ const ekuboQuoter = new EkuboQuoter(this.config);
155
+ const lstTokenInfo = this.asset();
156
+ const lstUnderlyingTokenInfo = this.getLSTUnderlyingTokenInfo();
157
+ const quote = await ekuboQuoter.getQuote(
158
+ lstTokenInfo.address.address,
159
+ lstUnderlyingTokenInfo.address.address,
160
+ this.quoteAmountToFetchPrice
161
+ );
162
+
163
+ // in Underlying
164
+ const outputAmount = Web3Number.fromWei(
165
+ quote.total_calculated,
166
+ lstUnderlyingTokenInfo.decimals
167
+ );
168
+ const price =
169
+ outputAmount.toNumber() / this.quoteAmountToFetchPrice.toNumber();
170
+ logger.verbose(`${this.getTag()}:: LST Dex Price: ${price}`);
171
+ return price;
172
+ }
173
+
174
+ async getAvnuSwapMultiplyCall(params: {
175
+ isDeposit: boolean;
176
+ leg1DepositAmount: Web3Number;
177
+ }) {
178
+ assert(
179
+ params.isDeposit,
180
+ "Only deposit is supported in getAvnuSwapMultiplyCall"
181
+ );
182
+ // TODO use a varibale for 1.02
183
+ const maxBorrowableAmounts = await this.getMaxBorrowableAmount({
184
+ isAPYComputation: false
185
+ });
186
+ const allVesuAdapters = this.getVesuAdapters();
187
+ let remainingAmount = params.leg1DepositAmount;
188
+ const lstExRate = await this.getLSTExchangeRate();
189
+ const baseAssetPrice = await this.pricer.getPrice(
190
+ this.getLSTUnderlyingTokenInfo().symbol
191
+ );
192
+ const lstPrice = baseAssetPrice.price * lstExRate;
193
+ for (let i = 0; i < maxBorrowableAmounts.maxBorrowables.length; i++) {
194
+ const maxBorrowable = maxBorrowableAmounts.maxBorrowables[i];
195
+ const vesuAdapter = allVesuAdapters.find((adapter) =>
196
+ adapter.config.debt.address.eq(
197
+ maxBorrowable.borrowableAsset.address
198
+ )
199
+ );
200
+ if (!vesuAdapter) {
201
+ throw new Error(
202
+ `${this.getTag()}::getAvnuSwapMultiplyCall: vesuAdapter not found for borrowable asset: ${
203
+ maxBorrowable.borrowableAsset.symbol
204
+ }`
205
+ );
206
+ }
207
+ const maxLTV = await vesuAdapter.getLTVConfig(this.config);
208
+ const debtPrice = await this.pricer.getPrice(
209
+ maxBorrowable.borrowableAsset.symbol
210
+ );
211
+ const maxAmountToDeposit =
212
+ HealthFactorMath.getMinCollateralRequiredOnLooping(
213
+ maxBorrowable.amount,
214
+ debtPrice.price,
215
+ this.metadata.additionalInfo.targetHealthFactor,
216
+ maxLTV,
217
+ lstPrice,
218
+ this.asset()
219
+ );
220
+ const amountToDeposit = remainingAmount.minimum(maxAmountToDeposit);
221
+ logger.verbose(
222
+ `${this.getTag()}::getAvnuSwapMultiplyCall::${
223
+ vesuAdapter.config.debt.symbol
224
+ }:: remainingAmount: ${remainingAmount}, amountToDeposit: ${amountToDeposit}, depositAmount: ${amountToDeposit}, maxBorrowable: ${
225
+ maxBorrowable.amount
226
+ }`
227
+ );
228
+ const call = await this._getAvnuDepositSwapLegCall({
229
+ isDeposit: params.isDeposit,
230
+ // adjust decimals of debt asset
231
+ leg1DepositAmount: amountToDeposit,
232
+ minHF: 1.1, // undo
233
+ vesuAdapter: vesuAdapter
234
+ });
235
+ remainingAmount = remainingAmount.minus(amountToDeposit);
236
+
237
+ // return the first possible call because computing all calls at a time
238
+ // is not efficinet for swaps
239
+ return { call, vesuAdapter };
240
+ }
241
+ throw new Error(
242
+ `${this.getTag()}::getAvnuSwapMultiplyCall: no calls found`
243
+ );
244
+ }
245
+
246
+ async _getAvnuDepositSwapLegCall(params: {
247
+ isDeposit: boolean;
248
+ leg1DepositAmount: Web3Number;
249
+ minHF: number; // e.g. 1.01
250
+ vesuAdapter: VesuAdapter;
251
+ }) {
252
+ const { vesuAdapter } = params;
253
+ logger.verbose(
254
+ `${this.getTag()}::_getAvnuDepositSwapLegCall params: ${JSON.stringify(
255
+ params
256
+ )}`
257
+ );
258
+ assert(
259
+ params.isDeposit,
260
+ "Only deposit is supported in _getAvnuDepositSwapLegCall"
261
+ );
262
+ // add collateral
263
+ // borrow STRK (e.g.)
264
+ // approve and swap strk
265
+ // add collateral again
266
+
267
+ const legLTV = await vesuAdapter.getLTVConfig(this.config);
268
+ logger.verbose(
269
+ `${this.getTag()}::_getAvnuDepositSwapLegCall legLTV: ${legLTV}`
270
+ );
271
+ const existingPositions = await vesuAdapter.getPositions(this.config);
272
+ const collateralisation = await vesuAdapter.getCollateralization(
273
+ this.config
274
+ );
275
+ const existingCollateralInfo = existingPositions[0];
276
+ const existingDebtInfo = existingPositions[1];
277
+ logger.debug(`${this.getTag()}::_getAvnuDepositSwapLegCall existingCollateralInfo: ${JSON.stringify(
278
+ existingCollateralInfo
279
+ )},
280
+ existingDebtInfo: ${JSON.stringify(
281
+ existingDebtInfo
282
+ )}, collateralisation: ${JSON.stringify(collateralisation)}`);
283
+
284
+ // - Prices as seen by Vesu contracts, ideal for HF math
285
+ // Price 1 is ok as fallback bcz that would relatively price the
286
+ // collateral and debt as equal.
287
+ const collateralPrice =
288
+ collateralisation[0].usdValue > 0
289
+ ? collateralisation[0].usdValue /
290
+ existingCollateralInfo.amount.toNumber()
291
+ : 1;
292
+ const debtPrice =
293
+ collateralisation[1].usdValue > 0
294
+ ? collateralisation[1].usdValue /
295
+ existingDebtInfo.amount.toNumber()
296
+ : 1;
297
+ logger.debug(
298
+ `${this.getTag()}::_getAvnuDepositSwapLegCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`
299
+ );
300
+
301
+ const debtTokenInfo = vesuAdapter.config.debt;
302
+ let newDepositAmount = params.leg1DepositAmount;
303
+ const totalCollateral = existingCollateralInfo.amount.plus(
304
+ params.leg1DepositAmount
305
+ );
306
+ logger.verbose(
307
+ `${this.getTag()}::_getAvnuDepositSwapLegCall totalCollateral: ${totalCollateral}`
308
+ );
309
+ const totalDebtAmount = new Web3Number(
310
+ totalCollateral
311
+ .multipliedBy(collateralPrice)
312
+ .multipliedBy(legLTV)
313
+ .dividedBy(debtPrice)
314
+ .dividedBy(params.minHF)
315
+ .toString(),
316
+ debtTokenInfo.decimals
317
+ );
318
+ let debtAmount = totalDebtAmount.minus(existingDebtInfo.amount);
319
+ logger.verbose(
320
+ `${this.getTag()}::_getAvnuDepositSwapLegCall totalDebtAmount: ${totalDebtAmount}, initial computed debt: ${debtAmount}`
321
+ );
322
+ const maxBorrowable = await this.getMaxBorrowableAmountByVesuAdapter(
323
+ vesuAdapter,
324
+ false
325
+ );
326
+
327
+ // if the debt amount is greater than 0 and the max borrowable amount is 0, skip
328
+ if (debtAmount.gt(0) && maxBorrowable.amount.eq(0)) {
329
+ logger.verbose(
330
+ `${this.getTag()}::_getAvnuDepositSwapLegCall maxBorrowable is 0, skipping`
331
+ );
332
+ return undefined;
333
+ } else if (debtAmount.gt(0) && maxBorrowable.amount.gt(0)) {
334
+ debtAmount = maxBorrowable.amount.minimum(debtAmount);
335
+ const newDebtUSDValue = debtAmount.multipliedBy(debtPrice);
336
+ const totalCollateralRequired =
337
+ HealthFactorMath.getCollateralRequired(
338
+ debtAmount.plus(existingDebtInfo.amount),
339
+ debtPrice,
340
+ params.minHF,
341
+ legLTV,
342
+ collateralPrice,
343
+ this.asset()
344
+ );
345
+ newDepositAmount = totalCollateralRequired.minus(
346
+ existingCollateralInfo.amount
347
+ );
348
+ if (newDepositAmount.lt(0)) {
349
+ throw new Error(
350
+ `${this.getTag()}::_getAvnuDepositSwapLegCall newDepositAmount is less than 0, newDepositAmount: ${newDepositAmount}, totalCollateralRequired: ${totalCollateralRequired}, existingCollateralInfo.amount: ${
351
+ existingCollateralInfo.amount
352
+ }`
353
+ );
354
+ }
355
+ if (newDebtUSDValue.toNumber() < 100) {
356
+ // too less debt, skip
357
+ logger.verbose(
358
+ `${this.getTag()}::_getAvnuDepositSwapLegCall newDebtUSDValue is less than 100, skipping`
359
+ );
360
+ return undefined;
361
+ }
362
+ }
363
+
364
+ logger.verbose(
365
+ `${this.getTag()}::_getAvnuDepositSwapLegCall debtAmount: ${debtAmount}`
366
+ );
367
+ if (debtAmount.lt(0)) {
368
+ // this is to unwind the position to optimal HF.
369
+ const lstDEXPrice = await this.getLSTDexPrice();
370
+ const debtAmountInLST = debtAmount.abs().dividedBy(lstDEXPrice);
371
+ const calls = await this.getVesuMultiplyCall({
372
+ isDeposit: false,
373
+ leg1DepositAmount: debtAmountInLST
374
+ });
375
+ assert(
376
+ calls.length == 1,
377
+ `Expected 1 call for unwind, got ${calls.length}`
378
+ );
379
+ return calls[0];
380
+ }
381
+ console.log(
382
+ `debtAmount`,
383
+ debtAmount.toWei(),
384
+ params.leg1DepositAmount.toWei()
385
+ );
386
+ const STEP0 = UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1;
387
+ const manage0Info = this.getProofs<ApproveCallParams>(STEP0);
388
+ const manageCall0 = manage0Info.callConstructor({
389
+ amount: newDepositAmount
390
+ });
391
+ const STEP1 = getVesuLegId(
392
+ UNIVERSAL_MANAGE_IDS.VESU_LEG1,
393
+ vesuAdapter.config.debt.symbol
394
+ );
395
+ const manage1Info = this.getProofs<VesuModifyPositionCallParams>(STEP1);
396
+ const manageCall1 = manage1Info.callConstructor(
397
+ VesuAdapter.getDefaultModifyPositionCallParams({
398
+ collateralAmount: newDepositAmount,
399
+ isAddCollateral: params.isDeposit,
400
+ debtAmount: debtAmount,
401
+ isBorrow: params.isDeposit
402
+ })
403
+ );
404
+
405
+ console.log(
406
+ `manageCall1`,
407
+ manageCall1.call,
408
+ debtAmount.toWei(),
409
+ newDepositAmount.toWei()
410
+ );
411
+
412
+ const proofIds: string[] = [STEP0, STEP1];
413
+ const manageCalls: ManageCall[] = [manageCall0, manageCall1];
414
+
415
+ // approve and swap to LST using avnu
416
+ if (debtAmount.gt(0)) {
417
+ const STEP2 = getAvnuManageIDs(
418
+ LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_DEPOSIT,
419
+ vesuAdapter.config.debt.symbol
420
+ );
421
+ const manage2Info = this.getProofs<ApproveCallParams>(STEP2);
422
+ const manageCall2 = manage2Info.callConstructor({
423
+ amount: debtAmount
424
+ });
425
+
426
+ const debtTokenInfo = vesuAdapter.config.debt;
427
+ const lstTokenInfo = this.asset();
428
+ const avnuModule = new AvnuWrapper();
429
+ const quote = await avnuModule.getQuotes(
430
+ debtTokenInfo.address.address,
431
+ lstTokenInfo.address.address,
432
+ debtAmount.toWei(),
433
+ this.metadata.additionalInfo.vaultAllocator.address
434
+ );
435
+ const minAmount = await this._getMinOutputAmountLSTBuy(debtAmount);
436
+ const minAmountWei = minAmount.toWei();
437
+ logger.verbose(
438
+ `${this.getTag()}::_getAvnuDepositSwapLegCall minAmount: ${minAmount}`
439
+ );
440
+ const swapInfo = await avnuModule.getSwapInfo(
441
+ quote,
442
+ this.metadata.additionalInfo.vaultAllocator.address,
443
+ 0,
444
+ this.address.address,
445
+ minAmountWei
446
+ );
447
+ logger.verbose(
448
+ `${this.getTag()}::_getAvnuDepositSwapLegCall swapInfo: ${JSON.stringify(
449
+ swapInfo
450
+ )}`
451
+ );
452
+ const STEP3 = getAvnuManageIDs(
453
+ LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_DEPOSIT,
454
+ vesuAdapter.config.debt.symbol
455
+ );
456
+ const manage3Info = this.getProofs<AvnuSwapCallParams>(STEP3);
457
+ const manageCall3 = manage3Info.callConstructor({
458
+ props: swapInfo
459
+ });
460
+ proofIds.push(STEP2);
461
+ proofIds.push(STEP3);
462
+ manageCalls.push(manageCall2, manageCall3);
463
+
464
+ // if the created debt, when added is collateral will put the total HF above min, but below (target + 0.05),
465
+ // then lets close the looping cycle by adding this as collateral.
466
+ const newCollateral = minAmount.plus(totalCollateral);
467
+ const newHF = newCollateral
468
+ .multipliedBy(collateralPrice)
469
+ .multipliedBy(legLTV)
470
+ .dividedBy(totalDebtAmount)
471
+ .dividedBy(debtPrice)
472
+ .toNumber();
473
+ logger.verbose(
474
+ `${this.getTag()}::_getAvnuDepositSwapLegCall newHF: ${newHF}`
475
+ );
476
+ if (
477
+ newHF > this.metadata.additionalInfo.minHealthFactor &&
478
+ newHF < this.metadata.additionalInfo.targetHealthFactor + 0.05
479
+ ) {
480
+ logger.verbose(
481
+ `${this.getTag()}::_getAvnuDepositSwapLegCall newHF is above min and below target + 0.05, adding collateral on vesu`
482
+ );
483
+ // approve and add collateral on vesu (modify position)
484
+ const STEP4 = UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1;
485
+ const manage4Info = this.getProofs<ApproveCallParams>(STEP4);
486
+ const manageCall4 = manage4Info.callConstructor({
487
+ amount: minAmount
488
+ });
489
+
490
+ const STEP5 = getVesuLegId(
491
+ UNIVERSAL_MANAGE_IDS.VESU_LEG1,
492
+ vesuAdapter.config.debt.symbol
493
+ );
494
+ const manage5Info =
495
+ this.getProofs<VesuModifyPositionCallParams>(STEP5);
496
+ const manageCall5 = manage5Info.callConstructor(
497
+ VesuAdapter.getDefaultModifyPositionCallParams({
498
+ collateralAmount: minAmount,
499
+ isAddCollateral: true,
500
+ debtAmount: Web3Number.fromWei(
501
+ "0",
502
+ this.asset().decimals
503
+ ),
504
+ isBorrow: params.isDeposit
505
+ })
506
+ );
507
+ proofIds.push(STEP4, STEP5);
508
+ manageCalls.push(manageCall4, manageCall5);
509
+ }
510
+ }
511
+
512
+ const manageCall = this.getManageCall(proofIds, manageCalls);
513
+ return manageCall;
514
+ }
515
+
516
+ // todo unwind or not deposit when the yield is bad.
517
+
518
+ async getLSTMultiplierRebalanceCall(): Promise<{
519
+ shouldRebalance: boolean;
520
+ manageCalls: { vesuAdapter: VesuAdapter; manageCall: Call }[];
521
+ }> {
522
+ let shouldRebalance = false;
523
+ const calls: { vesuAdapter: VesuAdapter; manageCall: Call }[] = [];
524
+ // todo undo
525
+ const allVesuAdapters = this.getVesuAdapters().filter(
526
+ (vesuAdapter) => vesuAdapter.config.debt.symbol === "LBTC"
527
+ );
528
+ for (const vesuAdapter of allVesuAdapters) {
529
+ const call = await this._getLSTMultiplierRebalanceCall(vesuAdapter);
530
+ if (call.shouldRebalance && call.manageCall) {
531
+ shouldRebalance = true;
532
+ calls.push({ vesuAdapter, manageCall: call.manageCall });
533
+ }
534
+ }
535
+ return { shouldRebalance, manageCalls: calls };
536
+ }
537
+
538
+ async _getLSTMultiplierRebalanceCall(
539
+ vesuAdapter: VesuAdapter
540
+ ): Promise<{ shouldRebalance: boolean; manageCall: Call | undefined }> {
541
+ const positions = await vesuAdapter.getPositions(this.config);
542
+ assert(
543
+ positions.length == 2,
544
+ "Rebalance call is only supported for 2 positions"
545
+ );
546
+ const existingCollateralInfo = positions[0];
547
+ const existingDebtInfo = positions[1];
548
+ const unusedBalance = await this.getUnusedBalance();
549
+ const healthFactor = await vesuAdapter.getHealthFactor();
550
+
551
+ const collateralisation = await vesuAdapter.getCollateralization(
552
+ this.config
553
+ );
554
+ logger.debug(`${this.getTag()}::getVesuMultiplyCall::${
555
+ vesuAdapter.config.debt.symbol
556
+ } existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
557
+ existingDebtInfo: ${JSON.stringify(
558
+ existingDebtInfo
559
+ )}, collateralisation: ${JSON.stringify(collateralisation)}`);
560
+
561
+ // - Prices as seen by Vesu contracts, ideal for HF math
562
+ // Price 1 is ok as fallback bcz that would relatively price the
563
+ // collateral and debt as equal.
564
+ const collateralPrice =
565
+ collateralisation[0].usdValue > 0
566
+ ? collateralisation[0].usdValue /
567
+ existingCollateralInfo.amount.toNumber()
568
+ : 1;
569
+ const debtPrice =
570
+ collateralisation[1].usdValue > 0
571
+ ? collateralisation[1].usdValue /
572
+ existingDebtInfo.amount.toNumber()
573
+ : 1;
574
+ logger.debug(
575
+ `${this.getTag()}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`
576
+ );
577
+ logger.debug(
578
+ `${this.getTag()}::getVesuMultiplyCall healthFactor: ${healthFactor}`
579
+ );
580
+
581
+ const isHFTooLow =
582
+ healthFactor < this.metadata.additionalInfo.minHealthFactor;
583
+ const isHFTooHigh =
584
+ healthFactor >
585
+ this.metadata.additionalInfo.targetHealthFactor + 0.05;
586
+ if (isHFTooLow || isHFTooHigh || 1) {
587
+ // use unused collateral to target more.
588
+ const manageCall = await this._getAvnuDepositSwapLegCall({
589
+ isDeposit: true,
590
+ leg1DepositAmount: unusedBalance.amount,
591
+ minHF: 1.02, // todo, shouldnt use this 1.02 HF, if there isn;t more looping left.
592
+ vesuAdapter
593
+ });
594
+ return { shouldRebalance: true, manageCall };
595
+ } else {
596
+ // do nothing
597
+ return { shouldRebalance: false, manageCall: undefined };
598
+ }
82
599
  }
83
- return vesuMultipleAdapters;
84
- }
85
-
86
-
87
- // async getAvnuSwapMultiplyCall(params: {
88
- // isDeposit: boolean,
89
- // leg1DepositAmount: Web3Number
90
- // }) {
91
- // assert(params.isDeposit, 'Only deposit is supported in getAvnuSwapMultiplyCall')
92
- // // TODO use a varibale for 1.02
93
- // const maxBorrowableAmounts = await this.getMaxBorrowableAmount({ isAPYComputation: false });
94
- // const allVesuAdapters = this.getVesuAdapters();
95
- // let remainingAmount = params.leg1DepositAmount;
96
- // const lstExRate = await this.getLSTExchangeRate();
97
- // const baseAssetPrice = await this.pricer.getPrice(this.getLSTUnderlyingTokenInfo().symbol);
98
- // const lstPrice = baseAssetPrice.price * lstExRate;
99
- // for (let i = 0; i < maxBorrowableAmounts.maxBorrowables.length; i++) {
100
- // const maxBorrowable = maxBorrowableAmounts.maxBorrowables[i];
101
- // const vesuAdapter = allVesuAdapters.find(adapter => adapter.config.debt.address.eq(maxBorrowable.borrowableAsset.address));
102
- // if (!vesuAdapter) {
103
- // throw new Error(`${this.getTag()}::getAvnuSwapMultiplyCall: vesuAdapter not found for borrowable asset: ${maxBorrowable.borrowableAsset.symbol}`);
104
- // }
105
- // const maxLTV = await vesuAdapter.getLTVConfig(this.config);
106
- // const debtPrice = await this.pricer.getPrice(maxBorrowable.borrowableAsset.symbol);
107
- // const maxAmountToDeposit = HealthFactorMath.getMinCollateralRequiredOnLooping(
108
- // maxBorrowable.amount,
109
- // debtPrice.price,
110
- // this.metadata.additionalInfo.targetHealthFactor,
111
- // maxLTV,
112
- // lstPrice,
113
- // this.asset()
114
- // )
115
- // const amountToDeposit = remainingAmount.minimum(maxAmountToDeposit);
116
- // logger.verbose(`${this.getTag()}::getAvnuSwapMultiplyCall::${vesuAdapter.config.debt.symbol}:: remainingAmount: ${remainingAmount}, amountToDeposit: ${amountToDeposit}, depositAmount: ${amountToDeposit}, maxBorrowable: ${maxBorrowable.amount}`);
117
- // const call = await this._getAvnuDepositSwapLegCall({
118
- // isDeposit: params.isDeposit,
119
- // // adjust decimals of debt asset
120
- // leg1DepositAmount: amountToDeposit,
121
- // minHF: 1.1, // undo
122
- // vesuAdapter: vesuAdapter
123
- // });
124
- // remainingAmount = remainingAmount.minus(amountToDeposit);
125
-
126
- // // return the first possible call because computing all calls at a time
127
- // // is not efficinet for swaps
128
- // return {call, vesuAdapter};
129
- // }
130
- // throw new Error(`${this.getTag()}::getAvnuSwapMultiplyCall: no calls found`);
131
- // }
132
-
133
- // async _getAvnuDepositSwapLegCall(params: {
134
- // isDeposit: boolean,
135
- // leg1DepositAmount: Web3Number,
136
- // minHF: number, // e.g. 1.01
137
- // vesuAdapter: VesuAdapter
138
- // }) {
139
- // const { vesuAdapter } = params;
140
- // logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall params: ${JSON.stringify(params)}`);
141
- // assert(params.isDeposit, 'Only deposit is supported in _getAvnuDepositSwapLegCall')
142
- // // add collateral
143
- // // borrow STRK (e.g.)
144
- // // approve and swap strk
145
- // // add collateral again
146
-
147
-
148
- // const legLTV = await vesuAdapter.getLTVConfig(this.config);
149
- // logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall legLTV: ${legLTV}`);
150
- // const existingPositions = await vesuAdapter.getPositions(this.config);
151
- // const collateralisation = await vesuAdapter.getCollateralization(this.config);
152
- // const existingCollateralInfo = existingPositions[0];
153
- // const existingDebtInfo = existingPositions[1];
154
- // logger.debug(`${this.getTag()}::_getAvnuDepositSwapLegCall existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
155
- // existingDebtInfo: ${JSON.stringify(existingDebtInfo)}, collateralisation: ${JSON.stringify(collateralisation)}`);
156
-
157
- // // - Prices as seen by Vesu contracts, ideal for HF math
158
- // // Price 1 is ok as fallback bcz that would relatively price the
159
- // // collateral and debt as equal.
160
- // const collateralPrice = collateralisation[0].usdValue > 0 ? collateralisation[0].usdValue / existingCollateralInfo.amount.toNumber() : 1;
161
- // const debtPrice = collateralisation[1].usdValue > 0 ? collateralisation[1].usdValue / existingDebtInfo.amount.toNumber() : 1;
162
- // logger.debug(`${this.getTag()}::_getAvnuDepositSwapLegCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`);
163
-
164
-
165
- // const debtTokenInfo = vesuAdapter.config.debt;
166
- // let newDepositAmount = params.leg1DepositAmount;
167
- // const totalCollateral = existingCollateralInfo.amount.plus(params.leg1DepositAmount);
168
- // logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall totalCollateral: ${totalCollateral}`);
169
- // const totalDebtAmount = new Web3Number(
170
- // totalCollateral.multipliedBy(collateralPrice).multipliedBy(legLTV).dividedBy(debtPrice).dividedBy(params.minHF).toString(),
171
- // debtTokenInfo.decimals
172
- // );
173
- // let debtAmount = totalDebtAmount.minus(existingDebtInfo.amount);
174
- // logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall totalDebtAmount: ${totalDebtAmount}, initial computed debt: ${debtAmount}`);
175
- // const maxBorrowable = await this.getMaxBorrowableAmountByVesuAdapter(vesuAdapter, false);
176
-
177
- // // if the debt amount is greater than 0 and the max borrowable amount is 0, skip
178
- // if (debtAmount.gt(0) && maxBorrowable.amount.eq(0)) {
179
- // logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall maxBorrowable is 0, skipping`);
180
- // return undefined;
181
- // } else if (debtAmount.gt(0) && maxBorrowable.amount.gt(0)) {
182
- // debtAmount = maxBorrowable.amount.minimum(debtAmount);
183
- // const newDebtUSDValue = debtAmount.multipliedBy(debtPrice);
184
- // const totalCollateralRequired = HealthFactorMath.getCollateralRequired(
185
- // debtAmount.plus(existingDebtInfo.amount),
186
- // debtPrice,
187
- // params.minHF,
188
- // legLTV,
189
- // collateralPrice,
190
- // this.asset()
191
- // );
192
- // newDepositAmount = totalCollateralRequired.minus(existingCollateralInfo.amount);
193
- // if (newDepositAmount.lt(0)) {
194
- // throw new Error(`${this.getTag()}::_getAvnuDepositSwapLegCall newDepositAmount is less than 0, newDepositAmount: ${newDepositAmount}, totalCollateralRequired: ${totalCollateralRequired}, existingCollateralInfo.amount: ${existingCollateralInfo.amount}`);
195
- // }
196
- // if (newDebtUSDValue.toNumber() < 100) {
197
- // // too less debt, skip
198
- // logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall newDebtUSDValue is less than 100, skipping`);
199
- // return undefined;
200
- // }
201
- // }
202
-
203
- // logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall debtAmount: ${debtAmount}`);
204
- // if (debtAmount.lt(0)) {
205
- // // this is to unwind the position to optimal HF.
206
- // const lstDEXPrice = await this.getLSTDexPrice();
207
- // const debtAmountInLST = debtAmount.abs().dividedBy(lstDEXPrice);
208
- // const calls = await this.getVesuMultiplyCall({
209
- // isDeposit: false,
210
- // leg1DepositAmount: debtAmountInLST
211
- // })
212
- // assert(calls.length == 1, `Expected 1 call for unwind, got ${calls.length}`);
213
- // return calls[0];
214
- // }
215
- // console.log(`debtAmount`, debtAmount.toWei(), params.leg1DepositAmount.toWei());
216
- // const STEP0 = UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1;
217
- // const manage0Info = this.getProofs<ApproveCallParams>(STEP0);
218
- // const manageCall0 = manage0Info.callConstructor({
219
- // amount: newDepositAmount
220
- // });
221
- // const STEP1 = getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, vesuAdapter.config.debt.symbol);
222
- // const manage1Info = this.getProofs<VesuModifyPositionCallParams>(STEP1);
223
- // const manageCall1 = manage1Info.callConstructor(VesuAdapter.getDefaultModifyPositionCallParams({
224
- // collateralAmount: newDepositAmount,
225
- // isAddCollateral: params.isDeposit,
226
- // debtAmount: debtAmount,
227
- // isBorrow: params.isDeposit
228
- // }));
229
-
230
- // console.log(`manageCall1`, manageCall1.call, debtAmount.toWei(), newDepositAmount.toWei());
231
-
232
- // const proofIds: string[] = [STEP0, STEP1];
233
- // const manageCalls: ManageCall[] = [manageCall0, manageCall1];
234
-
235
- // // approve and swap to LST using avnu
236
- // if (debtAmount.gt(0)) {
237
- // const STEP2 = getAvnuManageIDs(LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_DEPOSIT, vesuAdapter.config.debt.symbol);
238
- // const manage2Info = this.getProofs<ApproveCallParams>(STEP2);
239
- // const manageCall2 = manage2Info.callConstructor({
240
- // amount: debtAmount
241
- // });
242
-
243
- // const debtTokenInfo = vesuAdapter.config.debt;
244
- // const lstTokenInfo = this.asset();
245
- // const avnuModule = new AvnuWrapper();
246
- // const quote = await avnuModule.getQuotes(
247
- // debtTokenInfo.address.address,
248
- // lstTokenInfo.address.address,
249
- // debtAmount.toWei(),
250
- // this.metadata.additionalInfo.vaultAllocator.address
251
- // );
252
- // const minAmount = await this._getMinOutputAmountLSTBuy(debtAmount);
253
- // const minAmountWei = (minAmount).toWei();
254
- // logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall minAmount: ${minAmount}`);
255
- // const swapInfo = await avnuModule.getSwapInfo(
256
- // quote,
257
- // this.metadata.additionalInfo.vaultAllocator.address,
258
- // 0,
259
- // this.address.address,
260
- // minAmountWei
261
- // );
262
- // logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall swapInfo: ${JSON.stringify(swapInfo)}`);
263
- // const STEP3 = getAvnuManageIDs(LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_DEPOSIT, vesuAdapter.config.debt.symbol);
264
- // const manage3Info = this.getProofs<AvnuSwapCallParams>(STEP3);
265
- // const manageCall3 = manage3Info.callConstructor({
266
- // props: swapInfo
267
- // });
268
- // proofIds.push(STEP2);
269
- // proofIds.push(STEP3);
270
- // manageCalls.push(manageCall2, manageCall3);
271
-
272
-
273
- // // if the created debt, when added is collateral will put the total HF above min, but below (target + 0.05),
274
- // // then lets close the looping cycle by adding this as collateral.
275
- // const newCollateral = minAmount.plus(totalCollateral);
276
- // const newHF = newCollateral.multipliedBy(collateralPrice).multipliedBy(legLTV).dividedBy(totalDebtAmount).dividedBy(debtPrice).toNumber();
277
- // logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall newHF: ${newHF}`);
278
- // if (newHF > this.metadata.additionalInfo.minHealthFactor && newHF < this.metadata.additionalInfo.targetHealthFactor + 0.05) {
279
- // logger.verbose(`${this.getTag()}::_getAvnuDepositSwapLegCall newHF is above min and below target + 0.05, adding collateral on vesu`);
280
- // // approve and add collateral on vesu (modify position)
281
- // const STEP4 = UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1;
282
- // const manage4Info = this.getProofs<ApproveCallParams>(STEP4);
283
- // const manageCall4 = manage4Info.callConstructor({
284
- // amount: minAmount
285
- // });
286
-
287
- // const STEP5 = getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, vesuAdapter.config.debt.symbol);
288
- // const manage5Info = this.getProofs<VesuModifyPositionCallParams>(STEP5);
289
- // const manageCall5 = manage5Info.callConstructor(VesuAdapter.getDefaultModifyPositionCallParams({
290
- // collateralAmount: minAmount,
291
- // isAddCollateral: true,
292
- // debtAmount: Web3Number.fromWei('0', this.asset().decimals),
293
- // isBorrow: params.isDeposit
294
- // }));
295
- // proofIds.push(STEP4, STEP5);
296
- // manageCalls.push(manageCall4, manageCall5);
297
- // }
298
- // }
299
-
300
- // const manageCall = this.getManageCall(proofIds, manageCalls);
301
- // return manageCall;
302
- // }
303
-
304
- // todo unwind or not deposit when the yield is bad.
305
-
306
- // async getLSTMultiplierRebalanceCall(): Promise<{ shouldRebalance: boolean, manageCalls: {vesuAdapter: VesuAdapter, manageCall: Call}[] }> {
307
- // let shouldRebalance = false;
308
- // const calls: {vesuAdapter: VesuAdapter, manageCall: Call}[] = [];
309
- // // todo undo
310
- // const allVesuAdapters = this.getVesuAdapters().filter(vesuAdapter => vesuAdapter.config.debt.symbol === 'LBTC');
311
- // for (const vesuAdapter of allVesuAdapters) {
312
- // const call = await this._getLSTMultiplierRebalanceCall(vesuAdapter);
313
- // if (call.shouldRebalance && call.manageCall) {
314
- // shouldRebalance = true;
315
- // calls.push({vesuAdapter, manageCall: call.manageCall});
316
- // }
317
- // }
318
- // return { shouldRebalance, manageCalls: calls };
319
- // }
320
-
321
- // async _getLSTMultiplierRebalanceCall(vesuAdapter: VesuAdapter): Promise<{ shouldRebalance: boolean, manageCall: Call | undefined }> {
322
- // const positions = await vesuAdapter.getPositions(this.config);
323
- // assert(positions.length == 2, 'Rebalance call is only supported for 2 positions');
324
- // const existingCollateralInfo = positions[0];
325
- // const existingDebtInfo = positions[1];
326
- // const unusedBalance = await this.getUnusedBalance();
327
- // const healthFactor = await vesuAdapter.getHealthFactor();
328
-
329
- // const collateralisation = await vesuAdapter.getCollateralization(this.config);
330
- // logger.debug(`${this.getTag()}::getVesuMultiplyCall::${vesuAdapter.config.debt.symbol} existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
331
- // existingDebtInfo: ${JSON.stringify(existingDebtInfo)}, collateralisation: ${JSON.stringify(collateralisation)}`);
332
-
333
- // // - Prices as seen by Vesu contracts, ideal for HF math
334
- // // Price 1 is ok as fallback bcz that would relatively price the
335
- // // collateral and debt as equal.
336
- // const collateralPrice = collateralisation[0].usdValue > 0 ? collateralisation[0].usdValue / existingCollateralInfo.amount.toNumber() : 1;
337
- // const debtPrice = collateralisation[1].usdValue > 0 ? collateralisation[1].usdValue / existingDebtInfo.amount.toNumber() : 1;
338
- // logger.debug(`${this.getTag()}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`);
339
- // logger.debug(`${this.getTag()}::getVesuMultiplyCall healthFactor: ${healthFactor}`);
340
-
341
- // const isHFTooLow = healthFactor < this.metadata.additionalInfo.minHealthFactor;
342
- // const isHFTooHigh = healthFactor > this.metadata.additionalInfo.targetHealthFactor + 0.05;
343
- // if (isHFTooLow || isHFTooHigh || 1) {
344
- // // use unused collateral to target more.
345
- // const manageCall = await this._getAvnuDepositSwapLegCall({
346
- // isDeposit: true,
347
- // leg1DepositAmount: unusedBalance.amount,
348
- // minHF: 1.02, // todo, shouldnt use this 1.02 HF, if there isn;t more looping left.
349
- // vesuAdapter
350
- // })
351
- // return { shouldRebalance: true, manageCall };
352
- // } else {
353
- // // do nothing
354
- // return { shouldRebalance: false, manageCall: undefined };
355
- // }
356
- // }
357
-
358
- // protected async getVesuAUM(adapter: VesuAdapter) {
359
- // const legAUM = await adapter.getPositions(this.config);
360
- // const underlying = this.asset();
361
- // // assert its an LST of Endur
362
- // assert(underlying.symbol.startsWith('x'), 'Underlying is not an LST of Endur');
363
-
364
- // let vesuAum = Web3Number.fromWei("0", underlying.decimals);
365
-
366
- // let tokenUnderlyingPrice = await this.getLSTExchangeRate();
367
- // // to offset for usual DEX lag, we multiply by 0.998 (i.e. 0.2% loss)
368
- // tokenUnderlyingPrice = tokenUnderlyingPrice * 0.998;
369
- // logger.verbose(`${this.getTag()} tokenUnderlyingPrice: ${tokenUnderlyingPrice}`);
370
-
371
- // // handle collateral
372
- // if (legAUM[0].token.address.eq(underlying.address)) {
373
- // vesuAum = vesuAum.plus(legAUM[0].amount);
374
- // } else {
375
- // vesuAum = vesuAum.plus(legAUM[0].amount.dividedBy(tokenUnderlyingPrice));
376
- // }
377
-
378
- // // handle debt
379
- // if (legAUM[1].token.address.eq(underlying.address)) {
380
- // vesuAum = vesuAum.minus(legAUM[1].amount);
381
- // } else {
382
- // vesuAum = vesuAum.minus(legAUM[1].amount.dividedBy(tokenUnderlyingPrice));
383
- // };
384
-
385
- // logger.verbose(`${this.getTag()} Vesu AUM: ${vesuAum}, legCollateral: ${legAUM[0].amount.toNumber()}, legDebt: ${legAUM[1].amount.toNumber()}`);
386
- // return vesuAum;
387
- // }
388
-
389
- //
390
- // private async _getMinOutputAmountLSTBuy(amountInUnderlying: Web3Number) {
391
- // const lstTruePrice = await this.getLSTExchangeRate();
392
- // // during buy, the purchase should always be <= true LST price.
393
- // const minOutputAmount = amountInUnderlying.dividedBy(lstTruePrice).multipliedBy(0.99979); // minus 0.021% to account for avnu fees
394
- // return new Web3Number(minOutputAmount.toString(), this.asset().decimals);
395
- // }
396
-
397
- // private async _getMinOutputAmountLSTSell(amountInLST: Web3Number) {
398
- // const lstTruePrice = await this.getLSTExchangeRate();
399
- // // during sell, the purchase should always be > 0.995 * true LST price.
400
- // const minOutputAmount = amountInLST.multipliedBy(lstTruePrice).multipliedBy(0.995);
401
- // return minOutputAmount;
402
- // }
403
-
404
-
405
-
406
- // todo add a function to findout max borrowable amount without fucking yield
407
- // if the current net yield < LST yield, add a function to calculate how much to unwind.
408
-
409
- /**
410
- * Uses vesu's multiple call to create leverage on LST
411
- * Deposit amount is in LST
412
- * @param params
413
- */
414
- async getFundManagementCall(params: {
415
- isDeposit: boolean,
416
- leg1DepositAmount: Web3Number
417
- }) {
418
- logger.verbose(`${this.getTag()}::getFundManagementCall params: ${JSON.stringify(params)}`);
419
- // const legLTV = await vesuAdapter1.getLTVConfig(this.config);
420
- // logger.verbose(`${this.getTag()}::getVesuMultiplyCall legLTV: ${legLTV}`);
421
- const allAdapters = this.metadata.additionalInfo.adapters.map(adapter => adapter.adapter);
422
- if (!params.isDeposit) {
423
- // try using unused balance to unwind.
424
- // no need to unwind.
425
- const unusedBalance = await this.getUnusedBalance();
426
- logger.verbose(`${this.getTag()}::getVesuMultiplyCall unusedBalance: ${unusedBalance.amount.toString()}, required: ${params.leg1DepositAmount.toString()}`);
427
- if (unusedBalance.amount.gte(params.leg1DepositAmount)) {
428
- return null;
429
- } else {
430
- const adapters = await AdapterOptimizer.getAdapterToUse(allAdapters, false, params.leg1DepositAmount);
431
- if (adapters.length > 0) {
432
- const proofsInfo = adapters.map(adapter => adapter.getProofs(false, this.getMerkleTree()));
433
- const calls: Call[] = [];
434
- for (const info of proofsInfo) {
435
- const proofGroups = info.proofs;
436
- const call = this.getManageCall(proofGroups, await info.callConstructor({amount: params.leg1DepositAmount}));
437
- calls.push(call);
438
- }
439
- return calls;
600
+
601
+ protected async getVesuAUM(adapter: VesuAdapter) {
602
+ const legAUM = await adapter.getPositions(this.config);
603
+ const underlying = this.asset();
604
+ // assert its an LST of Endur
605
+ assert(
606
+ underlying.symbol.startsWith("x"),
607
+ "Underlying is not an LST of Endur"
608
+ );
609
+
610
+ let vesuAum = Web3Number.fromWei("0", underlying.decimals);
611
+
612
+ let tokenUnderlyingPrice = await this.getLSTExchangeRate();
613
+ // to offset for usual DEX lag, we multiply by 0.998 (i.e. 0.2% loss)
614
+ tokenUnderlyingPrice = tokenUnderlyingPrice * 0.998;
615
+ logger.verbose(
616
+ `${this.getTag()} tokenUnderlyingPrice: ${tokenUnderlyingPrice}`
617
+ );
618
+
619
+ // handle collateral
620
+ if (legAUM[0].token.address.eq(underlying.address)) {
621
+ vesuAum = vesuAum.plus(legAUM[0].amount);
622
+ } else {
623
+ vesuAum = vesuAum.plus(
624
+ legAUM[0].amount.dividedBy(tokenUnderlyingPrice)
625
+ );
626
+ }
627
+
628
+ // handle debt
629
+ if (legAUM[1].token.address.eq(underlying.address)) {
630
+ vesuAum = vesuAum.minus(legAUM[1].amount);
440
631
  } else {
441
- throw new Error(`${this.getTag()}::getVesuMultiplyCall: no adapters to use for unused balance: ${unusedBalance.amount.toString()}`);
632
+ vesuAum = vesuAum.minus(
633
+ legAUM[1].amount.dividedBy(tokenUnderlyingPrice)
634
+ );
442
635
  }
443
- }
636
+
637
+ logger.verbose(
638
+ `${this.getTag()} Vesu AUM: ${vesuAum}, legCollateral: ${legAUM[0].amount.toNumber()}, legDebt: ${legAUM[1].amount.toNumber()}`
639
+ );
640
+ return vesuAum;
444
641
  }
445
642
 
446
- const adapters = await AdapterOptimizer.getAdapterToUse(allAdapters, true, params.leg1DepositAmount);
447
- if (adapters.length > 0) {
448
- const proofsInfo = adapters.map(adapter => adapter.getProofs(true, this.getMerkleTree()));
449
- const calls: Call[] = [];
450
- for (const info of proofsInfo) {
451
- const proofGroups = info.proofs;
452
- const call = this.getManageCall(proofGroups, await info.callConstructor({amount: params.leg1DepositAmount}));
453
- calls.push(call);
454
- }
455
- return calls;
456
- } else {
457
- throw new Error(`${this.getTag()}::getVesuMultiplyCall: no adapters to use for deposit: ${params.leg1DepositAmount.toString()}`);
643
+ //
644
+ private async _getMinOutputAmountLSTBuy(amountInUnderlying: Web3Number) {
645
+ const lstTruePrice = await this.getLSTExchangeRate();
646
+ // during buy, the purchase should always be <= true LST price.
647
+ const minOutputAmount = amountInUnderlying
648
+ .dividedBy(lstTruePrice)
649
+ .multipliedBy(0.99979); // minus 0.021% to account for avnu fees
650
+ return new Web3Number(
651
+ minOutputAmount.toString(),
652
+ this.asset().decimals
653
+ );
458
654
  }
459
655
 
460
- // const existingPositions = await vesuAdapter1.getPositions(this.config);
461
- // const collateralisation = await vesuAdapter1.getCollateralization(this.config);
462
- // const existingCollateralInfo = existingPositions[0];
463
- // const existingDebtInfo = existingPositions[1];
464
- // logger.debug(`${this.getTag()}::getVesuMultiplyCall existingCollateralInfo: ${JSON.stringify(existingCollateralInfo)},
465
- // existingDebtInfo: ${JSON.stringify(existingDebtInfo)}, collateralisation: ${JSON.stringify(collateralisation)}`);
466
-
467
- // // - Prices as seen by Vesu contracts, ideal for HF math
468
- // // Price 1 is ok as fallback bcz that would relatively price the
469
- // // collateral and debt as equal.
470
- // const collateralPrice = collateralisation[0].usdValue > 0 ? collateralisation[0].usdValue / existingCollateralInfo.amount.toNumber() : 1;
471
- // const debtPrice = collateralisation[1].usdValue > 0 ? collateralisation[1].usdValue / existingDebtInfo.amount.toNumber() : 1;
472
- // logger.debug(`${this.getTag()}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`);
473
-
474
- // // - Prices as seen by actual swap price
475
- // const dexPrice = await this.getLSTDexPrice();
476
-
477
- // // compute optimal amount of collateral and debt post addition/removal
478
- // // target hf = collateral * collateralPrice * ltv / debt * debtPrice
479
- // // assuming X to be the usd amount of debt borrowed or repaied (negative).
480
- // // target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv / (debt * debtPrice + X)
481
- // // => X * target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv - (debt * debtPrice * target hf)
482
- // // => X * (target hf - ltv)= ((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)
483
- // // => X = (((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)) / (target hf - ltv)
484
- // const addedCollateral = params.leg1DepositAmount
485
- // .multipliedBy(params.isDeposit ? 1 : -1)
486
- // logger.verbose(`${this.getTag()}::getVesuMultiplyCall addedCollateral: ${addedCollateral}`);
487
- // const numeratorPart1 = (existingCollateralInfo.amount.plus((addedCollateral))).multipliedBy(collateralPrice).multipliedBy(legLTV);
488
- // logger.verbose(`${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}`);
489
- // const numeratorPart2 = existingDebtInfo.amount.multipliedBy(debtPrice).multipliedBy(this.metadata.additionalInfo.targetHealthFactor);
490
- // logger.verbose(`${this.getTag()}::getVesuMultiplyCall numeratorPart2: ${numeratorPart2}`);
491
- // const denominatorPart = this.metadata.additionalInfo.targetHealthFactor - (legLTV / dexPrice);
492
- // logger.verbose(`${this.getTag()}::getVesuMultiplyCall denominatorPart: ${denominatorPart}`);
493
- // const x_debt_usd = numeratorPart1.minus(numeratorPart2).dividedBy(denominatorPart);
494
- // logger.verbose(`${this.getTag()}::getVesuMultiplyCall x_debt_usd: ${x_debt_usd}`);
495
- // logger.debug(`${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}, numeratorPart2: ${numeratorPart2}, denominatorPart: ${denominatorPart}`);
496
-
497
- // // both in underlying
498
- // const debtAmount = x_debt_usd.dividedBy(debtPrice);
499
- // const marginAmount = addedCollateral;
500
- // logger.verbose(`${this.getTag()}::getVesuMultiplyCall debtAmount: ${debtAmount}, marginAmount: ${marginAmount}`);
501
-
502
- // // Cases of lever increase (within the scopr of this function)
503
- // // 1. debtAmount > 0 and marginAmount > 0
504
- // // 2. debtAmount > 0 and marginAmount < 0
505
-
506
- // // Cases of lever decrease
507
- // // 3. debtAmount < 0 and marginAmount > 0
508
- // // 4. debtAmount < 0 and marginAmount < 0
509
- // return this.getModifyLeverCall({
510
- // marginAmount,
511
- // debtAmount,
512
- // lstDexPriceInUnderlying: dexPrice,
513
- // isIncrease: debtAmount.greaterThan(0)
514
- // });
515
- }
516
-
517
-
518
- getLSTUnderlyingTokenInfo() {
519
- return this.metadata.additionalInfo.underlyingToken;
520
- }
521
-
522
- // async getMaxBorrowableAmount(params: { isAPYComputation: boolean } = { isAPYComputation: false }) {
523
- // const vesuAdapters = this.getVesuAdapters();
524
- // let netMaxBorrowableAmount = Web3Number.fromWei("0", this.getLSTUnderlyingTokenInfo().decimals);
525
- // const maxBorrowables: {amount: Web3Number, dexSwappableAmount: Web3Number, maxBorrowableAmount: Web3Number, borrowableAsset: TokenInfo}[] = [];
526
- // for (const vesuAdapter of vesuAdapters) {
527
- // maxBorrowables.push(await this.getMaxBorrowableAmountByVesuAdapter(vesuAdapter, params.isAPYComputation));
528
- // }
529
- // maxBorrowables.sort((a, b) => b.amount.toNumber() - a.amount.toNumber());
530
- // netMaxBorrowableAmount = maxBorrowables.reduce((acc, curr) => acc.plus(curr.amount), Web3Number.fromWei("0", this.getLSTUnderlyingTokenInfo().decimals));
531
- // return {netMaxBorrowableAmount, maxBorrowables};
532
- // }
533
-
534
- // recursively, using binary search computes max swappable.
535
- // @dev assumes 1 token of from == 1 token of to
536
- // async getMaxSwappableWithMaxSlippage(fromToken: TokenInfo, toToken: TokenInfo, maxSlippage: number, maxAmount: Web3Number) {
537
- // const output = await findMaxInputWithSlippage({
538
- // apiGetOutput: async (inputAmount: number): Promise<number> => {
539
- // const ekuboQuoter = new EkuboQuoter(this.config);
540
- // await new Promise(resolve => setTimeout(resolve, 1000)); // artificial delay, to avoid rate limit
541
- // const quote = await ekuboQuoter.getQuote(fromToken.address.address, toToken.address.address, new Web3Number(inputAmount.toFixed(9), fromToken.decimals));
542
- // return Web3Number.fromWei(quote.total_calculated.toString(), toToken.decimals).toNumber();
543
- // },
544
- // maxInput: maxAmount.toNumber(),
545
- // maxSlippagePercent: maxSlippage,
546
- // tolerance: 0.001,
547
- // referenceRate: 1,
548
- // });
549
- // return new Web3Number(output.optimalInput, fromToken.decimals);
550
- // }
551
-
552
- // async getMaxBorrowableAmountByVesuAdapter(vesuAdapter: VesuAdapter, isAPYComputation: boolean) {
553
- // const lstAPY = await this.getLSTAPR(this.getLSTUnderlyingTokenInfo().address);
554
- // const maxInterestRate = lstAPY * 0.8;
555
- // const maxBorrowableAmount = await vesuAdapter.getMaxBorrowableByInterestRate(this.config, vesuAdapter.config.debt, maxInterestRate);
556
- // const debtCap = await vesuAdapter.getDebtCap(this.config);
557
-
558
- // const maxBorrowable = maxBorrowableAmount.minimum(debtCap).multipliedBy(0.999);
559
- // // Dont compute precise max swappable for APY computation
560
- // if (vesuAdapter.config.debt.address.eq(this.getLSTUnderlyingTokenInfo().address) || isAPYComputation) {
561
- // return {amount: maxBorrowable, dexSwappableAmount: maxBorrowable, maxBorrowableAmount: maxBorrowable, borrowableAsset: vesuAdapter.config.debt};
562
- // }
563
- // // Want < 0.02% slippage
564
- // try {
565
- // const maxSwappable = await this.getMaxSwappableWithMaxSlippage(vesuAdapter.config.debt, this.getLSTUnderlyingTokenInfo(), 0.0002, maxBorrowable);
566
- // return {amount: maxBorrowable.minimum(maxSwappable), dexSwappableAmount: maxSwappable, maxBorrowableAmount: maxBorrowable, borrowableAsset: vesuAdapter.config.debt};
567
- // } catch (error) {
568
- // logger.warn(`${this.getTag()}: Failed to get max swappable: ${error}`);
569
- // const maxSwappable = Web3Number.fromWei("0", vesuAdapter.config.debt.decimals);
570
- // return {amount: maxBorrowable.minimum(maxSwappable), dexSwappableAmount: maxSwappable, maxBorrowableAmount: maxBorrowable, borrowableAsset: vesuAdapter.config.debt};
571
- // }
572
- // }
573
-
574
- // todo how much to unwind to get back healthy APY zone again
575
- // if net APY < LST APR + 0.5%, we need to unwind to get back to LST APR + 1% atleast or 0 vesu position
576
- // For xSTRK, simply deposit in Vesu if looping is not viable
577
-
578
- // /**
579
- // * Gets LST APR for the strategy's underlying asset from Endur API
580
- // * @returns Promise<number> The LST APR (not divided by 1e18)
581
- // */
582
- // async getLSTAPR(_address: ContractAddr): Promise<number> {
583
- // try {
584
- // const vesuAdapter1 = this.getVesuSameTokenAdapter();
585
- // const apr = await LSTAPRService.getLSTAPR(vesuAdapter1.config.debt.address);
586
- // if (!apr) {
587
- // throw new Error('Failed to get LST APR');
588
- // }
589
- // return apr;
590
- // } catch (error) {
591
- // logger.warn(`${this.getTag()}: Failed to get LST APR: ${error}`);
592
- // return 0;
593
- // }
594
- // }
595
-
596
- // async maxNewDeposits(params: { isAPYComputation: boolean } = { isAPYComputation: false }) {
597
- // const maxBorrowableAmounts = await this.getMaxBorrowableAmount(params);
598
-
599
- // let ltv: number | undefined = undefined;
600
- // for (let adapter of this.getVesuAdapters()) {
601
- // const maxBorrowableAmount = maxBorrowableAmounts.maxBorrowables.find(b => b.borrowableAsset.address.eq(adapter.config.debt.address))?.amount;
602
- // if (!maxBorrowableAmount) {
603
- // throw new Error(`Max borrowable amount not found for adapter: ${adapter.config.debt.symbol}`);
604
- // }
605
- // const maxLTV = await adapter.getLTVConfig(this.config);
606
- // if (!ltv) {
607
- // ltv = maxLTV;
608
- // } else if (ltv != maxLTV) {
609
- // throw new Error(`LTV mismatch for adapter: ${adapter.config.debt.symbol}`);
610
- // }
611
- // }
612
- // if (!ltv) {
613
- // throw new Error('LTV not found');
614
- // }
615
- // // for simplicity, we assume 1 underlying = 1 LST
616
- // const numerator = this.metadata.additionalInfo.targetHealthFactor * maxBorrowableAmounts.netMaxBorrowableAmount.toNumber() / (ltv)
617
- // return numerator - maxBorrowableAmounts.netMaxBorrowableAmount.toNumber();
618
- // }
619
-
620
- async getAUM(): Promise<{net: SingleTokenInfo, prevAum: Web3Number, splits: PositionInfo[]}> {
621
- const allPositions: PositionInfo[] = [];
622
- for (let adapter of this.metadata.additionalInfo.adapters) {
623
- const positions = await adapter.adapter.getPositions();
624
- allPositions.push(...positions);
656
+ private async _getMinOutputAmountLSTSell(amountInLST: Web3Number) {
657
+ const lstTruePrice = await this.getLSTExchangeRate();
658
+ // during sell, the purchase should always be > 0.995 * true LST price.
659
+ const minOutputAmount = amountInLST
660
+ .multipliedBy(lstTruePrice)
661
+ .multipliedBy(0.995);
662
+ return minOutputAmount;
625
663
  }
626
664
 
627
- const assetPrice = await this.pricer.getPrice(this.asset().symbol);
628
- let netAUM = new Web3Number(0, this.asset().decimals);
629
- for (let position of allPositions) {
630
- if (position.tokenInfo.address.eq(this.asset().address)) {
631
- netAUM = netAUM.plus(position.amount);
632
- } else {
633
- netAUM = netAUM.plus(position.usdValue / assetPrice.price);
634
- }
665
+ // todo add a function to findout max borrowable amount without fucking yield
666
+ // if the current net yield < LST yield, add a function to calculate how much to unwind.
667
+
668
+ /**
669
+ * Uses vesu's multiple call to create leverage on LST
670
+ * Deposit amount is in LST
671
+ * @param params
672
+ */
673
+ async getVesuMultiplyCall(params: {
674
+ isDeposit: boolean;
675
+ leg1DepositAmount: Web3Number;
676
+ maxEkuboPriceImpact?: number;
677
+ }) {
678
+ const maxEkuboPriceImpact = params.maxEkuboPriceImpact || 0.01;
679
+ const vesuAdapter1 = this.getVesuSameTokenAdapter();
680
+ const legLTV = await vesuAdapter1.getLTVConfig(this.config);
681
+ logger.verbose(
682
+ `${this.getTag()}::getVesuMultiplyCall legLTV: ${legLTV}`
683
+ );
684
+
685
+ if (!params.isDeposit) {
686
+ // try using unused balance to unwind.
687
+ // no need to unwind.
688
+ const unusedBalance = await this.getUnusedBalance();
689
+ logger.verbose(
690
+ `${this.getTag()}::getVesuMultiplyCall unusedBalance: ${unusedBalance.amount.toString()}, required: ${params.leg1DepositAmount.toString()}`
691
+ );
692
+ // undo
693
+ // if (unusedBalance.amount.gte(params.leg1DepositAmount)) {
694
+ // return [];
695
+ // }
696
+ // throw new Error('Unused balance is less than the amount to unwind');
697
+ }
698
+
699
+ const existingPositions = await vesuAdapter1.getPositions(this.config);
700
+ const collateralisation = await vesuAdapter1.getCollateralization(
701
+ this.config
702
+ );
703
+ const existingCollateralInfo = existingPositions[0];
704
+ const existingDebtInfo = existingPositions[1];
705
+ logger.debug(`${this.getTag()}::getVesuMultiplyCall existingCollateralInfo: ${JSON.stringify(
706
+ existingCollateralInfo
707
+ )},
708
+ existingDebtInfo: ${JSON.stringify(
709
+ existingDebtInfo
710
+ )}, collateralisation: ${JSON.stringify(collateralisation)}`);
711
+
712
+ // - Prices as seen by Vesu contracts, ideal for HF math
713
+ // Price 1 is ok as fallback bcz that would relatively price the
714
+ // collateral and debt as equal.
715
+ const collateralPrice =
716
+ collateralisation[0].usdValue > 0
717
+ ? collateralisation[0].usdValue /
718
+ existingCollateralInfo.amount.toNumber()
719
+ : 1;
720
+ const debtPrice =
721
+ collateralisation[1].usdValue > 0
722
+ ? collateralisation[1].usdValue /
723
+ existingDebtInfo.amount.toNumber()
724
+ : 1;
725
+ logger.debug(
726
+ `${this.getTag()}::getVesuMultiplyCall collateralPrice: ${collateralPrice}, debtPrice: ${debtPrice}`
727
+ );
728
+
729
+ // - Prices as seen by actual swap price
730
+ const dexPrice = await this.getLSTDexPrice();
731
+
732
+ // compute optimal amount of collateral and debt post addition/removal
733
+ // target hf = collateral * collateralPrice * ltv / debt * debtPrice
734
+ // assuming X to be the usd amount of debt borrowed or repaied (negative).
735
+ // target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv / (debt * debtPrice + X)
736
+ // => X * target hf = (((collateral + legDepositAmount) * collateralPrice + X)) * ltv - (debt * debtPrice * target hf)
737
+ // => X * (target hf - ltv)= ((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)
738
+ // => X = (((collateral + legDepositAmount) * collateralPrice * ltv) - (debt * debtPrice * target hf)) / (target hf - ltv)
739
+ const addedCollateral = params.leg1DepositAmount.multipliedBy(
740
+ params.isDeposit ? 1 : -1
741
+ );
742
+ logger.verbose(
743
+ `${this.getTag()}::getVesuMultiplyCall addedCollateral: ${addedCollateral}`
744
+ );
745
+ const numeratorPart1 = existingCollateralInfo.amount
746
+ .plus(addedCollateral)
747
+ .multipliedBy(collateralPrice)
748
+ .multipliedBy(legLTV);
749
+ logger.verbose(
750
+ `${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}`
751
+ );
752
+ const numeratorPart2 = existingDebtInfo.amount
753
+ .multipliedBy(debtPrice)
754
+ .multipliedBy(this.metadata.additionalInfo.targetHealthFactor);
755
+ logger.verbose(
756
+ `${this.getTag()}::getVesuMultiplyCall numeratorPart2: ${numeratorPart2}`
757
+ );
758
+ const denominatorPart =
759
+ this.metadata.additionalInfo.targetHealthFactor - legLTV / dexPrice;
760
+ logger.verbose(
761
+ `${this.getTag()}::getVesuMultiplyCall denominatorPart: ${denominatorPart}`
762
+ );
763
+ const x_debt_usd = numeratorPart1
764
+ .minus(numeratorPart2)
765
+ .dividedBy(denominatorPart);
766
+ logger.verbose(
767
+ `${this.getTag()}::getVesuMultiplyCall x_debt_usd: ${x_debt_usd}`
768
+ );
769
+ logger.debug(
770
+ `${this.getTag()}::getVesuMultiplyCall numeratorPart1: ${numeratorPart1}, numeratorPart2: ${numeratorPart2}, denominatorPart: ${denominatorPart}`
771
+ );
772
+
773
+ // both in underlying
774
+ let debtAmount = x_debt_usd.dividedBy(debtPrice);
775
+ const marginAmount = addedCollateral;
776
+ logger.verbose(
777
+ `${this.getTag()}::getVesuMultiplyCall debtAmount: ${debtAmount}, marginAmount: ${marginAmount}`
778
+ );
779
+
780
+ if (marginAmount.lt(0) && debtAmount.gt(0)) {
781
+ // if we want to withdraw, but debt can go high, its conflicting between
782
+ // increasing and reducing lever. and in this case, the HF will go high,
783
+ // which is ok.
784
+ debtAmount = Web3Number.fromWei(0, this.asset().decimals);
785
+ }
786
+
787
+ // Cases of lever increase (within the scopr of this function)
788
+ // 1. debtAmount > 0 and marginAmount > 0
789
+ // 2. debtAmount > 0 and marginAmount < 0
790
+
791
+ // Cases of lever decrease
792
+ // 3. debtAmount < 0 and marginAmount > 0
793
+ // 4. debtAmount < 0 and marginAmount < 0
794
+ return this.getModifyLeverCall({
795
+ marginAmount,
796
+ debtAmount,
797
+ lstDexPriceInUnderlying: dexPrice,
798
+ isIncrease: debtAmount.greaterThan(0),
799
+ maxEkuboPriceImpact
800
+ });
635
801
  }
636
802
 
637
- const prevAum = await this.getPrevAUM();
638
- const realAUM: PositionInfo = {
639
- tokenInfo: this.asset(),
640
- amount: netAUM,
641
- usdValue: netAUM.toNumber() * assetPrice.price,
642
- apy: {apy: netAUM.toNumber() * assetPrice.price, type: APYType.BASE},
643
- remarks: AUMTypes.FINALISED,
644
- protocol: Protocols.NONE // just placeholder
645
- };
803
+ async getMaxBorrowableAmount(
804
+ params: { isAPYComputation: boolean } = { isAPYComputation: false }
805
+ ) {
806
+ const vesuAdapters = this.getVesuAdapters();
807
+ let netMaxBorrowableAmount = Web3Number.fromWei(
808
+ "0",
809
+ this.getLSTUnderlyingTokenInfo().decimals
810
+ );
811
+ const maxBorrowables: {
812
+ amount: Web3Number;
813
+ dexSwappableAmount: Web3Number;
814
+ maxBorrowableAmount: Web3Number;
815
+ borrowableAsset: TokenInfo;
816
+ }[] = [];
817
+ for (const vesuAdapter of vesuAdapters) {
818
+ maxBorrowables.push(
819
+ await this.getMaxBorrowableAmountByVesuAdapter(
820
+ vesuAdapter,
821
+ params.isAPYComputation
822
+ )
823
+ );
824
+ }
825
+ maxBorrowables.sort(
826
+ (a, b) => b.amount.toNumber() - a.amount.toNumber()
827
+ );
828
+ netMaxBorrowableAmount = maxBorrowables.reduce(
829
+ (acc, curr) => acc.plus(curr.amount),
830
+ Web3Number.fromWei("0", this.getLSTUnderlyingTokenInfo().decimals)
831
+ );
832
+ return { netMaxBorrowableAmount, maxBorrowables };
833
+ }
646
834
 
647
- const estimatedAUMDelta: PositionInfo = {
648
- tokenInfo: this.asset(),
649
- amount: Web3Number.fromWei('0', this.asset().decimals),
650
- usdValue: 0,
651
- apy: {apy: 0, type: APYType.BASE},
652
- remarks: AUMTypes.DEFISPRING,
653
- protocol: Protocols.NONE // just placeholder
654
- };
835
+ // recursively, using binary search computes max swappable.
836
+ // @dev assumes 1 token of from == 1 token of to
837
+ async getMaxSwappableWithMaxSlippage(
838
+ fromToken: TokenInfo,
839
+ toToken: TokenInfo,
840
+ maxSlippage: number,
841
+ maxAmount: Web3Number
842
+ ) {
843
+ const output = await findMaxInputWithSlippage({
844
+ apiGetOutput: async (inputAmount: number): Promise<number> => {
845
+ const ekuboQuoter = new EkuboQuoter(this.config);
846
+ await new Promise((resolve) => setTimeout(resolve, 1000)); // artificial delay, to avoid rate limit
847
+ const quote = await ekuboQuoter.getQuote(
848
+ fromToken.address.address,
849
+ toToken.address.address,
850
+ new Web3Number(inputAmount.toFixed(9), fromToken.decimals)
851
+ );
852
+ return Web3Number.fromWei(
853
+ quote.total_calculated.toString(),
854
+ toToken.decimals
855
+ ).toNumber();
856
+ },
857
+ maxInput: maxAmount.toNumber(),
858
+ maxSlippagePercent: maxSlippage,
859
+ tolerance: 0.001,
860
+ referenceRate: 1
861
+ });
862
+ return new Web3Number(output.optimalInput, fromToken.decimals);
863
+ }
864
+
865
+ async getMaxBorrowableAmountByVesuAdapter(
866
+ vesuAdapter: VesuAdapter,
867
+ isAPYComputation: boolean
868
+ ) {
869
+ const lstAPY = await this.getLSTAPR(
870
+ this.getLSTUnderlyingTokenInfo().address
871
+ );
872
+ const maxInterestRate = lstAPY * 0.8;
873
+ const maxBorrowableAmount =
874
+ await vesuAdapter.getMaxBorrowableByInterestRate(
875
+ this.config,
876
+ vesuAdapter.config.debt,
877
+ maxInterestRate
878
+ );
879
+ const debtCap = await vesuAdapter.getDebtCap(this.config);
880
+
881
+ const maxBorrowable = maxBorrowableAmount
882
+ .minimum(debtCap)
883
+ .multipliedBy(0.999);
884
+ // Dont compute precise max swappable for APY computation
885
+ if (
886
+ vesuAdapter.config.debt.address.eq(
887
+ this.getLSTUnderlyingTokenInfo().address
888
+ ) ||
889
+ isAPYComputation
890
+ ) {
891
+ return {
892
+ amount: maxBorrowable,
893
+ dexSwappableAmount: maxBorrowable,
894
+ maxBorrowableAmount: maxBorrowable,
895
+ borrowableAsset: vesuAdapter.config.debt
896
+ };
897
+ }
898
+ // Want < 0.02% slippage
899
+ try {
900
+ const maxSwappable = await this.getMaxSwappableWithMaxSlippage(
901
+ vesuAdapter.config.debt,
902
+ this.getLSTUnderlyingTokenInfo(),
903
+ 0.0002,
904
+ maxBorrowable
905
+ );
906
+ return {
907
+ amount: maxBorrowable.minimum(maxSwappable),
908
+ dexSwappableAmount: maxSwappable,
909
+ maxBorrowableAmount: maxBorrowable,
910
+ borrowableAsset: vesuAdapter.config.debt
911
+ };
912
+ } catch (error) {
913
+ logger.warn(
914
+ `${this.getTag()}: Failed to get max swappable: ${error}`
915
+ );
916
+ const maxSwappable = Web3Number.fromWei(
917
+ "0",
918
+ vesuAdapter.config.debt.decimals
919
+ );
920
+ return {
921
+ amount: maxBorrowable.minimum(maxSwappable),
922
+ dexSwappableAmount: maxSwappable,
923
+ maxBorrowableAmount: maxBorrowable,
924
+ borrowableAsset: vesuAdapter.config.debt
925
+ };
926
+ }
927
+ }
928
+
929
+ // todo how much to unwind to get back healthy APY zone again
930
+ // if net APY < LST APR + 0.5%, we need to unwind to get back to LST APR + 1% atleast or 0 vesu position
931
+ // For xSTRK, simply deposit in Vesu if looping is not viable
932
+
933
+ /**
934
+ * Gets LST APR for the strategy's underlying asset from Endur API
935
+ * @returns Promise<number> The LST APR (not divided by 1e18)
936
+ */
937
+ async getLSTAPR(_address: ContractAddr): Promise<number> {
938
+ try {
939
+ const vesuAdapter1 = this.getVesuSameTokenAdapter();
940
+ const apr = await LSTAPRService.getLSTAPR(
941
+ vesuAdapter1.config.debt.address
942
+ );
943
+ if (!apr) {
944
+ throw new Error("Failed to get LST APR");
945
+ }
946
+ return apr;
947
+ } catch (error) {
948
+ logger.warn(`${this.getTag()}: Failed to get LST APR: ${error}`);
949
+ return 0;
950
+ }
951
+ }
952
+
953
+ // todo undo this
954
+ async netAPY(): Promise<{
955
+ net: number;
956
+ splits: { apy: number; id: string }[];
957
+ }> {
958
+ const unusedBalance = await this.getUnusedBalance();
959
+ const maxNewDeposits = await this.maxNewDeposits({
960
+ isAPYComputation: true
961
+ });
962
+ const lstAPY = await this.getLSTAPR(
963
+ this.getLSTUnderlyingTokenInfo().address
964
+ );
965
+
966
+ // if unused balance is > max servicable from loan, we are limited by the max borrowing we can do
967
+ // we also allow accepting little higher deposits (1.5x) to have room for future looping when theres more liquidity or debt cap rises
968
+ if (maxNewDeposits * 1.5 < unusedBalance.amount.toNumber()) {
969
+ // we have excess, just use real APY
970
+ logger.verbose(
971
+ `${this.getTag()}::netAPY: unused balance is > max servicable from loan, lstAPY: ${lstAPY}`
972
+ );
973
+ const output = await super.netAPY();
974
+ output.splits.push({ apy: lstAPY, id: "lst_apy" });
975
+ return output;
976
+ } else {
977
+ // we have little bit room to accept more deposits, we use theoretical max APY
978
+ logger.verbose(
979
+ `${this.getTag()}::netAPY: we can take more deposits, use theoretical max APY`
980
+ );
981
+ const { positions, baseAPYs, rewardAPYs } =
982
+ await this.getVesuAPYs();
983
+ const weights = positions.map(
984
+ (p: VaultPosition, index: number) => p.usdValue * (index % 2 == 0 ? 1 : -1)
985
+ );
986
+ const aum = weights.reduce((acc: number, curr: number) => acc + curr, 0);
987
+ const output = await this.returnNetAPY(
988
+ baseAPYs,
989
+ rewardAPYs,
990
+ weights,
991
+ new Web3Number(
992
+ aum.toFixed(9),
993
+ this.getLSTUnderlyingTokenInfo().decimals
994
+ )
995
+ );
996
+ output.splits.push({ apy: lstAPY, id: "lst_apy" });
997
+ return output;
998
+ }
999
+ }
1000
+
1001
+ async maxNewDeposits(
1002
+ params: { isAPYComputation: boolean } = { isAPYComputation: false }
1003
+ ) {
1004
+ const maxBorrowableAmounts = await this.getMaxBorrowableAmount(params);
1005
+
1006
+ let ltv: number | undefined = undefined;
1007
+ for (let adapter of this.getVesuAdapters()) {
1008
+ const maxBorrowableAmount =
1009
+ maxBorrowableAmounts.maxBorrowables.find((b) =>
1010
+ b.borrowableAsset.address.eq(adapter.config.debt.address)
1011
+ )?.amount;
1012
+ if (!maxBorrowableAmount) {
1013
+ throw new Error(
1014
+ `Max borrowable amount not found for adapter: ${adapter.config.debt.symbol}`
1015
+ );
1016
+ }
1017
+ const maxLTV = await adapter.getLTVConfig(this.config);
1018
+ if (!ltv) {
1019
+ ltv = maxLTV;
1020
+ } else if (ltv != maxLTV) {
1021
+ throw new Error(
1022
+ `LTV mismatch for adapter: ${adapter.config.debt.symbol}`
1023
+ );
1024
+ }
1025
+ }
1026
+ if (!ltv) {
1027
+ throw new Error("LTV not found");
1028
+ }
1029
+ // for simplicity, we assume 1 underlying = 1 LST
1030
+ const numerator =
1031
+ (this.metadata.additionalInfo.targetHealthFactor *
1032
+ maxBorrowableAmounts.netMaxBorrowableAmount.toNumber()) /
1033
+ ltv;
1034
+ return (
1035
+ numerator - maxBorrowableAmounts.netMaxBorrowableAmount.toNumber()
1036
+ );
1037
+ }
655
1038
 
656
- return {net: {
657
- tokenInfo: this.asset(),
658
- amount: netAUM,
659
- usdValue: netAUM.toNumber() * assetPrice.price
660
- }, prevAum: prevAum, splits: [realAUM, estimatedAUMDelta]};
661
- }
662
-
663
- /**
664
- *
665
- * @param params marginAmount is in LST, debtAmount is in underlying
666
- */
667
- // async getModifyLeverCall(params: {
668
- // marginAmount: Web3Number, // >0 during deposit
669
- // debtAmount: Web3Number,
670
- // lstDexPriceInUnderlying: number,
671
- // isIncrease: boolean
672
- // }): Promise<Call[]> {
673
- // logger.verbose(`${this.getTag()}::getModifyLeverCall marginAmount: ${params.marginAmount}, debtAmount: ${params.debtAmount}, lstDexPriceInUnderlying: ${params.lstDexPriceInUnderlying}, isIncrease: ${params.isIncrease}`);
674
-
675
- // const vesuAdapter = this.getVesuSameTokenAdapter();
676
- // const manage0Info = vesuAdapter.
677
- // const manageCall0 = manage0Info.callConstructor({
678
- // amount: newDepositAmount
679
- // });
680
- // const manageCalls = await vesu
681
- // return [this.getManageCall(proofsIDs, manageCalls)];
682
- // }
1039
+ /**
1040
+ * Gets Vesu APYs for all positions
1041
+ * @returns Object with positions, baseAPYs, and rewardAPYs
1042
+ */
1043
+ async getVesuAPYs(): Promise<{
1044
+ positions: VaultPosition[];
1045
+ baseAPYs: number[];
1046
+ rewardAPYs: number[];
1047
+ }> {
1048
+ const positions = await this.getVesuPositions();
1049
+ const baseAPYs: number[] = [];
1050
+ const rewardAPYs: number[] = [];
1051
+
1052
+ // This is a stub implementation - needs to be properly implemented
1053
+ // to fetch actual APYs from Vesu adapters
1054
+ for (const position of positions) {
1055
+ baseAPYs.push(0);
1056
+ rewardAPYs.push(0);
1057
+ }
1058
+
1059
+ return { positions, baseAPYs, rewardAPYs };
1060
+ }
1061
+
1062
+ /**
1063
+ * Returns net APY based on base APYs, reward APYs, weights, and AUM
1064
+ * @param baseAPYs Array of base APY values
1065
+ * @param rewardAPYs Array of reward APY values
1066
+ * @param weights Array of weights for each position
1067
+ * @param aum Total assets under management
1068
+ * @returns Object with net APY and splits
1069
+ */
1070
+ protected async returnNetAPY(
1071
+ baseAPYs: number[],
1072
+ rewardAPYs: number[],
1073
+ weights: number[],
1074
+ aum: Web3Number
1075
+ ): Promise<{
1076
+ net: number;
1077
+ splits: { apy: number; id: string }[];
1078
+ }> {
1079
+ // Calculate weighted average APY
1080
+ let totalWeightedAPY = 0;
1081
+ let totalWeight = 0;
1082
+
1083
+ for (let i = 0; i < baseAPYs.length; i++) {
1084
+ const weight = Math.abs(weights[i]);
1085
+ const totalAPY = baseAPYs[i] + rewardAPYs[i];
1086
+ totalWeightedAPY += totalAPY * weight;
1087
+ totalWeight += weight;
1088
+ }
1089
+
1090
+ const net = totalWeight > 0 ? totalWeightedAPY / totalWeight : 0;
1091
+
1092
+ return {
1093
+ net,
1094
+ splits: []
1095
+ };
1096
+ }
1097
+
1098
+ // todo revisit cases where 0th adapters is used
1099
+ protected async getUnusedBalanceAPY() {
1100
+ const unusedBalance = await this.getUnusedBalance();
1101
+ const vesuAdapter = this.getVesuSameTokenAdapter();
1102
+ const underlying = vesuAdapter.config.debt;
1103
+ const lstAPY = await this.getLSTAPR(underlying.address);
1104
+ return {
1105
+ apy: lstAPY,
1106
+ weight: unusedBalance.usdValue
1107
+ };
1108
+ }
1109
+
1110
+ async getLSTExchangeRate() {
1111
+ const vesuAdapter1 = this.getVesuSameTokenAdapter();
1112
+ const lstTokenInfo = vesuAdapter1.config.collateral;
1113
+ const lstABI = new Contract({
1114
+ abi: ERC4626Abi,
1115
+ address: lstTokenInfo.address.address,
1116
+ providerOrAccount: this.config.provider
1117
+ });
1118
+
1119
+ const price: any = await lstABI.call("convert_to_assets", [
1120
+ uint256.bnToUint256(
1121
+ new Web3Number(1, lstTokenInfo.decimals).toWei()
1122
+ )
1123
+ ]);
1124
+ const exchangeRate =
1125
+ Number(uint256.uint256ToBN(price).toString()) /
1126
+ Math.pow(10, lstTokenInfo.decimals);
1127
+ logger.verbose(`${this.getTag()}:: LST Exchange Rate: ${exchangeRate}`);
1128
+ return exchangeRate;
1129
+ }
1130
+
1131
+ /**
1132
+ *
1133
+ * @param params marginAmount is in LST, debtAmount is in underlying
1134
+ */
1135
+ async getModifyLeverCall(params: {
1136
+ marginAmount: Web3Number; // >0 during deposit
1137
+ debtAmount: Web3Number;
1138
+ lstDexPriceInUnderlying: number;
1139
+ isIncrease: boolean;
1140
+ maxEkuboPriceImpact: number;
1141
+ }): Promise<Call[]> {
1142
+ logger.verbose(
1143
+ `${this.getTag()}::getModifyLeverCall marginAmount: ${
1144
+ params.marginAmount
1145
+ }, debtAmount: ${params.debtAmount}, lstDexPriceInUnderlying: ${
1146
+ params.lstDexPriceInUnderlying
1147
+ }, isIncrease: ${params.isIncrease}`
1148
+ );
1149
+ assert(
1150
+ !params.marginAmount.isZero() || !params.debtAmount.isZero(),
1151
+ "Deposit/debt must be non-0"
1152
+ );
1153
+
1154
+ const vesuAdapter1 = this.getVesuSameTokenAdapter();
1155
+ const lstTokenInfo = this.asset();
1156
+ const lstUnderlyingTokenInfo = vesuAdapter1.config.debt;
1157
+
1158
+ // todo make it more general
1159
+ // 500k STRK (~75k$) or 0.5 BTC (~60k$)
1160
+ const maxAmounts = lstTokenInfo.symbol == "xSTRK" ? 500000 : 0.5;
1161
+ if (params.marginAmount.greaterThan(maxAmounts)) {
1162
+ throw new Error(
1163
+ `Margin amount is greater than max amount: ${params.marginAmount.toNumber()} > ${maxAmounts}`
1164
+ );
1165
+ }
1166
+
1167
+ const proofsIDs: string[] = [];
1168
+ const manageCalls: ManageCall[] = [];
1169
+
1170
+ // approve token
1171
+ if (params.marginAmount.greaterThan(0)) {
1172
+ const STEP1_ID = LST_MULTIPLIER_MANAGE_IDS.MULTIPLE_APPROVE;
1173
+ const manage1Info = this.getProofs<ApproveCallParams>(STEP1_ID);
1174
+ const depositAmount = params.marginAmount;
1175
+ const manageCall1 = manage1Info.callConstructor({
1176
+ amount: depositAmount
1177
+ });
1178
+ proofsIDs.push(STEP1_ID);
1179
+ manageCalls.push(manageCall1);
1180
+ }
1181
+
1182
+ const lstDexPriceInUnderlying = params.lstDexPriceInUnderlying;
1183
+ const lstTrueExchangeRate = await this.getLSTExchangeRate();
1184
+ const ekuboQuoter = new EkuboQuoter(this.config);
1185
+
1186
+ // compute quotes for lever swap
1187
+ const MAX_SLIPPAGE = 0.002;
1188
+ // when increasing, debt is swapped to collateral (LST)
1189
+ // when decreasing, collateral is swapped to debt (underlying)
1190
+ // but both cases, we denominate amount in underlying. negative for decrease (exact amount out)
1191
+ const fromToken = params.isIncrease
1192
+ ? lstUnderlyingTokenInfo
1193
+ : lstTokenInfo;
1194
+ const toToken = params.isIncrease
1195
+ ? lstTokenInfo
1196
+ : lstUnderlyingTokenInfo;
1197
+ const leverSwapQuote = await ekuboQuoter.getQuote(
1198
+ fromToken.address.address,
1199
+ toToken.address.address,
1200
+ params.debtAmount // negative for exact amount out
1201
+ );
1202
+ logger.verbose(
1203
+ `${this.getTag()}::getModifyLeverCall leverSwapQuote: ${JSON.stringify(
1204
+ leverSwapQuote
1205
+ )}`
1206
+ );
1207
+ // Ekubo's price impact can randomly show high numbers sometimes.
1208
+ assert(
1209
+ leverSwapQuote.price_impact <= params.maxEkuboPriceImpact,
1210
+ "getIncreaseLeverCall: Price impact is too high [Debt swap]"
1211
+ );
1212
+ const leverSwap = ekuboQuoter.getVesuMultiplyQuote(
1213
+ leverSwapQuote,
1214
+ fromToken,
1215
+ toToken
1216
+ );
1217
+ logger.verbose(
1218
+ `${this.getTag()}::getModifyLeverCall leverSwap: ${JSON.stringify(
1219
+ leverSwap
1220
+ )}`
1221
+ );
1222
+
1223
+ // todo double check this logic
1224
+ // is Deposit
1225
+ let minLSTReceived = params.debtAmount
1226
+ .dividedBy(lstDexPriceInUnderlying)
1227
+ .multipliedBy(1 - MAX_SLIPPAGE); // used for increase
1228
+ const minLSTReceivedAsPerTruePrice =
1229
+ params.debtAmount.dividedBy(lstTrueExchangeRate); // execution output to be <= True LST price
1230
+ // if (minLSTReceived < minLSTReceivedAsPerTruePrice) {
1231
+ // minLSTReceived = minLSTReceivedAsPerTruePrice; // the execution shouldn't be bad than True price logi
1232
+ // }
1233
+ minLSTReceived = minLSTReceivedAsPerTruePrice; // in any case, we are ok with this, bcz the BTC LST spread shouldnt be high
1234
+ logger.verbose(
1235
+ `${this.getTag()}::getModifyLeverCall minLSTReceivedAsPerTruePrice: ${minLSTReceivedAsPerTruePrice}, minLSTReceived: ${minLSTReceived}`
1236
+ );
1237
+
1238
+ // is withdraw
1239
+ let maxUsedCollateral = params.debtAmount
1240
+ .abs()
1241
+ .dividedBy(lstDexPriceInUnderlying)
1242
+ .multipliedBy(1 + MAX_SLIPPAGE); // +ve for exact amount out, used for decrease
1243
+ const maxUsedCollateralInLST = params.debtAmount
1244
+ .abs()
1245
+ .dividedBy(lstTrueExchangeRate)
1246
+ .multipliedBy(1.005); // 0.5% slippage, worst case based on true price
1247
+ logger.verbose(
1248
+ `${this.getTag()}::getModifyLeverCall maxUsedCollateralInLST: ${maxUsedCollateralInLST}, maxUsedCollateral: ${maxUsedCollateral}`
1249
+ );
1250
+ // if (maxUsedCollateralInLST > maxUsedCollateral) {
1251
+ // maxUsedCollateral = maxUsedCollateralInLST;
1252
+ // }
1253
+ maxUsedCollateral = maxUsedCollateralInLST; // in any case, we are ok with this, bcz the BTC LST spread shouldnt be high
1254
+
1255
+ const STEP2_ID = LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_ON;
1256
+ const manage2Info =
1257
+ this.getProofs<VesuModifyDelegationCallParams>(STEP2_ID);
1258
+ const manageCall2 = manage2Info.callConstructor({
1259
+ delegation: true
1260
+ });
1261
+
1262
+ // deposit and borrow or repay and withdraw
1263
+ const STEP3_ID = getVesuLegId(
1264
+ LST_MULTIPLIER_MANAGE_IDS.MULTIPLY_VESU,
1265
+ vesuAdapter1.config.debt.symbol
1266
+ );
1267
+ const manage3Info = this.getProofs<VesuMultiplyCallParams>(STEP3_ID);
1268
+ const multiplyParams: VesuMultiplyCallParams = params.isIncrease
1269
+ ? {
1270
+ isIncrease: true,
1271
+ increaseParams: {
1272
+ add_margin: params.marginAmount,
1273
+ margin_swap: [],
1274
+ margin_swap_limit_amount: Web3Number.fromWei(
1275
+ 0,
1276
+ this.asset().decimals
1277
+ ),
1278
+ lever_swap: leverSwap,
1279
+ lever_swap_limit_amount: minLSTReceived
1280
+ }
1281
+ }
1282
+ : {
1283
+ isIncrease: false,
1284
+ decreaseParams: {
1285
+ sub_margin: params.marginAmount.multipliedBy(-1),
1286
+ lever_swap: leverSwap,
1287
+ lever_swap_limit_amount: maxUsedCollateral,
1288
+ // only required for close position
1289
+ lever_swap_weights: [],
1290
+ // no need to swap collateral to anything, and any residuals return our contract anyways.
1291
+ withdraw_swap: [],
1292
+ withdraw_swap_limit_amount: Web3Number.fromWei(
1293
+ 0,
1294
+ this.asset().decimals
1295
+ ),
1296
+ withdraw_swap_weights: [],
1297
+ close_position: false
1298
+ }
1299
+ };
1300
+ const manageCall3 = manage3Info.callConstructor(multiplyParams);
1301
+
1302
+ // switch delegation off
1303
+ const STEP4_ID = LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_OFF;
1304
+ const manage4Info =
1305
+ this.getProofs<VesuModifyDelegationCallParams>(STEP4_ID);
1306
+ const manageCall4 = manage4Info.callConstructor({
1307
+ delegation: false
1308
+ });
1309
+
1310
+ proofsIDs.push(STEP2_ID, STEP3_ID, STEP4_ID);
1311
+ manageCalls.push(manageCall2, manageCall3, manageCall4);
1312
+
1313
+ return [this.getManageCall(proofsIDs, manageCalls)];
1314
+ }
683
1315
  }
684
1316
 
685
1317
  export default function VaultDescription(
686
- lstSymbol: string,
687
- underlyingSymbol: string
1318
+ lstSymbol: string,
1319
+ underlyingSymbol: string
688
1320
  ) {
689
- const containerStyle = {
690
- maxWidth: "800px",
691
- margin: "0 auto",
692
- backgroundColor: "#111",
693
- color: "#eee",
694
- fontFamily: "Arial, sans-serif",
695
- borderRadius: "12px",
696
- };
697
-
698
- return (
699
- <div style={containerStyle}>
700
- <h1 style={{ fontSize: "18px", marginBottom: "10px" }}>Liquidation risk managed leverged {lstSymbol} Vault</h1>
701
- <p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
702
- This Levered Endur {lstSymbol} vault is a tokenized leveraged Vault, auto-compounding strategy that takes upto 5x leverage on {lstSymbol} by borrow {underlyingSymbol}. Borrowed amount
703
- is swapped to {lstSymbol} to create leverage. Depositors receive vault shares that
704
- represent a proportional claim on the underlying assets and accrued yield.
705
- </p>
706
-
707
- <p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
708
- This vault uses Vesu for lending and borrowing. The oracle used by this pool is a {highlightTextWithLinks("conversion rate oracle", [{highlight: "conversion rate oracle", link: "https://docs.pragma.build/starknet/development#conversion-rate"}])}
709
- {" "}which is resilient to liquidity issues and price volatility, hence reducing the risk of liquidation. However, overtime, if left un-monitored, debt can increase enough to trigger a liquidation. But no worries, our continuous monitoring systems look for situations with reduced health factor and balance collateral/debt to bring it back to safe levels. With Troves, you can have a peaceful sleep.
710
- </p>
711
-
712
- <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
713
- <p style={{ fontSize: "13px", color: "#ccc" }}>
714
- <strong>Withdrawals:</strong> Requests can take up to <strong>1-2 hours</strong> to process as the vault unwinds and settles routing.
715
- </p>
716
- </div>
717
- <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
718
- <p style={{ fontSize: "13px", color: "#ccc" }}>
719
- <strong>Debt limits:</strong> Pools on Vesu have debt caps that are gradually increased over time. Until caps are raised, deposited Tokens remain in the vault, generating a shared net return for all depositors. There is no additional fee taken by Troves on Yield token's APY, its only on added gain.
720
- </p>
721
- </div>
722
- {/* <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
1321
+ const containerStyle = {
1322
+ maxWidth: "800px",
1323
+ margin: "0 auto",
1324
+ backgroundColor: "#111",
1325
+ color: "#eee",
1326
+ fontFamily: "Arial, sans-serif",
1327
+ borderRadius: "12px"
1328
+ };
1329
+
1330
+ return (
1331
+ <div style={containerStyle}>
1332
+ <h1 style={{ fontSize: "18px", marginBottom: "10px" }}>
1333
+ Liquidation risk managed leverged {lstSymbol} Vault
1334
+ </h1>
1335
+ <p
1336
+ style={{
1337
+ fontSize: "14px",
1338
+ lineHeight: "1.5",
1339
+ marginBottom: "16px"
1340
+ }}
1341
+ >
1342
+ This Levered Endur {lstSymbol} vault is a tokenized leveraged
1343
+ Vault, auto-compounding strategy that takes upto 5x leverage on{" "}
1344
+ {lstSymbol} by borrow {underlyingSymbol}. Borrowed amount is
1345
+ swapped to {lstSymbol} to create leverage. Depositors receive
1346
+ vault shares that represent a proportional claim on the
1347
+ underlying assets and accrued yield.
1348
+ </p>
1349
+
1350
+ <p
1351
+ style={{
1352
+ fontSize: "14px",
1353
+ lineHeight: "1.5",
1354
+ marginBottom: "16px"
1355
+ }}
1356
+ >
1357
+ This vault uses Vesu for lending and borrowing. The oracle used
1358
+ by this pool is a{" "}
1359
+ {highlightTextWithLinks("conversion rate oracle", [
1360
+ {
1361
+ highlight: "conversion rate oracle",
1362
+ link: "https://docs.pragma.build/starknet/development#conversion-rate"
1363
+ }
1364
+ ])}{" "}
1365
+ which is resilient to liquidity issues and price volatility,
1366
+ hence reducing the risk of liquidation. However, overtime, if
1367
+ left un-monitored, debt can increase enough to trigger a
1368
+ liquidation. But no worries, our continuous monitoring systems
1369
+ look for situations with reduced health factor and balance
1370
+ collateral/debt to bring it back to safe levels. With Troves,
1371
+ you can have a peaceful sleep.
1372
+ </p>
1373
+
1374
+ <div
1375
+ style={{
1376
+ backgroundColor: "#222",
1377
+ padding: "10px",
1378
+ borderRadius: "8px",
1379
+ marginBottom: "20px",
1380
+ border: "1px solid #444"
1381
+ }}
1382
+ >
1383
+ <p style={{ fontSize: "13px", color: "#ccc" }}>
1384
+ <strong>Withdrawals:</strong> Requests can take up to{" "}
1385
+ <strong>1-2 hours</strong> to process as the vault unwinds
1386
+ and settles routing.
1387
+ </p>
1388
+ </div>
1389
+ <div
1390
+ style={{
1391
+ backgroundColor: "#222",
1392
+ padding: "10px",
1393
+ borderRadius: "8px",
1394
+ marginBottom: "20px",
1395
+ border: "1px solid #444"
1396
+ }}
1397
+ >
1398
+ <p style={{ fontSize: "13px", color: "#ccc" }}>
1399
+ <strong>Debt limits:</strong> Pools on Vesu have debt caps
1400
+ that are gradually increased over time. Until caps are
1401
+ raised, deposited LSTs remain in the vault, generating a
1402
+ shared net return for all depositors. There is no additional
1403
+ fee taken by Troves on LST APY, its only on added gain.
1404
+ </p>
1405
+ </div>
1406
+ {/* <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
723
1407
  <p style={{ fontSize: "13px", color: "#ccc" }}>
724
1408
  <strong>APY assumptions:</strong> APY shown is the max possible value given current LST and borrowing rates. True APY will be subject to the actual leverage, based on above point. More insights on exact APY will be added soon.
725
1409
  </p>
726
1410
  </div> */}
727
- </div>
728
- );
1411
+ </div>
1412
+ );
729
1413
  }
730
1414
 
731
-
732
1415
  function getDescription(tokenSymbol: string, underlyingSymbol: string) {
733
- return VaultDescription(tokenSymbol, underlyingSymbol);
1416
+ return VaultDescription(tokenSymbol, underlyingSymbol);
734
1417
  }
735
1418
 
736
-
737
- enum LST_MULTIPLIER_MANAGE_IDS {
738
- MULTIPLE_APPROVE = 'multiple_approve',
739
- MULTIPLY_VESU = 'multiply_vesu',
740
- SWITCH_DELEGATION_ON = 'switch_delegation_on',
741
- SWITCH_DELEGATION_OFF = 'switch_delegation_off',
742
- AVNU_MULTIPLY_APPROVE_DEPOSIT = 'avnu_mul_approve_dep',
743
- AVNU_MULTIPLY_SWAP_DEPOSIT = 'avnu_mul_swap_dep',
744
- AVNU_MULTIPLY_APPROVE_WITHDRAW = 'avnu_mul_approve_withdr',
745
- AVNU_MULTIPLY_SWAP_WITHDRAW = 'avnu_mul_swap_withdr',
1419
+ enum LST_MULTIPLIER_MANAGE_IDS {
1420
+ MULTIPLE_APPROVE = "multiple_approve",
1421
+ MULTIPLY_VESU = "multiply_vesu",
1422
+ SWITCH_DELEGATION_ON = "switch_delegation_on",
1423
+ SWITCH_DELEGATION_OFF = "switch_delegation_off",
1424
+ AVNU_MULTIPLY_APPROVE_DEPOSIT = "avnu_mul_approve_dep",
1425
+ AVNU_MULTIPLY_SWAP_DEPOSIT = "avnu_mul_swap_dep",
1426
+ AVNU_MULTIPLY_APPROVE_WITHDRAW = "avnu_mul_approve_withdr",
1427
+ AVNU_MULTIPLY_SWAP_WITHDRAW = "avnu_mul_swap_withdr"
746
1428
  }
747
1429
 
748
- function getAvnuManageIDs(baseID: LST_MULTIPLIER_MANAGE_IDS, debtTokenSymbol: string) {
749
- return `${baseID}_${debtTokenSymbol.toLowerCase()}`;
1430
+ function getAvnuManageIDs(
1431
+ baseID: LST_MULTIPLIER_MANAGE_IDS,
1432
+ debtTokenSymbol: string
1433
+ ) {
1434
+ return `${baseID}_${debtTokenSymbol.toLowerCase()}`;
750
1435
  }
751
1436
 
752
1437
  function getVesuLegId(baseID: string, debtTokenSymbol: string) {
753
- return `${baseID}_${debtTokenSymbol.toLowerCase()}`;
1438
+ return `${baseID}_${debtTokenSymbol.toLowerCase()}`;
754
1439
  }
755
1440
 
756
1441
  function getLooperSettings(
757
- lstSymbol: string,
758
- underlyingSymbol: string,
759
- vaultSettings: HyperLSTStrategySettings,
760
- pool1: ContractAddr,
1442
+ lstSymbol: string,
1443
+ underlyingSymbol: string,
1444
+ vaultSettings: HyperLSTStrategySettings,
1445
+ pool1: ContractAddr
761
1446
  ) {
762
- vaultSettings.leafAdapters = [];
763
-
764
- const lstToken = Global.getDefaultTokens().find(token => token.symbol === lstSymbol)!;
765
- const underlyingToken = Global.getDefaultTokens().find(token => token.symbol === underlyingSymbol)!;
766
-
767
- const baseAdapterConfig: BaseAdapterConfig = {
768
- baseToken: lstToken,
769
- supportedPositions: [{asset: lstToken, isDebt: false}],
770
- networkConfig: getMainnetConfig(),
771
- pricer: new PricerFromApi(getMainnetConfig(), Global.getDefaultTokens()),
772
- vaultAllocator: vaultSettings.vaultAllocator,
773
- vaultAddress: vaultSettings.vaultAddress
774
- }
775
- const vesuAdapterLST = new VesuSupplyOnlyAdapter({
776
- // xWBTC vToken on re7 xBTC pool
777
- vTokenContract: ContractAddr.from('0x062a162d0827db6f43ebb850cbef3c99fc7969e3070b83a2236c9f3713c89fd8'),
778
- ...baseAdapterConfig,
779
- })
780
-
781
- const vesuMultiplyAdapters = borrowableAssets.map(position => new VesuMultiplyAdapter({
782
- poolId: pool1,
783
- collateral: lstToken,
784
- debt: Global.getDefaultTokens().find(token => token.symbol === position)!,
785
- targetHealthFactor: vaultSettings.targetHealthFactor,
786
- minHealthFactor: vaultSettings.minHealthFactor,
787
- quoteAmountToFetchPrice: vaultSettings.quoteAmountToFetchPrice,
788
- ...baseAdapterConfig,
789
- supportedPositions: [{asset: lstToken, isDebt: false}, {asset: Global.getDefaultTokens().find(token => token.symbol === position)!, isDebt: true}],
790
- minimumVesuMovementAmount: 0
791
- }));
792
-
793
- const unusedBalanceAdapter = new UnusedBalanceAdapter({
794
- ...baseAdapterConfig,
795
- });
796
-
797
- vaultSettings.adapters.push({id: `${vesuAdapterLST.name}_${lstToken.symbol}_${underlyingToken.symbol}`, adapter: vesuAdapterLST});
798
- vesuMultiplyAdapters.map(adapter => vaultSettings.adapters.push({id: `${adapter.name}_${lstToken.symbol}_${underlyingToken.symbol}`, adapter: adapter}));
799
- vaultSettings.adapters.push({id: `${unusedBalanceAdapter.name}_${lstToken.symbol}`, adapter: unusedBalanceAdapter});
800
-
801
- const commonAdapter = new CommonAdapter({
802
- id: UNIVERSAL_MANAGE_IDS.FLASH_LOAN,
803
- vaultAddress: vaultSettings.vaultAddress,
804
- vaultAllocator: vaultSettings.vaultAllocator,
805
- manager: vaultSettings.manager,
806
- asset: lstToken.address
807
- })
808
-
809
- // push vesu adapter to leaf adapters
810
- vaultSettings.leafAdapters.push(() => vesuAdapterLST.getDepositLeaf());
811
- vaultSettings.leafAdapters.push(() => vesuAdapterLST.getWithdrawLeaf());
812
-
813
- // push vesu multiply adapter to leaf adapters
814
- vesuMultiplyAdapters.map(adapter => vaultSettings.leafAdapters.push(() => adapter.getDepositLeaf()));
815
- vesuMultiplyAdapters.map(adapter => vaultSettings.leafAdapters.push(() => adapter.getWithdrawLeaf()));
816
-
817
- // to bridge liquidity back to vault (used by bring_liquidity)
818
- vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(lstToken.address, vaultSettings.vaultAddress, UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY).bind(commonAdapter));
819
- vaultSettings.leafAdapters.push(commonAdapter.getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY).bind(commonAdapter));
820
-
821
- // claim rewards
822
- // vaultSettings.leafAdapters.push(vesuAdapterLST.getDefispringRewardsAdapter(UNIVERSAL_MANAGE_IDS.DEFISPRING_REWARDS).bind(vesuAdapterLST));
823
-
824
- // // avnu swap for claims rewards
825
- // const STRKToken = Global.getDefaultTokens().find(token => token.symbol === 'STRK')!;
826
- // vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(STRKToken.address, AVNU_EXCHANGE, UNIVERSAL_MANAGE_IDS.APPROVE_SWAP_TOKEN1).bind(commonAdapter));
827
- // vabaseAdapterConfigultSettings.leafAdapters.push(commonAdapter.getAvnuAdapter(STRKToken.address, lstToken.address, UNIVERSAL_MANAGE_IDS.AVNU_SWAP_REWARDS, false).bind(commonAdapter));
828
- return vaultSettings;
1447
+ vaultSettings.leafAdapters = [];
1448
+
1449
+ const lstToken = Global.getDefaultTokens().find(
1450
+ (token) => token.symbol === lstSymbol
1451
+ )!;
1452
+ const underlyingToken = Global.getDefaultTokens().find(
1453
+ (token) => token.symbol === underlyingSymbol
1454
+ )!;
1455
+
1456
+ const vesuAdapterLST = new VesuAdapter({
1457
+ poolId: pool1,
1458
+ collateral: lstToken,
1459
+ debt: underlyingToken,
1460
+ vaultAllocator: vaultSettings.vaultAllocator,
1461
+ id: getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, underlyingToken.symbol)
1462
+ });
1463
+
1464
+ const commonAdapter = new CommonAdapter({
1465
+ manager: vaultSettings.manager,
1466
+ asset: lstToken.address,
1467
+ id: "",
1468
+ vaultAddress: vaultSettings.vaultAddress,
1469
+ vaultAllocator: vaultSettings.vaultAllocator
1470
+ });
1471
+
1472
+ // Useful for returning adapter class objects that can compute
1473
+ // certain things for us (e.g. positions, hfs)
1474
+ vaultSettings.adapters.push(
1475
+ ...[
1476
+ {
1477
+ id: getVesuLegId(
1478
+ UNIVERSAL_MANAGE_IDS.VESU_LEG1,
1479
+ underlyingToken.symbol
1480
+ ),
1481
+ adapter: vesuAdapterLST
1482
+ },
1483
+ {
1484
+ id: UNIVERSAL_ADAPTERS.COMMON,
1485
+ adapter: commonAdapter
1486
+ }
1487
+ ]
1488
+ );
1489
+
1490
+ // avnu multiply
1491
+ const { isV2, addr: poolAddr } = getVesuSingletonAddress(pool1);
1492
+ // vesu multiply looping
1493
+ const VESU_MULTIPLY = isV2
1494
+ ? vesuAdapterLST.VESU_MULTIPLY
1495
+ : vesuAdapterLST.VESU_MULTIPLY_V1;
1496
+ vaultSettings.leafAdapters.push(
1497
+ commonAdapter
1498
+ .getApproveAdapter(
1499
+ lstToken.address,
1500
+ VESU_MULTIPLY,
1501
+ LST_MULTIPLIER_MANAGE_IDS.MULTIPLE_APPROVE
1502
+ )
1503
+ .bind(commonAdapter)
1504
+ );
1505
+ vaultSettings.leafAdapters.push(
1506
+ vesuAdapterLST
1507
+ .getVesuModifyDelegationAdapter(
1508
+ LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_ON
1509
+ )
1510
+ .bind(vesuAdapterLST)
1511
+ );
1512
+ vaultSettings.leafAdapters.push(
1513
+ vesuAdapterLST
1514
+ .getVesuModifyDelegationAdapter(
1515
+ LST_MULTIPLIER_MANAGE_IDS.SWITCH_DELEGATION_OFF
1516
+ )
1517
+ .bind(vesuAdapterLST)
1518
+ );
1519
+
1520
+ // approve lst once to avnu
1521
+ vaultSettings.leafAdapters.push(
1522
+ commonAdapter
1523
+ .getApproveAdapter(
1524
+ lstToken.address,
1525
+ AVNU_EXCHANGE,
1526
+ LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_WITHDRAW
1527
+ )
1528
+ .bind(commonAdapter)
1529
+ );
1530
+ for (let borrowableAsset of vaultSettings.borrowable_assets) {
1531
+ // in-efficient avnu swap looping (but good with endur integration)
1532
+ const debtAsset = borrowableAsset;
1533
+ const approve_debt_token_id = getAvnuManageIDs(
1534
+ LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_APPROVE_DEPOSIT,
1535
+ debtAsset.symbol
1536
+ );
1537
+ const swap_debt_token_id = getAvnuManageIDs(
1538
+ LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_DEPOSIT,
1539
+ debtAsset.symbol
1540
+ );
1541
+ const swap_lst_token_id = getAvnuManageIDs(
1542
+ LST_MULTIPLIER_MANAGE_IDS.AVNU_MULTIPLY_SWAP_WITHDRAW,
1543
+ debtAsset.symbol
1544
+ );
1545
+ vaultSettings.leafAdapters.push(
1546
+ commonAdapter
1547
+ .getApproveAdapter(
1548
+ debtAsset.address,
1549
+ AVNU_EXCHANGE,
1550
+ approve_debt_token_id
1551
+ )
1552
+ .bind(commonAdapter)
1553
+ );
1554
+ vaultSettings.leafAdapters.push(
1555
+ commonAdapter
1556
+ .getAvnuAdapter(
1557
+ debtAsset.address,
1558
+ lstToken.address,
1559
+ swap_debt_token_id,
1560
+ false
1561
+ )
1562
+ .bind(commonAdapter)
1563
+ );
1564
+ vaultSettings.leafAdapters.push(
1565
+ commonAdapter
1566
+ .getAvnuAdapter(
1567
+ lstToken.address,
1568
+ debtAsset.address,
1569
+ swap_lst_token_id,
1570
+ false
1571
+ )
1572
+ .bind(commonAdapter)
1573
+ );
1574
+
1575
+ // approve LST to add collateral
1576
+ const vesuAdapter = new VesuAdapter({
1577
+ poolId: pool1,
1578
+ collateral: lstToken,
1579
+ debt: debtAsset,
1580
+ vaultAllocator: vaultSettings.vaultAllocator,
1581
+ id: getVesuLegId(UNIVERSAL_MANAGE_IDS.VESU_LEG1, debtAsset.symbol)
1582
+ });
1583
+ vaultSettings.leafAdapters.push(
1584
+ commonAdapter
1585
+ .getApproveAdapter(
1586
+ lstToken.address,
1587
+ poolAddr,
1588
+ UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1
1589
+ )
1590
+ .bind(commonAdapter)
1591
+ );
1592
+ vaultSettings.leafAdapters.push(
1593
+ vesuAdapter.getModifyPosition.bind(vesuAdapter)
1594
+ );
1595
+
1596
+ // Vesu multiply
1597
+ const multiplID = getVesuLegId(
1598
+ LST_MULTIPLIER_MANAGE_IDS.MULTIPLY_VESU,
1599
+ debtAsset.symbol
1600
+ );
1601
+ vaultSettings.leafAdapters.push(
1602
+ vesuAdapter.getMultiplyAdapter(multiplID).bind(vesuAdapter)
1603
+ );
1604
+ }
1605
+
1606
+ // to bridge liquidity back to vault (used by bring_liquidity)
1607
+ vaultSettings.leafAdapters.push(
1608
+ commonAdapter
1609
+ .getApproveAdapter(
1610
+ lstToken.address,
1611
+ vaultSettings.vaultAddress,
1612
+ UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY
1613
+ )
1614
+ .bind(commonAdapter)
1615
+ );
1616
+ vaultSettings.leafAdapters.push(
1617
+ commonAdapter
1618
+ .getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
1619
+ .bind(commonAdapter)
1620
+ );
1621
+
1622
+ // claim rewards
1623
+ vaultSettings.leafAdapters.push(
1624
+ vesuAdapterLST
1625
+ .getDefispringRewardsAdapter(
1626
+ UNIVERSAL_MANAGE_IDS.DEFISPRING_REWARDS
1627
+ )
1628
+ .bind(vesuAdapterLST)
1629
+ );
1630
+
1631
+ // avnu swap for claims rewards
1632
+ const STRKToken = Global.getDefaultTokens().find(
1633
+ (token) => token.symbol === "STRK"
1634
+ )!;
1635
+ vaultSettings.leafAdapters.push(
1636
+ commonAdapter
1637
+ .getApproveAdapter(
1638
+ STRKToken.address,
1639
+ AVNU_EXCHANGE,
1640
+ UNIVERSAL_MANAGE_IDS.APPROVE_SWAP_TOKEN1
1641
+ )
1642
+ .bind(commonAdapter)
1643
+ );
1644
+ vaultSettings.leafAdapters.push(
1645
+ commonAdapter
1646
+ .getAvnuAdapter(
1647
+ STRKToken.address,
1648
+ lstToken.address,
1649
+ UNIVERSAL_MANAGE_IDS.AVNU_SWAP_REWARDS,
1650
+ false
1651
+ )
1652
+ .bind(commonAdapter)
1653
+ );
1654
+ return vaultSettings;
829
1655
  }
830
1656
 
831
- export const AUDIT_URL = 'https://docs.troves.fi/p/security#starknet-vault-kit'
1657
+ const AUDIT_URL = "https://docs.troves.fi/p/security#starknet-vault-kit";
1658
+
1659
+ function getFAQs(lstSymbol: string, underlyingSymbol: string): FAQ[] {
1660
+ return [
1661
+ {
1662
+ question: `What is the Hyper ${lstSymbol} Vault?`,
1663
+ answer: `The Hyper ${lstSymbol} Vault is a tokenized strategy that automatically loops your ${lstSymbol} to create up to 5x leverage to hence yield in a very low risk manner.`
1664
+ },
1665
+ {
1666
+ question: "How does yield allocation work?",
1667
+ answer: `The strategy uses deposited ${lstSymbol} to collateralize it on Vesu, borrow more ${underlyingSymbol} to loop further. Instead of manually doing this, using flash loan, this leverage is created in a single gas efficient step. Our continuous monitoring systems gauge current yield and available liquidity in real time to make sure yield is optimal. For instance, if the looping becomes in-efficient in future, the strategy will rebalance to reduce leverage or simply hold ${lstSymbol} to continue earning yield.`
1668
+ },
1669
+ {
1670
+ question: "Which protocols/dApp are used??",
1671
+ answer: (
1672
+ <span>
1673
+ Currently, the LST is from <strong>Endur</strong> while{" "}
1674
+ <strong>Vesu</strong> is used to collateralize the looped
1675
+ position.
1676
+ </span>
1677
+ )
1678
+ },
1679
+ {
1680
+ question: "Can I get liquidated?",
1681
+ answer: "The strategy uses highly correlated assets which drastically reduces the risk of liquidation. However, overtime, if left un-monitored, debt can increase enough to trigger a liquidation. But no worries, our continuous monitoring systems look for situations with reduced health factor and balance collateral/debt to bring it back to safe levels. With Troves, you can have a peaceful sleep."
1682
+ },
1683
+ {
1684
+ question: "What do I receive when I deposit?",
1685
+ answer: "Depositors receive vault tokens representing their proportional share of the vault. These tokens entitle holders to both the principal and accrued yield."
1686
+ },
1687
+ {
1688
+ question: "How long do withdrawals take?",
1689
+ answer: "Withdrawals may take up to 1-2 hours to process, as the vault unwinds and settles liquidity routing across integrated protocols. In case of large withdrawals, to avoid slippage, we may slowly unwind the position, which could make the withdrawals longer."
1690
+ },
1691
+ {
1692
+ question: "Is the Hyper xSTRK Vault non-custodial?",
1693
+ answer: "Yes. The Hyper xSTRK Vault operates entirely on-chain. Users always maintain control of their vault tokens, and the strategy is fully transparent."
1694
+ },
1695
+ {
1696
+ question: "Is the Vault audited?",
1697
+ answer: "Yes. The Hyper xSTRK Vault is audited by Zellic. Look for safety icon beside the strategy name for more details."
1698
+ },
1699
+ {
1700
+ question: "Are there any fees?",
1701
+ answer: "Troves charges a performance of 10% on the yield generated. The APY shown is net of this fee. This fee is only applied to the profits earned, ensuring that users retain their initial capital."
1702
+ }
1703
+ ];
1704
+ }
832
1705
 
833
- export function getFAQs(lstSymbol: string, underlyingSymbol: string, isLST: boolean): FAQ[] {
834
- return [
835
- {
836
- question: `What is the Hyper ${lstSymbol} Vault?`,
837
- answer:
838
- `The Hyper ${lstSymbol} Vault is a tokenized strategy that automatically loops your ${lstSymbol} to create up to 5x leverage to hence yield in a very low risk manner.`,
839
- },
840
- {
841
- question: "How does yield allocation work?",
842
- answer:
843
- `The strategy uses deposited ${lstSymbol} to collateralize it on Vesu, borrow more ${underlyingSymbol} to loop further. Instead of manually doing this, using flash loan, this leverage is created in a single gas efficient step. Our continuous monitoring systems gauge current yield and available liquidity in real time to make sure yield is optimal. For instance, if the looping becomes in-efficient in future, the strategy will rebalance to reduce leverage or simply hold ${lstSymbol} to continue earning yield.`,
844
- },
845
- {
846
- question: "Which protocols/dApp are used??",
847
- answer: isLST ? (
848
- <span>
849
- Currently, the LST is from <strong>Endur</strong> while <strong>Vesu</strong> is used to collateralize the looped position.
850
- </span>
851
- ) : (
852
- <span>
853
- Currently, the Yield Token is from <strong>Re7 Labs (Midas)</strong> while <strong>Vesu</strong> is used to collateralize the looped position.
854
- </span>
855
- ),
856
- },
1706
+ const _riskFactor: RiskFactor[] = [
857
1707
  {
858
- question: "Can I get liquidated?",
859
- answer: "The strategy uses highly correlated assets which drastically reduces the risk of liquidation. However, overtime, if left un-monitored, debt can increase enough to trigger a liquidation. But no worries, our continuous monitoring systems look for situations with reduced health factor and balance collateral/debt to bring it back to safe levels. With Troves, you can have a peaceful sleep.",
1708
+ type: RiskType.SMART_CONTRACT_RISK,
1709
+ value: SmartContractRiskLevel.WELL_AUDITED,
1710
+ weight: 25,
1711
+ reason: "Audited by Zellic"
860
1712
  },
861
1713
  {
862
- question: "What do I receive when I deposit?",
863
- answer:
864
- "Depositors receive vault tokens representing their proportional share of the vault. These tokens entitle holders to both the principal and accrued yield.",
1714
+ type: RiskType.LIQUIDATION_RISK,
1715
+ value: LiquidationRiskLevel.VERY_LOW_PROBABILITY,
1716
+ weight: 25,
1717
+ reason: "The collateral and debt are highly correlated"
865
1718
  },
866
1719
  {
867
- question: "How long do withdrawals take?",
868
- answer:
869
- "Withdrawals may take up to 1-2 hours to process, as the vault unwinds and settles liquidity routing across integrated protocols. In case of large withdrawals, to avoid slippage, we may slowly unwind the position, which could make the withdrawals longer.",
1720
+ type: RiskType.TECHNICAL_RISK,
1721
+ value: TechnicalRiskLevel.STABLE_INFRASTRUCTURE,
1722
+ weight: 25,
1723
+ reason: "Liquidation can only happen if vault is left un-monitored for weeks, which is highly unlikely. We actively monitor all services on a daily basis."
870
1724
  },
871
1725
  {
872
- question: `Is the Hyper ${lstSymbol} Vault non-custodial?`,
873
- answer:
874
- `Yes. The Hyper ${lstSymbol} Vault operates entirely on-chain. Users always maintain control of their vault tokens, and the strategy is fully transparent.`,
875
- },
876
- {
877
- question: "Is the Vault audited?",
878
- answer:
879
- `Yes. The Hyper ${lstSymbol} Vault is audited by Zellic. Look for safety icon beside the strategy name for more details.`,
880
- },
881
- {
882
- question: "Are there any fees?",
883
- answer:
884
- "Troves charges a performance of 10% on the yield generated. The APY shown is net of this fee. This fee is only applied to the profits earned, ensuring that users retain their initial capital.",
885
- },
886
- ];
887
- }
888
-
889
- export const _riskFactor: RiskFactor[] = [
890
- { type: RiskType.SMART_CONTRACT_RISK, value: SmartContractRiskLevel.WELL_AUDITED, weight: 25, reason: "Audited by Zellic" },
891
- { type: RiskType.LIQUIDATION_RISK, value: LiquidationRiskLevel.VERY_LOW_PROBABILITY, weight: 25, reason: "The collateral and debt are highly correlated" },
892
- { type: RiskType.TECHNICAL_RISK, value: TechnicalRiskLevel.STABLE_INFRASTRUCTURE, weight: 25, reason: "Liquidation can only happen if vault is left un-monitored for weeks, which is highly unlikely. We actively monitor all services on a daily basis." },
893
- {type: RiskType.DEPEG_RISK, value: DepegRiskLevel.GENERALLY_STABLE, weight: 25, reason: "Generally stable pegged assets" },
1726
+ type: RiskType.DEPEG_RISK,
1727
+ value: DepegRiskLevel.GENERALLY_STABLE,
1728
+ weight: 25,
1729
+ reason: "Generally stable pegged assets"
1730
+ }
894
1731
  ];
895
1732
 
896
- const borrowableAssets = [
897
- 'WBTC', 'tBTC', 'LBTC', 'solvBTC'
898
- ]
1733
+ const borrowableAssets = ["WBTC", "tBTC", "LBTC", "solvBTC"];
899
1734
 
900
1735
  const hyperxSTRK: HyperLSTStrategySettings = {
901
- vaultAddress: ContractAddr.from('0x46c7a54c82b1fe374353859f554a40b8bd31d3e30f742901579e7b57b1b5960'),
902
- manager: ContractAddr.from('0x5d499cd333757f461a0bedaca3dfc4d77320c773037e0aa299f22a6dbfdc03a'),
903
- vaultAllocator: ContractAddr.from('0x511d07953a09bc7c505970891507c5a2486d2ea22752601a14db092186d7caa'),
904
- redeemRequestNFT: ContractAddr.from('0x51e40b839dc0c2feca923f863072673b94abfa2483345be3b30b457a90d095'),
905
- aumOracle: ContractAddr.from('0x48cf709870a1a0d453d37de108e0c41b8b89819ef54f95abc0e2e1f98bbe937'),
906
- leafAdapters: [],
907
- adapters: [],
908
- targetHealthFactor: 1.1,
909
- minHealthFactor: 1.05,
910
- borrowable_assets: Global.getDefaultTokens().filter(token => token.symbol === 'STRK'),
911
- underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'STRK')!,
912
- quoteAmountToFetchPrice: new Web3Number('100', Global.getDefaultTokens().find(token => token.symbol === 'STRK')!.decimals),
913
- }
1736
+ vaultAddress: ContractAddr.from(
1737
+ "0x46c7a54c82b1fe374353859f554a40b8bd31d3e30f742901579e7b57b1b5960"
1738
+ ),
1739
+ manager: ContractAddr.from(
1740
+ "0x5d499cd333757f461a0bedaca3dfc4d77320c773037e0aa299f22a6dbfdc03a"
1741
+ ),
1742
+ vaultAllocator: ContractAddr.from(
1743
+ "0x511d07953a09bc7c505970891507c5a2486d2ea22752601a14db092186d7caa"
1744
+ ),
1745
+ redeemRequestNFT: ContractAddr.from(
1746
+ "0x51e40b839dc0c2feca923f863072673b94abfa2483345be3b30b457a90d095"
1747
+ ),
1748
+ aumOracle: ContractAddr.from(
1749
+ "0x48cf709870a1a0d453d37de108e0c41b8b89819ef54f95abc0e2e1f98bbe937"
1750
+ ),
1751
+ leafAdapters: [],
1752
+ adapters: [],
1753
+ targetHealthFactor: 1.1,
1754
+ minHealthFactor: 1.05,
1755
+ borrowable_assets: Global.getDefaultTokens().filter(
1756
+ (token) => token.symbol === "STRK"
1757
+ ),
1758
+ underlyingToken: Global.getDefaultTokens().find(
1759
+ (token) => token.symbol === "STRK"
1760
+ )!
1761
+ };
914
1762
 
915
1763
  const hyperxWBTC: HyperLSTStrategySettings = {
916
- vaultAddress: ContractAddr.from('0x2da9d0f96a46b453f55604313785dc866424240b1c6811d13bef594343db818'),
917
- manager: ContractAddr.from('0x75866db44c81e6986f06035206ee9c7d15833ddb22d6a22c016cfb5c866a491'),
918
- vaultAllocator: ContractAddr.from('0x57b5c1bb457b5e840a2714ae53ada87d77be2f3fd33a59b4fe709ef20c020c1'),
919
- redeemRequestNFT: ContractAddr.from('0x7a5dc288325456f05e70e9616e16bc02ffbe448f4b89f80b47c0970b989c7c'),
920
- aumOracle: ContractAddr.from('0x258f8a0ca0d21f542e48ad89d00e92dc4d9db4999084f50ef9c22dfb1e83023'),
921
- leafAdapters: [],
922
- adapters: [],
923
- targetHealthFactor: 1.1,
924
- minHealthFactor: 1.05,
925
- borrowable_assets: borrowableAssets.map(asset => Global.getDefaultTokens().find(token => token.symbol === asset)!),
926
- underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!,
927
- quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!.decimals),
928
- }
1764
+ vaultAddress: ContractAddr.from(
1765
+ "0x2da9d0f96a46b453f55604313785dc866424240b1c6811d13bef594343db818"
1766
+ ),
1767
+ manager: ContractAddr.from(
1768
+ "0x75866db44c81e6986f06035206ee9c7d15833ddb22d6a22c016cfb5c866a491"
1769
+ ),
1770
+ vaultAllocator: ContractAddr.from(
1771
+ "0x57b5c1bb457b5e840a2714ae53ada87d77be2f3fd33a59b4fe709ef20c020c1"
1772
+ ),
1773
+ redeemRequestNFT: ContractAddr.from(
1774
+ "0x7a5dc288325456f05e70e9616e16bc02ffbe448f4b89f80b47c0970b989c7c"
1775
+ ),
1776
+ aumOracle: ContractAddr.from(
1777
+ "0x258f8a0ca0d21f542e48ad89d00e92dc4d9db4999084f50ef9c22dfb1e83023"
1778
+ ),
1779
+ leafAdapters: [],
1780
+ adapters: [],
1781
+ targetHealthFactor: 1.1,
1782
+ minHealthFactor: 1.05,
1783
+ borrowable_assets: borrowableAssets.map(
1784
+ (asset) =>
1785
+ Global.getDefaultTokens().find((token) => token.symbol === asset)!
1786
+ ),
1787
+ underlyingToken: Global.getDefaultTokens().find(
1788
+ (token) => token.symbol === "WBTC"
1789
+ )!
1790
+ };
929
1791
 
930
1792
  const hyperxtBTC: HyperLSTStrategySettings = {
931
- vaultAddress: ContractAddr.from('0x47d5f68477e5637ce0e56436c6b5eee5a354e6828995dae106b11a48679328'),
932
- manager: ContractAddr.from('0xc4cc3e08029a0ae076f5fdfca70575abb78d23c5cd1c49a957f7e697885401'),
933
- vaultAllocator: ContractAddr.from('0x50bbd4fe69f841ecb13b2619fe50ebfa4e8944671b5d0ebf7868fd80c61b31e'),
934
- redeemRequestNFT: ContractAddr.from('0xeac9032f02057779816e38a6cb9185d12d86b3aacc9949b96b36de359c1e3'),
935
- aumOracle: ContractAddr.from('0x7e0d05cb7ba3f7db77a36c21c21583b5a524c2e685c08c24b3554911fb4a039'),
936
- leafAdapters: [],
937
- adapters: [],
938
- targetHealthFactor: 1.1,
939
- minHealthFactor: 1.05,
940
- borrowable_assets: borrowableAssets.map(asset => Global.getDefaultTokens().find(token => token.symbol === asset)!),
941
- underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'tBTC')!,
942
- quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'tBTC')!.decimals),
943
- }
1793
+ vaultAddress: ContractAddr.from(
1794
+ "0x47d5f68477e5637ce0e56436c6b5eee5a354e6828995dae106b11a48679328"
1795
+ ),
1796
+ manager: ContractAddr.from(
1797
+ "0xc4cc3e08029a0ae076f5fdfca70575abb78d23c5cd1c49a957f7e697885401"
1798
+ ),
1799
+ vaultAllocator: ContractAddr.from(
1800
+ "0x50bbd4fe69f841ecb13b2619fe50ebfa4e8944671b5d0ebf7868fd80c61b31e"
1801
+ ),
1802
+ redeemRequestNFT: ContractAddr.from(
1803
+ "0xeac9032f02057779816e38a6cb9185d12d86b3aacc9949b96b36de359c1e3"
1804
+ ),
1805
+ aumOracle: ContractAddr.from(
1806
+ "0x7e0d05cb7ba3f7db77a36c21c21583b5a524c2e685c08c24b3554911fb4a039"
1807
+ ),
1808
+ leafAdapters: [],
1809
+ adapters: [],
1810
+ targetHealthFactor: 1.1,
1811
+ minHealthFactor: 1.05,
1812
+ borrowable_assets: Global.getDefaultTokens().filter(
1813
+ (token) => token.symbol === "tBTC" || token.symbol === "WBTC"
1814
+ ),
1815
+ underlyingToken: Global.getDefaultTokens().find(
1816
+ (token) => token.symbol === "tBTC"
1817
+ )!
1818
+ };
944
1819
 
945
1820
  const hyperxsBTC: HyperLSTStrategySettings = {
946
- vaultAddress: ContractAddr.from('0x437ef1e7d0f100b2e070b7a65cafec0b2be31b0290776da8b4112f5473d8d9'),
947
- manager: ContractAddr.from('0xc9ac023090625b0be3f6532ca353f086746f9c09f939dbc1b2613f09e5f821'),
948
- vaultAllocator: ContractAddr.from('0x60c2d856936b975459a5b4eb28b8672d91f757bd76cebb6241f8d670185dc01'),
949
- redeemRequestNFT: ContractAddr.from('0x429e8ee8bc7ecd1ade72630d350a2e0f10f9a2507c45f188ba17fe8f2ab4cf3'),
950
- aumOracle: ContractAddr.from('0x149298ade3e79ec6cbdac6cfad289c57504eaf54e590939136ed1ceca60c345'),
951
- leafAdapters: [],
952
- adapters: [],
953
- targetHealthFactor: 1.1,
954
- minHealthFactor: 1.05,
955
- borrowable_assets: borrowableAssets.map(asset => Global.getDefaultTokens().find(token => token.symbol === asset)!),
956
- underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'solvBTC')!,
957
- quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'solvBTC')!.decimals),
958
- }
1821
+ vaultAddress: ContractAddr.from(
1822
+ "0x437ef1e7d0f100b2e070b7a65cafec0b2be31b0290776da8b4112f5473d8d9"
1823
+ ),
1824
+ manager: ContractAddr.from(
1825
+ "0xc9ac023090625b0be3f6532ca353f086746f9c09f939dbc1b2613f09e5f821"
1826
+ ),
1827
+ vaultAllocator: ContractAddr.from(
1828
+ "0x60c2d856936b975459a5b4eb28b8672d91f757bd76cebb6241f8d670185dc01"
1829
+ ),
1830
+ redeemRequestNFT: ContractAddr.from(
1831
+ "0x429e8ee8bc7ecd1ade72630d350a2e0f10f9a2507c45f188ba17fe8f2ab4cf3"
1832
+ ),
1833
+ aumOracle: ContractAddr.from(
1834
+ "0x149298ade3e79ec6cbdac6cfad289c57504eaf54e590939136ed1ceca60c345"
1835
+ ),
1836
+ leafAdapters: [],
1837
+ adapters: [],
1838
+ targetHealthFactor: 1.1,
1839
+ minHealthFactor: 1.05,
1840
+ borrowable_assets: Global.getDefaultTokens().filter(
1841
+ (token) => token.symbol === "solvBTC"
1842
+ ),
1843
+ underlyingToken: Global.getDefaultTokens().find(
1844
+ (token) => token.symbol === "solvBTC"
1845
+ )!
1846
+ };
959
1847
 
960
1848
  const hyperxLBTC: HyperLSTStrategySettings = {
961
- vaultAddress: ContractAddr.from('0x64cf24d4883fe569926419a0569ab34497c6956a1a308fa883257f7486d7030'),
962
- manager: ContractAddr.from('0x203530a4022a99b8f4b406aaf33b0849d43ad7422c1d5cc14ff8c667abec6c0'),
963
- vaultAllocator: ContractAddr.from('0x7dbc8ccd4eabce6ea6c19e0e5c9ccca3a93bd510303b9e071cbe25fc508546e'),
964
- redeemRequestNFT: ContractAddr.from('0x5ee66a39af9aef3d0d48982b4a63e8bd2a5bad021916bd87fb0eae3a26800b8'),
965
- aumOracle: ContractAddr.from('0x23d69e4391fa72d10e625e7575d8bddbb4aff96f04503f83fdde23123bf41d0'),
966
- leafAdapters: [],
967
- adapters: [],
968
- targetHealthFactor: 1.1,
969
- minHealthFactor: 1.05,
970
- borrowable_assets: borrowableAssets.map(asset => Global.getDefaultTokens().find(token => token.symbol === asset)!),
971
- underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'LBTC')!,
972
- quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'LBTC')!.decimals),
1849
+ vaultAddress: ContractAddr.from(
1850
+ "0x64cf24d4883fe569926419a0569ab34497c6956a1a308fa883257f7486d7030"
1851
+ ),
1852
+ manager: ContractAddr.from(
1853
+ "0x203530a4022a99b8f4b406aaf33b0849d43ad7422c1d5cc14ff8c667abec6c0"
1854
+ ),
1855
+ vaultAllocator: ContractAddr.from(
1856
+ "0x7dbc8ccd4eabce6ea6c19e0e5c9ccca3a93bd510303b9e071cbe25fc508546e"
1857
+ ),
1858
+ redeemRequestNFT: ContractAddr.from(
1859
+ "0x5ee66a39af9aef3d0d48982b4a63e8bd2a5bad021916bd87fb0eae3a26800b8"
1860
+ ),
1861
+ aumOracle: ContractAddr.from(
1862
+ "0x23d69e4391fa72d10e625e7575d8bddbb4aff96f04503f83fdde23123bf41d0"
1863
+ ),
1864
+ leafAdapters: [],
1865
+ adapters: [],
1866
+ targetHealthFactor: 1.1,
1867
+ minHealthFactor: 1.05,
1868
+ borrowable_assets: Global.getDefaultTokens().filter(
1869
+ (token) => token.symbol === "LBTC"
1870
+ ),
1871
+ underlyingToken: Global.getDefaultTokens().find(
1872
+ (token) => token.symbol === "LBTC"
1873
+ )!
1874
+ };
1875
+
1876
+ function getInvestmentSteps(lstSymbol: string, underlyingSymbol: string) {
1877
+ return [
1878
+ `Deposit ${lstSymbol} into the vault`,
1879
+ `The vault manager loops the ${underlyingSymbol} to buy ${lstSymbol}`,
1880
+ `The vault manager collateralizes the ${lstSymbol} on Vesu`,
1881
+ `The vault manager borrows more ${underlyingSymbol} to loop further`,
1882
+ `If required, adjust leverage or re-allocate assets within LST pool on Vesu to optimize yield`
1883
+ ];
973
1884
  }
974
1885
 
975
- const hypermRe7BTC: HyperLSTStrategySettings = {
976
- vaultAddress: ContractAddr.from('0x6c89b75d09de82477edb86b2c2918cfc1a5dc0177cfbdea46278386b6499645'),
977
- manager: ContractAddr.from('0x7bc2d0535e13352d3ab78ea047616a3162068294297304943d2302122a150a1'),
978
- vaultAllocator: ContractAddr.from('0x760f9cebca9d2ee624f4224591da6da8b3ea5fd2410590735709551bd4e7570'),
979
- redeemRequestNFT: ContractAddr.from('0x5e045ae0160f7650f8a4dd7c826f25630a89fe62434db4441e7e0075608180f'),
980
- aumOracle: ContractAddr.from('0x3958df341b838813c24efb9183c23bddd1c57d44b1b86c0dd57f67887b89fba'),
981
- leafAdapters: [],
982
- adapters: [],
983
- targetHealthFactor: 1.1,
984
- minHealthFactor: 1.05,
985
- borrowable_assets: borrowableAssets.map(asset => Global.getDefaultTokens().find(token => token.symbol === asset)!),
986
- underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!,
987
- quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'mRe7BTC')!.decimals),
988
- }
989
- // Contract deployed: Vault, addr: 0x42797ab4eb1f72787442e91a73d63a39e3a141c1106470a946ecc328db6896c
990
- // Contract deployed: RedeemRequest, addr: 0x4bbb25c2568af07967342833f7db1aece1be1be2330798dab4ee585aa6c2c72
991
- // Contract deployed: VaultAllocator, addr: 0x456c4c6afca90512aeb5c735d84405fea6e51ab06d1851ac8cdb0a235e14f15
992
- // Contract deployed: Manager, addr: 0x435b45d40fbb406cf69ac84bb471e7b7a4ea2295d0893c05dd2db565295e77f
993
-
994
- const hypermRe7YIELD: HyperLSTStrategySettings = {
995
- vaultAddress: ContractAddr.from('0x42797ab4eb1f72787442e91a73d63a39e3a141c1106470a946ecc328db6896c'),
996
- manager: ContractAddr.from('0x435b45d40fbb406cf69ac84bb471e7b7a4ea2295d0893c05dd2db565295e77f'),
997
- vaultAllocator: ContractAddr.from('0x456c4c6afca90512aeb5c735d84405fea6e51ab06d1851ac8cdb0a235e14f15'),
998
- redeemRequestNFT: ContractAddr.from('0x4bbb25c2568af07967342833f7db1aece1be1be2330798dab4ee585aa6c2c72'),
999
- aumOracle: ContractAddr.from('0x3e1f2825158cafccc9b42a8165d17ceb6b8e966474d9c63587d338746888382'),
1000
- leafAdapters: [],
1001
- adapters: [],
1002
- targetHealthFactor: 1.1,
1003
- minHealthFactor: 1.05,
1004
- borrowable_assets: [Global.getDefaultTokens().find(token => token.symbol === 'USDC')!],
1005
- underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'USDC')!,
1006
- quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'mRe7BTC')!.decimals),
1886
+ // Helper to get maxTVL based on LST symbol (matching client values)
1887
+ function getMaxTVL(lstSymbol: string): Web3Number {
1888
+ const lstMaxTVLs: Record<string, number> = {
1889
+ xWBTC: 5,
1890
+ xLBTC: 5,
1891
+ xtBTC: 5,
1892
+ xsBTC: 5,
1893
+ xSTRK: 550000
1894
+ };
1895
+
1896
+ const maxTVLValue = lstMaxTVLs[lstSymbol] || 0;
1897
+ const token = Global.getDefaultTokens().find(
1898
+ (token) => token.symbol === lstSymbol
1899
+ );
1900
+ if (!token) {
1901
+ return Web3Number.fromWei(0, 18);
1902
+ }
1903
+ return Web3Number.fromWei(maxTVLValue, token.decimals);
1007
1904
  }
1008
1905
 
1009
- export function getInvestmentSteps(lstSymbol: string, underlyingSymbol: string) {
1010
- return [
1011
- `Deposit ${lstSymbol} into the vault`,
1012
- `The vault manager loops the ${underlyingSymbol} to buy ${lstSymbol}`,
1013
- `The vault manager collateralizes the ${lstSymbol} on Vesu`,
1014
- `The vault manager borrows more ${underlyingSymbol} to loop further`,
1015
- `If required, adjust leverage or re-allocate assets within pool on Vesu to optimize yield`
1016
- ]
1906
+ // Helper to create Hyper LST strategy settings
1907
+ function createHyperLSTSettings(
1908
+ lstSymbol: string,
1909
+ underlyingSymbol: string
1910
+ ): StrategySettings {
1911
+ const depositToken = Global.getDefaultTokens().find(
1912
+ (token) => token.symbol === lstSymbol
1913
+ )!;
1914
+ return {
1915
+ maxTVL: getMaxTVL(lstSymbol),
1916
+ isPaused: false,
1917
+ liveStatus: StrategyLiveStatus.HOT,
1918
+ isAudited: true,
1919
+ isInstantWithdrawal: false,
1920
+ hideHarvestInfo: true,
1921
+ quoteToken: depositToken,
1922
+ showWithdrawalWarningModal: false,
1923
+ alerts: [
1924
+ {
1925
+ tab: "withdraw" as const,
1926
+ text: "On withdrawal, you will receive an NFT representing your withdrawal request. The funds will be automatically sent to your wallet (NFT owner) in 24 hours (In this initial phase of Launch). You can monitor the status in transactions tab.",
1927
+ type: "info" as const
1928
+ },
1929
+ {
1930
+ tab: "deposit" as const,
1931
+ text: (
1932
+ <>
1933
+ To acquire the LST, please visit{" "}
1934
+ <a
1935
+ href="https://app.endur.fi"
1936
+ target="_blank"
1937
+ rel="noopener noreferrer"
1938
+ >
1939
+ endur.fi
1940
+ </a>
1941
+ </>
1942
+ ),
1943
+ type: "info" as const
1944
+ },
1945
+ {
1946
+ tab: "deposit" as const,
1947
+ text: "It may take up to one week for your deposit to appreciate in value. This delay occurs because the LST price is sourced from DEXes and liquidity is usually rebased once a week.",
1948
+ type: "info" as const
1949
+ }
1950
+ ]
1951
+ };
1017
1952
  }
1018
1953
 
1019
- function getStrategySettings(lstSymbol: string, underlyingSymbol: string, addresses: HyperLSTStrategySettings, isPreview: boolean = false, isLST: boolean): IStrategyMetadata<HyperLSTStrategySettings> {
1020
- return {
1021
- name: `Hyper ${lstSymbol}`,
1022
- description: getDescription(lstSymbol, underlyingSymbol),
1023
- address: addresses.vaultAddress,
1024
- launchBlock: 0,
1025
- type: 'Other',
1026
- depositTokens: [Global.getDefaultTokens().find(token => token.symbol === lstSymbol)!],
1027
- additionalInfo: getLooperSettings(lstSymbol, underlyingSymbol, addresses, lstSymbol === 'xSTRK' ? VesuPools.Re7xSTRK : VesuPools.Re7xBTC),
1028
- risk: {
1029
- riskFactor: _riskFactor,
1030
- netRisk:
1031
- _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
1032
- _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
1033
- notARisks: getNoRiskTags(_riskFactor)
1954
+ const HYPER_LST_SECURITY = {
1955
+ auditStatus: AuditStatus.AUDITED,
1956
+ sourceCode: {
1957
+ type: SourceCodeType.CLOSED_SOURCE,
1958
+ contractLink: "https://github.com/trovesfi/troves-contracts"
1034
1959
  },
1035
- auditUrl: AUDIT_URL,
1036
- protocols: [Protocols.ENDUR, Protocols.VESU],
1037
- maxTVL: Web3Number.fromWei(0, 18),
1038
- contractDetails: getContractDetails(addresses),
1039
- faqs: getFAQs(lstSymbol, underlyingSymbol, isLST),
1040
- investmentSteps: getInvestmentSteps(lstSymbol, underlyingSymbol),
1041
- isPreview: isPreview,
1042
- apyMethodology: isLST ? 'Current annualized APY in terms of base asset of the LST. There is no additional fee taken by Troves on LST APY. We charge a 10% performance fee on the additional gain which is already accounted in the APY shown.' : 'Current annualized APY in terms of base asset of the Yield Token. There is no additional fee taken by Troves on yield token APY. We charge a 10% performance fee on the additional gain which is already accounted in the APY shown.'
1043
- }
1960
+ accessControl: {
1961
+ type: AccessControlType.STANDARD_ACCOUNT,
1962
+ addresses: [ContractAddr.from("0x0")],
1963
+ timeLock: "2 Days"
1964
+ }
1965
+ };
1966
+
1967
+ const HYPER_LST_REDEMPTION_INFO = {
1968
+ instantWithdrawalVault: InstantWithdrawalVault.NO,
1969
+ expectedRedemptionTime: {
1970
+ upto1M: "1-2hrs",
1971
+ upto10M: "24hrs",
1972
+ above10M: "2-3 Days"
1973
+ }
1974
+ };
1975
+
1976
+ function getStrategySettings(
1977
+ lstSymbol: string,
1978
+ underlyingSymbol: string,
1979
+ addresses: HyperLSTStrategySettings,
1980
+ isPreview: boolean = false
1981
+ ): IStrategyMetadata<HyperLSTStrategySettings> {
1982
+ return {
1983
+ id: `hyper_${lstSymbol.toLowerCase()}`,
1984
+ name: `Hyper ${lstSymbol}`,
1985
+ description: getDescription(lstSymbol, underlyingSymbol),
1986
+ address: addresses.vaultAddress,
1987
+ launchBlock: 0,
1988
+ type: "Other",
1989
+ depositTokens: [
1990
+ Global.getDefaultTokens().find(
1991
+ (token) => token.symbol === lstSymbol
1992
+ )!
1993
+ ],
1994
+ additionalInfo: getLooperSettings(
1995
+ lstSymbol,
1996
+ underlyingSymbol,
1997
+ addresses,
1998
+ lstSymbol === "xSTRK" ? VesuPools.Re7xSTRK : VesuPools.Re7xBTC
1999
+ ),
2000
+ risk: {
2001
+ riskFactor: _riskFactor,
2002
+ netRisk:
2003
+ _riskFactor.reduce(
2004
+ (acc, curr) => acc + curr.value * curr.weight,
2005
+ 0
2006
+ ) / _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
2007
+ notARisks: getNoRiskTags(_riskFactor)
2008
+ },
2009
+ auditUrl: AUDIT_URL,
2010
+ protocols: [Protocols.ENDUR, Protocols.VESU],
2011
+ settings: createHyperLSTSettings(lstSymbol, underlyingSymbol),
2012
+ contractDetails: getContractDetails(addresses),
2013
+ faqs: getFAQs(lstSymbol, underlyingSymbol),
2014
+ investmentSteps: getInvestmentSteps(lstSymbol, underlyingSymbol),
2015
+ isPreview: isPreview,
2016
+ apyMethodology:
2017
+ "Current annualized APY in terms of base asset of the LST. There is no additional fee taken by Troves on LST APY. We charge a 10% performance fee on the additional gain which is already accounted in the APY shown.",
2018
+ category: StrategyCategory.META_VAULTS,
2019
+ tags: [StrategyTag.HYPER_LST],
2020
+ security: HYPER_LST_SECURITY,
2021
+ redemptionInfo: HYPER_LST_REDEMPTION_INFO
2022
+ };
1044
2023
  }
1045
2024
 
1046
-
1047
-
1048
- // const hyperxWBTCTest: HyperLSTStrategySettings = {
1049
- // vaultAddress: ContractAddr.from('0x5535e01a0d0438a888267ba6cd6519a40e653cdfb5dce4af475221d9cf11e63'),
1050
- // manager: ContractAddr.from('0x3caa816a9d9b55a6621a47c1a7e6773141dd05fb3ca4ec4b774656f360b32a1'),
1051
- // vaultAllocator: ContractAddr.from('0x3d4f82f8bfa8e5b2f0242c4d8ed87287c9ad5427be8b982a31a0393cf3075d1'),
1052
- // redeemRequestNFT: ContractAddr.from('0x6190460d0f1fd5d142bd5378d7b3270e70253bdd652d6826175f6c0b1ad4f32'),
1053
- // aumOracle: ContractAddr.from('0x43f5aa7c67b29b5e69ad03ab427f7613e43350d6ddede481e42e8184f49cb2f'),
1054
- // leafAdapters: [],
1055
- // adapters: [],
1056
- // targetHealthFactor: 1.1,
1057
- // minHealthFactor: 1.05,
1058
- // borrowable_assets: borrowableAssets.map(asset => Global.getDefaultTokens().find(token => token.symbol === asset)!),
1059
- // underlyingToken: Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!,
1060
- // quoteAmountToFetchPrice: new Web3Number('0.001', Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!.decimals),
1061
- // }
1062
-
1063
2025
  export const HyperLSTStrategies: IStrategyMetadata<HyperLSTStrategySettings>[] =
1064
- [
1065
- getStrategySettings('xSTRK', 'STRK', hyperxSTRK, false, true),
1066
- getStrategySettings('xWBTC', 'WBTC', hyperxWBTC, false, false),
1067
- getStrategySettings('xtBTC', 'tBTC', hyperxtBTC, false, false),
1068
- getStrategySettings('xsBTC', 'solvBTC', hyperxsBTC, false, false),
1069
- getStrategySettings('xLBTC', 'LBTC', hyperxLBTC, false, false),
1070
- getStrategySettings('mRe7BTC', 'mRe7BTC', hypermRe7BTC, false, false),
1071
- getStrategySettings('mRe7YIELD', 'mRe7YIELD', hypermRe7YIELD, false, false),
1072
- ]
2026
+ [
2027
+ getStrategySettings("xSTRK", "STRK", hyperxSTRK, false),
2028
+ getStrategySettings("xWBTC", "WBTC", hyperxWBTC, false),
2029
+ getStrategySettings("xtBTC", "tBTC", hyperxtBTC, false),
2030
+ getStrategySettings("xsBTC", "solvBTC", hyperxsBTC, false),
2031
+ getStrategySettings("xLBTC", "LBTC", hyperxLBTC, false)
2032
+ ];