@strkfarm/sdk 1.1.72 → 2.0.0-dev.10

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 (51) hide show
  1. package/dist/index.browser.global.js +58203 -50929
  2. package/dist/index.browser.mjs +26163 -18879
  3. package/dist/index.d.ts +2020 -798
  4. package/dist/index.js +30543 -23235
  5. package/dist/index.mjs +30397 -23130
  6. package/package.json +80 -76
  7. package/src/data/extended-deposit.abi.json +3613 -0
  8. package/src/data/universal-vault.abi.json +135 -20
  9. package/src/dataTypes/address.ts +7 -0
  10. package/src/global.ts +240 -193
  11. package/src/interfaces/common.tsx +26 -2
  12. package/src/modules/ExtendedWrapperSDk/index.ts +62 -0
  13. package/src/modules/ExtendedWrapperSDk/types.ts +311 -0
  14. package/src/modules/ExtendedWrapperSDk/wrapper.ts +395 -0
  15. package/src/modules/avnu.ts +17 -4
  16. package/src/modules/ekubo-quoter.ts +87 -10
  17. package/src/modules/erc20.ts +67 -21
  18. package/src/modules/harvests.ts +29 -43
  19. package/src/modules/index.ts +5 -1
  20. package/src/modules/lst-apr.ts +36 -0
  21. package/src/modules/midas.ts +159 -0
  22. package/src/modules/pricer-from-api.ts +2 -2
  23. package/src/modules/pricer-lst.ts +1 -1
  24. package/src/modules/pricer.ts +3 -38
  25. package/src/modules/token-market-data.ts +202 -0
  26. package/src/node/deployer.ts +1 -36
  27. package/src/strategies/autoCompounderStrk.ts +1 -1
  28. package/src/strategies/base-strategy.ts +20 -3
  29. package/src/strategies/ekubo-cl-vault.tsx +123 -306
  30. package/src/strategies/index.ts +4 -1
  31. package/src/strategies/svk-strategy.ts +247 -0
  32. package/src/strategies/universal-adapters/adapter-optimizer.ts +65 -0
  33. package/src/strategies/universal-adapters/adapter-utils.ts +5 -1
  34. package/src/strategies/universal-adapters/avnu-adapter.ts +415 -0
  35. package/src/strategies/universal-adapters/baseAdapter.ts +181 -153
  36. package/src/strategies/universal-adapters/common-adapter.ts +98 -77
  37. package/src/strategies/universal-adapters/extended-adapter.ts +972 -0
  38. package/src/strategies/universal-adapters/index.ts +5 -1
  39. package/src/strategies/universal-adapters/unused-balance-adapter.ts +109 -0
  40. package/src/strategies/universal-adapters/vesu-adapter.ts +221 -221
  41. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1306 -0
  42. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +58 -51
  43. package/src/strategies/universal-lst-muliplier-strategy.tsx +716 -844
  44. package/src/strategies/universal-strategy.tsx +1103 -1180
  45. package/src/strategies/vesu-extended-strategy/services/operationService.ts +34 -0
  46. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +77 -0
  47. package/src/strategies/vesu-extended-strategy/utils/constants.ts +50 -0
  48. package/src/strategies/vesu-extended-strategy/utils/helper.ts +370 -0
  49. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +1410 -0
  50. package/src/strategies/vesu-rebalance.tsx +16 -19
  51. package/src/utils/health-factor-math.ts +11 -5
@@ -0,0 +1,1410 @@
1
+ import {
2
+ getMainnetConfig,
3
+ IConfig,
4
+ IStrategyMetadata,
5
+ TokenInfo,
6
+ } from "@/interfaces";
7
+ import {
8
+ UNIVERSAL_MANAGE_IDS,
9
+ UniversalStrategySettings,
10
+ } from "../universal-strategy";
11
+ import { calculateExtendedLevergae } from "./utils/helper";
12
+ import { logger } from "@/utils";
13
+ import { AUDIT_URL } from "../universal-lst-muliplier-strategy";
14
+ import { getNoRiskTags } from "@/interfaces";
15
+ import { _riskFactor } from "../universal-lst-muliplier-strategy";
16
+ import { BUFFER_USDC_IN_WITHDRAWAL, EXTENDED_QTY_PRECISION, LIMIT_BALANCE, LIMIT_BALANCE_VALUE, MAX_PRICE_DIFFERENCE_BETWEEN_AVNU_AND_EXTENDED, MIN_PRICE_DIFFERENCE_BETWEEN_AVNU_AND_EXTENDED, MINIMUM_EXTENDED_POSITION_SIZE, USDC_TOKEN_DECIMALS, WALLET_ADDRESS, WBTC_TOKEN_DECIMALS } from "./utils/constants";
17
+ import { PricerBase } from "@/modules/pricerBase";
18
+ import { ContractAddr, Web3Number } from "@/dataTypes";
19
+ import { Global } from "@/global";
20
+ import { ERC20 } from "@/modules";
21
+ import { Balance, OrderSide } from "@/modules/ExtendedWrapperSDk";
22
+ import { Protocols } from "@/interfaces";
23
+ import { MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP } from "./utils/constants";
24
+ import { getInvestmentSteps, getFAQs } from "../universal-lst-muliplier-strategy";
25
+ import { getContractDetails } from "../universal-strategy";
26
+ import { highlightTextWithLinks } from "@/interfaces";
27
+ import { PositionInfo } from "../universal-adapters";
28
+ import { APYType } from "../universal-adapters";
29
+ import { AUMTypes } from "../universal-strategy";
30
+ import { VesuPools } from "../universal-adapters";
31
+ import {
32
+ BaseAdapterConfig,
33
+ CommonAdapter,
34
+ UnusedBalanceAdapter,
35
+ VesuMultiplyAdapter,
36
+ } from "../universal-adapters";
37
+ import { Operations } from "./services/operationService";
38
+ import {
39
+ AVNU_EXCHANGE,
40
+ AVNU_QUOTE_URL,
41
+ AVNU_MIDDLEWARE,
42
+ EXTENDED_CONTRACT,
43
+ } from "../universal-adapters/adapter-utils";
44
+ import { PricerFromApi } from "@/modules";
45
+ import { ExtendedAdapter } from "../universal-adapters/extended-adapter";
46
+ import { SVKStrategy } from "../svk-strategy";
47
+ import { AvnuAdapter } from "../universal-adapters/avnu-adapter";
48
+ import {
49
+ calculateAmountDistribution,
50
+ calculateAmountDistributionForWithdrawal,
51
+ calculateVesuLeverage,
52
+ calculateVesUPositionSizeGivenExtended
53
+ } from "./utils/helper";
54
+ import { SingleTokenInfo } from "../base-strategy";
55
+ import { Call } from "starknet";
56
+ import { PositionTypeAvnuExtended} from "../universal-strategy";
57
+
58
+
59
+ export interface VesuExtendedStrategySettings
60
+ extends UniversalStrategySettings {
61
+ underlyingToken: TokenInfo;
62
+ borrowable_assets: TokenInfo[];
63
+ targetHealthFactor: number;
64
+ quoteAmountToFetchPrice: Web3Number;
65
+ minHealthFactor: number;
66
+ aumOracle: ContractAddr;
67
+ minimumWBTCDifferenceForAvnuSwap: number;
68
+ }
69
+
70
+ export class VesuExtendedMultiplierStrategy<
71
+ S extends VesuExtendedStrategySettings
72
+ >
73
+ extends SVKStrategy<S>
74
+ implements Operations {
75
+ constructor(
76
+ config: IConfig,
77
+ pricer: PricerBase,
78
+ metadata: IStrategyMetadata<S>
79
+ ) {
80
+ super(config, pricer, metadata);
81
+ this.metadata.additionalInfo.adapters.forEach((adapter) => {
82
+ adapter.adapter.config.networkConfig = this.config;
83
+ adapter.adapter.config.pricer = this.pricer;
84
+ if ((adapter.adapter as VesuMultiplyAdapter).vesuAdapter) {
85
+ (adapter.adapter as VesuMultiplyAdapter).vesuAdapter.networkConfig =
86
+ this.config;
87
+ (adapter.adapter as VesuMultiplyAdapter).vesuAdapter.pricer =
88
+ this.pricer;
89
+ }
90
+ });
91
+ }
92
+
93
+ getTag() {
94
+ return `${VesuExtendedMultiplierStrategy.name}:${this.metadata.name}`;
95
+ }
96
+
97
+ async getAssetPrices() {
98
+ const wbtcToken = Global.getDefaultTokens().find(
99
+ (token) => token.symbol === "WBTC"
100
+ )!;
101
+ const usdcToken = Global.getDefaultTokens().find(
102
+ (token) => token.symbol === "USDC"
103
+ )!;
104
+ const collateralPrice = await this.pricer.getPrice(wbtcToken.symbol);
105
+ const debtPrice = await this.pricer.getPrice(usdcToken.symbol);
106
+ return {
107
+ collateralPrice,
108
+ debtPrice,
109
+ };
110
+ }
111
+
112
+ async getUnusedBalanceUSDCE(): Promise<SingleTokenInfo> {
113
+ const usdceToken = Global.getDefaultTokens().find(
114
+ (token) => token.symbol === "USDCe"
115
+ )!;
116
+ const balance = await new ERC20(this.config).balanceOf(
117
+ usdceToken.address,
118
+ WALLET_ADDRESS,
119
+ usdceToken.decimals
120
+ );
121
+ const price = await this.pricer.getPrice(usdceToken.symbol);
122
+ const usdValue =
123
+ Number(balance.toFixed(usdceToken.decimals)) * price.price;
124
+ return {
125
+ tokenInfo: usdceToken,
126
+ amount: balance,
127
+ usdValue,
128
+ };
129
+ }
130
+
131
+
132
+ async getUnusedBalanceWBTC(): Promise<SingleTokenInfo> {
133
+ const collateralToken = this.metadata.additionalInfo.borrowable_assets[0]!;
134
+ const balance = await new ERC20(this.config).balanceOf(
135
+ collateralToken.address,
136
+ this.metadata.additionalInfo.vaultAllocator,
137
+ collateralToken.decimals
138
+ );
139
+ const price = await this.pricer.getPrice(collateralToken.symbol);
140
+ const usdValue =
141
+ Number(balance.toFixed(collateralToken.decimals)) * price.price;
142
+ return {
143
+ tokenInfo: collateralToken,
144
+ amount: balance,
145
+ usdValue,
146
+ };
147
+ }
148
+
149
+ async getVesuAdapter(): Promise<VesuMultiplyAdapter | null> {
150
+ const vesuAdapter = this.metadata.additionalInfo.adapters.find(
151
+ (adapter) => adapter.adapter.name === VesuMultiplyAdapter.name
152
+ );
153
+ if (!vesuAdapter) {
154
+ logger.error("vesu adapter not found");
155
+ return null;
156
+ }
157
+ return vesuAdapter.adapter as VesuMultiplyAdapter;
158
+ }
159
+
160
+ async getAvnuAdapter(): Promise<AvnuAdapter | null> {
161
+ const avnuAdapter = this.metadata.additionalInfo.adapters.find(
162
+ (adapter) => adapter.adapter.name === AvnuAdapter.name
163
+ );
164
+ if (!avnuAdapter) {
165
+ logger.error("avnu adapter not found");
166
+ return null;
167
+ }
168
+ return avnuAdapter.adapter as AvnuAdapter;
169
+ }
170
+
171
+ async getExtendedAdapter(): Promise<ExtendedAdapter | null> {
172
+ const extendedAdapter = this.metadata.additionalInfo.adapters.find(
173
+ (adapter) => adapter.adapter.name === ExtendedAdapter.name
174
+ );
175
+ if (!extendedAdapter) {
176
+ logger.error("extended adapter not found");
177
+ return null;
178
+ }
179
+ return extendedAdapter.adapter as ExtendedAdapter;
180
+ }
181
+
182
+ async moveAssetsToVaultAllocator(amount: Web3Number, extendedAdapter: ExtendedAdapter): Promise<Call[]> {
183
+ try {
184
+ const usdceToken = Global.getDefaultTokens().find(
185
+ (token) => token.symbol === "USDCe"
186
+ )!;
187
+ const approveCall = new ERC20(this.config).approve(
188
+ usdceToken.address,
189
+ this.metadata.additionalInfo.vaultAllocator,
190
+ amount
191
+ );
192
+ const transferCall = new ERC20(this.config).transfer(
193
+ usdceToken.address,
194
+ this.metadata.additionalInfo.vaultAllocator,
195
+ amount
196
+ );
197
+ const proofsInfo = extendedAdapter.getProofsForFromLegacySwap(
198
+ this.getMerkleTree()
199
+ );
200
+ const proofGroups = proofsInfo.proofs;
201
+ const call = this.getManageCall(
202
+ proofGroups,
203
+ await proofsInfo.callConstructor({ amount: amount })
204
+ );
205
+ return [approveCall, transferCall, call];
206
+ } catch (err) {
207
+ logger.error(`error moving assets to vault allocator: ${err}`);
208
+ return [];
209
+ }
210
+ }
211
+
212
+ async shouldInvest(): Promise<{
213
+ shouldInvest: boolean;
214
+ vesuAmount: Web3Number;
215
+ extendedAmount: Web3Number;
216
+ extendedLeverage: number;
217
+ collateralPrice: number;
218
+ debtPrice: number;
219
+ vesuLeverage: number;
220
+ }> {
221
+ try {
222
+ logger.info(`${VesuExtendedMultiplierStrategy.name}::shouldInvest starting`);
223
+ const vesuAdapter = await this.getVesuAdapter();
224
+ const extendedAdapter = await this.getExtendedAdapter();
225
+ logger.info(`${VesuExtendedMultiplierStrategy.name}::shouldInvest adapters fetched: vesuAdapter=${!!vesuAdapter}, extendedAdapter=${!!extendedAdapter}, extendedAdapter.client=${!!extendedAdapter?.client}`);
226
+
227
+ if (!vesuAdapter) {
228
+ logger.error(
229
+ `Vesu adapter not configured in metadata. This is a configuration issue, not a temporary failure.`
230
+ );
231
+ return {
232
+ shouldInvest: false,
233
+ vesuAmount: new Web3Number(0, 0),
234
+ extendedAmount: new Web3Number(0, 0),
235
+ extendedLeverage: 0,
236
+ collateralPrice: 0,
237
+ debtPrice: 0,
238
+ vesuLeverage: 0,
239
+ };
240
+ }
241
+ if (!extendedAdapter) {
242
+ logger.error(
243
+ `Extended adapter not configured in metadata. This is a configuration issue, not a temporary failure.`
244
+ );
245
+ return {
246
+ shouldInvest: false,
247
+ vesuAmount: new Web3Number(0, 0),
248
+ extendedAmount: new Web3Number(0, 0),
249
+ extendedLeverage: 0,
250
+ collateralPrice: 0,
251
+ debtPrice: 0,
252
+ vesuLeverage: 0,
253
+ };
254
+ }
255
+ if (!extendedAdapter.client) {
256
+ logger.error(
257
+ `Extended adapter client not initialized. This may be a temporary initialization failure - check network connectivity and API availability.`
258
+ );
259
+ return {
260
+ shouldInvest: false,
261
+ vesuAmount: new Web3Number(0, 0),
262
+ extendedAmount: new Web3Number(0, 0),
263
+ extendedLeverage: 0,
264
+ collateralPrice: 0,
265
+ debtPrice: 0,
266
+ vesuLeverage: 0,
267
+ };
268
+ }
269
+
270
+ logger.info(`${VesuExtendedMultiplierStrategy.name}::shouldInvest calling getUnusedBalance`);
271
+ const balance = await this.getUnusedBalance();
272
+
273
+ if (!Number.isFinite(balance.usdValue) || balance.usdValue < 0) {
274
+ logger.error(
275
+ `Invalid balance.usdValue: ${balance.usdValue}. Expected a finite, non-negative number.`
276
+ );
277
+ return {
278
+ shouldInvest: false,
279
+ vesuAmount: new Web3Number(0, 0),
280
+ extendedAmount: new Web3Number(0, 0),
281
+ extendedLeverage: 0,
282
+ collateralPrice: 0,
283
+ debtPrice: 0,
284
+ vesuLeverage: 0,
285
+ };
286
+ }
287
+ logger.info(`${VesuExtendedMultiplierStrategy.name}::shouldInvest balance: ${balance.usdValue}`);
288
+ const usdcBalanceOnExtended = await extendedAdapter.getExtendedDepositAmount();
289
+
290
+ if (usdcBalanceOnExtended) {
291
+ const availableForWithdrawal = parseFloat(usdcBalanceOnExtended.availableForWithdrawal);
292
+ if (!Number.isFinite(availableForWithdrawal) || availableForWithdrawal < 0) {
293
+ logger.error(
294
+ `Invalid usdcBalanceOnExtended.availableForWithdrawal: ${usdcBalanceOnExtended.availableForWithdrawal}. Expected a finite, non-negative number.`
295
+ );
296
+ return {
297
+ shouldInvest: false,
298
+ vesuAmount: new Web3Number(0, 0),
299
+ extendedAmount: new Web3Number(0, 0),
300
+ extendedLeverage: 0,
301
+ collateralPrice: 0,
302
+ debtPrice: 0,
303
+ vesuLeverage: 0,
304
+ };
305
+ }
306
+ }
307
+
308
+ /** The LIMIT_BALANCE is the bffer amount to keep in the investing Cycle */
309
+ const amountToInvest = new Web3Number(balance.usdValue, USDC_TOKEN_DECIMALS).plus(usdcBalanceOnExtended?.availableForWithdrawal ?? 0).multipliedBy(1 - LIMIT_BALANCE);
310
+
311
+
312
+ const amountToInvestNumber = amountToInvest.toNumber();
313
+ if (!Number.isFinite(amountToInvestNumber)) {
314
+ logger.error(
315
+ `Invalid amountToInvest calculation result: ${amountToInvestNumber}. Calculation may have produced NaN or Infinity.`
316
+ );
317
+ return {
318
+ shouldInvest: false,
319
+ vesuAmount: new Web3Number(0, 0),
320
+ extendedAmount: new Web3Number(0, 0),
321
+ extendedLeverage: 0,
322
+ collateralPrice: 0,
323
+ debtPrice: 0,
324
+ vesuLeverage: 0,
325
+ };
326
+ }
327
+
328
+ logger.info(`${VesuExtendedMultiplierStrategy.name}::shouldInvest amountToInvest: ${amountToInvestNumber}`);
329
+ if (amountToInvest.lessThan(LIMIT_BALANCE_VALUE)) {
330
+ return {
331
+ shouldInvest: false,
332
+ vesuAmount: new Web3Number(0, 0),
333
+ extendedAmount: new Web3Number(0, 0),
334
+ extendedLeverage: 0,
335
+ collateralPrice: 0,
336
+ debtPrice: 0,
337
+ vesuLeverage: 0,
338
+ };
339
+ }
340
+
341
+ const extendedPositon = await extendedAdapter.getAllOpenPositions();
342
+ if (!extendedPositon) {
343
+ logger.error("error getting extended position to decide move assets");
344
+ return {
345
+ shouldInvest: false,
346
+ vesuAmount: new Web3Number(0, 0),
347
+ extendedAmount: new Web3Number(0, 0),
348
+ extendedLeverage: 0,
349
+ collateralPrice: 0,
350
+ debtPrice: 0,
351
+ vesuLeverage: 0,
352
+ };
353
+ }
354
+ const { collateralTokenAmount } =
355
+ await vesuAdapter.vesuAdapter.getAssetPrices();
356
+
357
+ const {
358
+ collateralPrice,
359
+ debtPrice
360
+ } = await this.getAssetPrices();
361
+
362
+
363
+ if (!Number.isFinite(collateralPrice.price) || collateralPrice.price <= 0) {
364
+ logger.error(
365
+ `Invalid collateralPrice: ${collateralPrice.price}. Expected a finite, positive number.`
366
+ );
367
+ return {
368
+ shouldInvest: false,
369
+ vesuAmount: new Web3Number(0, 0),
370
+ extendedAmount: new Web3Number(0, 0),
371
+ extendedLeverage: 0,
372
+ collateralPrice: 0,
373
+ debtPrice: 0,
374
+ vesuLeverage: 0,
375
+ };
376
+ }
377
+ if (!Number.isFinite(debtPrice.price) || debtPrice.price <= 0) {
378
+ logger.error(
379
+ `Invalid debtPrice: ${debtPrice.price}. Expected a finite, positive number.`
380
+ );
381
+ return {
382
+ shouldInvest: false,
383
+ vesuAmount: new Web3Number(0, 0),
384
+ extendedAmount: new Web3Number(0, 0),
385
+ extendedLeverage: 0,
386
+ collateralPrice: 0,
387
+ debtPrice: 0,
388
+ vesuLeverage: 0,
389
+ };
390
+ }
391
+
392
+ const { vesu_amount, extended_amount, extended_leverage, vesu_leverage } =
393
+ await calculateAmountDistribution(
394
+ amountToInvest.toNumber(),
395
+ extendedAdapter.client,
396
+ extendedAdapter.config.extendedMarketName,
397
+ collateralPrice.price,
398
+ debtPrice.price,
399
+ collateralTokenAmount,
400
+ extendedPositon
401
+ );
402
+ if (
403
+ !vesu_amount ||
404
+ !extended_amount ||
405
+ !extended_leverage ||
406
+ !vesu_leverage
407
+ ) {
408
+ logger.error(
409
+ `Not enough amount to invest: vesu_amount=${vesu_amount}, extended_amount=${extended_amount}`
410
+ );
411
+ return {
412
+ shouldInvest: false,
413
+ vesuAmount: new Web3Number(0, 0),
414
+ extendedAmount: new Web3Number(0, 0),
415
+ extendedLeverage: 0,
416
+ collateralPrice: 0,
417
+ debtPrice: 0,
418
+ vesuLeverage: 0,
419
+ };
420
+ }
421
+ logger.info(`${VesuExtendedMultiplierStrategy.name}::shouldInvest vesu_amount: ${vesu_amount.toNumber()}, extended_amount: ${extended_amount.toNumber()}`);
422
+ return {
423
+ shouldInvest: true,
424
+ vesuAmount: vesu_amount,
425
+ extendedAmount: extended_amount,
426
+ extendedLeverage: extended_leverage,
427
+ vesuLeverage: vesu_leverage,
428
+ collateralPrice: collateralPrice.price,
429
+ debtPrice: debtPrice.price,
430
+ };
431
+ } catch (err) {
432
+ logger.error(`error deciding invest: ${err}`);
433
+ return {
434
+ shouldInvest: false,
435
+ vesuAmount: new Web3Number(0, 0),
436
+ extendedAmount: new Web3Number(0, 0),
437
+ extendedLeverage: 0,
438
+ collateralPrice: 0,
439
+ debtPrice: 0,
440
+ vesuLeverage: 0,
441
+ };
442
+ }
443
+ }
444
+
445
+ async shouldMoveAssets(extendedAmount: Web3Number, vesuAmount: Web3Number): Promise<Call[]> {
446
+ try {
447
+ const vesuAdapter = await this.getVesuAdapter();
448
+ const extendedAdapter = await this.getExtendedAdapter();
449
+ if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
450
+ logger.error(
451
+ `vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
452
+ );
453
+ return [];
454
+ }
455
+
456
+ const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
457
+ if (!extendedHoldings) {
458
+ logger.error(`error getting extended holdings: ${extendedHoldings}`);
459
+ return [];
460
+ }
461
+ const usdcAmountInWallet = (await this.getUnusedBalance()).amount;
462
+ const usdcAmountOnExtendedAvailableForWithdrawal = parseFloat(
463
+ extendedHoldings.availableForWithdrawal
464
+ );
465
+
466
+ logger.info(`${VesuExtendedMultiplierStrategy.name}::shouldMoveAssets calculating movements - Extended current: ${usdcAmountOnExtendedAvailableForWithdrawal}, Wallet: ${usdcAmountInWallet.toNumber()}, Target Extended: ${extendedAmount.toNumber()}, Target Vesu: ${vesuAmount.toNumber()}`);
467
+
468
+ let totalExtendedWithdrawal = new Web3Number(0, USDC_TOKEN_DECIMALS);
469
+ let totalExtendedDeposit = new Web3Number(0, USDC_TOKEN_DECIMALS);
470
+
471
+ if (extendedAmount.isNegative() && extendedAmount.abs().greaterThan(extendedAdapter.minimumExtendedMovementAmount)) {
472
+ totalExtendedWithdrawal = totalExtendedWithdrawal.plus(extendedAmount.abs());
473
+ }
474
+
475
+ // Calculate remaining Extended difference (target vs current)
476
+ // If extendedAmount was negative, we've already accounted for that withdrawal
477
+ // So we calculate based on what Extended will be after that withdrawal
478
+ const extendedTargetAmount = extendedAmount.abs(); // Use absolute value as target
479
+ let projectedExtendedBalance = usdcAmountOnExtendedAvailableForWithdrawal;
480
+
481
+ if (extendedAmount.isNegative()) {
482
+ projectedExtendedBalance = projectedExtendedBalance - extendedAmount.abs().toNumber();
483
+ }
484
+
485
+ const extendedAmountDifference = extendedTargetAmount.minus(projectedExtendedBalance);
486
+ const extendedAmountDifferenceAbs = extendedAmountDifference.abs();
487
+
488
+ // Track additional Extended movements
489
+ if (extendedAmountDifference.lessThan(0)) {
490
+ totalExtendedWithdrawal = totalExtendedWithdrawal.plus(extendedAmountDifferenceAbs);
491
+ } else if (extendedAmountDifference.greaterThan(0)) {
492
+ totalExtendedDeposit = totalExtendedDeposit.plus(extendedAmountDifference);
493
+ }
494
+
495
+ const vesuTargetAmount = vesuAmount.abs();
496
+ const projectedWalletBalance = usdcAmountInWallet
497
+ .plus(totalExtendedWithdrawal)
498
+ .minus(totalExtendedDeposit);
499
+
500
+ let vesuAmountDifference = vesuTargetAmount.minus(projectedWalletBalance);
501
+ const vesuAmountDifferenceAbs = vesuAmountDifference.abs();
502
+
503
+ logger.info(`${VesuExtendedMultiplierStrategy.name}::shouldMoveAssets calculated movements - Extended withdrawal: ${totalExtendedWithdrawal.toNumber()}, Extended deposit: ${totalExtendedDeposit.toNumber()}, Extended diff: ${extendedAmountDifference.toNumber()}, Projected wallet: ${projectedWalletBalance.toNumber()}, Vesu diff: ${vesuAmountDifference.toNumber()}`);
504
+ let calls: Call[] = [];
505
+
506
+ // Handle negative extendedAmount (initial withdrawal needed)
507
+ if (extendedAmount.isNegative() && extendedAmount.abs().greaterThan(extendedAdapter.minimumExtendedMovementAmount)) {
508
+ try {
509
+ const { calls: extendedCalls, status: extendedStatus } = await this.moveAssets(
510
+ {
511
+ to: Protocols.VAULT.name,
512
+ from: Protocols.EXTENDED.name,
513
+ amount: extendedAmount.abs(),
514
+ },
515
+ extendedAdapter,
516
+ vesuAdapter
517
+ );
518
+ if (extendedStatus) {
519
+ calls.push(...extendedCalls);
520
+ } else {
521
+ return [];
522
+ }
523
+ } catch (err) {
524
+ logger.error(`Failed moving assets to vault: ${err}`);
525
+ }
526
+ }
527
+
528
+ if (vesuAmount.isNegative() && vesuAmount.abs().greaterThan(vesuAdapter.minimumVesuMovementAmount)) {
529
+ try {
530
+ const { calls: vesuCalls, status: vesuStatus } = await this.moveAssets(
531
+ {
532
+ to: Protocols.EXTENDED.name,
533
+ from: Protocols.VESU.name,
534
+ amount: vesuAmount.abs(),
535
+ },
536
+ extendedAdapter,
537
+ vesuAdapter
538
+ );
539
+ calls.push(...vesuCalls);
540
+ if (!vesuStatus) {
541
+ return [];
542
+ }
543
+ } catch (err) {
544
+ logger.error(`Failed moving assets to vault: ${err}`);
545
+ }
546
+ }
547
+
548
+ // Handle Extended adjustments based on calculated difference
549
+ if (extendedAmountDifferenceAbs.greaterThan(extendedAdapter.minimumExtendedMovementAmount)) {
550
+ if (extendedAmountDifference.greaterThan(0)) {
551
+ try {
552
+ const { calls: extendedCalls, status: extendedStatus } = await this.moveAssets(
553
+ {
554
+ to: Protocols.EXTENDED.name,
555
+ from: Protocols.VAULT.name,
556
+ amount: extendedAmountDifference,
557
+ },
558
+ extendedAdapter,
559
+ vesuAdapter
560
+ );
561
+ if (extendedStatus) {
562
+ calls.push(...extendedCalls);
563
+ } else {
564
+ logger.error(`Failed to move assets to extended - operation returned false status`);
565
+ return [];
566
+ }
567
+ } catch (err) {
568
+ logger.error(`Failed moving assets to extended: ${err}`);
569
+ return [];
570
+ }
571
+ } else if (extendedAmountDifference.lessThan(0)) {
572
+ try {
573
+ const { calls: extendedCalls, status: extendedStatus } = await this.moveAssets(
574
+ {
575
+ to: Protocols.VAULT.name,
576
+ from: Protocols.EXTENDED.name,
577
+ amount: extendedAmountDifferenceAbs,
578
+ },
579
+ extendedAdapter,
580
+ vesuAdapter
581
+ );
582
+ if (extendedStatus) {
583
+ calls.push(...extendedCalls);
584
+ } else {
585
+ logger.error(`Failed to withdraw from extended - operation returned false status`);
586
+ return [];
587
+ }
588
+ } catch (err) {
589
+ logger.error(`Failed moving assets from extended to vault: ${err}`);
590
+ return [];
591
+ }
592
+ }
593
+ }
594
+
595
+ // Handle Vesu adjustments based on calculated difference (already adjusted for Extended movements)
596
+ if (vesuAmountDifferenceAbs.greaterThan(vesuAdapter.minimumVesuMovementAmount)) {
597
+ if (vesuAmountDifference.lessThanOrEqualTo(0)) {
598
+ logger.warn(
599
+ `Vesu amount difference is negative or zero: ${vesuAmountDifference.toNumber()}. Skipping operation.`
600
+ );
601
+ } else {
602
+ // Move assets from Extended to Vault (which will then go to Vesu)
603
+ try {
604
+ const { calls: vesuCalls, status: vesuStatus } = await this.moveAssets(
605
+ {
606
+ to: Protocols.VAULT.name,
607
+ from: Protocols.EXTENDED.name,
608
+ amount: vesuAmountDifference,
609
+ },
610
+ extendedAdapter,
611
+ vesuAdapter
612
+ );
613
+ if (!vesuStatus) {
614
+ logger.error(`Failed to move assets to vesu - operation returned false status`);
615
+ return [];
616
+ }
617
+ calls.push(...vesuCalls);
618
+ } catch (err) {
619
+ logger.error(`Failed moving assets to vault: ${err}`);
620
+ return [];
621
+ }
622
+ }
623
+ }
624
+
625
+ return calls;
626
+ } catch (err) {
627
+ logger.error(`Failed moving assets to vesu: ${err}`);
628
+ return [];
629
+ }
630
+ }
631
+
632
+ async moveAssets(
633
+ params: {
634
+ amount: Web3Number;
635
+ from: string;
636
+ to: string;
637
+ },
638
+ extendedAdapter: ExtendedAdapter,
639
+ vesuAdapter: VesuMultiplyAdapter
640
+ ): Promise<{
641
+ calls: Call[];
642
+ status: boolean;
643
+ }> {
644
+ try {
645
+ // Validate amount is positive before starting operations
646
+ if (params.amount.lessThanOrEqualTo(0)) {
647
+ logger.error(
648
+ `Invalid amount for moveAssets: ${params.amount.toNumber()}. Amount must be positive.`
649
+ );
650
+ return {
651
+ calls: [],
652
+ status: false
653
+ };
654
+ }
655
+
656
+ // Check minimum movement amounts before starting operations
657
+ const amountAbs = params.amount.abs();
658
+ if (params.from === Protocols.EXTENDED.name || params.to === Protocols.EXTENDED.name) {
659
+ if (amountAbs.lessThanOrEqualTo(extendedAdapter.minimumExtendedMovementAmount)) {
660
+ logger.warn(
661
+ `Amount ${amountAbs.toNumber()} is below minimum Extended movement amount ${extendedAdapter.minimumExtendedMovementAmount}. Skipping operation.`
662
+ );
663
+ return {
664
+ calls: [],
665
+ status: false
666
+ };
667
+ }
668
+ }
669
+ if (params.from === Protocols.VESU.name || params.to === Protocols.VESU.name) {
670
+ if (amountAbs.lessThanOrEqualTo(vesuAdapter.minimumVesuMovementAmount)) {
671
+ logger.warn(
672
+ `Amount ${amountAbs.toNumber()} is below minimum Vesu movement amount ${vesuAdapter.minimumVesuMovementAmount}. Skipping operation.`
673
+ );
674
+ return {
675
+ calls: [],
676
+ status: false
677
+ };
678
+ }
679
+ }
680
+
681
+ const avnuAdapter = await this.getAvnuAdapter();
682
+ if (!avnuAdapter) {
683
+ logger.error(`avnu adapter not found: ${avnuAdapter}`);
684
+ return {
685
+ calls: [],
686
+ status: false
687
+ };
688
+ }
689
+ logger.info(`moveAssets params, ${JSON.stringify(params)}`);
690
+ const collateralToken = vesuAdapter.config.supportedPositions[0].asset;
691
+ const {
692
+ collateralPrice,
693
+ } = await this.getAssetPrices();
694
+
695
+ if (params.to === Protocols.EXTENDED.name && params.from === Protocols.VAULT.name) {
696
+ const proofsInfo = extendedAdapter.getProofs(
697
+ true,
698
+ this.getMerkleTree()
699
+ );
700
+ const calls = [];
701
+ const proofGroups = proofsInfo.proofs;
702
+ const call = this.getManageCall(
703
+ proofGroups,
704
+ await proofsInfo.callConstructor({ amount: params.amount })
705
+ );
706
+ calls.push(call);
707
+ return {
708
+ calls: [call],
709
+ status: true
710
+ };
711
+ } else if (params.to === Protocols.VAULT.name && params.from === Protocols.EXTENDED.name) {
712
+ const extendedLeverage = calculateExtendedLevergae();
713
+ const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
714
+ if (!extendedHoldings) {
715
+ logger.error(`error getting extended holdings: ${extendedHoldings}`);
716
+ return {
717
+ calls: [],
718
+ status: false
719
+ };
720
+ }
721
+ const extendedHoldingAmount = new Web3Number(
722
+ extendedHoldings.availableForWithdrawal,
723
+ USDC_TOKEN_DECIMALS
724
+ );
725
+ logger.info(`${VesuExtendedMultiplierStrategy.name}::moveAssets extendedHoldingAmount: ${extendedHoldingAmount.toNumber()}`);
726
+ if (params.amount.abs().greaterThan(extendedHoldingAmount)) {
727
+ const leftAmountAfterWithdrawalAmountInAccount = params.amount.abs().minus(extendedHoldingAmount);
728
+ logger.info(`${VesuExtendedMultiplierStrategy.name}::moveAssets leftAmountAfterWithdrawalAmountInAccount: ${leftAmountAfterWithdrawalAmountInAccount.toNumber()}`);
729
+ const btcAmount = leftAmountAfterWithdrawalAmountInAccount.dividedBy(collateralPrice.price);
730
+ const openLongPosition = btcAmount.multipliedBy(3).greaterThan(MINIMUM_EXTENDED_POSITION_SIZE) ? await extendedAdapter.createOrder(
731
+ extendedLeverage.toString(),
732
+ btcAmount.toNumber(),
733
+ OrderSide.BUY
734
+ ) : await extendedAdapter.createOrder(
735
+ extendedLeverage.toString(),
736
+ 0.000035, // just in case amount falls short then we need to create a withdrawal
737
+ OrderSide.BUY
738
+ )
739
+ if (!openLongPosition) {
740
+ logger.error(`error opening long position: ${openLongPosition}`);
741
+ }
742
+ const updatedHoldings = await extendedAdapter.getExtendedDepositAmount();
743
+ if (!updatedHoldings || new Web3Number(updatedHoldings.availableForWithdrawal, USDC_TOKEN_DECIMALS).lessThan(params.amount.abs())) {
744
+ logger.error(`Insufficient balance after opening position. Available: ${updatedHoldings?.availableForWithdrawal}, Needed: ${params.amount.abs()}`);
745
+ return { calls: [], status: false };
746
+ }
747
+ }
748
+ const withdrawalFromExtended =
749
+ await extendedAdapter.withdrawFromExtended(params.amount);
750
+ if (withdrawalFromExtended) {
751
+ /**
752
+ * We need to move assets from my wallet back to vault contract
753
+ */
754
+ const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
755
+ logger.info(`extendedHoldings after withdrawal ${extendedHoldings?.availableForWithdrawal}`);
756
+ await new Promise(resolve => setTimeout(resolve, 5000));
757
+ const calls = await this.moveAssetsToVaultAllocator(params.amount, extendedAdapter);
758
+ if (calls.length > 0) {
759
+ return {
760
+ calls: calls,
761
+ status: true
762
+ };
763
+ }
764
+ } else {
765
+ logger.error("withdrawal from extended failed");
766
+ return {
767
+ calls: [],
768
+ status: false
769
+ };
770
+ }
771
+ } else if (params.to === Protocols.VAULT.name && params.from === Protocols.VESU.name) {
772
+ const isPriceDifferenceBetweenAvnuAndExtended = await this.checkPriceDifferenceBetweenAvnuAndExtended(extendedAdapter, vesuAdapter, avnuAdapter, PositionTypeAvnuExtended.CLOSE);
773
+ if (!isPriceDifferenceBetweenAvnuAndExtended) {
774
+ logger.warn(`price difference between avnu and extended doesn't fit the range for close position, ${avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing}`);
775
+ return {
776
+ calls: [],
777
+ status: false
778
+ };
779
+ }
780
+ //withdraw from vesu
781
+ const vesuAmountInBTC = new Web3Number(
782
+ params.amount.dividedBy(collateralPrice.price).toNumber(),
783
+ collateralToken.decimals
784
+ );
785
+ const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
786
+ const calls = [];
787
+ const proofGroups = proofsInfo.proofs;
788
+ const call = this.getManageCall(
789
+ proofGroups,
790
+ await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
791
+ );
792
+ calls.push(call);
793
+ const swapProofsInfo = avnuAdapter.getProofs(false, this.getMerkleTree());
794
+ const swapProofGroups = swapProofsInfo.proofs;
795
+ const swapCall = this.getManageCall(
796
+ swapProofGroups,
797
+ await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
798
+ );
799
+ calls.push(swapCall);
800
+ return {
801
+ calls: calls,
802
+ status: true
803
+ };
804
+ } else if (params.to === Protocols.EXTENDED.name && params.from === Protocols.VESU.name) {
805
+ const isPriceDifferenceBetweenAvnuAndExtended = await this.checkPriceDifferenceBetweenAvnuAndExtended(extendedAdapter, vesuAdapter, avnuAdapter, PositionTypeAvnuExtended.CLOSE);
806
+ if (!isPriceDifferenceBetweenAvnuAndExtended) {
807
+ logger.warn(`price difference between avnu and extended doesn't fit the range for close position, ${avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing}`);
808
+ return {
809
+ calls: [],
810
+ status: false
811
+ };
812
+ }
813
+ const vesuAmountInBTC = new Web3Number(
814
+ params.amount.dividedBy(collateralPrice.price).toNumber(),
815
+ collateralToken.decimals
816
+ );
817
+ const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
818
+ const calls = [];
819
+ const proofGroups = proofsInfo.proofs;
820
+ const call = this.getManageCall(
821
+ proofGroups,
822
+ await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
823
+ );
824
+ calls.push(call);
825
+ const swapProofsInfo = avnuAdapter.getProofs(false, this.getMerkleTree());
826
+ const swapProofGroups = swapProofsInfo.proofs;
827
+ const swapCall = this.getManageCall(
828
+ swapProofGroups,
829
+ await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
830
+ );
831
+ calls.push(swapCall);
832
+ const proofsInfoDeposit = extendedAdapter.getProofs(
833
+ true,
834
+ this.getMerkleTree()
835
+ );
836
+ //Deposit Amount would still be in usdc
837
+ const proofGroupsDeposit = proofsInfoDeposit.proofs;
838
+ const callDeposit = this.getManageCall(
839
+ proofGroupsDeposit,
840
+ await proofsInfoDeposit.callConstructor({ amount: params.amount })
841
+ );
842
+ calls.push(callDeposit);
843
+ return {
844
+ calls: calls,
845
+ status: true
846
+ };
847
+ }
848
+ return {
849
+ calls: [],
850
+ status: false
851
+ };
852
+ } catch (err) {
853
+ logger.error(`error moving assets: ${err}`);
854
+ return {
855
+ calls: [],
856
+ status: false
857
+ };
858
+ }
859
+ }
860
+
861
+ async handleDeposit(): Promise<{
862
+ extendedAmountInBTC: Web3Number,
863
+ calls: Call[]
864
+ }> {
865
+ try {
866
+ const vesuAdapter = await this.getVesuAdapter();
867
+ const extendedAdapter = await this.getExtendedAdapter();
868
+ const avnuAdapter = await this.getAvnuAdapter();
869
+ if (
870
+ !vesuAdapter ||
871
+ !extendedAdapter ||
872
+ !extendedAdapter.client ||
873
+ !avnuAdapter
874
+ ) {
875
+ logger.error(
876
+ "vesu or extended adapter not found",
877
+ vesuAdapter,
878
+ extendedAdapter
879
+ );
880
+ return {
881
+ extendedAmountInBTC: new Web3Number(0, 0),
882
+ calls: [],
883
+ };
884
+ }
885
+ const extendedLeverage = calculateExtendedLevergae();
886
+ const isPriceDifferenceBetweenAvnuAndExtended = await this.checkPriceDifferenceBetweenAvnuAndExtended(extendedAdapter, vesuAdapter, avnuAdapter, PositionTypeAvnuExtended.OPEN);
887
+ if (!isPriceDifferenceBetweenAvnuAndExtended) {
888
+ logger.error("price difference between avnu and extended doesn't fit the range");
889
+ return {
890
+ extendedAmountInBTC: new Web3Number(0, 0),
891
+ calls: [],
892
+ };
893
+ }
894
+ const position = await extendedAdapter.getAllOpenPositions();
895
+ if (!position) {
896
+ logger.error("error getting extended position", position);
897
+ return {
898
+ extendedAmountInBTC: new Web3Number(0, 0),
899
+ calls: [],
900
+ };
901
+ }
902
+ const extendedPositionValue = position.length > 0 ? parseFloat(position[0].value) : 0;
903
+ const BUFFER_AMOUNT_IN_AVAILABLE_FOR_TRADE = BUFFER_USDC_IN_WITHDRAWAL;
904
+ const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
905
+ if (!extendedHoldings) {
906
+ logger.error(`error getting extended holdings: ${extendedHoldings}`);
907
+ return {
908
+ extendedAmountInBTC: new Web3Number(0, 0),
909
+ calls: [],
910
+ };
911
+ }
912
+ const extendedHoldingAmount = new Web3Number(
913
+ extendedHoldings.availableForWithdrawal,
914
+ USDC_TOKEN_DECIMALS
915
+ );
916
+ const {
917
+ collateralTokenAmount,
918
+ } = await vesuAdapter.vesuAdapter.getAssetPrices();
919
+ const { collateralPrice } = await this.getAssetPrices();
920
+ const { vesuAmountInBTC, extendedAmountInBTC } = calculateVesUPositionSizeGivenExtended(
921
+ extendedPositionValue,
922
+ extendedHoldingAmount.minus(BUFFER_AMOUNT_IN_AVAILABLE_FOR_TRADE),
923
+ collateralTokenAmount,
924
+ collateralPrice.price
925
+ );
926
+ logger.info(`vesuAmountInBTC ${vesuAmountInBTC}, extendedAmountInBTC ${extendedAmountInBTC}`);
927
+
928
+ let calls: Call[] = [];
929
+ if (vesuAmountInBTC.greaterThan(MINIMUM_EXTENDED_POSITION_SIZE)) {
930
+ const proofsInfo = vesuAdapter.getProofs(true, this.getMerkleTree());
931
+ const proofGroups = proofsInfo.proofs;
932
+ const call = this.getManageCall(
933
+ proofGroups,
934
+ await proofsInfo.callConstructor({
935
+ amount: vesuAmountInBTC,
936
+ })
937
+ );
938
+ const { amount: wbtcAmountInVaultAllocator } = await this.getUnusedBalanceWBTC();
939
+ if (wbtcAmountInVaultAllocator.lessThan(vesuAmountInBTC)) {
940
+ const swapProofsInfo = avnuAdapter.getProofs(true, this.getMerkleTree());
941
+ const swapProofGroups = swapProofsInfo.proofs;
942
+ const swapCall = this.getManageCall(
943
+ swapProofGroups,
944
+ await swapProofsInfo.callConstructor({
945
+ amount: vesuAmountInBTC,
946
+ })
947
+ );
948
+ calls.push(swapCall);
949
+ }
950
+ calls.push(call);
951
+ }
952
+ const shortPosition = extendedAmountInBTC.multipliedBy(3).abs().greaterThan(MINIMUM_EXTENDED_POSITION_SIZE) ? await extendedAdapter.createOrder(
953
+ extendedLeverage.toString(),
954
+ extendedAmountInBTC.toNumber(),
955
+ OrderSide.SELL
956
+ ) : null;
957
+ if (!shortPosition && extendedAmountInBTC.multipliedBy(3).abs().greaterThan(MINIMUM_EXTENDED_POSITION_SIZE)) {
958
+ logger.error(`error creating short position thus no position to be opened on vesu: ${shortPosition}`);
959
+ return {
960
+ extendedAmountInBTC: new Web3Number(0, 0),
961
+ calls: [],
962
+ };
963
+ }
964
+ return {
965
+ extendedAmountInBTC: extendedAmountInBTC,
966
+ calls: calls,
967
+ };
968
+ } catch (err) {
969
+ logger.error(`error handling deposit: ${err}`);
970
+ return {
971
+ extendedAmountInBTC: new Web3Number(0, 0),
972
+ calls: [],
973
+ };
974
+ }
975
+ }
976
+
977
+ async checkPriceDifferenceBetweenAvnuAndExtended(extendedAdapter: ExtendedAdapter, vesuAdapter: VesuMultiplyAdapter, avnuAdapter: AvnuAdapter, positionType: PositionTypeAvnuExtended): Promise<boolean> {
978
+ const {
979
+ ask, bid
980
+ } = await extendedAdapter.fetchOrderBookBTCUSDC();
981
+ const price = ask.plus(bid).dividedBy(2);
982
+ const btcToken = vesuAdapter.config.supportedPositions[0].asset;
983
+ const btcPriceAvnu = await avnuAdapter.getPriceOfToken(btcToken.address.toString());
984
+ if (!btcPriceAvnu) {
985
+ logger.error(`error getting btc price avnu: ${btcPriceAvnu}`);
986
+ return false;
987
+ }
988
+ const priceDifference = new Web3Number(price.minus(btcPriceAvnu).toFixed(2), 0);
989
+ if(priceDifference.isNegative()){
990
+ return false;
991
+ }
992
+ if(positionType === PositionTypeAvnuExtended.OPEN){
993
+ logger.info(`price difference between avnu and extended for open position: ${priceDifference.toNumber()}, minimumExtendedPriceDifferenceForSwapOpen: ${avnuAdapter.config.minimumExtendedPriceDifferenceForSwapOpen}`);
994
+ const result = priceDifference.greaterThanOrEqualTo(avnuAdapter.config.minimumExtendedPriceDifferenceForSwapOpen); // 500 for now
995
+ logger.info(`result: ${result}`);
996
+ return result;
997
+ }else{
998
+ logger.info(`price difference between avnu and extended for close position: ${priceDifference.toNumber()}, maximumExtendedPriceDifferenceForSwapClosing: ${avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing}`);
999
+ const result = priceDifference.lessThanOrEqualTo(avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing); // 1000 for now
1000
+ logger.info(`result: ${result}`);
1001
+ return result;
1002
+ }
1003
+ }
1004
+
1005
+ async handleWithdraw(amount: Web3Number): Promise<{ calls: Call[], status: boolean }> {
1006
+ try {
1007
+ const usdcBalanceVaultAllocator = await this.getUnusedBalance();
1008
+ const usdcBalanceDifference = amount.plus(BUFFER_USDC_IN_WITHDRAWAL).minus(usdcBalanceVaultAllocator.usdValue);
1009
+ logger.info(`usdcBalanceDifference, ${usdcBalanceDifference.toNumber()}`);
1010
+ let calls: Call[] = [];
1011
+ let status: boolean = true;
1012
+ if (usdcBalanceDifference.lessThan(0)) {
1013
+ const withdrawCall = await this.getBringLiquidityCall({
1014
+ amount: usdcBalanceVaultAllocator.amount
1015
+ })
1016
+ logger.info("withdraw call", withdrawCall);
1017
+ calls.push(withdrawCall);
1018
+ return {
1019
+ calls: calls,
1020
+ status: true
1021
+ };
1022
+ }
1023
+ const vesuAdapter = await this.getVesuAdapter();
1024
+ const extendedAdapter = await this.getExtendedAdapter();
1025
+ if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
1026
+ status = false;
1027
+ logger.error(
1028
+ `vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
1029
+ );
1030
+ return {
1031
+ calls: calls,
1032
+ status: status
1033
+ };
1034
+ }
1035
+ const { collateralTokenAmount } =
1036
+ await vesuAdapter.vesuAdapter.getAssetPrices();
1037
+ const {
1038
+ collateralPrice
1039
+ } = await this.getAssetPrices();
1040
+ const extendedPositon = await extendedAdapter.getAllOpenPositions();
1041
+ if (!extendedPositon) {
1042
+ status = false;
1043
+ logger.error("error getting extended position", extendedPositon);
1044
+ return {
1045
+ calls: calls,
1046
+ status: status
1047
+ }
1048
+ }
1049
+ const amountDistributionForWithdrawal =
1050
+ await calculateAmountDistributionForWithdrawal(
1051
+ usdcBalanceDifference,
1052
+ collateralPrice.price,
1053
+ collateralTokenAmount,
1054
+ extendedPositon
1055
+ );
1056
+ if (!amountDistributionForWithdrawal) {
1057
+ status = false;
1058
+ logger.error(
1059
+ `error calculating amount distribution for withdrawal: ${amountDistributionForWithdrawal}`
1060
+ );
1061
+ return {
1062
+ calls: calls,
1063
+ status: status
1064
+ };
1065
+ }
1066
+ const { vesu_amount, extended_amount } = amountDistributionForWithdrawal;
1067
+
1068
+ if (status && vesu_amount.greaterThan(0)) {
1069
+ const { calls: vesuCalls, status: vesuStatus } = await this.moveAssets(
1070
+ {
1071
+ amount: vesu_amount,
1072
+ from: Protocols.VESU.name,
1073
+ to: Protocols.VAULT.name,
1074
+ },
1075
+ extendedAdapter,
1076
+ vesuAdapter
1077
+ );
1078
+ status = vesuStatus;
1079
+ calls.push(...vesuCalls);
1080
+ }
1081
+ if (status && extended_amount.greaterThan(0)) {
1082
+ const { calls: extendedCalls, status: extendedStatus } = await this.moveAssets(
1083
+ {
1084
+ amount: extended_amount,
1085
+ from: Protocols.EXTENDED.name,
1086
+ to: Protocols.VAULT.name,
1087
+ },
1088
+ extendedAdapter,
1089
+ vesuAdapter
1090
+ );
1091
+ status = extendedStatus;
1092
+ if (status) {
1093
+ calls.push(...extendedCalls);
1094
+ } else {
1095
+ logger.error("error moving assets to vault: extendedStatus: ${extendedStatus}");
1096
+ return {
1097
+ calls: [],
1098
+ status: status
1099
+ };
1100
+ }
1101
+ }
1102
+ const withdrawCall = await this.getBringLiquidityCall({
1103
+ amount: amount
1104
+ })
1105
+ logger.info("withdraw call", withdrawCall);
1106
+ calls.push(withdrawCall);
1107
+ return {
1108
+ calls: calls,
1109
+ status: status
1110
+ };
1111
+ } catch (err) {
1112
+ logger.error(`error handling withdrawal: ${err}`);
1113
+ return {
1114
+ calls: [],
1115
+ status: false
1116
+ };
1117
+ }
1118
+ }
1119
+
1120
+ async getAUM(): Promise<{ net: SingleTokenInfo, prevAum: Web3Number, splits: PositionInfo[] }> {
1121
+ const allPositions: PositionInfo[] = [];
1122
+ for (let adapter of this.metadata.additionalInfo.adapters) {
1123
+ const positions = await adapter.adapter.getPositions();
1124
+ allPositions.push(...positions);
1125
+ }
1126
+
1127
+ const assetPrice = await this.pricer.getPrice(this.asset().symbol);
1128
+ let netAUM = new Web3Number(0, this.asset().decimals);
1129
+ for (let position of allPositions) {
1130
+ if (position.tokenInfo.address.eq(this.asset().address)) {
1131
+ netAUM = netAUM.plus(position.amount);
1132
+ } else {
1133
+ netAUM = netAUM.plus(position.usdValue / assetPrice.price);
1134
+ }
1135
+ }
1136
+
1137
+ const prevAum = await this.getPrevAUM();
1138
+ const realAUM: PositionInfo = {
1139
+ tokenInfo: this.asset(),
1140
+ amount: netAUM,
1141
+ usdValue: netAUM.toNumber() * assetPrice.price,
1142
+ apy: { apy: netAUM.toNumber() * assetPrice.price, type: APYType.BASE },
1143
+ remarks: AUMTypes.FINALISED,
1144
+ protocol: Protocols.NONE // just placeholder
1145
+ };
1146
+
1147
+ const estimatedAUMDelta: PositionInfo = {
1148
+ tokenInfo: this.asset(),
1149
+ amount: Web3Number.fromWei('0', this.asset().decimals),
1150
+ usdValue: 0,
1151
+ apy: { apy: 0, type: APYType.BASE },
1152
+ remarks: AUMTypes.DEFISPRING,
1153
+ protocol: Protocols.NONE // just placeholder
1154
+ };
1155
+
1156
+ return {
1157
+ net: {
1158
+ tokenInfo: this.asset(),
1159
+ amount: netAUM,
1160
+ usdValue: netAUM.toNumber() * assetPrice.price
1161
+ }, prevAum: prevAum, splits: [realAUM, estimatedAUMDelta]
1162
+ };
1163
+ }
1164
+
1165
+ }
1166
+
1167
+ function getLooperSettings(
1168
+ lstSymbol: string,
1169
+ underlyingSymbol: string,
1170
+ vaultSettings: VesuExtendedStrategySettings,
1171
+ pool1: ContractAddr,
1172
+ extendedBackendUrl: string,
1173
+ extendedApiKey: string,
1174
+ vaultIdExtended: number,
1175
+ minimumExtendedMovementAmount: number,
1176
+ minimumVesuMovementAmount: number,
1177
+ minimumExtendedRetriesDelayForOrderStatus: number,
1178
+ minimumExtendedPriceDifferenceForSwapOpen: number,
1179
+ maximumExtendedPriceDifferenceForSwapClosing: number,
1180
+ ) {
1181
+ vaultSettings.leafAdapters = [];
1182
+
1183
+ const wbtcToken = Global.getDefaultTokens().find(
1184
+ (token) => token.symbol === lstSymbol
1185
+ )!;
1186
+ const usdcToken = Global.getDefaultTokens().find(
1187
+ (token) => token.symbol === underlyingSymbol
1188
+ )!;
1189
+
1190
+ const baseAdapterConfig: BaseAdapterConfig = {
1191
+ baseToken: wbtcToken,
1192
+ supportedPositions: [
1193
+ { asset: usdcToken, isDebt: true },
1194
+ { asset: wbtcToken, isDebt: false },
1195
+ ],
1196
+ //Since we open 2 positions, we need to add both positions, one is debt another is collateral
1197
+ networkConfig: getMainnetConfig(),
1198
+ pricer: new PricerFromApi(getMainnetConfig(), Global.getDefaultTokens()),
1199
+ vaultAllocator: vaultSettings.vaultAllocator,
1200
+ vaultAddress: vaultSettings.vaultAddress,
1201
+ };
1202
+
1203
+ const avnuAdapter = new AvnuAdapter({
1204
+ ...baseAdapterConfig,
1205
+ avnuContract: AVNU_MIDDLEWARE,
1206
+ slippage: 0.01,
1207
+ baseUrl: AVNU_QUOTE_URL,
1208
+ minimumExtendedPriceDifferenceForSwapOpen: minimumExtendedPriceDifferenceForSwapOpen,
1209
+ maximumExtendedPriceDifferenceForSwapClosing: maximumExtendedPriceDifferenceForSwapClosing,
1210
+ });
1211
+
1212
+ const extendedAdapter = new ExtendedAdapter({
1213
+ ...baseAdapterConfig,
1214
+ supportedPositions: [
1215
+ { asset: usdcToken, isDebt: true },
1216
+ ],
1217
+ vaultIdExtended: vaultIdExtended,
1218
+ extendedContract: EXTENDED_CONTRACT,
1219
+ extendedBackendUrl: extendedBackendUrl,
1220
+ extendedApiKey: extendedApiKey,
1221
+ extendedTimeout: 30000,
1222
+ extendedRetries: 3,
1223
+ extendedBaseUrl: "https://api.starknet.extended.exchange",
1224
+ extendedMarketName: "BTC-USD",
1225
+ extendedPrecision: 5,
1226
+ avnuAdapter: avnuAdapter,
1227
+ retryDelayForOrderStatus: minimumExtendedRetriesDelayForOrderStatus ?? 3000,
1228
+ minimumExtendedMovementAmount: minimumExtendedMovementAmount ?? 5, //5 usdcs
1229
+ });
1230
+
1231
+ const vesuMultiplyAdapter = new VesuMultiplyAdapter({
1232
+ poolId: pool1,
1233
+ collateral: wbtcToken,
1234
+ debt: usdcToken,
1235
+ targetHealthFactor: vaultSettings.targetHealthFactor,
1236
+ minHealthFactor: vaultSettings.minHealthFactor,
1237
+ quoteAmountToFetchPrice: vaultSettings.quoteAmountToFetchPrice,
1238
+ ...baseAdapterConfig,
1239
+ supportedPositions: [
1240
+ { asset: wbtcToken, isDebt: false },
1241
+ { asset: usdcToken, isDebt: true },
1242
+ ],
1243
+ minimumVesuMovementAmount: minimumVesuMovementAmount ?? 5, //5 usdc
1244
+ });
1245
+
1246
+ const unusedBalanceAdapter = new UnusedBalanceAdapter({
1247
+ ...baseAdapterConfig,
1248
+ });
1249
+
1250
+ vaultSettings.adapters.push({
1251
+ id: `${vesuMultiplyAdapter.name}_${wbtcToken.symbol}_${usdcToken.symbol}`,
1252
+ adapter: vesuMultiplyAdapter,
1253
+ });
1254
+
1255
+ vaultSettings.adapters.push({
1256
+ id: `${unusedBalanceAdapter.name}_${wbtcToken.symbol}`,
1257
+ adapter: unusedBalanceAdapter,
1258
+ });
1259
+
1260
+ vaultSettings.adapters.push({
1261
+ id: `${extendedAdapter.name}_${wbtcToken.symbol}`,
1262
+ adapter: extendedAdapter,
1263
+ });
1264
+
1265
+ vaultSettings.adapters.push({
1266
+ id: `${avnuAdapter.name}_${wbtcToken.symbol}`,
1267
+ adapter: avnuAdapter,
1268
+ });
1269
+
1270
+ const commonAdapter = new CommonAdapter({
1271
+ id: UNIVERSAL_MANAGE_IDS.FLASH_LOAN,
1272
+ vaultAddress: vaultSettings.vaultAddress,
1273
+ vaultAllocator: vaultSettings.vaultAllocator,
1274
+ manager: vaultSettings.manager,
1275
+ asset: wbtcToken.address,
1276
+ });
1277
+
1278
+ vaultSettings.leafAdapters.push(() => vesuMultiplyAdapter.getDepositLeaf());
1279
+ vaultSettings.leafAdapters.push(() =>
1280
+ vesuMultiplyAdapter.getWithdrawLeaf()
1281
+ );
1282
+ vaultSettings.leafAdapters.push(() => extendedAdapter.getDepositLeaf());
1283
+ vaultSettings.leafAdapters.push(() => extendedAdapter.getSwapFromLegacyLeaf());
1284
+ vaultSettings.leafAdapters.push(() => avnuAdapter.getDepositLeaf());
1285
+ vaultSettings.leafAdapters.push(() => avnuAdapter.getWithdrawLeaf());
1286
+ // Doubt here, should this be usdcToken.address, or wbtcToken.address?
1287
+ vaultSettings.leafAdapters.push(
1288
+ commonAdapter
1289
+ .getApproveAdapter(
1290
+ usdcToken.address,
1291
+ vaultSettings.vaultAddress,
1292
+ UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY
1293
+ )
1294
+ .bind(commonAdapter)
1295
+ );
1296
+
1297
+ vaultSettings.leafAdapters.push(
1298
+ commonAdapter
1299
+ .getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
1300
+ .bind(commonAdapter)
1301
+ );
1302
+ return vaultSettings;
1303
+ }
1304
+
1305
+
1306
+ function getDescription(tokenSymbol: string, underlyingSymbol: string) {
1307
+ return VaultDescription(tokenSymbol, underlyingSymbol);
1308
+ }
1309
+
1310
+ export default function VaultDescription(
1311
+ lstSymbol: string,
1312
+ underlyingSymbol: string
1313
+ ) {
1314
+ const containerStyle = {
1315
+ maxWidth: "800px",
1316
+ margin: "0 auto",
1317
+ backgroundColor: "#111",
1318
+ color: "#eee",
1319
+ fontFamily: "Arial, sans-serif",
1320
+ borderRadius: "12px",
1321
+ };
1322
+
1323
+ return (
1324
+ <div style={containerStyle}>
1325
+ <h1 style={{ fontSize: "18px", marginBottom: "10px" }}>Liquidation risk managed leverged {lstSymbol} Vault</h1>
1326
+ <p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
1327
+ 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
1328
+ is swapped to {lstSymbol} to create leverage. Depositors receive vault shares that
1329
+ represent a proportional claim on the underlying assets and accrued yield.
1330
+ </p>
1331
+
1332
+ <p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
1333
+ 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" }])}
1334
+ {" "}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.
1335
+ </p>
1336
+
1337
+ <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
1338
+ <p style={{ fontSize: "13px", color: "#ccc" }}>
1339
+ <strong>Withdrawals:</strong> Requests can take up to <strong>1-2 hours</strong> to process as the vault unwinds and settles routing.
1340
+ </p>
1341
+ </div>
1342
+ <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
1343
+ <p style={{ fontSize: "13px", color: "#ccc" }}>
1344
+ <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.
1345
+ </p>
1346
+ </div>
1347
+ {/* <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
1348
+ <p style={{ fontSize: "13px", color: "#ccc" }}>
1349
+ <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.
1350
+ </p>
1351
+ </div> */}
1352
+ </div>
1353
+ );
1354
+ }
1355
+
1356
+ const re7UsdcPrimeDevansh: VesuExtendedStrategySettings = {
1357
+ vaultAddress: ContractAddr.from("0x520a2e945dd0762e5284fc1b012f62ca04e238e105eb362d5e0ce208928729d"),
1358
+ manager: ContractAddr.from("0x6abe24d31cbc16d7c4acb2421a826f96273e225b4a2d168d91e65123da0bfb9"),
1359
+ vaultAllocator: ContractAddr.from("0x6e4f716e22efb164ee4a831233e513f45396ad3be4cff3c07386be0226febed"),
1360
+ redeemRequestNFT: ContractAddr.from("0x66060e1874e05506b18e6029188ec49bf231a411ad57642311bbdf3cb22e5f"),
1361
+ aumOracle: ContractAddr.from("0x301d883b9b45c76132638e39326b3f464c492599623263d405ec0df991e27ab"),
1362
+ leafAdapters: [],
1363
+ adapters: [],
1364
+ targetHealthFactor: 1.4,
1365
+ minHealthFactor: 1.05,
1366
+ underlyingToken: Global.getDefaultTokens().find(
1367
+ (token) => token.symbol === "USDC"
1368
+ )!,
1369
+ quoteAmountToFetchPrice: new Web3Number(
1370
+ "0.001",
1371
+ Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!.decimals
1372
+ ),
1373
+ borrowable_assets: [Global.getDefaultTokens().find(token => token.symbol === "WBTC")!],
1374
+ minimumWBTCDifferenceForAvnuSwap: MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP,
1375
+ }
1376
+
1377
+ export const VesuExtendedTestStrategies = (extendedBackendUrl: string, extendedApiKey: string, vaultIdExtended: number, minimumExtendedMovementAmount: number, minimumVesuMovementAmount: number, minimumExtendedRetriesDelayForOrderStatus: number, minimumExtendedPriceDifferenceForSwapOpen: number, maximumExtendedPriceDifferenceForSwapClosing: number): IStrategyMetadata<VesuExtendedStrategySettings>[] => {
1378
+ return [
1379
+ getStrategySettingsVesuExtended('WBTC', 'USDC', re7UsdcPrimeDevansh, false, false, extendedBackendUrl, extendedApiKey, vaultIdExtended, minimumExtendedMovementAmount, minimumVesuMovementAmount, minimumExtendedRetriesDelayForOrderStatus, minimumExtendedPriceDifferenceForSwapOpen, maximumExtendedPriceDifferenceForSwapClosing),
1380
+ ]
1381
+ }
1382
+
1383
+
1384
+
1385
+ function getStrategySettingsVesuExtended(lstSymbol: string, underlyingSymbol: string, addresses: VesuExtendedStrategySettings, isPreview: boolean = false, isLST: boolean, extendedBackendUrl: string, extendedApiKey: string, vaultIdExtended: number, minimumExtendedMovementAmount: number, minimumVesuMovementAmount: number, minimumExtendedRetriesDelayForOrderStatus: number, minimumExtendedPriceDifferenceForSwapOpen: number, maximumExtendedPriceDifferenceForSwapClosing: number): IStrategyMetadata<VesuExtendedStrategySettings> {
1386
+ return {
1387
+ name: `Extended Test ${underlyingSymbol}`,
1388
+ description: getDescription(lstSymbol, underlyingSymbol),
1389
+ address: addresses.vaultAddress,
1390
+ launchBlock: 0,
1391
+ type: 'Other',
1392
+ depositTokens: [Global.getDefaultTokens().find(token => token.symbol === underlyingSymbol)!],
1393
+ additionalInfo: getLooperSettings(lstSymbol, underlyingSymbol, addresses, VesuPools.Re7USDCPrime, extendedBackendUrl, extendedApiKey, vaultIdExtended, minimumExtendedMovementAmount, minimumVesuMovementAmount, minimumExtendedRetriesDelayForOrderStatus, minimumExtendedPriceDifferenceForSwapOpen, maximumExtendedPriceDifferenceForSwapClosing),
1394
+ risk: {
1395
+ riskFactor: _riskFactor,
1396
+ netRisk:
1397
+ _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
1398
+ _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
1399
+ notARisks: getNoRiskTags(_riskFactor)
1400
+ },
1401
+ auditUrl: AUDIT_URL,
1402
+ protocols: [Protocols.ENDUR, Protocols.VESU],
1403
+ maxTVL: Web3Number.fromWei(0, 18),
1404
+ contractDetails: getContractDetails(addresses),
1405
+ faqs: getFAQs(lstSymbol, underlyingSymbol, isLST),
1406
+ investmentSteps: getInvestmentSteps(lstSymbol, underlyingSymbol),
1407
+ isPreview: isPreview,
1408
+ 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.'
1409
+ }
1410
+ }