@strkfarm/sdk 1.2.0 → 2.0.0-dev-strategy2.1

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