@strkfarm/sdk 2.0.0-dev.3 → 2.0.0-dev.30

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 (75) hide show
  1. package/dist/cli.js +190 -36
  2. package/dist/cli.mjs +188 -34
  3. package/dist/index.browser.global.js +78478 -45620
  4. package/dist/index.browser.mjs +19583 -9901
  5. package/dist/index.d.ts +3763 -1424
  6. package/dist/index.js +20980 -11063
  7. package/dist/index.mjs +20948 -11087
  8. package/package.json +1 -1
  9. package/src/data/avnu.abi.json +840 -0
  10. package/src/data/ekubo-price-fethcer.abi.json +265 -0
  11. package/src/dataTypes/_bignumber.ts +13 -4
  12. package/src/dataTypes/bignumber.browser.ts +6 -1
  13. package/src/dataTypes/bignumber.node.ts +5 -1
  14. package/src/dataTypes/index.ts +3 -2
  15. package/src/dataTypes/mynumber.ts +141 -0
  16. package/src/global.ts +76 -41
  17. package/src/index.browser.ts +2 -1
  18. package/src/interfaces/common.tsx +175 -3
  19. package/src/modules/ExtendedWrapperSDk/types.ts +28 -5
  20. package/src/modules/ExtendedWrapperSDk/wrapper.ts +275 -59
  21. package/src/modules/apollo-client-config.ts +28 -0
  22. package/src/modules/avnu.ts +4 -4
  23. package/src/modules/ekubo-pricer.ts +79 -0
  24. package/src/modules/ekubo-quoter.ts +48 -30
  25. package/src/modules/erc20.ts +17 -0
  26. package/src/modules/harvests.ts +43 -29
  27. package/src/modules/pragma.ts +23 -8
  28. package/src/modules/pricer-from-api.ts +156 -15
  29. package/src/modules/pricer-lst.ts +1 -1
  30. package/src/modules/pricer.ts +40 -4
  31. package/src/modules/pricerBase.ts +2 -1
  32. package/src/node/deployer.ts +36 -1
  33. package/src/node/pricer-redis.ts +2 -1
  34. package/src/strategies/base-strategy.ts +78 -10
  35. package/src/strategies/ekubo-cl-vault.tsx +906 -347
  36. package/src/strategies/factory.ts +159 -0
  37. package/src/strategies/index.ts +7 -1
  38. package/src/strategies/registry.ts +239 -0
  39. package/src/strategies/sensei.ts +335 -7
  40. package/src/strategies/svk-strategy.ts +97 -27
  41. package/src/strategies/types.ts +4 -0
  42. package/src/strategies/universal-adapters/adapter-utils.ts +2 -1
  43. package/src/strategies/universal-adapters/avnu-adapter.ts +180 -265
  44. package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
  45. package/src/strategies/universal-adapters/common-adapter.ts +206 -203
  46. package/src/strategies/universal-adapters/extended-adapter.ts +490 -316
  47. package/src/strategies/universal-adapters/index.ts +11 -8
  48. package/src/strategies/universal-adapters/svk-troves-adapter.ts +364 -0
  49. package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
  50. package/src/strategies/universal-adapters/usdc<>usdce-adapter.ts +200 -0
  51. package/src/strategies/universal-adapters/vesu-adapter.ts +120 -82
  52. package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +476 -0
  53. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1067 -704
  54. package/src/strategies/universal-adapters/vesu-position-common.ts +251 -0
  55. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
  56. package/src/strategies/universal-lst-muliplier-strategy.tsx +397 -204
  57. package/src/strategies/universal-strategy.tsx +1426 -1173
  58. package/src/strategies/vesu-extended-strategy/services/executionService.ts +2233 -0
  59. package/src/strategies/vesu-extended-strategy/services/extended-vesu-state-manager.ts +4087 -0
  60. package/src/strategies/vesu-extended-strategy/services/ltv-imbalance-rebalance-math.ts +783 -0
  61. package/src/strategies/vesu-extended-strategy/services/operationService.ts +38 -16
  62. package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +88 -0
  63. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +1 -0
  64. package/src/strategies/vesu-extended-strategy/utils/constants.ts +5 -6
  65. package/src/strategies/vesu-extended-strategy/utils/helper.ts +259 -103
  66. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +688 -817
  67. package/src/strategies/vesu-rebalance.tsx +255 -152
  68. package/src/utils/cacheClass.ts +11 -2
  69. package/src/utils/health-factor-math.ts +4 -1
  70. package/src/utils/index.ts +3 -1
  71. package/src/utils/logger.browser.ts +22 -4
  72. package/src/utils/logger.node.ts +259 -24
  73. package/src/utils/starknet-call-parser.ts +1036 -0
  74. package/src/utils/strategy-utils.ts +61 -0
  75. package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
@@ -3,25 +3,43 @@ import {
3
3
  IConfig,
4
4
  IStrategyMetadata,
5
5
  TokenInfo,
6
+ AuditStatus,
7
+ SourceCodeType,
8
+ AccessControlType,
9
+ InstantWithdrawalVault,
10
+ VaultType,
11
+ VaultPosition,
6
12
  } from "@/interfaces";
7
13
  import {
8
14
  UNIVERSAL_MANAGE_IDS,
9
15
  UniversalStrategySettings,
10
16
  } from "../universal-strategy";
11
- import { calculateExtendedLevergae } from "./utils/helper";
17
+ import {
18
+ ExtendedSVKVesuStateManager,
19
+ StateManagerConfig,
20
+ } from "./services/extended-vesu-state-manager";
21
+ import { ExecutionService, ExecutionConfig } from "./services/executionService";
12
22
  import { logger } from "@/utils";
13
23
  import { AUDIT_URL } from "../universal-lst-muliplier-strategy";
14
24
  import { getNoRiskTags } from "@/interfaces";
15
25
  import { _riskFactor } from "../universal-lst-muliplier-strategy";
16
- import { BUFFER_USDC_IN_WITHDRAWAL, EXTENDED_QTY_PRECISION, LIMIT_BALANCE, 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";
26
+ import {
27
+ LIMIT_BALANCE,
28
+ USDC_TOKEN_DECIMALS,
29
+ WALLET_ADDRESS,
30
+ WBTC_TOKEN_DECIMALS,
31
+ } from "./utils/constants";
32
+ import { ExecutionCallback } from "./types/transaction-metadata";
17
33
  import { PricerBase } from "@/modules/pricerBase";
18
34
  import { ContractAddr, Web3Number } from "@/dataTypes";
19
35
  import { Global } from "@/global";
20
36
  import { ERC20 } from "@/modules";
21
- import { Balance, OrderSide } from "@/modules/ExtendedWrapperSDk";
22
37
  import { Protocols } from "@/interfaces";
23
38
  import { MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP } from "./utils/constants";
24
- import { getInvestmentSteps, getFAQs } from "../universal-lst-muliplier-strategy";
39
+ import {
40
+ getInvestmentSteps,
41
+ getFAQs,
42
+ } from "../universal-lst-muliplier-strategy";
25
43
  import { getContractDetails } from "../universal-strategy";
26
44
  import { highlightTextWithLinks } from "@/interfaces";
27
45
  import { PositionInfo } from "../universal-adapters";
@@ -31,12 +49,11 @@ import { VesuPools } from "../universal-adapters";
31
49
  import {
32
50
  BaseAdapterConfig,
33
51
  CommonAdapter,
34
- UnusedBalanceAdapter,
52
+ TokenTransferAdapter,
53
+ VesuModifyPositionAdapter,
35
54
  VesuMultiplyAdapter,
36
55
  } from "../universal-adapters";
37
- import { Operations } from "./services/operationService";
38
56
  import {
39
- AVNU_EXCHANGE,
40
57
  AVNU_QUOTE_URL,
41
58
  AVNU_MIDDLEWARE,
42
59
  EXTENDED_CONTRACT,
@@ -45,18 +62,10 @@ import { PricerFromApi } from "@/modules";
45
62
  import { ExtendedAdapter } from "../universal-adapters/extended-adapter";
46
63
  import { SVKStrategy } from "../svk-strategy";
47
64
  import { AvnuAdapter } from "../universal-adapters/avnu-adapter";
48
- import {
49
- calculateAmountDistribution,
50
- calculateAmountDistributionForWithdrawal,
51
- calculateVesuLeverage,
52
- calculateVesUPositionSizeGivenExtended
53
- } from "./utils/helper";
54
65
  import { SingleTokenInfo } from "../base-strategy";
55
- import { Call } from "starknet";
66
+ import { VesuConfig } from "./utils/config.runtime";
56
67
 
57
-
58
- export interface VesuExtendedStrategySettings
59
- extends UniversalStrategySettings {
68
+ export interface VesuExtendedStrategySettings extends UniversalStrategySettings {
60
69
  underlyingToken: TokenInfo;
61
70
  borrowable_assets: TokenInfo[];
62
71
  targetHealthFactor: number;
@@ -64,36 +73,80 @@ export interface VesuExtendedStrategySettings
64
73
  minHealthFactor: number;
65
74
  aumOracle: ContractAddr;
66
75
  minimumWBTCDifferenceForAvnuSwap: number;
76
+ walletAddress: string;
67
77
  }
68
78
 
69
79
  export class VesuExtendedMultiplierStrategy<
70
- S extends VesuExtendedStrategySettings
71
- >
72
- extends SVKStrategy<S>
73
- implements Operations {
80
+ S extends VesuExtendedStrategySettings,
81
+ > extends SVKStrategy<S> {
82
+ public wbtcToken: TokenInfo;
83
+ public usdcToken: TokenInfo;
84
+ public readonly stateManager: ExtendedSVKVesuStateManager;
74
85
 
75
86
  constructor(
76
87
  config: IConfig,
77
88
  pricer: PricerBase,
78
- metadata: IStrategyMetadata<S>
89
+ metadata: IStrategyMetadata<S>,
79
90
  ) {
80
91
  super(config, pricer, metadata);
81
92
  this.metadata.additionalInfo.adapters.forEach((adapter) => {
82
93
  adapter.adapter.config.networkConfig = this.config;
83
94
  adapter.adapter.config.pricer = this.pricer;
84
- if ((adapter.adapter as VesuMultiplyAdapter).vesuAdapter) {
85
- (adapter.adapter as VesuMultiplyAdapter).vesuAdapter.networkConfig =
95
+ if ((adapter.adapter as any)._vesuAdapter) {
96
+ (adapter.adapter as any)._vesuAdapter.networkConfig =
86
97
  this.config;
87
- (adapter.adapter as VesuMultiplyAdapter).vesuAdapter.pricer =
98
+ (adapter.adapter as any)._vesuAdapter.pricer =
88
99
  this.pricer;
89
100
  }
90
101
  });
102
+ // todo check if this can be generalized
103
+ this.wbtcToken = Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!;
104
+ this.usdcToken = this.metadata.additionalInfo.borrowable_assets[0]!;
105
+ this.stateManager = this._initializeStateManager();
106
+ }
107
+
108
+ /**
109
+ * Extracts the required adapters from metadata and constructs the
110
+ * state manager used by shouldInvest / handleWithdraw.
111
+ */
112
+ private _initializeStateManager(): ExtendedSVKVesuStateManager {
113
+ const vesuAdapters = this.metadata.additionalInfo.adapters
114
+ .filter((a) => a.adapter.name === VesuMultiplyAdapter.name)
115
+ .map((a) => a.adapter as VesuMultiplyAdapter);
116
+
117
+ const extendedAdapterEntry = this.metadata.additionalInfo.adapters.find(
118
+ (a) => a.adapter.name === ExtendedAdapter.name,
119
+ );
120
+ if (!extendedAdapterEntry) {
121
+ throw new Error(
122
+ `${this.getTag()} ExtendedAdapter not found in adapters — cannot initialise state manager.`,
123
+ );
124
+ }
125
+
126
+ const stateManagerConfig: StateManagerConfig = {
127
+ pricer: this.pricer,
128
+ networkConfig: this.config,
129
+ vesuAdapters,
130
+ extendedAdapter: extendedAdapterEntry.adapter as ExtendedAdapter,
131
+ vaultAllocator: this.metadata.additionalInfo.vaultAllocator,
132
+ walletAddress: this.metadata.additionalInfo.walletAddress,
133
+ assetToken: this.asset(),
134
+ usdcToken: this.usdcToken,
135
+ collateralToken: this.wbtcToken,
136
+ limitBalanceBufferFactor: LIMIT_BALANCE,
137
+ };
138
+
139
+ return new ExtendedSVKVesuStateManager(stateManagerConfig);
91
140
  }
92
141
 
93
142
  getTag() {
94
143
  return `${VesuExtendedMultiplierStrategy.name}:${this.metadata.name}`;
95
144
  }
96
145
 
146
+ /**
147
+ * Fetches current WBTC (collateral) and USDC (debt) prices from the pricer.
148
+ * Validates that both prices are finite and positive, throwing if not.
149
+ */
97
150
  async getAssetPrices() {
98
151
  const wbtcToken = Global.getDefaultTokens().find(
99
152
  (token) => token.symbol === "WBTC"
@@ -103,767 +156,157 @@ export class VesuExtendedMultiplierStrategy<
103
156
  )!;
104
157
  const collateralPrice = await this.pricer.getPrice(wbtcToken.symbol);
105
158
  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
159
 
160
+ if (!Number.isFinite(collateralPrice.price) || collateralPrice.price <= 0) {
161
+ throw new Error(
162
+ `${this.getTag()} Invalid collateralPrice: ${collateralPrice.price}. Expected a finite, positive number.`
163
+ );
164
+ }
165
+ if (!Number.isFinite(debtPrice.price) || debtPrice.price <= 0) {
166
+ throw new Error(
167
+ `${this.getTag()} Invalid debtPrice: ${debtPrice.price}. Expected a finite, positive number.`
168
+ );
169
+ }
131
170
 
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
- };
171
+ return { collateralPrice, debtPrice };
147
172
  }
148
173
 
149
- async getVesuAdapter(): Promise<VesuMultiplyAdapter | null> {
174
+ async getVesuMultiplyAdapter(): Promise<VesuMultiplyAdapter> {
150
175
  const vesuAdapter = this.metadata.additionalInfo.adapters.find(
151
- (adapter) => adapter.adapter.name === VesuMultiplyAdapter.name
176
+ (adapter) => adapter.adapter.name === VesuMultiplyAdapter.name,
152
177
  );
153
178
  if (!vesuAdapter) {
154
- logger.error("vesu adapter not found");
155
- return null;
179
+ throw new Error(
180
+ `${this.getTag()} Vesu adapter not configured in metadata.`
181
+ );
156
182
  }
157
183
  return vesuAdapter.adapter as VesuMultiplyAdapter;
158
184
  }
159
185
 
160
- async getAvnuAdapter(): Promise<AvnuAdapter | null> {
161
- const avnuAdapter = this.metadata.additionalInfo.adapters.find(
162
- (adapter) => adapter.adapter.name === AvnuAdapter.name
186
+ async getVesuModifyPositionAdapter(): Promise<VesuModifyPositionAdapter> {
187
+ const vesuModifyPositionAdapter = this.metadata.additionalInfo.adapters.find(
188
+ (adapter) => adapter.adapter.name === VesuModifyPositionAdapter.name,
163
189
  );
164
- if (!avnuAdapter) {
165
- logger.error("avnu adapter not found");
166
- return null;
190
+ if (!vesuModifyPositionAdapter) {
191
+ throw new Error(
192
+ `${this.getTag()} Vesu modify position adapter not configured in metadata.`,
193
+ );
167
194
  }
168
- return avnuAdapter.adapter as AvnuAdapter;
195
+ return vesuModifyPositionAdapter.adapter as VesuModifyPositionAdapter;
169
196
  }
170
197
 
171
- async getExtendedAdapter(): Promise<ExtendedAdapter | null> {
172
- const extendedAdapter = this.metadata.additionalInfo.adapters.find(
173
- (adapter) => adapter.adapter.name === ExtendedAdapter.name
198
+ async getUsdcTransferAdapter(): Promise<TokenTransferAdapter> {
199
+ const usdcTransferAdapter = this.metadata.additionalInfo.adapters.find(
200
+ (adapter) => adapter.adapter.name === TokenTransferAdapter.name,
174
201
  );
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 })
202
+ if (!usdcTransferAdapter) {
203
+ throw new Error(
204
+ `${this.getTag()} Usdc transfer adapter not configured in metadata.`
204
205
  );
205
- return [approveCall, transferCall, call];
206
- } catch (err) {
207
- logger.error(`error moving assets to vault allocator: ${err}`);
208
- return [];
209
206
  }
207
+ return usdcTransferAdapter.adapter as TokenTransferAdapter;
210
208
  }
211
209
 
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
- const vesuAdapter = await this.getVesuAdapter();
223
- const extendedAdapter = await this.getExtendedAdapter();
224
- if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
225
- logger.error(
226
- `vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
227
- );
228
- return {
229
- shouldInvest: false,
230
- vesuAmount: new Web3Number(0, 0),
231
- extendedAmount: new Web3Number(0, 0),
232
- extendedLeverage: 0,
233
- collateralPrice: 0,
234
- debtPrice: 0,
235
- vesuLeverage: 0,
236
- };
237
- }
238
- const balance = await this.getUnusedBalance();
239
- const usdcBalanceOnExtended = await extendedAdapter.getExtendedDepositAmount();
240
- /** The LIMIT_BALANCE is the bffer amount to keep in the investing Cycle */
241
- const amountToInvest = balance.amount.plus(usdcBalanceOnExtended?.availableForWithdrawal ?? 0).minus(LIMIT_BALANCE);
242
-
243
- if (amountToInvest.lessThan(0)) {
244
- return {
245
- shouldInvest: false,
246
- vesuAmount: new Web3Number(0, 0),
247
- extendedAmount: new Web3Number(0, 0),
248
- extendedLeverage: 0,
249
- collateralPrice: 0,
250
- debtPrice: 0,
251
- vesuLeverage: 0,
252
- };
253
- }
254
-
255
- const extendedPositon = await extendedAdapter.getAllOpenPositions();
256
- if (!extendedPositon) {
257
- logger.error("error getting extended position to decide move assets");
258
- return {
259
- shouldInvest: false,
260
- vesuAmount: new Web3Number(0, 0),
261
- extendedAmount: new Web3Number(0, 0),
262
- extendedLeverage: 0,
263
- collateralPrice: 0,
264
- debtPrice: 0,
265
- vesuLeverage: 0,
266
- };
267
- }
268
- const { collateralTokenAmount } =
269
- await vesuAdapter.vesuAdapter.getAssetPrices();
270
-
271
- const {
272
- collateralPrice,
273
- debtPrice
274
- } = await this.getAssetPrices();
275
- const { vesu_amount, extended_amount, extended_leverage, vesu_leverage } =
276
- await calculateAmountDistribution(
277
- amountToInvest.toNumber(),
278
- extendedAdapter.client,
279
- extendedAdapter.config.extendedMarketName,
280
- collateralPrice.price,
281
- debtPrice.price,
282
- collateralTokenAmount,
283
- extendedPositon
284
- );
285
- if (
286
- !vesu_amount ||
287
- !extended_amount ||
288
- !extended_leverage ||
289
- !vesu_leverage
290
- ) {
291
- logger.error(
292
- `Not enough amount to invest: vesu_amount=${vesu_amount}, extended_amount=${extended_amount}`
293
- );
294
- return {
295
- shouldInvest: false,
296
- vesuAmount: new Web3Number(0, 0),
297
- extendedAmount: new Web3Number(0, 0),
298
- extendedLeverage: 0,
299
- collateralPrice: 0,
300
- debtPrice: 0,
301
- vesuLeverage: 0,
302
- };
303
- }
304
- return {
305
- shouldInvest: true,
306
- vesuAmount: vesu_amount,
307
- extendedAmount: extended_amount,
308
- extendedLeverage: extended_leverage,
309
- vesuLeverage: vesu_leverage,
310
- collateralPrice: collateralPrice.price,
311
- debtPrice: debtPrice.price,
312
- };
313
- } catch (err) {
314
- logger.error(`error deciding invest: ${err}`);
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
-
327
- async shouldMoveAssets(extendedAmount: Web3Number, vesuAmount: Web3Number): Promise<Call[]> {
328
- try {
329
- const vesuAdapter = await this.getVesuAdapter();
330
- const extendedAdapter = await this.getExtendedAdapter();
331
- let calls: Call[] = [];
332
- if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
333
- logger.error(
334
- `vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
335
- );
336
- return calls;
337
- }
338
- if (extendedAmount.lessThan(0)) {
339
- try {
340
- const { calls: extendedCalls, status: extendedStatus } = await this.moveAssets(
341
- {
342
- to: Protocols.VAULT.name,
343
- from: Protocols.EXTENDED.name,
344
- amount: extendedAmount.abs(),
345
- },
346
- extendedAdapter,
347
- vesuAdapter
348
- );
349
- //If withdrawal succesfull, then do further
350
- if (extendedStatus) {
351
- calls.push(...extendedCalls);
352
- } else {
353
- return [];
354
- }
355
- } catch (err) {
356
- logger.error(`Failed moving assets to vault: ${err}`);
357
- }
358
- }
359
-
360
- if (vesuAmount.lessThan(0)) {
361
- try {
362
- const { calls: vesuCalls, status: vesuStatus } = await this.moveAssets(
363
- {
364
- to: Protocols.EXTENDED.name,
365
- from: Protocols.VESU.name,
366
- amount: vesuAmount.abs(),
367
- },
368
- extendedAdapter,
369
- vesuAdapter
370
- );
371
- calls.push(...vesuCalls);
372
- if (!vesuStatus) {
373
- return [];
374
- }
375
- } catch (err) {
376
- logger.error(`Failed moving assets to vault: ${err}`);
377
- }
378
- }
379
-
380
- const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
381
- if (!extendedHoldings) {
382
- logger.error(`error getting extended holdings: ${extendedHoldings}`);
383
- return calls;
384
- }
385
- const usdcAmountInWallet = (await this.getUnusedBalance()).amount;
386
- const usdcAmountOnExtended = parseFloat(
387
- extendedHoldings.availableForTrade
210
+ async getAvnuAdapter(): Promise<AvnuAdapter> {
211
+ const avnuAdapter = this.metadata.additionalInfo.adapters.find(
212
+ (adapter) => adapter.adapter.name === AvnuAdapter.name,
213
+ );
214
+ if (!avnuAdapter) {
215
+ throw new Error(
216
+ `${this.getTag()} Avnu adapter not configured in metadata.`
388
217
  );
389
- if (extendedAmount.plus(BUFFER_USDC_IN_WITHDRAWAL).minus(usdcAmountOnExtended).greaterThan(0)) {
390
- //move assets to extended
391
- try {
392
- const { calls: extendedCalls } = await this.moveAssets(
393
- {
394
- to: Protocols.EXTENDED.name,
395
- from: Protocols.VAULT.name,
396
- amount: extendedAmount.plus(BUFFER_USDC_IN_WITHDRAWAL).minus(usdcAmountOnExtended),
397
- },
398
- extendedAdapter,
399
- vesuAdapter
400
- );
401
- calls.push(...extendedCalls);
402
- } catch (err) {
403
- logger.error(`Failed moving assets to extended: ${err}`);
404
- }
405
- }
406
- if (vesuAmount.minus(usdcAmountInWallet).greaterThan(0)) {
407
- //move assets to vesu
408
- try {
409
- const { calls: vesuCalls, status: vesuStatus } = await this.moveAssets(
410
- {
411
- to: Protocols.VAULT.name,
412
- from: Protocols.EXTENDED.name,
413
- amount: vesuAmount.minus(usdcAmountInWallet),
414
- },
415
- extendedAdapter,
416
- vesuAdapter
417
- );
418
- if (!vesuStatus) {
419
- return [];
420
- }
421
- calls.push(...vesuCalls);
422
- } catch (err) {
423
- logger.error(`Failed moving assets to vault: ${err}`);
424
- }
425
- }
426
- return calls;
427
- } catch (err) {
428
- logger.error(`Failed moving assets to vesu: ${err}`);
429
- return [];
430
- }
431
- }
432
-
433
- async moveAssets(
434
- params: {
435
- amount: Web3Number;
436
- from: string;
437
- to: string;
438
- },
439
- extendedAdapter: ExtendedAdapter,
440
- vesuAdapter: VesuMultiplyAdapter
441
- ): Promise<{
442
- calls: Call[];
443
- status: boolean;
444
- }> {
445
- try {
446
- const avnuAdapter = await this.getAvnuAdapter();
447
- if (!avnuAdapter) {
448
- logger.error(`avnu adapter not found: ${avnuAdapter}`);
449
- return {
450
- calls: [],
451
- status: false
452
- };
453
- }
454
- logger.info(`moveAssets params, ${JSON.stringify(params)}`);
455
- const collateralToken = vesuAdapter.config.supportedPositions[0].asset;
456
- const {
457
- collateralPrice,
458
- } = await this.getAssetPrices();
459
-
460
- if (params.to === Protocols.EXTENDED.name && params.from === Protocols.VAULT.name) {
461
- const proofsInfo = extendedAdapter.getProofs(
462
- true,
463
- this.getMerkleTree()
464
- );
465
- const calls = [];
466
- const proofGroups = proofsInfo.proofs;
467
- const call = this.getManageCall(
468
- proofGroups,
469
- await proofsInfo.callConstructor({ amount: params.amount })
470
- );
471
- calls.push(call);
472
- return {
473
- calls: [call],
474
- status: true
475
- };
476
- } else if (params.to === Protocols.VAULT.name && params.from === Protocols.EXTENDED.name) {
477
- const extendedLeverage = calculateExtendedLevergae();
478
- const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
479
- if (!extendedHoldings) {
480
- logger.error(`error getting extended holdings: ${extendedHoldings}`);
481
- return {
482
- calls: [],
483
- status: false
484
- };
485
- }
486
- const extendedHoldingAmount = new Web3Number(
487
- extendedHoldings.availableForWithdrawal,
488
- USDC_TOKEN_DECIMALS
489
- );
490
- logger.info(`${VesuExtendedMultiplierStrategy.name}::moveAssets extendedHoldingAmount: ${extendedHoldingAmount.toNumber()}`);
491
- if (params.amount.abs().greaterThan(extendedHoldingAmount)) {
492
- const leftAmountAfterWithdrawalAmountInAccount = params.amount.abs().minus(extendedHoldingAmount);
493
- logger.info(`${VesuExtendedMultiplierStrategy.name}::moveAssets leftAmountAfterWithdrawalAmountInAccount: ${leftAmountAfterWithdrawalAmountInAccount.toNumber()}`);
494
- const btcAmount = leftAmountAfterWithdrawalAmountInAccount.dividedBy(collateralPrice.price);
495
- const openLongPosition = btcAmount.multipliedBy(3).greaterThan(MINIMUM_EXTENDED_POSITION_SIZE) ? await extendedAdapter.createOrder(
496
- extendedLeverage.toString(),
497
- btcAmount.toNumber(),
498
- OrderSide.BUY
499
- ) : await extendedAdapter.createOrder(
500
- extendedLeverage.toString(),
501
- 0.000035, // just in case amount falls short then we need to create a withdrawal
502
- OrderSide.BUY
503
- )
504
- if (!openLongPosition) {
505
- logger.error(`error opening long position: ${openLongPosition}`);
506
- return {
507
- calls: [],
508
- status: false
509
- };
510
- }
511
- await new Promise(resolve => setTimeout(resolve, 5000));
512
- }
513
- const withdrawalFromExtended =
514
- await extendedAdapter.withdrawFromExtended(params.amount);
515
- if (withdrawalFromExtended) {
516
- /**
517
- * We need to move assets from my wallet back to vault contract
518
- */
519
- const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
520
- logger.info(`extendedHoldings after withdrawal ${extendedHoldings}`);
521
- await new Promise(resolve => setTimeout(resolve, 10000));
522
- const calls = await this.moveAssetsToVaultAllocator(params.amount, extendedAdapter);
523
- if (calls.length > 0) {
524
- return {
525
- calls: calls,
526
- status: true
527
- };
528
- }
529
- } else {
530
- logger.error("withdrawal from extended failed");
531
- return {
532
- calls: [],
533
- status: false
534
- };
535
- }
536
- } else if (params.to === Protocols.VAULT.name && params.from === Protocols.VESU.name) {
537
- //withdraw from vesu
538
- const vesuAmountInBTC = new Web3Number(
539
- params.amount.dividedBy(collateralPrice.price).toNumber(),
540
- collateralToken.decimals
541
- );
542
- const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
543
- const calls = [];
544
- const proofGroups = proofsInfo.proofs;
545
- const call = this.getManageCall(
546
- proofGroups,
547
- await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
548
- );
549
- calls.push(call);
550
- const swapProofsInfo = avnuAdapter.getProofs(false, this.getMerkleTree());
551
- const swapProofGroups = swapProofsInfo.proofs;
552
- const swapCall = this.getManageCall(
553
- swapProofGroups,
554
- await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
555
- );
556
- calls.push(swapCall);
557
- return {
558
- calls: calls,
559
- status: true
560
- };
561
- } else if (params.to === Protocols.EXTENDED.name && params.from === Protocols.VESU.name) {
562
- const vesuAmountInBTC = new Web3Number(
563
- params.amount.dividedBy(collateralPrice.price).toNumber(),
564
- collateralToken.decimals
565
- );
566
- const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
567
- const calls = [];
568
- const proofGroups = proofsInfo.proofs;
569
- const call = this.getManageCall(
570
- proofGroups,
571
- await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
572
- );
573
- calls.push(call);
574
- const swapProofsInfo = avnuAdapter.getProofs(false, this.getMerkleTree());
575
- const swapProofGroups = swapProofsInfo.proofs;
576
- const swapCall = this.getManageCall(
577
- swapProofGroups,
578
- await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
579
- );
580
- calls.push(swapCall);
581
- const proofsInfoDeposit = extendedAdapter.getProofs(
582
- true,
583
- this.getMerkleTree()
584
- );
585
- //Deposit Amount would still be in usdc
586
- const proofGroupsDeposit = proofsInfoDeposit.proofs;
587
- const callDeposit = this.getManageCall(
588
- proofGroupsDeposit,
589
- await proofsInfoDeposit.callConstructor({ amount: params.amount })
590
- );
591
- calls.push(callDeposit);
592
- return {
593
- calls: calls,
594
- status: true
595
- };
596
- }
597
- return {
598
- calls: [],
599
- status: false
600
- };
601
- } catch (err) {
602
- logger.error(`error moving assets: ${err}`);
603
- return {
604
- calls: [],
605
- status: false
606
- };
607
218
  }
219
+ return avnuAdapter.adapter as AvnuAdapter;
608
220
  }
609
221
 
610
- async handleDeposit(): Promise<{
611
- extendedAmountInBTC: Web3Number,
612
- calls: Call[]
613
- }> {
614
- try {
615
- const vesuAdapter = await this.getVesuAdapter();
616
- const extendedAdapter = await this.getExtendedAdapter();
617
- const avnuAdapter = await this.getAvnuAdapter();
618
- if (
619
- !vesuAdapter ||
620
- !extendedAdapter ||
621
- !extendedAdapter.client ||
622
- !avnuAdapter
623
- ) {
624
- logger.error(
625
- "vesu or extended adapter not found",
626
- vesuAdapter,
627
- extendedAdapter
628
- );
629
- return {
630
- extendedAmountInBTC: new Web3Number(0, 0),
631
- calls: [],
632
- };
633
- }
634
- const extendedLeverage = calculateExtendedLevergae();
635
- const isPriceDifferenceBetweenAvnuAndExtended = await this.checkPriceDifferenceBetweenAvnuAndExtended(extendedAdapter, vesuAdapter, avnuAdapter);
636
- if (!isPriceDifferenceBetweenAvnuAndExtended) {
637
- logger.error("price difference between avnu and extended doesn't fit the range");
638
- return {
639
- extendedAmountInBTC: new Web3Number(0, 0),
640
- calls: [],
641
- };
642
- }
643
- const position = await extendedAdapter.getAllOpenPositions();
644
- if (!position) {
645
- logger.error("error getting extended position", position);
646
- return {
647
- extendedAmountInBTC: new Web3Number(0, 0),
648
- calls: [],
649
- };
650
- }
651
- const extendedPositionValue = position.length > 0 ? parseFloat(position[0].value) : 0;
652
- const BUFFER_AMOUNT_IN_AVAILABLE_FOR_TRADE = BUFFER_USDC_IN_WITHDRAWAL;
653
- const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
654
- if (!extendedHoldings) {
655
- logger.error(`error getting extended holdings: ${extendedHoldings}`);
656
- return {
657
- extendedAmountInBTC: new Web3Number(0, 0),
658
- calls: [],
659
- };
660
- }
661
- const extendedHoldingAmount = new Web3Number(
662
- extendedHoldings.availableForTrade,
663
- USDC_TOKEN_DECIMALS
664
- );
665
- const {
666
- collateralTokenAmount,
667
- } = await vesuAdapter.vesuAdapter.getAssetPrices();
668
- const { collateralPrice } = await this.getAssetPrices();
669
- const { vesuAmountInBTC, extendedAmountInBTC } = calculateVesUPositionSizeGivenExtended(
670
- extendedPositionValue,
671
- extendedHoldingAmount.minus(BUFFER_AMOUNT_IN_AVAILABLE_FOR_TRADE),
672
- collateralTokenAmount,
673
- collateralPrice.price
222
+ async getExtendedAdapter(): Promise<ExtendedAdapter> {
223
+ const extendedAdapter = this.metadata.additionalInfo.adapters.find(
224
+ (adapter) => adapter.adapter.name === ExtendedAdapter.name,
225
+ );
226
+ if (!extendedAdapter) {
227
+ throw new Error(
228
+ `${this.getTag()} Extended adapter not configured in metadata.`
674
229
  );
675
- logger.info(`vesuAmountInBTC ${vesuAmountInBTC}, extendedAmountInBTC ${extendedAmountInBTC}`);
676
-
677
- let calls: Call[] = [];
678
- if (vesuAmountInBTC.greaterThan(MINIMUM_EXTENDED_POSITION_SIZE)) {
679
- const proofsInfo = vesuAdapter.getProofs(true, this.getMerkleTree());
680
- const proofGroups = proofsInfo.proofs;
681
- const call = this.getManageCall(
682
- proofGroups,
683
- await proofsInfo.callConstructor({
684
- amount: vesuAmountInBTC,
685
- })
686
- );
687
- const { amount: wbtcAmountInVaultAllocator } = await this.getUnusedBalanceWBTC();
688
- if (wbtcAmountInVaultAllocator.lessThan(vesuAmountInBTC)) {
689
- const swapProofsInfo = avnuAdapter.getProofs(true, this.getMerkleTree());
690
- const swapProofGroups = swapProofsInfo.proofs;
691
- const swapCall = this.getManageCall(
692
- swapProofGroups,
693
- await swapProofsInfo.callConstructor({
694
- amount: vesuAmountInBTC,
695
- })
696
- );
697
- calls.push(swapCall);
698
- }
699
- calls.push(call);
700
- }
701
- const shortPosition = extendedAmountInBTC.multipliedBy(3).abs().greaterThan(MINIMUM_EXTENDED_POSITION_SIZE) ? await extendedAdapter.createOrder(
702
- extendedLeverage.toString(),
703
- extendedAmountInBTC.toNumber(),
704
- OrderSide.SELL
705
- ) : null;
706
- if (!shortPosition && extendedAmountInBTC.multipliedBy(3).abs().greaterThan(MINIMUM_EXTENDED_POSITION_SIZE)) {
707
- logger.error(`error creating short position thus no position to be opened on vesu: ${shortPosition}`);
708
- return {
709
- extendedAmountInBTC: new Web3Number(0, 0),
710
- calls: [],
711
- };
712
- }
713
- return {
714
- extendedAmountInBTC: extendedAmountInBTC,
715
- calls: calls,
716
- };
717
- } catch (err) {
718
- logger.error(`error handling deposit: ${err}`);
719
- return {
720
- extendedAmountInBTC: new Web3Number(0, 0),
721
- calls: [],
722
- };;
723
- }
724
- }
725
-
726
- async checkPriceDifferenceBetweenAvnuAndExtended(extendedAdapter: ExtendedAdapter, vesuAdapter: VesuMultiplyAdapter, avnuAdapter: AvnuAdapter): Promise<boolean> {
727
- const {
728
- ask, bid
729
- } = await extendedAdapter.fetchOrderBookBTCUSDC();
730
- const price = ask.plus(bid).dividedBy(2);
731
- const btcToken = vesuAdapter.config.supportedPositions[0].asset;
732
- const btcPriceAvnu = await avnuAdapter.getPriceOfToken(btcToken.address.toString());
733
- if (!btcPriceAvnu) {
734
- logger.error(`error getting btc price avnu: ${btcPriceAvnu}`);
735
- return false;
736
230
  }
737
- const priceDifference = price.minus(btcPriceAvnu).toNumber();
738
- if (priceDifference < MAX_PRICE_DIFFERENCE_BETWEEN_AVNU_AND_EXTENDED && priceDifference > MIN_PRICE_DIFFERENCE_BETWEEN_AVNU_AND_EXTENDED) {
739
- return true;
231
+ if (!extendedAdapter.adapter || !(extendedAdapter.adapter as ExtendedAdapter).client) {
232
+ throw new Error(
233
+ `${this.getTag()} Extended adapter client not initialized.`
234
+ );
740
235
  }
741
- logger.error(`price difference between avnu and extended doesn't fit the range, priceDifference: ${priceDifference}`);
742
- return false;
236
+ return extendedAdapter.adapter as ExtendedAdapter;
743
237
  }
744
238
 
745
- async handleWithdraw(amount: Web3Number): Promise<{ calls: Call[], status: boolean }> {
746
- try {
747
- const usdcBalanceVaultAllocator = await this.getUnusedBalance()
748
- const usdcBalanceDifference = amount.plus(BUFFER_USDC_IN_WITHDRAWAL).minus(usdcBalanceVaultAllocator.usdValue);
749
- logger.info(`usdcBalanceDifference, ${usdcBalanceDifference.toNumber()}`);
750
- let calls: Call[] = [];
751
- let status: boolean = true;
752
- if (usdcBalanceDifference.lessThan(0)) {
753
- const withdrawCall = await this.getBringLiquidityCall({
754
- amount: usdcBalanceVaultAllocator.amount
755
- })
756
- logger.info("withdraw call", withdrawCall);
757
- calls.push(withdrawCall);
758
- return {
759
- calls: calls,
760
- status: true
761
- };
762
- }
763
- const vesuAdapter = await this.getVesuAdapter();
764
- const extendedAdapter = await this.getExtendedAdapter();
765
- if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
766
- status = false;
767
- logger.error(
768
- `vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
769
- );
770
- return {
771
- calls: calls,
772
- status: status
773
- };
774
- }
775
- const { collateralTokenAmount } =
776
- await vesuAdapter.vesuAdapter.getAssetPrices();
777
- const {
778
- collateralPrice
779
- } = await this.getAssetPrices();
780
- const extendedPositon = await extendedAdapter.getAllOpenPositions();
781
- if (!extendedPositon) {
782
- status = false;
783
- logger.error("error getting extended position", extendedPositon);
784
- return {
785
- calls: calls,
786
- status: status
787
- }
788
- }
789
- const amountDistributionForWithdrawal =
790
- await calculateAmountDistributionForWithdrawal(
791
- usdcBalanceDifference,
792
- collateralPrice.price,
793
- collateralTokenAmount,
794
- extendedPositon
795
- );
796
- if (!amountDistributionForWithdrawal) {
797
- status = false;
798
- logger.error(
799
- `error calculating amount distribution for withdrawal: ${amountDistributionForWithdrawal}`
800
- );
801
- return {
802
- calls: calls,
803
- status: status
804
- };
805
- }
806
- const { vesu_amount, extended_amount } = amountDistributionForWithdrawal;
239
+ /**
240
+ * Creates an ExecutionService wired to this strategy's adapters and config.
241
+ * Use with `stateManager.solve()` to get a SolveResult, then pass it to
242
+ * `executionService.execute(solveResult)` for execution.
243
+ *
244
+ * @param onExecutionEvent - Optional callback for execution lifecycle events (DB persistence, alerts, etc.)
245
+ * @param extendedAcceptableSlippageBps - Slippage for Extended limit orders (default: 10 = 0.1%)
246
+ * @param maxPriceDivergenceBps - Max price divergence between Extended and AVNU (default: 50 = 0.5%)
247
+ */
248
+ async createExecutionService(opts?: {
249
+ onExecutionEvent?: ExecutionCallback;
250
+ extendedAcceptableSlippageBps?: number;
251
+ maxPriceDivergenceBps?: number;
252
+ }): Promise<ExecutionService> {
253
+ const [
254
+ vesuMultiplyAdapter,
255
+ vesuModifyPositionAdapter,
256
+ extendedAdapter,
257
+ avnuAdapter,
258
+ usdcTransferAdapter,
259
+ ] =
260
+ await Promise.all([
261
+ this.getVesuMultiplyAdapter(),
262
+ this.getVesuModifyPositionAdapter(),
263
+ this.getExtendedAdapter(),
264
+ this.getAvnuAdapter(),
265
+ this.getUsdcTransferAdapter(),
266
+ ]);
267
+
268
+ const executionConfig: ExecutionConfig = {
269
+ networkConfig: this.config,
270
+ pricer: this.pricer,
271
+ vesuMultiplyAdapter,
272
+ vesuModifyPositionAdapter,
273
+ extendedAdapter,
274
+ avnuAdapter,
275
+ usdcTransferAdapter,
276
+ vaultAllocator: this.metadata.additionalInfo.vaultAllocator,
277
+ walletAddress: this.metadata.additionalInfo.walletAddress,
278
+ wbtcToken: this.wbtcToken,
279
+ usdcToken: this.usdcToken,
280
+ getMerkleTree: () => this.getMerkleTree(),
281
+ getManageCall: (proofs, manageCalls) => this.getManageCall(proofs, manageCalls),
282
+ getBringLiquidityCall: (params) => this.getBringLiquidityCall(params),
283
+ onExecutionEvent: opts?.onExecutionEvent,
284
+ extendedAcceptableSlippageBps: opts?.extendedAcceptableSlippageBps,
285
+ maxPriceDivergenceBps: opts?.maxPriceDivergenceBps,
286
+ };
807
287
 
808
- if (status && vesu_amount.greaterThan(0)) {
809
- const { calls: vesuCalls, status: vesuStatus } = await this.moveAssets(
810
- {
811
- amount: vesu_amount,
812
- from: Protocols.VESU.name,
813
- to: Protocols.VAULT.name,
814
- },
815
- extendedAdapter,
816
- vesuAdapter
817
- );
818
- status = vesuStatus;
819
- calls.push(...vesuCalls);
820
- }
821
- if (status && extended_amount.greaterThan(0)) {
822
- const { calls: extendedCalls, status: extendedStatus } = await this.moveAssets(
823
- {
824
- amount: extended_amount,
825
- from: Protocols.EXTENDED.name,
826
- to: Protocols.VAULT.name,
827
- },
828
- extendedAdapter,
829
- vesuAdapter
830
- );
831
- status = extendedStatus;
832
- if (status) {
833
- calls.push(...extendedCalls);
834
- } else {
835
- logger.error("error moving assets to vault: extendedStatus: ${extendedStatus}");
836
- return {
837
- calls: [],
838
- status: status
839
- };
840
- }
841
- }
842
- const withdrawCall = await this.getBringLiquidityCall({
843
- amount: amount
844
- })
845
- logger.info("withdraw call", withdrawCall);
846
- calls.push(withdrawCall);
847
- return {
848
- calls: calls,
849
- status: status
850
- };
851
- } catch (err) {
852
- logger.error(`error handling withdrawal: ${err}`);
853
- return {
854
- calls: [],
855
- status: false
856
- };
857
- }
288
+ return new ExecutionService(executionConfig);
858
289
  }
859
290
 
860
- async getAUM(): Promise<{ net: SingleTokenInfo, prevAum: Web3Number, splits: PositionInfo[] }> {
291
+ /**
292
+ * Calculates the total Assets Under Management across all adapters.
293
+ * Aggregates position values from every adapter, converts to the vault's
294
+ * base asset, and returns the net AUM along with the previous AUM snapshot
295
+ * and per-position breakdowns.
296
+ */
297
+ async getAUM(): Promise<{
298
+ net: SingleTokenInfo;
299
+ prevAum: Web3Number;
300
+ splits: PositionInfo[];
301
+ }> {
861
302
  const allPositions: PositionInfo[] = [];
862
303
  for (let adapter of this.metadata.additionalInfo.adapters) {
863
- const positions = await adapter.adapter.getPositions();
864
- allPositions.push(...positions);
304
+ let positions = await adapter.adapter.getPositions();
305
+ if (positions && positions.length > 0) {
306
+ const filteredPositions = positions;
307
+ allPositions.push(...filteredPositions);
308
+ }
865
309
  }
866
-
867
310
  const assetPrice = await this.pricer.getPrice(this.asset().symbol);
868
311
  let netAUM = new Web3Number(0, this.asset().decimals);
869
312
  for (let position of allPositions) {
@@ -874,6 +317,16 @@ export class VesuExtendedMultiplierStrategy<
874
317
  }
875
318
  }
876
319
 
320
+ // ! IMO should also include USDC in wallet.
321
+ const walletHoldings = await this.getWalletHoldings();
322
+ for (let holding of walletHoldings) {
323
+ if (holding.tokenInfo.address.eq(this.asset().address)) {
324
+ netAUM = netAUM.plus(holding.amount);
325
+ } else {
326
+ netAUM = netAUM.plus(holding.usdValue / assetPrice.price);
327
+ }
328
+ }
329
+
877
330
  const prevAum = await this.getPrevAUM();
878
331
  const realAUM: PositionInfo = {
879
332
  tokenInfo: this.asset(),
@@ -881,49 +334,249 @@ export class VesuExtendedMultiplierStrategy<
881
334
  usdValue: netAUM.toNumber() * assetPrice.price,
882
335
  apy: { apy: netAUM.toNumber() * assetPrice.price, type: APYType.BASE },
883
336
  remarks: AUMTypes.FINALISED,
884
- protocol: Protocols.NONE // just placeholder
337
+ protocol: Protocols.NONE, // just placeholder
885
338
  };
886
339
 
887
340
  const estimatedAUMDelta: PositionInfo = {
888
341
  tokenInfo: this.asset(),
889
- amount: Web3Number.fromWei('0', this.asset().decimals),
342
+ amount: Web3Number.fromWei("0", this.asset().decimals),
890
343
  usdValue: 0,
891
344
  apy: { apy: 0, type: APYType.BASE },
892
345
  remarks: AUMTypes.DEFISPRING,
893
- protocol: Protocols.NONE // just placeholder
346
+ protocol: Protocols.NONE, // just placeholder
894
347
  };
895
348
 
896
349
  return {
897
350
  net: {
898
351
  tokenInfo: this.asset(),
899
352
  amount: netAUM,
900
- usdValue: netAUM.toNumber() * assetPrice.price
901
- }, prevAum: prevAum, splits: [realAUM, estimatedAUMDelta]
353
+ usdValue: netAUM.toNumber() * assetPrice.price,
354
+ },
355
+ prevAum: prevAum,
356
+ splits: [realAUM, estimatedAUMDelta],
902
357
  };
903
358
  }
904
359
 
360
+
361
+ /**
362
+ * Computes the maximum additional USDC that can be borrowed from Vesu
363
+ * while keeping the strategy profitable. Uses the Extended funding rate
364
+ * and Vesu supply APY to derive a break-even borrow APY, then queries
365
+ * Vesu for the max borrowable amount at that rate.
366
+ */
367
+ async getMaxBorrowableAmount(): Promise<Web3Number> {
368
+ const vesuAdapter = await this.getVesuModifyPositionAdapter();
369
+ const extendedAdapter = await this.getExtendedAdapter();
370
+ const extendedFundingRate = new Web3Number(
371
+ (await extendedAdapter.getNetAPY()).toFixed(4),
372
+ 0,
373
+ );
374
+ const extendedPositions = await extendedAdapter.getAllOpenPositions();
375
+ if (!extendedPositions || extendedPositions.length === 0) {
376
+ logger.warn(`${this.getTag()} getMaxBorrowableAmount: no extended positions found`);
377
+ return new Web3Number(0, 0);
378
+ }
379
+ const extendePositionSizeUSD = new Web3Number(
380
+ extendedPositions[0].value || 0,
381
+ 0,
382
+ );
383
+ const vesuPositions = await vesuAdapter.getPositions();
384
+ const vesuSupplyApy = vesuPositions[0].apy.apy;
385
+ const vesuCollateralSizeUSD = new Web3Number(
386
+ vesuPositions[0].usdValue.toFixed(USDC_TOKEN_DECIMALS),
387
+ USDC_TOKEN_DECIMALS,
388
+ );
389
+ const vesuDebtSizeUSD = new Web3Number(
390
+ vesuPositions[1].usdValue.toFixed(USDC_TOKEN_DECIMALS),
391
+ USDC_TOKEN_DECIMALS,
392
+ );
393
+ const num1 = extendePositionSizeUSD.multipliedBy(extendedFundingRate);
394
+ const num2 = vesuCollateralSizeUSD.multipliedBy(vesuSupplyApy);
395
+ const num3 = vesuDebtSizeUSD.abs();
396
+ const maxBorrowApy = num1.plus(num2).minus(0.1).dividedBy(num3);
397
+ const vesuMaxBorrowableResult =
398
+ await vesuAdapter._vesuAdapter.getMaxBorrowableByInterestRate(
399
+ this.config,
400
+ vesuAdapter.config.debt,
401
+ maxBorrowApy.toNumber(),
402
+ );
403
+ return new Web3Number(
404
+ vesuMaxBorrowableResult.maxDebtToHave.toFixed(USDC_TOKEN_DECIMALS),
405
+ USDC_TOKEN_DECIMALS,
406
+ );
407
+ }
408
+
409
+ /**
410
+ * Returns the current health metrics for the strategy:
411
+ * [0] Vesu health factor (maxLTV / actualLTV) — higher is safer.
412
+ * [1] Extended margin ratio (as percentage) — higher means more margin available.
413
+ */
414
+ async getVesuHealthFactors(): Promise<number[]> {
415
+ const vesuAdapter = await this.getVesuModifyPositionAdapter();
416
+ const extendedAdapter = await this.getExtendedAdapter();
417
+ const vesuPositions = await vesuAdapter.getPositions();
418
+ const vesuCollateralSizeUSD = new Web3Number(
419
+ vesuPositions[0].usdValue.toFixed(USDC_TOKEN_DECIMALS),
420
+ 0,
421
+ );
422
+ const vesuDebtSizeUSD = new Web3Number(
423
+ vesuPositions[1].usdValue.toFixed(USDC_TOKEN_DECIMALS),
424
+ 0,
425
+ );
426
+ const actualLtv = vesuDebtSizeUSD.dividedBy(vesuCollateralSizeUSD).abs();
427
+ logger.debug(`${this.getTag()} getVesuHealthFactors: actualLtv=${actualLtv.toNumber()}`);
428
+ const maxLtv = new Web3Number(
429
+ await vesuAdapter._vesuAdapter.getLTVConfig(this.config),
430
+ 4,
431
+ );
432
+ const healthFactor = new Web3Number(
433
+ maxLtv.dividedBy(actualLtv).toFixed(4),
434
+ 4,
435
+ );
436
+ logger.debug(`${this.getTag()} getVesuHealthFactors: healthFactor=${healthFactor.toNumber()}`);
437
+ const extendedBalance = await extendedAdapter.getExtendedDepositAmount();
438
+ if (!extendedBalance) {
439
+ return [0, 0];
440
+ }
441
+ const extendedLeverage = new Web3Number(
442
+ (Number(extendedBalance.marginRatio) * 100).toFixed(4),
443
+ 4,
444
+ );
445
+ logger.debug(`${this.getTag()} getVesuHealthFactors: extendedLeverage=${extendedLeverage.toNumber()}`);
446
+ return [healthFactor.toNumber(), extendedLeverage.toNumber()];
447
+ }
448
+
449
+ /**
450
+ * Calculates the weighted net APY of the strategy across all positions.
451
+ * Combines Vesu supply APY (scaled by 0.1 performance fee) and Extended
452
+ * position APY, weighted by their respective USD values.
453
+ * Also returns per-position APY splits.
454
+ */
455
+ async netAPY(): Promise<{
456
+ net: number;
457
+ splits: { apy: number; id: string }[];
458
+ }> {
459
+ const allPositions: PositionInfo[] = [];
460
+ for (let adapter of this.metadata.additionalInfo.adapters) {
461
+ if (adapter.adapter.name !== ExtendedAdapter.name) {
462
+ let positions = await adapter.adapter.getPositions();
463
+ if (positions.length > 0) {
464
+ allPositions.push(...positions);
465
+ }
466
+ }
467
+ }
468
+ const extendedAdapter = await this.getExtendedAdapter();
469
+ let vesuPositions = allPositions.filter(
470
+ (item) => item.protocol === Protocols.VESU,
471
+ );
472
+ vesuPositions.map((item) => {
473
+ item.apy.apy = item.apy.apy * 0.1;
474
+ });
475
+ const extendedPositions = await extendedAdapter.getAllOpenPositions();
476
+ if (!extendedPositions || !this.usdcToken) {
477
+ return {
478
+ net: 0,
479
+ splits: [],
480
+ };
481
+ }
482
+ const extendedPosition = extendedPositions[0] || 0;
483
+ const extendedEquity =
484
+ (await extendedAdapter.getExtendedDepositAmount())?.equity || 0;
485
+ const extendedApy = await extendedAdapter.getNetAPY();
486
+ const totalHoldingsUSDValue =
487
+ allPositions.reduce((acc, curr) => acc + curr.usdValue, 0) +
488
+ Number(extendedEquity);
489
+ const extendedPositionSizeMultipliedByApy =
490
+ Number(extendedPosition.value) * extendedApy;
491
+ let weightedAPYs =
492
+ allPositions.reduce(
493
+ (acc, curr) => acc + curr.apy.apy * curr.usdValue,
494
+ 0,
495
+ ) + extendedPositionSizeMultipliedByApy;
496
+ const netAPY = weightedAPYs / totalHoldingsUSDValue;
497
+ logger.debug(
498
+ `${this.getTag()} netAPY: holdingsUsd=${totalHoldingsUSDValue}, weightedApy=${weightedAPYs}, net=${netAPY}`,
499
+ );
500
+ allPositions.push({
501
+ tokenInfo: this.usdcToken,
502
+ amount: new Web3Number(extendedPosition.size, 0),
503
+ usdValue: Number(extendedEquity),
504
+ apy: { apy: extendedApy, type: APYType.BASE },
505
+ remarks: AUMTypes.FINALISED,
506
+ protocol: Protocols.EXTENDED,
507
+ });
508
+ return {
509
+ net: netAPY,
510
+ splits: allPositions.map((p) => ({
511
+ apy: p.apy.apy,
512
+ id: p.remarks ?? "",
513
+ })),
514
+ };
515
+ }
516
+
517
+ /**
518
+ * Fetches the operator wallet's current holdings for USDC and WBTC,
519
+ * returning each token's balance and USD value.
520
+ */
521
+ async getWalletHoldings(): Promise<
522
+ {
523
+ tokenInfo: TokenInfo;
524
+ amount: Web3Number;
525
+ usdValue: number;
526
+ }[]
527
+ > {
528
+ if (!this.wbtcToken || !this.usdcToken) {
529
+ return [];
530
+ }
531
+ const walletAddress = this.metadata.additionalInfo.walletAddress;
532
+ const usdcWalletBalance = await new ERC20(this.config).balanceOf(
533
+ this.usdcToken.address,
534
+ walletAddress,
535
+ this.usdcToken.decimals,
536
+ );
537
+ const price = await this.pricer.getPrice(this.usdcToken.symbol);
538
+ const usdcUsdValue =
539
+ Number(usdcWalletBalance.toFixed(this.usdcToken.decimals)) * price.price;
540
+ return [
541
+ {
542
+ tokenInfo: this.usdcToken,
543
+ amount: usdcWalletBalance,
544
+ usdValue: usdcUsdValue,
545
+ }
546
+ ];
547
+ }
905
548
  }
906
549
 
550
+ /**
551
+ * Configures all adapters (Vesu, Extended, Avnu, TokenTransfer)
552
+ * and registers their leaf adapters on the vault settings. This is the central
553
+ * wiring function that connects the strategy to its underlying protocol adapters.
554
+ */
907
555
  function getLooperSettings(
908
- lstSymbol: string,
556
+ collateralSymbol: string,
909
557
  underlyingSymbol: string,
910
558
  vaultSettings: VesuExtendedStrategySettings,
911
559
  pool1: ContractAddr,
912
- extendedBackendUrl: string,
913
- extendedApiKey: string,
560
+ extendedBackendReadUrl: string,
561
+ extendedBackendWriteUrl: string,
914
562
  vaultIdExtended: number,
563
+ minimumExtendedMovementAmount: number,
564
+ minimumVesuMovementAmount: number,
565
+ minimumExtendedRetriesDelayForOrderStatus: number,
566
+ minimumExtendedPriceDifferenceForSwapOpen: number,
567
+ maximumExtendedPriceDifferenceForSwapClosing: number,
915
568
  ) {
916
569
  vaultSettings.leafAdapters = [];
917
570
 
918
571
  const wbtcToken = Global.getDefaultTokens().find(
919
- (token) => token.symbol === lstSymbol
572
+ (token) => token.symbol === collateralSymbol,
920
573
  )!;
921
574
  const usdcToken = Global.getDefaultTokens().find(
922
- (token) => token.symbol === underlyingSymbol
575
+ (token) => token.symbol === underlyingSymbol,
923
576
  )!;
924
577
 
925
578
  const baseAdapterConfig: BaseAdapterConfig = {
926
- baseToken: wbtcToken,
579
+ baseToken: usdcToken,
927
580
  supportedPositions: [
928
581
  { asset: usdcToken, isDebt: true },
929
582
  { asset: wbtcToken, isDebt: false },
@@ -940,29 +593,36 @@ function getLooperSettings(
940
593
  avnuContract: AVNU_MIDDLEWARE,
941
594
  slippage: 0.01,
942
595
  baseUrl: AVNU_QUOTE_URL,
596
+ minimumExtendedPriceDifferenceForSwapOpen:
597
+ minimumExtendedPriceDifferenceForSwapOpen,
598
+ maximumExtendedPriceDifferenceForSwapClosing:
599
+ maximumExtendedPriceDifferenceForSwapClosing,
943
600
  });
944
601
 
945
602
  const extendedAdapter = new ExtendedAdapter({
946
603
  ...baseAdapterConfig,
947
604
  supportedPositions: [
948
- { asset: usdcToken, isDebt: true },
605
+ { asset: usdcToken, isDebt: false },
949
606
  ],
950
607
  vaultIdExtended: vaultIdExtended,
951
608
  extendedContract: EXTENDED_CONTRACT,
952
- extendedBackendUrl: extendedBackendUrl,
953
- extendedApiKey: extendedApiKey,
609
+ extendedBackendWriteUrl: extendedBackendWriteUrl,
610
+ extendedBackendReadUrl: extendedBackendReadUrl,
954
611
  extendedTimeout: 30000,
955
612
  extendedRetries: 3,
956
613
  extendedBaseUrl: "https://api.starknet.extended.exchange",
957
614
  extendedMarketName: "BTC-USD",
958
615
  extendedPrecision: 5,
959
616
  avnuAdapter: avnuAdapter,
617
+ retryDelayForOrderStatus: minimumExtendedRetriesDelayForOrderStatus ?? 3000,
618
+ minimumExtendedMovementAmount: minimumExtendedMovementAmount ?? 5, //5 usdcs
960
619
  });
961
620
 
962
621
  const vesuMultiplyAdapter = new VesuMultiplyAdapter({
963
622
  poolId: pool1,
964
623
  collateral: wbtcToken,
965
624
  debt: usdcToken,
625
+ marginToken: usdcToken,
966
626
  targetHealthFactor: vaultSettings.targetHealthFactor,
967
627
  minHealthFactor: vaultSettings.minHealthFactor,
968
628
  quoteAmountToFetchPrice: vaultSettings.quoteAmountToFetchPrice,
@@ -971,10 +631,31 @@ function getLooperSettings(
971
631
  { asset: wbtcToken, isDebt: false },
972
632
  { asset: usdcToken, isDebt: true },
973
633
  ],
634
+ minimumVesuMovementAmount: minimumVesuMovementAmount ?? 5, //5 usdc
635
+ });
636
+
637
+ const vesuModifyPositionMaxLtv = VesuConfig.maxLtv;
638
+ const vesuModifyPositionTargetLtv = VesuConfig.targetLtv;
639
+ const vesuModifyPositionAdapter = new VesuModifyPositionAdapter({
640
+ poolId: pool1,
641
+ collateral: wbtcToken,
642
+ debt: usdcToken,
643
+ targetLtv: vesuModifyPositionTargetLtv,
644
+ maxLtv: vesuModifyPositionMaxLtv,
645
+ ...baseAdapterConfig,
646
+ supportedPositions: [
647
+ { asset: wbtcToken, isDebt: false },
648
+ { asset: usdcToken, isDebt: true },
649
+ ],
974
650
  });
975
651
 
976
- const unusedBalanceAdapter = new UnusedBalanceAdapter({
652
+ // Transfers USDC between the vault allocator (fromAddress) and the operator wallet (toAddress)
653
+ const usdcTransferAdapter = new TokenTransferAdapter({
977
654
  ...baseAdapterConfig,
655
+ baseToken: usdcToken,
656
+ supportedPositions: [{ asset: usdcToken, isDebt: false }],
657
+ fromAddress: vaultSettings.vaultAllocator,
658
+ toAddress: ContractAddr.from(vaultSettings.walletAddress),
978
659
  });
979
660
 
980
661
  vaultSettings.adapters.push({
@@ -983,8 +664,13 @@ function getLooperSettings(
983
664
  });
984
665
 
985
666
  vaultSettings.adapters.push({
986
- id: `${unusedBalanceAdapter.name}_${wbtcToken.symbol}`,
987
- adapter: unusedBalanceAdapter,
667
+ id: `${vesuModifyPositionAdapter.name}_${wbtcToken.symbol}_${usdcToken.symbol}`,
668
+ adapter: vesuModifyPositionAdapter,
669
+ });
670
+
671
+ vaultSettings.adapters.push({
672
+ id: `${usdcTransferAdapter.name}_${usdcToken.symbol}`,
673
+ adapter: usdcTransferAdapter,
988
674
  });
989
675
 
990
676
  vaultSettings.adapters.push({
@@ -1002,44 +688,43 @@ function getLooperSettings(
1002
688
  vaultAddress: vaultSettings.vaultAddress,
1003
689
  vaultAllocator: vaultSettings.vaultAllocator,
1004
690
  manager: vaultSettings.manager,
1005
- asset: wbtcToken.address,
691
+ asset: usdcToken.address,
1006
692
  });
1007
693
 
1008
694
  vaultSettings.leafAdapters.push(() => vesuMultiplyAdapter.getDepositLeaf());
1009
- vaultSettings.leafAdapters.push(() =>
1010
- vesuMultiplyAdapter.getWithdrawLeaf()
1011
- );
695
+ vaultSettings.leafAdapters.push(() => vesuMultiplyAdapter.getWithdrawLeaf());
696
+ vaultSettings.leafAdapters.push(() => vesuModifyPositionAdapter.getDepositLeaf());
697
+ vaultSettings.leafAdapters.push(() => vesuModifyPositionAdapter.getWithdrawLeaf());
1012
698
  vaultSettings.leafAdapters.push(() => extendedAdapter.getDepositLeaf());
1013
- vaultSettings.leafAdapters.push(() => extendedAdapter.getSwapFromLegacyLeaf());
1014
699
  vaultSettings.leafAdapters.push(() => avnuAdapter.getDepositLeaf());
1015
700
  vaultSettings.leafAdapters.push(() => avnuAdapter.getWithdrawLeaf());
1016
- // Doubt here, should this be usdcToken.address, or wbtcToken.address?
701
+ vaultSettings.leafAdapters.push(() => usdcTransferAdapter.getDepositLeaf());
702
+ vaultSettings.leafAdapters.push(() => usdcTransferAdapter.getWithdrawLeaf());
1017
703
  vaultSettings.leafAdapters.push(
1018
704
  commonAdapter
1019
705
  .getApproveAdapter(
1020
706
  usdcToken.address,
1021
707
  vaultSettings.vaultAddress,
1022
- UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY
708
+ UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY,
1023
709
  )
1024
- .bind(commonAdapter)
710
+ .bind(commonAdapter),
1025
711
  );
1026
712
 
1027
713
  vaultSettings.leafAdapters.push(
1028
714
  commonAdapter
1029
715
  .getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
1030
- .bind(commonAdapter)
716
+ .bind(commonAdapter),
1031
717
  );
1032
718
  return vaultSettings;
1033
719
  }
1034
720
 
1035
-
1036
721
  function getDescription(tokenSymbol: string, underlyingSymbol: string) {
1037
722
  return VaultDescription(tokenSymbol, underlyingSymbol);
1038
723
  }
1039
724
 
1040
725
  export default function VaultDescription(
1041
726
  lstSymbol: string,
1042
- underlyingSymbol: string
727
+ underlyingSymbol: string,
1043
728
  ) {
1044
729
  const containerStyle = {
1045
730
  maxWidth: "800px",
@@ -1052,26 +737,64 @@ export default function VaultDescription(
1052
737
 
1053
738
  return (
1054
739
  <div style={containerStyle}>
1055
- <h1 style={{ fontSize: "18px", marginBottom: "10px" }}>Liquidation risk managed leverged {lstSymbol} Vault</h1>
740
+ <h1 style={{ fontSize: "18px", marginBottom: "10px" }}>
741
+ Liquidation risk managed leverged {lstSymbol} Vault
742
+ </h1>
1056
743
  <p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
1057
- 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
1058
- is swapped to {lstSymbol} to create leverage. Depositors receive vault shares that
1059
- represent a proportional claim on the underlying assets and accrued yield.
744
+ This Levered Endur {lstSymbol} vault is a tokenized leveraged Vault,
745
+ auto-compounding strategy that takes upto 5x leverage on {lstSymbol} by
746
+ borrow {underlyingSymbol}. Borrowed amount is swapped to {lstSymbol} to
747
+ create leverage. Depositors receive vault shares that represent a
748
+ proportional claim on the underlying assets and accrued yield.
1060
749
  </p>
1061
750
 
1062
751
  <p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
1063
- 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" }])}
1064
- {" "}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.
752
+ This vault uses Vesu for lending and borrowing. The oracle used by this
753
+ pool is a{" "}
754
+ {highlightTextWithLinks("conversion rate oracle", [
755
+ {
756
+ highlight: "conversion rate oracle",
757
+ link: "https://docs.pragma.build/starknet/development#conversion-rate",
758
+ },
759
+ ])}{" "}
760
+ which is resilient to liquidity issues and price volatility, hence
761
+ reducing the risk of liquidation. However, overtime, if left
762
+ un-monitored, debt can increase enough to trigger a liquidation. But no
763
+ worries, our continuous monitoring systems look for situations with
764
+ reduced health factor and balance collateral/debt to bring it back to
765
+ safe levels. With Troves, you can have a peaceful sleep.
1065
766
  </p>
1066
767
 
1067
- <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
768
+ <div
769
+ style={{
770
+ backgroundColor: "#222",
771
+ padding: "10px",
772
+ borderRadius: "8px",
773
+ marginBottom: "20px",
774
+ border: "1px solid #444",
775
+ }}
776
+ >
1068
777
  <p style={{ fontSize: "13px", color: "#ccc" }}>
1069
- <strong>Withdrawals:</strong> Requests can take up to <strong>1-2 hours</strong> to process as the vault unwinds and settles routing.
778
+ <strong>Withdrawals:</strong> Requests can take up to{" "}
779
+ <strong>1-2 hours</strong> to process as the vault unwinds and settles
780
+ routing.
1070
781
  </p>
1071
782
  </div>
1072
- <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
783
+ <div
784
+ style={{
785
+ backgroundColor: "#222",
786
+ padding: "10px",
787
+ borderRadius: "8px",
788
+ marginBottom: "20px",
789
+ border: "1px solid #444",
790
+ }}
791
+ >
1073
792
  <p style={{ fontSize: "13px", color: "#ccc" }}>
1074
- <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.
793
+ <strong>Debt limits:</strong> Pools on Vesu have debt caps that are
794
+ gradually increased over time. Until caps are raised, deposited Tokens
795
+ remain in the vault, generating a shared net return for all
796
+ depositors. There is no additional fee taken by Troves on Yield
797
+ token's APY, its only on added gain.
1075
798
  </p>
1076
799
  </div>
1077
800
  {/* <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
@@ -1084,57 +807,205 @@ export default function VaultDescription(
1084
807
  }
1085
808
 
1086
809
  const re7UsdcPrimeDevansh: VesuExtendedStrategySettings = {
1087
- vaultAddress: ContractAddr.from("0x520a2e945dd0762e5284fc1b012f62ca04e238e105eb362d5e0ce208928729d"),
1088
- manager: ContractAddr.from("0x6abe24d31cbc16d7c4acb2421a826f96273e225b4a2d168d91e65123da0bfb9"),
1089
- vaultAllocator: ContractAddr.from("0x6e4f716e22efb164ee4a831233e513f45396ad3be4cff3c07386be0226febed"),
1090
- redeemRequestNFT: ContractAddr.from("0x66060e1874e05506b18e6029188ec49bf231a411ad57642311bbdf3cb22e5f"),
1091
- aumOracle: ContractAddr.from("0x301d883b9b45c76132638e39326b3f464c492599623263d405ec0df991e27ab"),
810
+ vaultAddress: ContractAddr.from(
811
+ "0x772d6cf5038c18ff5ab89f8945017bbf4d2c6959891339975c70a4f74ac6c8e",
812
+ ),
813
+ manager: ContractAddr.from(
814
+ "0x3340c9d7231838e2dccff72b9004f1598a74e65c74b954f07fe1ea19d04a625",
815
+ ),
816
+ vaultAllocator: ContractAddr.from(
817
+ "0x537353b35eee5ca2d9a45eb646977baddd4e89ce870a231dcada79884117292",
818
+ ),
819
+ redeemRequestNFT: ContractAddr.from(
820
+ "0x6117d1a8c72c0457948083757e1a17ee8c0833b969d5c959b629e5f8feb56ec",
821
+ ),
822
+ aumOracle: ContractAddr.from(
823
+ "0x6d7d68045bf5e0b5a4cec43241549851cb9645f7a73a20894152165dbe7083a",
824
+ ),
1092
825
  leafAdapters: [],
1093
826
  adapters: [],
1094
827
  targetHealthFactor: 1.4,
1095
828
  minHealthFactor: 1.05,
1096
829
  underlyingToken: Global.getDefaultTokens().find(
1097
- (token) => token.symbol === "USDC"
830
+ (token) => token.symbol === "USDC",
1098
831
  )!,
1099
832
  quoteAmountToFetchPrice: new Web3Number(
1100
833
  "0.001",
1101
- Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!.decimals
834
+ Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!
835
+ .decimals,
1102
836
  ),
1103
- borrowable_assets: [Global.getDefaultTokens().find(token => token.symbol === "WBTC")!],
837
+ borrowable_assets: [
838
+ Global.getDefaultTokens().find((token) => token.symbol === "USDC")!,
839
+ ],
1104
840
  minimumWBTCDifferenceForAvnuSwap: MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP,
1105
- }
1106
-
1107
- export const VesuExtendedTestStrategies = (extendedBackendUrl: string, extendedApiKey: string, vaultIdExtended: number): IStrategyMetadata<VesuExtendedStrategySettings>[] => {
1108
- return [
1109
- getStrategySettingsVesuExtended('WBTC', 'USDC', re7UsdcPrimeDevansh, false, false, extendedBackendUrl, extendedApiKey, vaultIdExtended),
1110
- ]
1111
- }
1112
-
841
+ walletAddress: '0x024b563C1C7d41B32BF4EFB9F38828508a65Be2d6e25268E9f63F22C5e9E51c5',
842
+ };
1113
843
 
844
+ const pureUsdc: VesuExtendedStrategySettings = {
845
+ vaultAddress: ContractAddr.from(
846
+ "0x745c972db65bdee10022fd875dd328c7f40a90849135b6a0f875a40f3c632ae",
847
+ ),
848
+ manager: ContractAddr.from(
849
+ "0x364e0894edefb616ec090f57f5c0274517fcd98ab276ae1f021c5e962fa1deb",
850
+ ),
851
+ vaultAllocator: ContractAddr.from(
852
+ "0x6fceed28e03a96091877568893df0dd89b9bb80fec30da2b742dacbd5526179",
853
+ ),
854
+ redeemRequestNFT: ContractAddr.from(
855
+ "0x501c2b87728e22c6dfcebe4c0b2b3a9fba5845606e4d59fa7bf591badcbb42",
856
+ ),
857
+ aumOracle: ContractAddr.from(
858
+ "0x6ccd95f5765242695d3c75e1440b1d0b30efac8babb864ce15729977b97cb82",
859
+ ),
860
+ leafAdapters: [],
861
+ adapters: [],
862
+ targetHealthFactor: 1.4,
863
+ minHealthFactor: 1.35,
864
+ underlyingToken: Global.getDefaultTokens().find(
865
+ (token) => token.symbol === "USDC",
866
+ )!,
867
+ quoteAmountToFetchPrice: new Web3Number(
868
+ "0.001",
869
+ Global.getDefaultTokens().find((token) => token.symbol === "USDC")!
870
+ .decimals,
871
+ ),
872
+ borrowable_assets: [
873
+ Global.getDefaultTokens().find((token) => token.symbol === "USDC")!,
874
+ ],
875
+ minimumWBTCDifferenceForAvnuSwap: MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP,
876
+ walletAddress: '0x058571C23da5FEdd4e36003FAE3fE2fA9782f2692E552f081839142B10770D0B',
877
+ };
1114
878
 
1115
- function getStrategySettingsVesuExtended(lstSymbol: string, underlyingSymbol: string, addresses: VesuExtendedStrategySettings, isPreview: boolean = false, isLST: boolean, extendedBackendUrl: string, extendedApiKey: string, vaultIdExtended: number): IStrategyMetadata<VesuExtendedStrategySettings> {
879
+ export const VesuExtendedTestStrategies = (
880
+ extendedBackendReadUrl: string,
881
+ extendedBackendWriteUrl: string,
882
+ vaultIdExtended: number,
883
+ minimumExtendedMovementAmount: number,
884
+ minimumVesuMovementAmount: number,
885
+ minimumExtendedRetriesDelayForOrderStatus: number,
886
+ minimumExtendedPriceDifferenceForSwapOpen: number,
887
+ maximumExtendedPriceDifferenceForSwapClosing: number,
888
+ ): IStrategyMetadata<VesuExtendedStrategySettings>[] => {
889
+ return [
890
+ getStrategySettingsVesuExtended(
891
+ "WBTC",
892
+ "USDC",
893
+ re7UsdcPrimeDevansh,
894
+ false,
895
+ false,
896
+ extendedBackendReadUrl,
897
+ extendedBackendWriteUrl,
898
+ vaultIdExtended,
899
+ minimumExtendedMovementAmount,
900
+ minimumVesuMovementAmount,
901
+ minimumExtendedRetriesDelayForOrderStatus,
902
+ minimumExtendedPriceDifferenceForSwapOpen,
903
+ maximumExtendedPriceDifferenceForSwapClosing,
904
+ ),
905
+ getStrategySettingsVesuExtended(
906
+ "WBTC",
907
+ "USDC",
908
+ pureUsdc,
909
+ false,
910
+ false,
911
+ extendedBackendReadUrl,
912
+ extendedBackendWriteUrl,
913
+ vaultIdExtended,
914
+ minimumExtendedMovementAmount,
915
+ minimumVesuMovementAmount,
916
+ minimumExtendedRetriesDelayForOrderStatus,
917
+ minimumExtendedPriceDifferenceForSwapOpen,
918
+ maximumExtendedPriceDifferenceForSwapClosing,
919
+ ),
920
+ ];
921
+ };
922
+
923
+ /**
924
+ * Constructs a complete IStrategyMetadata object for a Vesu-Extended strategy,
925
+ * including adapter wiring, risk configuration, FAQ, and UI description.
926
+ */
927
+ function getStrategySettingsVesuExtended(
928
+ collateralSymbol: string,
929
+ underlyingSymbol: string,
930
+ addresses: VesuExtendedStrategySettings,
931
+ isPreview: boolean = false,
932
+ isLST: boolean,
933
+ extendedBackendReadUrl: string,
934
+ extendedBackendWriteUrl: string,
935
+ vaultIdExtended: number,
936
+ minimumExtendedMovementAmount: number,
937
+ minimumVesuMovementAmount: number,
938
+ minimumExtendedRetriesDelayForOrderStatus: number,
939
+ minimumExtendedPriceDifferenceForSwapOpen: number,
940
+ maximumExtendedPriceDifferenceForSwapClosing: number,
941
+ ): IStrategyMetadata<VesuExtendedStrategySettings> {
1116
942
  return {
943
+ id: `extended_${underlyingSymbol.toLowerCase()}_test`,
1117
944
  name: `Extended Test ${underlyingSymbol}`,
1118
- description: getDescription(lstSymbol, underlyingSymbol),
945
+ description: getDescription(collateralSymbol, underlyingSymbol),
1119
946
  address: addresses.vaultAddress,
1120
947
  launchBlock: 0,
1121
- type: 'Other',
1122
- depositTokens: [Global.getDefaultTokens().find(token => token.symbol === underlyingSymbol)!],
1123
- additionalInfo: getLooperSettings(lstSymbol, underlyingSymbol, addresses, VesuPools.Re7USDCPrime, extendedBackendUrl, extendedApiKey, vaultIdExtended),
948
+ type: "Other",
949
+ vaultType: {
950
+ type: VaultType.DELTA_NEUTRAL,
951
+ description: "Delta Neutral strategy using extended position on Vesu"
952
+ },
953
+ depositTokens: [
954
+ Global.getDefaultTokens().find(
955
+ (token) => token.symbol === 'WBTC',
956
+ )!,
957
+ ],
958
+ additionalInfo: getLooperSettings(
959
+ collateralSymbol,
960
+ underlyingSymbol,
961
+ addresses,
962
+ VesuPools.Re7USDCPrime,
963
+ extendedBackendReadUrl,
964
+ extendedBackendWriteUrl,
965
+ vaultIdExtended,
966
+ minimumExtendedMovementAmount,
967
+ minimumVesuMovementAmount,
968
+ minimumExtendedRetriesDelayForOrderStatus,
969
+ minimumExtendedPriceDifferenceForSwapOpen,
970
+ maximumExtendedPriceDifferenceForSwapClosing,
971
+ ),
1124
972
  risk: {
1125
973
  riskFactor: _riskFactor,
1126
974
  netRisk:
1127
975
  _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
1128
976
  _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
1129
- notARisks: getNoRiskTags(_riskFactor)
977
+ notARisks: getNoRiskTags(_riskFactor),
1130
978
  },
1131
979
  auditUrl: AUDIT_URL,
1132
980
  protocols: [Protocols.ENDUR, Protocols.VESU],
1133
- maxTVL: Web3Number.fromWei(0, 18),
1134
981
  contractDetails: getContractDetails(addresses),
1135
- faqs: getFAQs(lstSymbol, underlyingSymbol, isLST),
1136
- investmentSteps: getInvestmentSteps(lstSymbol, underlyingSymbol),
982
+ faqs: getFAQs(collateralSymbol, underlyingSymbol, isLST),
983
+ investmentSteps: getInvestmentSteps(collateralSymbol, underlyingSymbol),
1137
984
  isPreview: isPreview,
1138
- 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.'
1139
- }
1140
- }
985
+ apyMethodology: isLST
986
+ ? "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."
987
+ : "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.",
988
+ security: {
989
+ auditStatus: AuditStatus.NOT_AUDITED,
990
+ sourceCode: {
991
+ type: SourceCodeType.OPEN_SOURCE,
992
+ contractLink: AUDIT_URL,
993
+ },
994
+ accessControl: {
995
+ type: AccessControlType.MULTISIG_ACCOUNT,
996
+ addresses: [addresses.vaultAddress],
997
+ timeLock: "None",
998
+ },
999
+ },
1000
+ redemptionInfo: {
1001
+ instantWithdrawalVault: InstantWithdrawalVault.NO,
1002
+ redemptionsInfo: [{
1003
+ title: "Up to $500k",
1004
+ description: "1-24 hours",
1005
+ }],
1006
+ alerts: [],
1007
+ },
1008
+ usualTimeToEarnings: null,
1009
+ usualTimeToEarningsDescription: null,
1010
+ };
1011
+ }