@strkfarm/sdk 2.0.0-dev.27 → 2.0.0-dev.28

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 (70) hide show
  1. package/dist/cli.js +190 -36
  2. package/dist/cli.mjs +188 -34
  3. package/dist/index.browser.global.js +79130 -49357
  4. package/dist/index.browser.mjs +18039 -11434
  5. package/dist/index.d.ts +2869 -898
  6. package/dist/index.js +19036 -12210
  7. package/dist/index.mjs +18942 -12161
  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/index.ts +3 -2
  13. package/src/dataTypes/mynumber.ts +141 -0
  14. package/src/global.ts +76 -41
  15. package/src/index.browser.ts +2 -1
  16. package/src/interfaces/common.tsx +167 -2
  17. package/src/modules/ExtendedWrapperSDk/types.ts +26 -4
  18. package/src/modules/ExtendedWrapperSDk/wrapper.ts +110 -67
  19. package/src/modules/apollo-client-config.ts +28 -0
  20. package/src/modules/avnu.ts +4 -4
  21. package/src/modules/ekubo-pricer.ts +79 -0
  22. package/src/modules/ekubo-quoter.ts +46 -30
  23. package/src/modules/erc20.ts +17 -0
  24. package/src/modules/harvests.ts +43 -29
  25. package/src/modules/pragma.ts +23 -8
  26. package/src/modules/pricer-from-api.ts +156 -15
  27. package/src/modules/pricer-lst.ts +1 -1
  28. package/src/modules/pricer.ts +40 -4
  29. package/src/modules/pricerBase.ts +2 -1
  30. package/src/node/deployer.ts +36 -1
  31. package/src/node/pricer-redis.ts +2 -1
  32. package/src/strategies/base-strategy.ts +78 -10
  33. package/src/strategies/ekubo-cl-vault.tsx +906 -347
  34. package/src/strategies/factory.ts +159 -0
  35. package/src/strategies/index.ts +6 -1
  36. package/src/strategies/registry.ts +239 -0
  37. package/src/strategies/sensei.ts +335 -7
  38. package/src/strategies/svk-strategy.ts +97 -27
  39. package/src/strategies/types.ts +4 -0
  40. package/src/strategies/universal-adapters/adapter-utils.ts +2 -1
  41. package/src/strategies/universal-adapters/avnu-adapter.ts +177 -268
  42. package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
  43. package/src/strategies/universal-adapters/common-adapter.ts +206 -203
  44. package/src/strategies/universal-adapters/extended-adapter.ts +155 -336
  45. package/src/strategies/universal-adapters/index.ts +9 -8
  46. package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
  47. package/src/strategies/universal-adapters/usdc<>usdce-adapter.ts +200 -0
  48. package/src/strategies/universal-adapters/vesu-adapter.ts +110 -75
  49. package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +476 -0
  50. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +762 -844
  51. package/src/strategies/universal-adapters/vesu-position-common.ts +251 -0
  52. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
  53. package/src/strategies/universal-lst-muliplier-strategy.tsx +396 -204
  54. package/src/strategies/universal-strategy.tsx +1426 -1178
  55. package/src/strategies/vesu-extended-strategy/services/executionService.ts +2251 -0
  56. package/src/strategies/vesu-extended-strategy/services/extended-vesu-state-manager.ts +2941 -0
  57. package/src/strategies/vesu-extended-strategy/services/operationService.ts +12 -1
  58. package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +52 -0
  59. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +1 -0
  60. package/src/strategies/vesu-extended-strategy/utils/constants.ts +2 -0
  61. package/src/strategies/vesu-extended-strategy/utils/helper.ts +158 -124
  62. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +377 -1788
  63. package/src/strategies/vesu-rebalance.tsx +255 -152
  64. package/src/utils/health-factor-math.ts +4 -1
  65. package/src/utils/index.ts +2 -1
  66. package/src/utils/logger.browser.ts +22 -4
  67. package/src/utils/logger.node.ts +259 -24
  68. package/src/utils/starknet-call-parser.ts +1036 -0
  69. package/src/utils/strategy-utils.ts +61 -0
  70. package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
@@ -3,37 +3,37 @@ 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
17
  import {
12
- calculateDebtAmount,
13
- calculateDeltaDebtAmount,
14
- calculateExtendedLevergae,
15
- calculatePositionToCloseToWithdrawAmount,
16
- } from "./utils/helper";
18
+ ExtendedSVKVesuStateManager,
19
+ StateManagerConfig,
20
+ } from "./services/extended-vesu-state-manager";
21
+ import { ExecutionService, ExecutionConfig } from "./services/executionService";
17
22
  import { logger } from "@/utils";
18
23
  import { AUDIT_URL } from "../universal-lst-muliplier-strategy";
19
24
  import { getNoRiskTags } from "@/interfaces";
20
25
  import { _riskFactor } from "../universal-lst-muliplier-strategy";
21
26
  import {
22
- BUFFER_USDC_IN_WITHDRAWAL,
23
27
  LIMIT_BALANCE,
24
- LIMIT_BALANCE_VALUE,
25
- MAX_LTV_BTC_USDC,
26
- MINIMUM_EXTENDED_POSITION_SIZE,
27
28
  USDC_TOKEN_DECIMALS,
28
29
  WALLET_ADDRESS,
29
30
  WBTC_TOKEN_DECIMALS,
30
31
  } from "./utils/constants";
31
- import { CycleType } from "./types/transaction-metadata";
32
+ import { ExecutionCallback } from "./types/transaction-metadata";
32
33
  import { PricerBase } from "@/modules/pricerBase";
33
34
  import { ContractAddr, Web3Number } from "@/dataTypes";
34
35
  import { Global } from "@/global";
35
36
  import { ERC20 } from "@/modules";
36
- import { Balance, OrderSide } from "@/modules/ExtendedWrapperSDk";
37
37
  import { Protocols } from "@/interfaces";
38
38
  import { MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP } from "./utils/constants";
39
39
  import {
@@ -49,12 +49,11 @@ import { VesuPools } from "../universal-adapters";
49
49
  import {
50
50
  BaseAdapterConfig,
51
51
  CommonAdapter,
52
- UnusedBalanceAdapter,
52
+ TokenTransferAdapter,
53
+ VesuModifyPositionAdapter,
53
54
  VesuMultiplyAdapter,
54
55
  } from "../universal-adapters";
55
- import { Operations } from "./services/operationService";
56
56
  import {
57
- AVNU_EXCHANGE,
58
57
  AVNU_QUOTE_URL,
59
58
  AVNU_MIDDLEWARE,
60
59
  EXTENDED_CONTRACT,
@@ -63,22 +62,11 @@ import { PricerFromApi } from "@/modules";
63
62
  import { ExtendedAdapter } from "../universal-adapters/extended-adapter";
64
63
  import { SVKStrategy } from "../svk-strategy";
65
64
  import { AvnuAdapter } from "../universal-adapters/avnu-adapter";
66
- import {
67
- calculateAmountDistribution,
68
- calculateAmountDistributionForWithdrawal,
69
- calculateVesuLeverage,
70
- calculateVesUPositionSizeGivenExtended,
71
- } from "./utils/helper";
72
65
  import { SingleTokenInfo } from "../base-strategy";
73
- import { Call } from "starknet";
74
- import { PositionTypeAvnuExtended } from "../universal-strategy";
75
- import {
76
- TransactionMetadata,
77
- TransactionResult,
78
- } from "./types/transaction-metadata";
66
+ import { UsdcToUsdceAdapter } from "../universal-adapters/usdc<>usdce-adapter";
67
+ import { VesuConfig } from "./utils/config.runtime";
79
68
 
80
- export interface VesuExtendedStrategySettings
81
- extends UniversalStrategySettings {
69
+ export interface VesuExtendedStrategySettings extends UniversalStrategySettings {
82
70
  underlyingToken: TokenInfo;
83
71
  borrowable_assets: TokenInfo[];
84
72
  targetHealthFactor: number;
@@ -90,33 +78,81 @@ export interface VesuExtendedStrategySettings
90
78
  }
91
79
 
92
80
  export class VesuExtendedMultiplierStrategy<
93
- S extends VesuExtendedStrategySettings
94
- >
95
- extends SVKStrategy<S>
96
- implements Operations
97
- {
81
+ S extends VesuExtendedStrategySettings,
82
+ > extends SVKStrategy<S> {
83
+ public wbtcToken: TokenInfo;
84
+ public usdcToken: TokenInfo;
85
+ public usdceToken: TokenInfo;
86
+ public readonly stateManager: ExtendedSVKVesuStateManager;
87
+
98
88
  constructor(
99
89
  config: IConfig,
100
90
  pricer: PricerBase,
101
- metadata: IStrategyMetadata<S>
91
+ metadata: IStrategyMetadata<S>,
102
92
  ) {
103
93
  super(config, pricer, metadata);
104
94
  this.metadata.additionalInfo.adapters.forEach((adapter) => {
105
95
  adapter.adapter.config.networkConfig = this.config;
106
96
  adapter.adapter.config.pricer = this.pricer;
107
- if ((adapter.adapter as VesuMultiplyAdapter).vesuAdapter) {
108
- (adapter.adapter as VesuMultiplyAdapter).vesuAdapter.networkConfig =
97
+ if ((adapter.adapter as any)._vesuAdapter) {
98
+ (adapter.adapter as any)._vesuAdapter.networkConfig =
109
99
  this.config;
110
- (adapter.adapter as VesuMultiplyAdapter).vesuAdapter.pricer =
100
+ (adapter.adapter as any)._vesuAdapter.pricer =
111
101
  this.pricer;
112
102
  }
113
103
  });
104
+ // todo check if this can be generalized
105
+ this.wbtcToken = Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!;
106
+ this.usdcToken = this.metadata.additionalInfo.borrowable_assets[0]!;
107
+ this.usdceToken = Global.getDefaultTokens().find(
108
+ (token) => token.symbol === "USDC.e",
109
+ )!;
110
+
111
+ this.stateManager = this._initializeStateManager();
112
+ }
113
+
114
+ /**
115
+ * Extracts the required adapters from metadata and constructs the
116
+ * state manager used by shouldInvest / handleWithdraw.
117
+ */
118
+ private _initializeStateManager(): ExtendedSVKVesuStateManager {
119
+ const vesuAdapters = this.metadata.additionalInfo.adapters
120
+ .filter((a) => a.adapter.name === VesuMultiplyAdapter.name)
121
+ .map((a) => a.adapter as VesuMultiplyAdapter);
122
+
123
+ const extendedAdapterEntry = this.metadata.additionalInfo.adapters.find(
124
+ (a) => a.adapter.name === ExtendedAdapter.name,
125
+ );
126
+ if (!extendedAdapterEntry) {
127
+ throw new Error(
128
+ `${this.getTag()} ExtendedAdapter not found in adapters — cannot initialise state manager.`,
129
+ );
130
+ }
131
+
132
+ const stateManagerConfig: StateManagerConfig = {
133
+ pricer: this.pricer,
134
+ networkConfig: this.config,
135
+ vesuAdapters,
136
+ extendedAdapter: extendedAdapterEntry.adapter as ExtendedAdapter,
137
+ vaultAllocator: this.metadata.additionalInfo.vaultAllocator,
138
+ walletAddress: this.metadata.additionalInfo.walletAddress,
139
+ assetToken: Global.getDefaultTokens().find((token) => token.symbol === 'USDC')!, // ! TODO change to asset() latest
140
+ usdceToken: this.usdceToken,
141
+ collateralToken: this.wbtcToken,
142
+ limitBalanceBufferFactor: LIMIT_BALANCE,
143
+ };
144
+
145
+ return new ExtendedSVKVesuStateManager(stateManagerConfig);
114
146
  }
115
147
 
116
148
  getTag() {
117
149
  return `${VesuExtendedMultiplierStrategy.name}:${this.metadata.name}`;
118
150
  }
119
151
 
152
+ /**
153
+ * Fetches current WBTC (collateral) and USDC (debt) prices from the pricer.
154
+ * Validates that both prices are finite and positive, throwing if not.
155
+ */
120
156
  async getAssetPrices() {
121
157
  const wbtcToken = Global.getDefaultTokens().find(
122
158
  (token) => token.symbol === "WBTC"
@@ -126,1640 +162,160 @@ export class VesuExtendedMultiplierStrategy<
126
162
  )!;
127
163
  const collateralPrice = await this.pricer.getPrice(wbtcToken.symbol);
128
164
  const debtPrice = await this.pricer.getPrice(usdcToken.symbol);
129
- return {
130
- collateralPrice,
131
- debtPrice,
132
- };
133
- }
134
165
 
135
- async getUnusedBalanceUSDCE(): Promise<SingleTokenInfo> {
136
- const usdceToken = Global.getDefaultTokens().find(
137
- (token) => token.symbol === "USDCe"
138
- )!;
139
- const balance = await new ERC20(this.config).balanceOf(
140
- usdceToken.address,
141
- WALLET_ADDRESS,
142
- usdceToken.decimals
143
- );
144
- const price = await this.pricer.getPrice(usdceToken.symbol);
145
- const usdValue = Number(balance.toFixed(usdceToken.decimals)) * price.price;
146
- return {
147
- tokenInfo: usdceToken,
148
- amount: balance,
149
- usdValue,
150
- };
151
- }
166
+ if (!Number.isFinite(collateralPrice.price) || collateralPrice.price <= 0) {
167
+ throw new Error(
168
+ `${this.getTag()} Invalid collateralPrice: ${collateralPrice.price}. Expected a finite, positive number.`
169
+ );
170
+ }
171
+ if (!Number.isFinite(debtPrice.price) || debtPrice.price <= 0) {
172
+ throw new Error(
173
+ `${this.getTag()} Invalid debtPrice: ${debtPrice.price}. Expected a finite, positive number.`
174
+ );
175
+ }
152
176
 
153
- async getUnusedBalanceWBTC(): Promise<SingleTokenInfo> {
154
- const collateralToken = this.metadata.additionalInfo.borrowable_assets[0]!;
155
- const balance = await new ERC20(this.config).balanceOf(
156
- collateralToken.address,
157
- this.metadata.additionalInfo.vaultAllocator,
158
- collateralToken.decimals
159
- );
160
- const price = await this.pricer.getPrice(collateralToken.symbol);
161
- const usdValue =
162
- Number(balance.toFixed(collateralToken.decimals)) * price.price;
163
- return {
164
- tokenInfo: collateralToken,
165
- amount: balance,
166
- usdValue,
167
- };
177
+ return { collateralPrice, debtPrice };
168
178
  }
169
179
 
170
- async getVesuAdapter(): Promise<VesuMultiplyAdapter | null> {
180
+ async getVesuAdapter(): Promise<VesuMultiplyAdapter> {
171
181
  const vesuAdapter = this.metadata.additionalInfo.adapters.find(
172
- (adapter) => adapter.adapter.name === VesuMultiplyAdapter.name
182
+ (adapter) => adapter.adapter.name === VesuMultiplyAdapter.name,
173
183
  );
174
184
  if (!vesuAdapter) {
175
- logger.error("vesu adapter not found");
176
- return null;
185
+ throw new Error(
186
+ `${this.getTag()} Vesu adapter not configured in metadata.`
187
+ );
177
188
  }
178
189
  return vesuAdapter.adapter as VesuMultiplyAdapter;
179
190
  }
180
191
 
181
- async getAvnuAdapter(): Promise<AvnuAdapter | null> {
182
- const avnuAdapter = this.metadata.additionalInfo.adapters.find(
183
- (adapter) => adapter.adapter.name === AvnuAdapter.name
192
+ async getVesuModifyPositionAdapter(): Promise<VesuModifyPositionAdapter> {
193
+ const vesuModifyPositionAdapter = this.metadata.additionalInfo.adapters.find(
194
+ (adapter) => adapter.adapter.name === VesuModifyPositionAdapter.name,
184
195
  );
185
- if (!avnuAdapter) {
186
- logger.error("avnu adapter not found");
187
- return null;
196
+ if (!vesuModifyPositionAdapter) {
197
+ throw new Error(
198
+ `${this.getTag()} Vesu modify position adapter not configured in metadata.`,
199
+ );
188
200
  }
189
- return avnuAdapter.adapter as AvnuAdapter;
201
+ return vesuModifyPositionAdapter.adapter as VesuModifyPositionAdapter;
190
202
  }
191
203
 
192
- async getExtendedAdapter(): Promise<ExtendedAdapter | null> {
193
- const extendedAdapter = this.metadata.additionalInfo.adapters.find(
194
- (adapter) => adapter.adapter.name === ExtendedAdapter.name
204
+ async getUsdceTransferAdapter(): Promise<TokenTransferAdapter> {
205
+ const usdceTransferAdapter = this.metadata.additionalInfo.adapters.find(
206
+ (adapter) => adapter.adapter.name === TokenTransferAdapter.name,
195
207
  );
196
- if (!extendedAdapter) {
197
- logger.error("extended adapter not found");
198
- return null;
199
- }
200
- return extendedAdapter.adapter as ExtendedAdapter;
201
- }
202
-
203
- async moveAssetsToVaultAllocator(
204
- amount: Web3Number,
205
- extendedAdapter: ExtendedAdapter
206
- ): Promise<{
207
- calls: Call[];
208
- status: boolean;
209
- }> {
210
- try {
211
- const usdceToken = Global.getDefaultTokens().find(
212
- (token) => token.symbol === "USDCe"
213
- )!;
214
- const walletBalance = await new ERC20(this.config).balanceOf(
215
- usdceToken.address,
216
- WALLET_ADDRESS,
217
- usdceToken.decimals
218
- );
219
- logger.info(
220
- `${VesuExtendedMultiplierStrategy.name}::moveAssetsToVaultAllocator walletBalance: ${walletBalance}`
221
- );
222
- const amountToBeTransferred = amount.minimum(walletBalance);
223
- logger.info(
224
- `${
225
- VesuExtendedMultiplierStrategy.name
226
- }::moveAssetsToVaultAllocator amountToBeTransferred: ${amountToBeTransferred.toNumber()}`
227
- );
228
-
229
- if (amountToBeTransferred.lessThan(0)) {
230
- logger.error(
231
- `${
232
- VesuExtendedMultiplierStrategy.name
233
- }::moveAssetsToVaultAllocator amountToBeTransferred is less than 0: ${amountToBeTransferred.toNumber()}`
234
- );
235
- return {
236
- calls: [],
237
- status: false,
238
- };
239
- }
240
-
241
- const approveCall = new ERC20(this.config).approve(
242
- usdceToken.address,
243
- this.metadata.additionalInfo.vaultAllocator,
244
- amountToBeTransferred
245
- );
246
- const transferCall = new ERC20(this.config).transfer(
247
- usdceToken.address,
248
- this.metadata.additionalInfo.vaultAllocator,
249
- amountToBeTransferred
250
- );
251
- const proofsInfo = extendedAdapter.getProofsForFromLegacySwap(
252
- this.getMerkleTree()
208
+ if (!usdceTransferAdapter) {
209
+ throw new Error(
210
+ `${this.getTag()} Usdce transfer adapter not configured in metadata.`
253
211
  );
254
- const proofGroups = proofsInfo.proofs;
255
- const call = this.getManageCall(
256
- proofGroups,
257
- await proofsInfo.callConstructor({ amount: amountToBeTransferred })
258
- );
259
- return {
260
- calls: [approveCall, transferCall, call],
261
- status: true,
262
- };
263
- } catch (err) {
264
- logger.error(`error moving assets to vault allocator: ${err}`);
265
- return {
266
- calls: [],
267
- status: false,
268
- };
269
212
  }
213
+ return usdceTransferAdapter.adapter as TokenTransferAdapter;
270
214
  }
271
215
 
272
- async shouldInvest(): Promise<{
273
- shouldInvest: boolean;
274
- vesuAmount: Web3Number;
275
- extendedAmount: Web3Number;
276
- extendedLeverage: number;
277
- collateralPrice: number;
278
- debtPrice: number;
279
- vesuLeverage: number;
280
- debtAmountToBeRepaid: Web3Number;
281
- }> {
282
- try {
283
- logger.info(
284
- `${VesuExtendedMultiplierStrategy.name}::shouldInvest starting`
285
- );
286
- const vesuAdapter = await this.getVesuAdapter();
287
- const extendedAdapter = await this.getExtendedAdapter();
288
- logger.info(
289
- `${
290
- VesuExtendedMultiplierStrategy.name
291
- }::shouldInvest adapters fetched: vesuAdapter=${!!vesuAdapter}, extendedAdapter=${!!extendedAdapter}, extendedAdapter.client=${!!extendedAdapter?.client}`
292
- );
293
-
294
- if (!vesuAdapter) {
295
- logger.error(
296
- `Vesu adapter not configured in metadata. This is a configuration issue, not a temporary failure.`
297
- );
298
- return {
299
- shouldInvest: false,
300
- vesuAmount: new Web3Number(0, 0),
301
- extendedAmount: new Web3Number(0, 0),
302
- extendedLeverage: 0,
303
- collateralPrice: 0,
304
- debtPrice: 0,
305
- vesuLeverage: 0,
306
- debtAmountToBeRepaid: new Web3Number(0, 0),
307
- };
308
- }
309
- if (!extendedAdapter) {
310
- logger.error(
311
- `Extended adapter not configured in metadata. This is a configuration issue, not a temporary failure.`
312
- );
313
- return {
314
- shouldInvest: false,
315
- vesuAmount: new Web3Number(0, 0),
316
- extendedAmount: new Web3Number(0, 0),
317
- extendedLeverage: 0,
318
- collateralPrice: 0,
319
- debtPrice: 0,
320
- vesuLeverage: 0,
321
- debtAmountToBeRepaid: new Web3Number(0, 0),
322
- };
323
- }
324
- if (!extendedAdapter.client) {
325
- logger.error(
326
- `Extended adapter client not initialized. This may be a temporary initialization failure - check network connectivity and API availability.`
327
- );
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
- debtAmountToBeRepaid: new Web3Number(0, 0),
337
- };
338
- }
339
-
340
- logger.info(
341
- `${VesuExtendedMultiplierStrategy.name}::shouldInvest calling getUnusedBalance`
342
- );
343
- const balance = await this.getUnusedBalance();
344
-
345
- if (!Number.isFinite(balance.usdValue) || balance.usdValue < 0) {
346
- logger.error(
347
- `Invalid balance.usdValue: ${balance.usdValue}. Expected a finite, non-negative number.`
348
- );
349
- return {
350
- shouldInvest: false,
351
- vesuAmount: new Web3Number(0, 0),
352
- extendedAmount: new Web3Number(0, 0),
353
- extendedLeverage: 0,
354
- collateralPrice: 0,
355
- debtPrice: 0,
356
- vesuLeverage: 0,
357
- debtAmountToBeRepaid: new Web3Number(0, 0),
358
- };
359
- }
360
- logger.info(
361
- `${VesuExtendedMultiplierStrategy.name}::shouldInvest balance: ${balance.usdValue}`
362
- );
363
- const usdcBalanceOnExtended =
364
- await extendedAdapter.getExtendedDepositAmount();
365
-
366
- if (usdcBalanceOnExtended) {
367
- const availableForTrade= parseFloat(
368
- usdcBalanceOnExtended.availableForTrade
369
- );
370
- if (
371
- !Number.isFinite(availableForTrade) ||
372
- availableForTrade < 0
373
- ) {
374
- logger.error(
375
- `Invalid usdcBalanceOnExtended.availableForWithdrawal: ${usdcBalanceOnExtended.availableForWithdrawal}. Expected a finite, non-negative number.`
376
- );
377
- return {
378
- shouldInvest: false,
379
- vesuAmount: new Web3Number(0, 0),
380
- extendedAmount: new Web3Number(0, 0),
381
- extendedLeverage: 0,
382
- collateralPrice: 0,
383
- debtPrice: 0,
384
- vesuLeverage: 0,
385
- debtAmountToBeRepaid: new Web3Number(0, 0),
386
- };
387
- }
388
- }
389
-
390
- const amountUsedFromExtended = new Web3Number(usdcBalanceOnExtended?.availableForTrade ?? 0, USDC_TOKEN_DECIMALS);
391
-
392
- /**
393
- * The LIMIT_BALANCE is the bffer amount to keep in the investing Cycle
394
- * 5% buffer amount is kept for extended amounts available to trade as they changes very suddenly
395
- */
396
- const amountToInvest = new Web3Number(
397
- balance.usdValue,
398
- USDC_TOKEN_DECIMALS
399
- )
400
- .plus(amountUsedFromExtended.multipliedBy(1 - LIMIT_BALANCE) ?? 0)
401
- .multipliedBy(1 - LIMIT_BALANCE);
402
-
403
- const amountToInvestNumber = amountToInvest.toNumber();
404
- if (!Number.isFinite(amountToInvestNumber)) {
405
- logger.error(
406
- `Invalid amountToInvest calculation result: ${amountToInvestNumber}. Calculation may have produced NaN or Infinity.`
407
- );
408
- return {
409
- shouldInvest: false,
410
- vesuAmount: new Web3Number(0, 0),
411
- extendedAmount: new Web3Number(0, 0),
412
- extendedLeverage: 0,
413
- collateralPrice: 0,
414
- debtPrice: 0,
415
- vesuLeverage: 0,
416
- debtAmountToBeRepaid: new Web3Number(0, 0),
417
- };
418
- }
419
-
420
- logger.info(
421
- `${VesuExtendedMultiplierStrategy.name}::shouldInvest amountToInvest: ${amountToInvestNumber}`
422
- );
423
-
424
- if (amountToInvest.lessThan(LIMIT_BALANCE_VALUE)) {
425
- return {
426
- shouldInvest: false,
427
- vesuAmount: new Web3Number(0, 0),
428
- extendedAmount: new Web3Number(0, 0),
429
- extendedLeverage: 0,
430
- collateralPrice: 0,
431
- debtPrice: 0,
432
- vesuLeverage: 0,
433
- debtAmountToBeRepaid: new Web3Number(0, 0),
434
- };
435
- }
436
-
437
- const extendedPositon = await extendedAdapter.getAllOpenPositions();
438
- if (!extendedPositon) {
439
- logger.error("error getting extended position to decide move assets");
440
- return {
441
- shouldInvest: false,
442
- vesuAmount: new Web3Number(0, 0),
443
- extendedAmount: new Web3Number(0, 0),
444
- extendedLeverage: 0,
445
- collateralPrice: 0,
446
- debtPrice: 0,
447
- vesuLeverage: 0,
448
- debtAmountToBeRepaid: new Web3Number(0, 0),
449
- };
450
- }
451
- const { collateralTokenAmount, debtTokenAmount } =
452
- await vesuAdapter.vesuAdapter.getAssetPrices();
453
-
454
- const { collateralPrice, debtPrice } = await this.getAssetPrices();
455
-
456
- if (
457
- !Number.isFinite(collateralPrice.price) ||
458
- collateralPrice.price <= 0
459
- ) {
460
- logger.error(
461
- `Invalid collateralPrice: ${collateralPrice.price}. Expected a finite, positive number.`
462
- );
463
- return {
464
- shouldInvest: false,
465
- vesuAmount: new Web3Number(0, 0),
466
- extendedAmount: new Web3Number(0, 0),
467
- extendedLeverage: 0,
468
- collateralPrice: 0,
469
- debtPrice: 0,
470
- vesuLeverage: 0,
471
- debtAmountToBeRepaid: new Web3Number(0, 0),
472
- };
473
- }
474
- if (!Number.isFinite(debtPrice.price) || debtPrice.price <= 0) {
475
- logger.error(
476
- `Invalid debtPrice: ${debtPrice.price}. Expected a finite, positive number.`
477
- );
478
- return {
479
- shouldInvest: false,
480
- vesuAmount: new Web3Number(0, 0),
481
- extendedAmount: new Web3Number(0, 0),
482
- extendedLeverage: 0,
483
- collateralPrice: 0,
484
- debtPrice: 0,
485
- vesuLeverage: 0,
486
- debtAmountToBeRepaid: new Web3Number(0, 0),
487
- };
488
- }
489
-
490
- let debtAmountToBeRepaid = calculateDeltaDebtAmount(
491
- MAX_LTV_BTC_USDC,
492
- collateralTokenAmount,
493
- debtTokenAmount,
494
- collateralPrice.price,
495
- debtPrice.price,
496
- this.metadata.additionalInfo.targetHealthFactor
497
- );
498
- if (!debtAmountToBeRepaid) {
499
- logger.error("error calculating debt amount to be repaid");
500
- return {
501
- shouldInvest: false,
502
- vesuAmount: new Web3Number(0, 0),
503
- extendedAmount: new Web3Number(0, 0),
504
- extendedLeverage: 0,
505
- collateralPrice: 0,
506
- debtPrice: 0,
507
- vesuLeverage: 0,
508
- debtAmountToBeRepaid: new Web3Number(0, 0),
509
- };
510
- }
511
- logger.info(
512
- `${
513
- VesuExtendedMultiplierStrategy.name
514
- }::shouldInvest debtAmountToBeRepaid: ${debtAmountToBeRepaid.toNumber()}`
515
- );
516
- /**
517
- * Since the debt amount is negative, we need to add it to the amount to invest
518
- * to maintain the ltv
519
- */
520
- const vesuLeverage = calculateVesuLeverage();
521
- debtAmountToBeRepaid = debtAmountToBeRepaid.toNumber() !== 0 ? debtAmountToBeRepaid.dividedBy(vesuLeverage-1) : new Web3Number(0, 0);
522
- const amountToInvestAfterRepayingDebt =
523
- amountToInvest.plus(debtAmountToBeRepaid);
524
- const { vesu_amount, extended_amount, extended_leverage, vesu_leverage } =
525
- await calculateAmountDistribution(
526
- amountToInvestAfterRepayingDebt.toNumber(),
527
- extendedAdapter.client,
528
- extendedAdapter.config.extendedMarketName,
529
- collateralPrice.price,
530
- debtPrice.price,
531
- collateralTokenAmount,
532
- extendedPositon
533
- );
534
- if (
535
- !vesu_amount ||
536
- !extended_amount ||
537
- !extended_leverage ||
538
- !vesu_leverage
539
- ) {
540
- logger.error(
541
- `Not enough amount to invest: vesu_amount=${vesu_amount}, extended_amount=${extended_amount}`
542
- );
543
- return {
544
- shouldInvest: false,
545
- vesuAmount: new Web3Number(0, 0),
546
- extendedAmount: new Web3Number(0, 0),
547
- extendedLeverage: 0,
548
- collateralPrice: 0,
549
- debtPrice: 0,
550
- vesuLeverage: 0,
551
- debtAmountToBeRepaid: new Web3Number(0, 0),
552
- };
553
- }
554
- logger.info(
555
- `${
556
- VesuExtendedMultiplierStrategy.name
557
- }::shouldInvest vesu_amount: ${vesu_amount.toNumber()}, extended_amount: ${extended_amount.toNumber()}`
216
+ async getAvnuAdapter(): Promise<AvnuAdapter> {
217
+ const avnuAdapter = this.metadata.additionalInfo.adapters.find(
218
+ (adapter) => adapter.adapter.name === AvnuAdapter.name,
219
+ );
220
+ if (!avnuAdapter) {
221
+ throw new Error(
222
+ `${this.getTag()} Avnu adapter not configured in metadata.`
558
223
  );
559
- return {
560
- shouldInvest: true,
561
- vesuAmount: vesu_amount,
562
- extendedAmount: extended_amount,
563
- extendedLeverage: extended_leverage,
564
- vesuLeverage: vesu_leverage,
565
- collateralPrice: collateralPrice.price,
566
- debtPrice: debtPrice.price,
567
- debtAmountToBeRepaid: debtAmountToBeRepaid,
568
- };
569
- } catch (err) {
570
- logger.error(`error deciding invest: ${err}`);
571
- return {
572
- shouldInvest: false,
573
- vesuAmount: new Web3Number(0, 0),
574
- extendedAmount: new Web3Number(0, 0),
575
- extendedLeverage: 0,
576
- collateralPrice: 0,
577
- debtPrice: 0,
578
- vesuLeverage: 0,
579
- debtAmountToBeRepaid: new Web3Number(0, 0),
580
- };
581
224
  }
225
+ return avnuAdapter.adapter as AvnuAdapter;
582
226
  }
583
227
 
584
- async shouldMoveAssets(
585
- extendedAmount: Web3Number,
586
- vesuAmount: Web3Number
587
- ): Promise<TransactionResult[]> {
588
- try {
589
- const vesuAdapter = await this.getVesuAdapter();
590
- const extendedAdapter = await this.getExtendedAdapter();
591
- if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
592
- logger.error(
593
- `vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
594
- );
595
- return [];
596
- }
597
-
598
- const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
599
- if (!extendedHoldings) {
600
- logger.error(`error getting extended holdings: ${extendedHoldings}`);
601
- return [];
602
- }
603
- const usdcAmountInWallet = (await this.getUnusedBalance()).amount;
604
- /**
605
- * Trade is the correct metric, since we can close position for some vesu amount, otherwise underutilisation of funds will be a huge issue
606
- */
607
- const usdcAmountOnExtendedAvailableForTrade = parseFloat(
608
- extendedHoldings.availableForTrade
609
- );
610
-
611
- logger.info(
612
- `${
613
- VesuExtendedMultiplierStrategy.name
614
- }::shouldMoveAssets calculating movements - Extended current: ${usdcAmountOnExtendedAvailableForTrade}, Wallet: ${usdcAmountInWallet.toNumber()}, Target Extended: ${extendedAmount.toNumber()}, Target Vesu: ${vesuAmount.toNumber()}`
615
- );
616
-
617
- let totalExtendedWithdrawal = new Web3Number(0, USDC_TOKEN_DECIMALS);
618
- let totalExtendedDeposit = new Web3Number(0, USDC_TOKEN_DECIMALS);
619
-
620
- if (
621
- extendedAmount.isNegative() &&
622
- extendedAmount
623
- .abs()
624
- .greaterThan(extendedAdapter.minimumExtendedMovementAmount)
625
- ) {
626
- totalExtendedWithdrawal = totalExtendedWithdrawal.plus(
627
- extendedAmount.abs()
628
- );
629
- }
630
-
631
- // Calculate remaining Extended difference (target vs current)
632
- // If extendedAmount was negative, we've already accounted for that withdrawal
633
- // So we calculate based on what Extended will be after that withdrawal
634
- const extendedTargetAmount = extendedAmount.abs(); // Use absolute value as target
635
- let projectedExtendedBalance = usdcAmountOnExtendedAvailableForTrade;
636
-
637
- if (extendedAmount.isNegative()) {
638
- projectedExtendedBalance =
639
- projectedExtendedBalance - extendedAmount.abs().toNumber();
640
- }
641
-
642
- const extendedAmountDifference = extendedTargetAmount.minus(
643
- projectedExtendedBalance
644
- );
645
- const extendedAmountDifferenceAbs = extendedAmountDifference.abs();
646
-
647
- // Track additional Extended movements
648
- if (extendedAmountDifference.lessThan(0)) {
649
- totalExtendedWithdrawal = totalExtendedWithdrawal.plus(
650
- extendedAmountDifferenceAbs
651
- );
652
- } else if (extendedAmountDifference.greaterThan(0)) {
653
- totalExtendedDeposit = totalExtendedDeposit.plus(
654
- extendedAmountDifference
655
- );
656
- }
657
-
658
- const vesuTargetAmount = vesuAmount.abs();
659
- const projectedWalletBalance = usdcAmountInWallet
660
- .plus(totalExtendedWithdrawal)
661
- .minus(totalExtendedDeposit);
662
-
663
- let vesuAmountDifference = vesuTargetAmount.minus(projectedWalletBalance);
664
- const vesuAmountDifferenceAbs = vesuAmountDifference.abs();
665
-
666
- logger.info(
667
- `${
668
- VesuExtendedMultiplierStrategy.name
669
- }::shouldMoveAssets calculated movements - Extended withdrawal: ${totalExtendedWithdrawal.toNumber()}, Extended deposit: ${totalExtendedDeposit.toNumber()}, Extended diff: ${extendedAmountDifference.toNumber()}, Projected wallet: ${projectedWalletBalance.toNumber()}, Vesu diff: ${vesuAmountDifference.toNumber()}`
228
+ async getExtendedAdapter(): Promise<ExtendedAdapter> {
229
+ const extendedAdapter = this.metadata.additionalInfo.adapters.find(
230
+ (adapter) => adapter.adapter.name === ExtendedAdapter.name,
231
+ );
232
+ if (!extendedAdapter) {
233
+ throw new Error(
234
+ `${this.getTag()} Extended adapter not configured in metadata.`
670
235
  );
671
- let transactionResults: TransactionResult[] = [];
672
-
673
- // Handle negative extendedAmount (initial withdrawal needed)
674
- if (
675
- extendedAmount.isNegative() &&
676
- extendedAmount
677
- .abs()
678
- .greaterThan(extendedAdapter.minimumExtendedMovementAmount)
679
- ) {
680
- try {
681
- const {
682
- calls: extendedCalls,
683
- status: extendedStatus,
684
- transactionMetadata: extendedTransactionMetadata,
685
- } = await this.moveAssets(
686
- {
687
- to: Protocols.VAULT.name,
688
- from: Protocols.EXTENDED.name,
689
- amount: extendedAmount.abs(),
690
- cycleType: CycleType.INVESTMENT,
691
- },
692
- extendedAdapter,
693
- vesuAdapter
694
- );
695
- if (extendedStatus) {
696
- transactionResults.push({
697
- status: extendedStatus,
698
- calls: extendedCalls,
699
- transactionMetadata: {
700
- ...extendedTransactionMetadata,
701
- transactionType: "DEPOSIT",
702
- },
703
- });
704
- } else {
705
- return [
706
- this.createTransactionResult(
707
- [],
708
- false,
709
- {
710
- from: Protocols.EXTENDED.name,
711
- to: Protocols.VAULT.name,
712
- amount: extendedAmount.abs(),
713
- },
714
- "NONE",
715
- CycleType.INVESTMENT
716
- ),
717
- ];
718
- }
719
- } catch (err) {
720
- logger.error(`Failed moving assets to vault: ${err}`);
721
- return [
722
- this.createTransactionResult(
723
- [],
724
- false,
725
- {
726
- from: Protocols.EXTENDED.name,
727
- to: Protocols.VAULT.name,
728
- amount: extendedAmount.abs(),
729
- },
730
- "NONE",
731
- CycleType.INVESTMENT
732
- ),
733
- ];
734
- }
735
- }
736
-
737
- if (
738
- vesuAmount.isNegative() &&
739
- vesuAmount.abs().greaterThan(vesuAdapter.minimumVesuMovementAmount)
740
- ) {
741
- try {
742
- const {
743
- calls: vesuCalls,
744
- status: vesuStatus,
745
- transactionMetadata: vesuTransactionMetadata,
746
- } = await this.moveAssets(
747
- {
748
- to: Protocols.EXTENDED.name,
749
- from: Protocols.VESU.name,
750
- amount: vesuAmount.abs(),
751
- cycleType: CycleType.INVESTMENT,
752
- },
753
- extendedAdapter,
754
- vesuAdapter
755
- );
756
- if (!vesuStatus) {
757
- return [
758
- this.createTransactionResult(
759
- [],
760
- false,
761
- {
762
- from: Protocols.VESU.name,
763
- to: Protocols.EXTENDED.name,
764
- amount: vesuAmount.abs(),
765
- },
766
- "NONE",
767
- CycleType.INVESTMENT
768
- ),
769
- ];
770
- }
771
- transactionResults.push({
772
- status: vesuStatus,
773
- calls: vesuCalls,
774
- transactionMetadata: {
775
- ...vesuTransactionMetadata,
776
- transactionType: "DEPOSIT",
777
- },
778
- });
779
- } catch (err) {
780
- logger.error(
781
- `Failed moving assets to extended via vault allocator: ${err}`
782
- );
783
- return [
784
- this.createTransactionResult(
785
- [],
786
- false,
787
- {
788
- from: Protocols.VESU.name,
789
- to: Protocols.EXTENDED.name,
790
- amount: vesuAmount.abs(),
791
- },
792
- "NONE",
793
- CycleType.INVESTMENT
794
- ),
795
- ];
796
- }
797
- }
798
-
799
- // Handle Extended adjustments based on calculated difference
800
- if (
801
- extendedAmountDifferenceAbs.greaterThan(
802
- extendedAdapter.minimumExtendedMovementAmount
803
- )
804
- ) {
805
- if (extendedAmountDifference.greaterThan(0)) {
806
- try {
807
- const {
808
- calls: extendedCalls,
809
- status: extendedStatus,
810
- transactionMetadata: extendedTransactionMetadata,
811
- } = await this.moveAssets(
812
- {
813
- to: Protocols.EXTENDED.name,
814
- from: Protocols.VAULT.name,
815
- amount: extendedAmountDifference,
816
- cycleType: CycleType.INVESTMENT,
817
- },
818
- extendedAdapter,
819
- vesuAdapter
820
- );
821
- if (extendedStatus) {
822
- transactionResults.push({
823
- status: extendedStatus,
824
- calls: extendedCalls,
825
- transactionMetadata: extendedTransactionMetadata,
826
- });
827
- } else {
828
- logger.error(
829
- `Failed to move assets to extended - operation returned false status`
830
- );
831
- return [
832
- this.createTransactionResult(
833
- [],
834
- false,
835
- {
836
- from: Protocols.VAULT.name,
837
- to: Protocols.EXTENDED.name,
838
- amount: extendedAmountDifference,
839
- },
840
- "NONE",
841
- CycleType.INVESTMENT
842
- ),
843
- ];
844
- }
845
- } catch (err) {
846
- logger.error(`Failed moving assets to extended: ${err}`);
847
- return [
848
- this.createTransactionResult(
849
- [],
850
- false,
851
- {
852
- from: Protocols.VAULT.name,
853
- to: Protocols.EXTENDED.name,
854
- amount: extendedAmountDifference,
855
- },
856
- "NONE",
857
- CycleType.INVESTMENT
858
- ),
859
- ];
860
- }
861
- } else if (extendedAmountDifference.lessThan(0)) {
862
- try {
863
- const {
864
- calls: extendedCalls,
865
- status: extendedStatus,
866
- transactionMetadata: extendedTransactionMetadata,
867
- } = await this.moveAssets(
868
- {
869
- to: Protocols.VAULT.name,
870
- from: Protocols.EXTENDED.name,
871
- amount: extendedAmountDifferenceAbs,
872
- cycleType: CycleType.INVESTMENT,
873
- },
874
- extendedAdapter,
875
- vesuAdapter
876
- );
877
- if (extendedStatus) {
878
- transactionResults.push({
879
- status: extendedStatus,
880
- calls: extendedCalls,
881
- transactionMetadata: {
882
- ...extendedTransactionMetadata,
883
- transactionType: "DEPOSIT",
884
- },
885
- });
886
- } else {
887
- logger.error(
888
- `Failed to withdraw from extended - operation returned false status`
889
- );
890
- return [
891
- this.createTransactionResult(
892
- [],
893
- false,
894
- {
895
- from: Protocols.EXTENDED.name,
896
- to: Protocols.VAULT.name,
897
- amount: extendedAmountDifferenceAbs,
898
- },
899
- "NONE",
900
- CycleType.INVESTMENT
901
- ),
902
- ];
903
- }
904
- } catch (err) {
905
- logger.error(`Failed moving assets from extended to vault: ${err}`);
906
- return [
907
- this.createTransactionResult(
908
- [],
909
- false,
910
- {
911
- from: Protocols.EXTENDED.name,
912
- to: Protocols.VAULT.name,
913
- amount: extendedAmountDifferenceAbs,
914
- },
915
- "NONE",
916
- CycleType.INVESTMENT
917
- ),
918
- ];
919
- }
920
- }
921
- }
922
-
923
- // Handle Vesu adjustments based on calculated difference (already adjusted for Extended movements)
924
- if (
925
- vesuAmountDifferenceAbs.greaterThan(
926
- vesuAdapter.minimumVesuMovementAmount
927
- )
928
- ) {
929
- if (vesuAmountDifference.lessThanOrEqualTo(0)) {
930
- logger.warn(
931
- `Vesu amount difference is negative or zero: ${vesuAmountDifference.toNumber()}. Skipping operation.`
932
- );
933
- } else {
934
- // Move assets from Extended to Vault (which will then go to Vesu)
935
- try {
936
- const {
937
- calls: vesuCalls,
938
- status: vesuStatus,
939
- transactionMetadata: vesuTransactionMetadata,
940
- } = await this.moveAssets(
941
- {
942
- to: Protocols.VAULT.name,
943
- from: Protocols.EXTENDED.name,
944
- amount: vesuAmountDifference,
945
- cycleType: CycleType.INVESTMENT,
946
- },
947
- extendedAdapter,
948
- vesuAdapter
949
- );
950
- if (!vesuStatus) {
951
- logger.error(
952
- `Failed to move assets to vesu - operation returned false status`
953
- );
954
- return [
955
- this.createTransactionResult(
956
- [],
957
- false,
958
- {
959
- from: Protocols.EXTENDED.name,
960
- to: Protocols.VAULT.name,
961
- amount: vesuAmountDifference,
962
- },
963
- "NONE",
964
- CycleType.INVESTMENT
965
- ),
966
- ];
967
- }
968
- transactionResults.push({
969
- status: vesuStatus,
970
- calls: vesuCalls,
971
- transactionMetadata: {
972
- ...vesuTransactionMetadata,
973
- transactionType: "DEPOSIT",
974
- },
975
- });
976
- } catch (err) {
977
- logger.error(`Failed moving assets to vault: ${err}`);
978
- return [
979
- this.createTransactionResult(
980
- [],
981
- false,
982
- {
983
- from: Protocols.EXTENDED.name,
984
- to: Protocols.VAULT.name,
985
- amount: vesuAmountDifference,
986
- },
987
- "NONE",
988
- CycleType.INVESTMENT
989
- ),
990
- ];
991
- }
992
- }
993
- }
994
- return transactionResults;
995
- } catch (err) {
996
- logger.error(`Failed moving assets to vesu: ${err}`);
997
- return [
998
- this.createTransactionResult(
999
- [],
1000
- false,
1001
- {
1002
- from: Protocols.EXTENDED.name,
1003
- to: Protocols.VAULT.name,
1004
- amount: new Web3Number(0, USDC_TOKEN_DECIMALS),
1005
- },
1006
- "NONE",
1007
- CycleType.INVESTMENT
1008
- ),
1009
- ];
1010
- }
1011
- }
1012
-
1013
- /**
1014
- * Helper method to create transaction result with metadata
1015
- */
1016
- private createTransactionResult(
1017
- calls: Call[],
1018
- status: boolean,
1019
- params: { from: string; to: string; amount: Web3Number },
1020
- transactionType: "DEPOSIT" | "WITHDRAWAL" | "NONE",
1021
- cycleType: CycleType
1022
- ): TransactionResult {
1023
- if (status) {
1024
- return {
1025
- calls,
1026
- status: status,
1027
- transactionMetadata: {
1028
- protocolFrom: params.from,
1029
- protocolTo: params.to,
1030
- transactionType: transactionType,
1031
- usdAmount: params.amount.abs().toFixed(),
1032
- status: "PENDING",
1033
- cycleType: cycleType,
1034
- },
1035
- };
1036
236
  }
1037
- return {
1038
- calls: [],
1039
- status: false,
1040
- transactionMetadata: {
1041
- protocolFrom: "",
1042
- protocolTo: "",
1043
- transactionType: "DEPOSIT",
1044
- usdAmount: "0",
1045
- status: "FAILED",
1046
- cycleType: cycleType,
1047
- },
1048
- };
1049
- }
1050
-
1051
- /**
1052
- * This method is used to move assets between protocols
1053
- * @param params - The parameters for the move assets operation
1054
- * @param extendedAdapter - The extended adapter
1055
- * @param vesuAdapter - The vesu adapter
1056
- * @returns The transaction result
1057
- * If Extended amount is greater than amount of withdrawal from extended, then we need to open a long position
1058
- * so that the amount of withdrawal from extended is fullfilled
1059
- */
1060
- async moveAssets(
1061
- params: {
1062
- amount: Web3Number;
1063
- from: string;
1064
- to: string;
1065
- cycleType: CycleType;
1066
- },
1067
- extendedAdapter: ExtendedAdapter,
1068
- vesuAdapter: VesuMultiplyAdapter
1069
- ): Promise<TransactionResult> {
1070
- try {
1071
- // Validate amount is positive before starting operations
1072
- if (params.amount.lessThanOrEqualTo(0)) {
1073
- logger.error(
1074
- `Invalid amount for moveAssets: ${params.amount.toNumber()}. Amount must be positive.`
1075
- );
1076
- return this.createTransactionResult(
1077
- [],
1078
- false,
1079
- params,
1080
- "NONE",
1081
- params.cycleType
1082
- );
1083
- }
1084
-
1085
- // Check minimum movement amounts before starting operations
1086
- // const amountAbs = params.amount.abs();
1087
- // if (params.from === Protocols.EXTENDED.name || params.to === Protocols.EXTENDED.name) {
1088
- // if (amountAbs.lessThanOrEqualTo(extendedAdapter.minimumExtendedMovementAmount)) {
1089
- // logger.warn(
1090
- // `Amount ${amountAbs.toNumber()} is below minimum Extended movement amount ${extendedAdapter.minimumExtendedMovementAmount}. Skipping operation.`
1091
- // );
1092
- // return this.createTransactionResult([], false, params, "NONE", params.cycleType);
1093
- // }
1094
- // }
1095
- // if (params.from === Protocols.VESU.name || params.to === Protocols.VESU.name) {
1096
- // if (amountAbs.lessThanOrEqualTo(vesuAdapter.minimumVesuMovementAmount)) {
1097
- // logger.warn(
1098
- // `Amount ${amountAbs.toNumber()} is below minimum Vesu movement amount ${vesuAdapter.minimumVesuMovementAmount}. Skipping operation.`
1099
- // );
1100
- // return this.createTransactionResult([], false, params, "NONE", params.cycleType);
1101
- // }
1102
- // }
1103
-
1104
- const avnuAdapter = await this.getAvnuAdapter();
1105
- if (!avnuAdapter) {
1106
- logger.error(`avnu adapter not found: ${avnuAdapter}`);
1107
- return this.createTransactionResult(
1108
- [],
1109
- false,
1110
- params,
1111
- "NONE",
1112
- params.cycleType
1113
- );
1114
- }
1115
- logger.info(`moveAssets params, ${JSON.stringify(params)}`);
1116
- const collateralToken = vesuAdapter.config.supportedPositions[0].asset;
1117
- const { collateralPrice } = await this.getAssetPrices();
1118
-
1119
- if (
1120
- params.to === Protocols.EXTENDED.name &&
1121
- params.from === Protocols.VAULT.name
1122
- ) {
1123
- const proofsInfo = extendedAdapter.getProofs(
1124
- true,
1125
- this.getMerkleTree()
1126
- );
1127
- const calls = [];
1128
- const proofGroups = proofsInfo.proofs;
1129
- const call = this.getManageCall(
1130
- proofGroups,
1131
- await proofsInfo.callConstructor({ amount: params.amount })
1132
- );
1133
- calls.push(call);
1134
- return this.createTransactionResult(
1135
- calls,
1136
- true,
1137
- params,
1138
- "DEPOSIT",
1139
- params.cycleType
1140
- );
1141
- } else if (
1142
- params.to === Protocols.VAULT.name &&
1143
- params.from === Protocols.EXTENDED.name
1144
- ) {
1145
- const extendedLeverage = calculateExtendedLevergae();
1146
- const extendedHoldings =
1147
- await extendedAdapter.getExtendedDepositAmount();
1148
- if (!extendedHoldings) {
1149
- logger.error(`error getting extended holdings: ${extendedHoldings}`);
1150
- return this.createTransactionResult(
1151
- [],
1152
- false,
1153
- params,
1154
- "NONE",
1155
- params.cycleType
1156
- );
1157
- }
1158
- const extendedHoldingAmount = new Web3Number(
1159
- extendedHoldings.availableForWithdrawal,
1160
- USDC_TOKEN_DECIMALS
1161
- );
1162
- logger.info(
1163
- `${
1164
- VesuExtendedMultiplierStrategy.name
1165
- }::moveAssets extendedHoldingAmount: ${extendedHoldingAmount.toNumber()}`
1166
- );
1167
- const extendedPositions = await extendedAdapter.getAllOpenPositions();
1168
- if (!extendedPositions) {
1169
- logger.error(
1170
- `error getting extended positions: ${extendedPositions} while moving assets from extended to vault`
1171
- );
1172
- return this.createTransactionResult(
1173
- [],
1174
- false,
1175
- params,
1176
- "NONE",
1177
- params.cycleType
1178
- );
1179
- }
1180
- if (params.amount.abs().greaterThan(extendedHoldingAmount)) {
1181
- const leftAmountAfterWithdrawalAmountInAccount = new Web3Number(
1182
- Math.ceil(
1183
- params.amount.abs().minus(extendedHoldingAmount).toNumber()
1184
- ),
1185
- USDC_TOKEN_DECIMALS
1186
- );
1187
- const positionAmountToClose =
1188
- await calculatePositionToCloseToWithdrawAmount(
1189
- extendedHoldings,
1190
- extendedPositions[0],
1191
- params.amount
1192
- );
1193
- logger.info(
1194
- `positionAmountToClose: ${positionAmountToClose} this is without leverage`
1195
- );
1196
- logger.info(
1197
- `${
1198
- VesuExtendedMultiplierStrategy.name
1199
- }::moveAssets leftAmountAfterWithdrawalAmountInAccount: ${leftAmountAfterWithdrawalAmountInAccount.toNumber()}`
1200
- );
1201
- let priceOfBTC;
1202
- const { ask, bid, status } =
1203
- await extendedAdapter.fetchOrderBookBTCUSDC();
1204
- const price = ask.plus(bid).dividedBy(2);
1205
- if (status) {
1206
- priceOfBTC = price;
1207
- } else {
1208
- logger.error(`error fetching order book btc usdc: ${status}`);
1209
- priceOfBTC = collateralPrice.price;
1210
- }
1211
- const btcAmount = positionAmountToClose.dividedBy(priceOfBTC);
1212
- /**
1213
- * If amount for withdrawal is greater than the amount in extended available for withdrawal,
1214
- * then we need to open a long position depending on the difference between the two
1215
- */
1216
- const openLongPosition = btcAmount
1217
- .multipliedBy(3)
1218
- .greaterThan(MINIMUM_EXTENDED_POSITION_SIZE)
1219
- ? await extendedAdapter.createOrder(
1220
- extendedLeverage.toString(),
1221
- btcAmount.toNumber(),
1222
- OrderSide.BUY
1223
- )
1224
- : await extendedAdapter.createOrder(
1225
- extendedLeverage.toString(),
1226
- 0.000034, // just in case amount falls short then we need to create a withdrawal
1227
- OrderSide.BUY
1228
- );
1229
- if (!openLongPosition) {
1230
- logger.error(`error opening long position: ${openLongPosition}`);
1231
- }
1232
- const updatedHoldings =
1233
- await extendedAdapter.getExtendedDepositAmount();
1234
- if (
1235
- !updatedHoldings ||
1236
- new Web3Number(
1237
- updatedHoldings.availableForWithdrawal,
1238
- USDC_TOKEN_DECIMALS
1239
- ).lessThan(params.amount.abs())
1240
- ) {
1241
- logger.error(
1242
- `Insufficient balance after opening position. Available: ${
1243
- updatedHoldings?.availableForWithdrawal
1244
- }, Needed: ${params.amount.abs()}`
1245
- );
1246
- return this.createTransactionResult(
1247
- [],
1248
- false,
1249
- params,
1250
- "NONE",
1251
- params.cycleType
1252
- );
1253
- }
1254
- }
1255
- const {
1256
- status: withdrawalFromExtendedStatus,
1257
- receivedTxnHash: withdrawalFromExtendedTxnHash,
1258
- } = await extendedAdapter.withdrawFromExtended(params.amount);
1259
- /**
1260
- * This logic needs fixing
1261
- */
1262
- logger.info(
1263
- `withdrawalFromExtendedStatus: ${withdrawalFromExtendedStatus}, withdrawalFromExtendedTxnHash: ${withdrawalFromExtendedTxnHash}`
1264
- );
1265
- if (withdrawalFromExtendedStatus && withdrawalFromExtendedTxnHash) {
1266
- /**
1267
- * We need to move assets from my wallet back to vault contract
1268
- */
1269
- const extendedHoldings =
1270
- await extendedAdapter.getExtendedDepositAmount();
1271
- logger.info(
1272
- `extendedHoldings after withdrawal ${extendedHoldings?.availableForWithdrawal}`
1273
- );
1274
- await new Promise((resolve) => setTimeout(resolve, 5000));
1275
- const { calls, status } = await this.moveAssetsToVaultAllocator(
1276
- params.amount,
1277
- extendedAdapter
1278
- );
1279
- if (calls.length > 0 && status) {
1280
- return this.createTransactionResult(
1281
- calls,
1282
- true,
1283
- params,
1284
- "WITHDRAWAL",
1285
- params.cycleType
1286
- );
1287
- } else {
1288
- /**
1289
- * This is a fallback scenario, where the funds were withdrawn from extended, but didn't get transferred to the wallet
1290
- * We need to return a successful transaction result, but with no calls
1291
- * Db update will be handled by the risk engine for this specific case
1292
- */
1293
- return this.createTransactionResult(
1294
- [],
1295
- true,
1296
- params,
1297
- "WITHDRAWAL",
1298
- params.cycleType
1299
- );
1300
- }
1301
- } else if (
1302
- withdrawalFromExtendedStatus &&
1303
- !withdrawalFromExtendedTxnHash
1304
- ) {
1305
- logger.error(
1306
- "withdrawal from extended successful, but funds didn't get transferred to the wallet"
1307
- );
1308
- return this.createTransactionResult(
1309
- [],
1310
- true,
1311
- params,
1312
- "WITHDRAWAL",
1313
- params.cycleType
1314
- );
1315
- } else {
1316
- logger.error("withdrawal from extended failed");
1317
- return this.createTransactionResult(
1318
- [],
1319
- false,
1320
- params,
1321
- "NONE",
1322
- params.cycleType
1323
- );
1324
- }
1325
- } else if (
1326
- params.to === Protocols.VAULT.name &&
1327
- params.from === Protocols.VESU.name
1328
- ) {
1329
- const isPriceDifferenceBetweenAvnuAndExtended =
1330
- await this.checkPriceDifferenceBetweenAvnuAndExtended(
1331
- extendedAdapter,
1332
- vesuAdapter,
1333
- avnuAdapter,
1334
- PositionTypeAvnuExtended.CLOSE
1335
- );
1336
- if (
1337
- !isPriceDifferenceBetweenAvnuAndExtended &&
1338
- params.cycleType === CycleType.WITHDRAWAL
1339
- ) {
1340
- logger.warn(
1341
- `price difference between avnu and extended doesn't fit the range for close position, ${avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing}`
1342
- );
1343
- return this.createTransactionResult(
1344
- [],
1345
- false,
1346
- params,
1347
- "NONE",
1348
- params.cycleType
1349
- );
1350
- }
1351
- //withdraw from vesu
1352
- const vesuAmountInBTC = new Web3Number(
1353
- params.amount
1354
- .dividedBy(collateralPrice.price)
1355
- .toFixed(WBTC_TOKEN_DECIMALS),
1356
- collateralToken.decimals
1357
- );
1358
- const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
1359
- const calls = [];
1360
- const proofGroups = proofsInfo.proofs;
1361
- const call = this.getManageCall(
1362
- proofGroups,
1363
- await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
1364
- );
1365
- calls.push(call);
1366
- const swapProofsInfo = avnuAdapter.getProofs(
1367
- false,
1368
- this.getMerkleTree()
1369
- );
1370
- const swapProofGroups = swapProofsInfo.proofs;
1371
- const swapCall = this.getManageCall(
1372
- swapProofGroups,
1373
- await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
1374
- );
1375
- calls.push(swapCall);
1376
- return this.createTransactionResult(
1377
- calls,
1378
- true,
1379
- params,
1380
- "WITHDRAWAL",
1381
- params.cycleType
1382
- );
1383
- } else if (
1384
- params.to === Protocols.EXTENDED.name &&
1385
- params.from === Protocols.VESU.name
1386
- ) {
1387
- const isPriceDifferenceBetweenAvnuAndExtended =
1388
- await this.checkPriceDifferenceBetweenAvnuAndExtended(
1389
- extendedAdapter,
1390
- vesuAdapter,
1391
- avnuAdapter,
1392
- PositionTypeAvnuExtended.CLOSE
1393
- );
1394
- if (!isPriceDifferenceBetweenAvnuAndExtended) {
1395
- logger.warn(
1396
- `price difference between avnu and extended doesn't fit the range for close position, ${avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing}`
1397
- );
1398
- return this.createTransactionResult(
1399
- [],
1400
- false,
1401
- params,
1402
- "NONE",
1403
- params.cycleType
1404
- );
1405
- }
1406
- const vesuAmountInBTC = new Web3Number(
1407
- params.amount.dividedBy(collateralPrice.price).toNumber(),
1408
- collateralToken.decimals
1409
- );
1410
- const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
1411
- const calls = [];
1412
- const proofGroups = proofsInfo.proofs;
1413
- const call = this.getManageCall(
1414
- proofGroups,
1415
- await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
1416
- );
1417
- calls.push(call);
1418
- const swapProofsInfo = avnuAdapter.getProofs(
1419
- false,
1420
- this.getMerkleTree()
1421
- );
1422
- const swapProofGroups = swapProofsInfo.proofs;
1423
- const swapCall = this.getManageCall(
1424
- swapProofGroups,
1425
- await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
1426
- );
1427
- calls.push(swapCall);
1428
- const proofsInfoDeposit = extendedAdapter.getProofs(
1429
- true,
1430
- this.getMerkleTree()
1431
- );
1432
- //Deposit Amount would still be in usdc
1433
- const proofGroupsDeposit = proofsInfoDeposit.proofs;
1434
- const callDeposit = this.getManageCall(
1435
- proofGroupsDeposit,
1436
- await proofsInfoDeposit.callConstructor({ amount: params.amount })
1437
- );
1438
- calls.push(callDeposit);
1439
- return this.createTransactionResult(
1440
- calls,
1441
- true,
1442
- params,
1443
- "DEPOSIT",
1444
- params.cycleType
1445
- );
1446
- }
1447
- logger.error(
1448
- `Unsupported assets movement: ${params.from} to ${params.to}`
1449
- );
1450
- return this.createTransactionResult(
1451
- [],
1452
- false,
1453
- params,
1454
- "NONE",
1455
- params.cycleType
1456
- );
1457
- } catch (err) {
1458
- logger.error(`error moving assets: ${err}`);
1459
- return this.createTransactionResult(
1460
- [],
1461
- false,
1462
- params,
1463
- "NONE",
1464
- params.cycleType
237
+ if (!extendedAdapter.adapter || !(extendedAdapter.adapter as ExtendedAdapter).client) {
238
+ throw new Error(
239
+ `${this.getTag()} Extended adapter client not initialized.`
1465
240
  );
1466
241
  }
242
+ return extendedAdapter.adapter as ExtendedAdapter;
1467
243
  }
1468
244
 
1469
- async handleDeposit(): Promise<TransactionResult> {
1470
- try {
1471
- /**
1472
- * Just a demo function, not used in the risk engine
1473
- */
1474
- return this.createTransactionResult(
1475
- [],
1476
- false,
1477
- {
1478
- from: Protocols.VAULT.name,
1479
- to: Protocols.VAULT.name,
1480
- amount: new Web3Number(0, 0),
1481
- },
1482
- "NONE",
1483
- CycleType.INVESTMENT
1484
- );
1485
- } catch (err) {
1486
- logger.error(`error handling deposit: ${err}`);
1487
- return this.createTransactionResult(
1488
- [],
1489
- false,
1490
- {
1491
- from: Protocols.VAULT.name,
1492
- to: Protocols.VAULT.name,
1493
- amount: new Web3Number(0, 0),
1494
- },
1495
- "NONE",
1496
- CycleType.INVESTMENT
245
+ async getUsdcToUsdceAdapter(): Promise<UsdcToUsdceAdapter> {
246
+ const usdcToUsdceAdapter = this.metadata.additionalInfo.adapters.find(
247
+ (adapter) => adapter.adapter.name === UsdcToUsdceAdapter.name,
248
+ );
249
+ if (!usdcToUsdceAdapter) {
250
+ throw new Error(
251
+ `${this.getTag()} UsdcToUsdce adapter not configured in metadata.`
1497
252
  );
1498
253
  }
254
+ return usdcToUsdceAdapter.adapter as UsdcToUsdceAdapter;
1499
255
  }
1500
256
 
1501
257
  /**
1502
- * Check if the price difference between avnu and extended is within the acceptable range to enhance the position size or close the position
1503
- * @param extendedAdapter - the extended adapter
1504
- * @param vesuAdapter - the vesu adapter
1505
- * @param avnuAdapter - the avnu adapter
1506
- * @param positionType - the position type (open or close)
1507
- * @returns true if the price difference is within the acceptable range, false otherwise
258
+ * Creates an ExecutionService wired to this strategy's adapters and config.
259
+ * Use with `stateManager.solve()` to get a SolveResult, then pass it to
260
+ * `executionService.execute(solveResult)` for execution.
261
+ *
262
+ * @param onExecutionEvent - Optional callback for execution lifecycle events (DB persistence, alerts, etc.)
263
+ * @param extendedAcceptableSlippageBps - Slippage for Extended limit orders (default: 10 = 0.1%)
264
+ * @param maxPriceDivergenceBps - Max price divergence between Extended and AVNU (default: 50 = 0.5%)
1508
265
  */
1509
- async checkPriceDifferenceBetweenAvnuAndExtended(
1510
- extendedAdapter: ExtendedAdapter,
1511
- vesuAdapter: VesuMultiplyAdapter,
1512
- avnuAdapter: AvnuAdapter,
1513
- positionType: PositionTypeAvnuExtended
1514
- ): Promise<boolean> {
1515
- const { ask, bid } = await extendedAdapter.fetchOrderBookBTCUSDC();
1516
- const price = ask.plus(bid).dividedBy(2);
1517
- const btcToken = vesuAdapter.config.supportedPositions[0].asset;
1518
- const btcPriceAvnu = await avnuAdapter.getPriceOfToken(
1519
- btcToken.address.toString()
1520
- );
266
+ async createExecutionService(opts?: {
267
+ onExecutionEvent?: ExecutionCallback;
268
+ extendedAcceptableSlippageBps?: number;
269
+ maxPriceDivergenceBps?: number;
270
+ }): Promise<ExecutionService> {
271
+ const [
272
+ vesuAdapter,
273
+ vesuModifyPositionAdapter,
274
+ usdceTransferAdapter,
275
+ extendedAdapter,
276
+ avnuAdapter,
277
+ usdcToUsdceAdapter,
278
+ ] =
279
+ await Promise.all([
280
+ this.getVesuAdapter(),
281
+ this.getVesuModifyPositionAdapter(),
282
+ this.getUsdceTransferAdapter(),
283
+ this.getExtendedAdapter(),
284
+ this.getAvnuAdapter(),
285
+ this.getUsdcToUsdceAdapter(),
286
+ ]);
287
+
288
+ const executionConfig: ExecutionConfig = {
289
+ networkConfig: this.config,
290
+ pricer: this.pricer,
291
+ vesuAdapter,
292
+ vesuModifyPositionAdapter,
293
+ extendedAdapter,
294
+ avnuAdapter,
295
+ usdcToUsdceAdapter,
296
+ vaultAllocator: this.metadata.additionalInfo.vaultAllocator,
297
+ walletAddress: this.metadata.additionalInfo.walletAddress,
298
+ usdceTransferAdapter,
299
+ wbtcToken: this.wbtcToken,
300
+ usdcToken: this.usdcToken,
301
+ usdceToken: this.usdceToken,
302
+ getMerkleTree: () => this.getMerkleTree(),
303
+ getManageCall: (proofs, manageCalls) => this.getManageCall(proofs, manageCalls),
304
+ getBringLiquidityCall: (params) => this.getBringLiquidityCall(params),
305
+ onExecutionEvent: opts?.onExecutionEvent,
306
+ extendedAcceptableSlippageBps: opts?.extendedAcceptableSlippageBps,
307
+ maxPriceDivergenceBps: opts?.maxPriceDivergenceBps,
308
+ };
1521
309
 
1522
- if (!btcPriceAvnu) {
1523
- logger.error(`error getting btc price avnu: ${btcPriceAvnu}`);
1524
- return false;
1525
- }
1526
- logger.info(`price: ${price}`);
1527
- logger.info(`btcPriceAvnu: ${btcPriceAvnu}`);
1528
- const priceDifference = new Web3Number(
1529
- price.minus(btcPriceAvnu).toFixed(2),
1530
- 0
1531
- );
1532
- logger.info(`priceDifference: ${priceDifference}`);
1533
- if (priceDifference.isNegative()) {
1534
- return false;
1535
- }
1536
- if (positionType === PositionTypeAvnuExtended.OPEN) {
1537
- logger.info(
1538
- `price difference between avnu and extended for open position: ${priceDifference.toNumber()}, minimumExtendedPriceDifferenceForSwapOpen: ${
1539
- avnuAdapter.config.minimumExtendedPriceDifferenceForSwapOpen
1540
- }`
1541
- );
1542
- const result = priceDifference.greaterThanOrEqualTo(
1543
- avnuAdapter.config.minimumExtendedPriceDifferenceForSwapOpen
1544
- ); // 500 for now
1545
- logger.info(`result: ${result}`);
1546
- return result;
1547
- } else {
1548
- logger.info(
1549
- `price difference between avnu and extended for close position: ${priceDifference.toNumber()}, maximumExtendedPriceDifferenceForSwapClosing: ${
1550
- avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing
1551
- }`
1552
- );
1553
- const result = priceDifference.lessThanOrEqualTo(
1554
- avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing
1555
- ); // 1000 for now
1556
- logger.info(`result: ${result}`);
1557
- return result;
1558
- }
310
+ return new ExecutionService(executionConfig);
1559
311
  }
1560
312
 
1561
313
  /**
1562
- * Handle the withdrawal of assets from the vault
1563
- * @param amount - the amount to withdraw in USDC
1564
- * @returns the calls to be executed and the status of the calls generated along with the metadata for the calls
314
+ * Calculates the total Assets Under Management across all adapters.
315
+ * Aggregates position values from every adapter, converts to the vault's
316
+ * base asset, and returns the net AUM along with the previous AUM snapshot
317
+ * and per-position breakdowns.
1565
318
  */
1566
- async handleWithdraw(amount: Web3Number): Promise<TransactionResult[]> {
1567
- try {
1568
- const usdcBalanceVaultAllocator = await this.getUnusedBalance();
1569
- const usdcBalanceDifference = amount
1570
- .plus(BUFFER_USDC_IN_WITHDRAWAL)
1571
- .minus(usdcBalanceVaultAllocator.usdValue);
1572
- logger.info(`usdcBalanceDifference, ${usdcBalanceDifference.toNumber()}`);
1573
- let calls: Call[] = [];
1574
- let status: boolean = true;
1575
- if (usdcBalanceDifference.lessThan(0)) {
1576
- const withdrawCall = await this.getBringLiquidityCall({
1577
- amount: usdcBalanceVaultAllocator.amount,
1578
- });
1579
- calls.push(withdrawCall);
1580
- return [
1581
- this.createTransactionResult(
1582
- calls,
1583
- true,
1584
- {
1585
- from: Protocols.VAULT.name,
1586
- to: Protocols.NONE.name,
1587
- amount: amount,
1588
- },
1589
- "WITHDRAWAL",
1590
- CycleType.WITHDRAWAL
1591
- ),
1592
- ];
1593
- }
1594
- const vesuAdapter = await this.getVesuAdapter();
1595
- const extendedAdapter = await this.getExtendedAdapter();
1596
- if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
1597
- status = false;
1598
- logger.error(
1599
- `vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
1600
- );
1601
- return [
1602
- this.createTransactionResult(
1603
- calls,
1604
- status,
1605
- {
1606
- from: Protocols.VAULT.name,
1607
- to: Protocols.NONE.name,
1608
- amount: amount,
1609
- },
1610
- "NONE",
1611
- CycleType.WITHDRAWAL
1612
- ),
1613
- ];
1614
- }
1615
- let transactionResults: TransactionResult[] = [];
1616
- const { collateralTokenAmount } =
1617
- await vesuAdapter.vesuAdapter.getAssetPrices();
1618
- const { collateralPrice } = await this.getAssetPrices();
1619
- const extendedPositon = await extendedAdapter.getAllOpenPositions();
1620
- if (!extendedPositon) {
1621
- status = false;
1622
- logger.error("error getting extended position", extendedPositon);
1623
- return [
1624
- this.createTransactionResult(
1625
- calls,
1626
- status,
1627
- {
1628
- from: Protocols.VAULT.name,
1629
- to: Protocols.NONE.name,
1630
- amount: amount,
1631
- },
1632
- "NONE",
1633
- CycleType.WITHDRAWAL
1634
- ),
1635
- ];
1636
- }
1637
- const amountDistributionForWithdrawal =
1638
- await calculateAmountDistributionForWithdrawal(
1639
- usdcBalanceDifference,
1640
- collateralPrice.price,
1641
- collateralTokenAmount,
1642
- extendedPositon
1643
- );
1644
- if (!amountDistributionForWithdrawal) {
1645
- status = false;
1646
- logger.error(
1647
- `error calculating amount distribution for withdrawal: ${amountDistributionForWithdrawal}`
1648
- );
1649
- return [
1650
- this.createTransactionResult(
1651
- calls,
1652
- status,
1653
- {
1654
- from: Protocols.VAULT.name,
1655
- to: Protocols.NONE.name,
1656
- amount: amount,
1657
- },
1658
- "NONE",
1659
- CycleType.WITHDRAWAL
1660
- ),
1661
- ];
1662
- }
1663
- const { vesu_amount, extended_amount } = amountDistributionForWithdrawal;
1664
-
1665
- if (status && vesu_amount.greaterThan(0)) {
1666
- const {
1667
- calls: vesuCalls,
1668
- status: vesuStatus,
1669
- transactionMetadata: vesuTransactionMetadata,
1670
- } = await this.moveAssets(
1671
- {
1672
- amount: vesu_amount,
1673
- from: Protocols.VESU.name,
1674
- to: Protocols.VAULT.name,
1675
- cycleType: CycleType.WITHDRAWAL,
1676
- },
1677
- extendedAdapter,
1678
- vesuAdapter
1679
- );
1680
- status = vesuStatus;
1681
- transactionResults.push({
1682
- status: vesuStatus,
1683
- calls: vesuCalls,
1684
- transactionMetadata: vesuTransactionMetadata,
1685
- });
1686
- }
1687
- if (status && extended_amount.greaterThan(0)) {
1688
- const {
1689
- calls: extendedCalls,
1690
- status: extendedStatus,
1691
- transactionMetadata: extendedTransactionMetadata,
1692
- } = await this.moveAssets(
1693
- {
1694
- amount: extended_amount,
1695
- from: Protocols.EXTENDED.name,
1696
- to: Protocols.VAULT.name,
1697
- cycleType: CycleType.WITHDRAWAL,
1698
- },
1699
- extendedAdapter,
1700
- vesuAdapter
1701
- );
1702
- status = extendedStatus;
1703
- if (status) {
1704
- transactionResults.push({
1705
- status: extendedStatus,
1706
- calls: extendedCalls,
1707
- transactionMetadata: extendedTransactionMetadata,
1708
- });
1709
- } else {
1710
- logger.error(
1711
- "error moving assets to vault: extendedStatus: ${extendedStatus}"
1712
- );
1713
- return [
1714
- this.createTransactionResult(
1715
- [],
1716
- status,
1717
- {
1718
- from: Protocols.VAULT.name,
1719
- to: Protocols.NONE.name,
1720
- amount: amount,
1721
- },
1722
- "NONE",
1723
- CycleType.WITHDRAWAL
1724
- ),
1725
- ];
1726
- }
1727
- }
1728
- const withdrawCall = await this.getBringLiquidityCall({
1729
- amount: amount,
1730
- });
1731
- logger.info("withdraw call", withdrawCall);
1732
- transactionResults.push({
1733
- status: status,
1734
- calls: [withdrawCall],
1735
- transactionMetadata: {
1736
- protocolFrom: Protocols.VAULT.name,
1737
- protocolTo: Protocols.NONE.name,
1738
- transactionType: "WITHDRAWAL",
1739
- usdAmount: amount.toFixed(),
1740
- status: "PENDING",
1741
- cycleType: CycleType.WITHDRAWAL,
1742
- },
1743
- });
1744
- return transactionResults;
1745
- } catch (err) {
1746
- logger.error(`error handling withdrawal: ${err}`);
1747
- return [
1748
- this.createTransactionResult(
1749
- [],
1750
- false,
1751
- {
1752
- from: Protocols.VAULT.name,
1753
- to: Protocols.NONE.name,
1754
- amount: amount,
1755
- },
1756
- "NONE",
1757
- CycleType.WITHDRAWAL
1758
- ),
1759
- ];
1760
- }
1761
- }
1762
-
1763
319
  async getAUM(): Promise<{
1764
320
  net: SingleTokenInfo;
1765
321
  prevAum: Web3Number;
@@ -1767,10 +323,14 @@ export class VesuExtendedMultiplierStrategy<
1767
323
  }> {
1768
324
  const allPositions: PositionInfo[] = [];
1769
325
  for (let adapter of this.metadata.additionalInfo.adapters) {
1770
- const positions = await adapter.adapter.getPositions();
1771
- allPositions.push(...positions);
326
+ let positions = await adapter.adapter.getPositions();
327
+ if (positions && positions.length > 0) {
328
+ const filteredPositions = positions.filter((position) => {
329
+ return position.tokenInfo.address !== this.usdceToken.address;
330
+ });
331
+ allPositions.push(...filteredPositions);
332
+ }
1772
333
  }
1773
-
1774
334
  const assetPrice = await this.pricer.getPrice(this.asset().symbol);
1775
335
  let netAUM = new Web3Number(0, this.asset().decimals);
1776
336
  for (let position of allPositions) {
@@ -1781,6 +341,16 @@ export class VesuExtendedMultiplierStrategy<
1781
341
  }
1782
342
  }
1783
343
 
344
+ // ! IMO should also include USDC in wallet.
345
+ const walletHoldings = await this.getWalletHoldings();
346
+ for (let holding of walletHoldings) {
347
+ if (holding.tokenInfo.address.eq(this.asset().address)) {
348
+ netAUM = netAUM.plus(holding.amount);
349
+ } else {
350
+ netAUM = netAUM.plus(holding.usdValue / assetPrice.price);
351
+ }
352
+ }
353
+
1784
354
  const prevAum = await this.getPrevAUM();
1785
355
  const realAUM: PositionInfo = {
1786
356
  tokenInfo: this.asset(),
@@ -1811,140 +381,101 @@ export class VesuExtendedMultiplierStrategy<
1811
381
  };
1812
382
  }
1813
383
 
1814
- async processTransactionDataFromSDK(
1815
- txnData: TransactionResult<any>[]
1816
- ): Promise<{
1817
- callsToBeExecutedFinal: Call[];
1818
- txnMetadata: TransactionMetadata[];
1819
- } | null> {
1820
- try {
1821
- const txnsToBeExecuted = txnData.filter((txn) => {
1822
- return (
1823
- txn.transactionMetadata.transactionType !== "NONE" &&
1824
- txn.transactionMetadata.protocolFrom !== "" &&
1825
- txn.transactionMetadata.protocolTo !== ""
1826
- );
1827
- });
1828
- const callsToBeExecutedFinal = txnsToBeExecuted.flatMap(
1829
- (txn) => txn.calls
1830
- );
1831
- const txnMetadata = txnsToBeExecuted.map(
1832
- (txn) => txn.transactionMetadata
1833
- );
1834
- return { callsToBeExecutedFinal, txnMetadata };
1835
- } catch (err) {
1836
- logger.error(`error processing transaction data from SDK: ${err}`);
1837
- return null;
1838
- }
1839
- }
1840
-
1841
- async processTransactionMetadata(
1842
- txnMetadata: TransactionMetadata[],
1843
- extendedIntentFulfilled: boolean
1844
- ): Promise<TransactionMetadata[] | null> {
1845
- try {
1846
- const txnMetadataNew = txnMetadata.map((txn) => {
1847
- const isExtendedProtocol =
1848
- txn.protocolFrom === Protocols.EXTENDED.name ||
1849
- txn.protocolTo === Protocols.EXTENDED.name;
1850
- // Only update status for extended protocol transactions since thsoe only cause delays
1851
- if (isExtendedProtocol) {
1852
- txn.status = extendedIntentFulfilled ? "COMPLETED" : "PENDING";
1853
- } else {
1854
- txn.status = "COMPLETED";
1855
- }
1856
- return txn;
1857
- });
1858
- return txnMetadataNew;
1859
- } catch (err) {
1860
- logger.error(`error processing transaction data from SDK: ${err}`);
1861
- return null;
1862
- }
1863
- }
1864
384
 
385
+ /**
386
+ * Computes the maximum additional USDC that can be borrowed from Vesu
387
+ * while keeping the strategy profitable. Uses the Extended funding rate
388
+ * and Vesu supply APY to derive a break-even borrow APY, then queries
389
+ * Vesu for the max borrowable amount at that rate.
390
+ */
1865
391
  async getMaxBorrowableAmount(): Promise<Web3Number> {
1866
- const vesuAdapter = await this.getVesuAdapter();
392
+ const vesuAdapter = await this.getVesuModifyPositionAdapter();
1867
393
  const extendedAdapter = await this.getExtendedAdapter();
1868
- if (!vesuAdapter || !extendedAdapter) {
1869
- return new Web3Number(0, 0);
1870
- }
1871
394
  const extendedFundingRate = new Web3Number(
1872
395
  (await extendedAdapter.getNetAPY()).toFixed(4),
1873
- 0
396
+ 0,
1874
397
  );
1875
398
  const extendedPositions = await extendedAdapter.getAllOpenPositions();
1876
399
  if (!extendedPositions || extendedPositions.length === 0) {
1877
- logger.info(`no extended positions found`);
400
+ logger.warn(`${this.getTag()} getMaxBorrowableAmount: no extended positions found`);
1878
401
  return new Web3Number(0, 0);
1879
402
  }
1880
403
  const extendePositionSizeUSD = new Web3Number(
1881
404
  extendedPositions[0].value || 0,
1882
- 0
405
+ 0,
1883
406
  );
1884
407
  const vesuPositions = await vesuAdapter.getPositions();
1885
408
  const vesuSupplyApy = vesuPositions[0].apy.apy;
1886
409
  const vesuCollateralSizeUSD = new Web3Number(
1887
410
  vesuPositions[0].usdValue.toFixed(USDC_TOKEN_DECIMALS),
1888
- USDC_TOKEN_DECIMALS
411
+ USDC_TOKEN_DECIMALS,
1889
412
  );
1890
413
  const vesuDebtSizeUSD = new Web3Number(
1891
414
  vesuPositions[1].usdValue.toFixed(USDC_TOKEN_DECIMALS),
1892
- USDC_TOKEN_DECIMALS
415
+ USDC_TOKEN_DECIMALS,
1893
416
  );
1894
417
  const num1 = extendePositionSizeUSD.multipliedBy(extendedFundingRate);
1895
418
  const num2 = vesuCollateralSizeUSD.multipliedBy(vesuSupplyApy);
1896
419
  const num3 = vesuDebtSizeUSD.abs();
1897
420
  const maxBorrowApy = num1.plus(num2).minus(0.1).dividedBy(num3);
1898
- const vesuMaxBorrowableAmount =
1899
- await vesuAdapter.vesuAdapter.getMaxBorrowableByInterestRate(
421
+ const vesuMaxBorrowableResult =
422
+ await vesuAdapter._vesuAdapter.getMaxBorrowableByInterestRate(
1900
423
  this.config,
1901
424
  vesuAdapter.config.debt,
1902
- maxBorrowApy.toNumber()
425
+ maxBorrowApy.toNumber(),
1903
426
  );
1904
427
  return new Web3Number(
1905
- vesuMaxBorrowableAmount.toFixed(USDC_TOKEN_DECIMALS),
1906
- USDC_TOKEN_DECIMALS
428
+ vesuMaxBorrowableResult.maxDebtToHave.toFixed(USDC_TOKEN_DECIMALS),
429
+ USDC_TOKEN_DECIMALS,
1907
430
  );
1908
431
  }
1909
432
 
433
+ /**
434
+ * Returns the current health metrics for the strategy:
435
+ * [0] Vesu health factor (maxLTV / actualLTV) — higher is safer.
436
+ * [1] Extended margin ratio (as percentage) — higher means more margin available.
437
+ */
1910
438
  async getVesuHealthFactors(): Promise<number[]> {
1911
- const vesuAdapter = await this.getVesuAdapter();
439
+ const vesuAdapter = await this.getVesuModifyPositionAdapter();
1912
440
  const extendedAdapter = await this.getExtendedAdapter();
1913
- if (!vesuAdapter || !extendedAdapter) {
1914
- return [0, 0];
1915
- }
1916
441
  const vesuPositions = await vesuAdapter.getPositions();
1917
442
  const vesuCollateralSizeUSD = new Web3Number(
1918
443
  vesuPositions[0].usdValue.toFixed(USDC_TOKEN_DECIMALS),
1919
- 0
444
+ 0,
1920
445
  );
1921
446
  const vesuDebtSizeUSD = new Web3Number(
1922
447
  vesuPositions[1].usdValue.toFixed(USDC_TOKEN_DECIMALS),
1923
- 0
448
+ 0,
1924
449
  );
1925
450
  const actualLtv = vesuDebtSizeUSD.dividedBy(vesuCollateralSizeUSD).abs();
1926
- logger.info(`actualLtv: ${actualLtv.toNumber()}`);
451
+ logger.debug(`${this.getTag()} getVesuHealthFactors: actualLtv=${actualLtv.toNumber()}`);
1927
452
  const maxLtv = new Web3Number(
1928
- await vesuAdapter.vesuAdapter.getLTVConfig(this.config),
1929
- 4
453
+ await vesuAdapter._vesuAdapter.getLTVConfig(this.config),
454
+ 4,
1930
455
  );
1931
456
  const healthFactor = new Web3Number(
1932
457
  maxLtv.dividedBy(actualLtv).toFixed(4),
1933
- 4
458
+ 4,
1934
459
  );
1935
- logger.info(`healthFactor: ${healthFactor.toNumber()}`);
460
+ logger.debug(`${this.getTag()} getVesuHealthFactors: healthFactor=${healthFactor.toNumber()}`);
1936
461
  const extendedBalance = await extendedAdapter.getExtendedDepositAmount();
1937
462
  if (!extendedBalance) {
1938
463
  return [0, 0];
1939
464
  }
1940
465
  const extendedLeverage = new Web3Number(
1941
466
  (Number(extendedBalance.marginRatio) * 100).toFixed(4),
1942
- 4
467
+ 4,
1943
468
  );
1944
- logger.info(`extendedLeverage: ${extendedLeverage.toNumber()}`);
469
+ logger.debug(`${this.getTag()} getVesuHealthFactors: extendedLeverage=${extendedLeverage.toNumber()}`);
1945
470
  return [healthFactor.toNumber(), extendedLeverage.toNumber()];
1946
471
  }
1947
472
 
473
+ /**
474
+ * Calculates the weighted net APY of the strategy across all positions.
475
+ * Combines Vesu supply APY (scaled by 0.1 performance fee) and Extended
476
+ * position APY, weighted by their respective USD values.
477
+ * Also returns per-position APY splits.
478
+ */
1948
479
  async netAPY(): Promise<{
1949
480
  net: number;
1950
481
  splits: { apy: number; id: string }[];
@@ -1959,23 +490,14 @@ export class VesuExtendedMultiplierStrategy<
1959
490
  }
1960
491
  }
1961
492
  const extendedAdapter = await this.getExtendedAdapter();
1962
- if (!extendedAdapter) {
1963
- return {
1964
- net: 0,
1965
- splits: [],
1966
- };
1967
- }
1968
493
  let vesuPositions = allPositions.filter(
1969
- (item) => item.protocol === Protocols.VESU
494
+ (item) => item.protocol === Protocols.VESU,
1970
495
  );
1971
496
  vesuPositions.map((item) => {
1972
497
  item.apy.apy = item.apy.apy * 0.1;
1973
498
  });
1974
499
  const extendedPositions = await extendedAdapter.getAllOpenPositions();
1975
- const usdcToken = Global.getDefaultTokens().find(
1976
- (token) => token.symbol === "USDC"
1977
- );
1978
- if (!extendedPositions || !usdcToken) {
500
+ if (!extendedPositions || !this.usdcToken) {
1979
501
  return {
1980
502
  net: 0,
1981
503
  splits: [],
@@ -1988,19 +510,19 @@ export class VesuExtendedMultiplierStrategy<
1988
510
  const totalHoldingsUSDValue =
1989
511
  allPositions.reduce((acc, curr) => acc + curr.usdValue, 0) +
1990
512
  Number(extendedEquity);
1991
- console.log(totalHoldingsUSDValue);
1992
513
  const extendedPositionSizeMultipliedByApy =
1993
514
  Number(extendedPosition.value) * extendedApy;
1994
515
  let weightedAPYs =
1995
516
  allPositions.reduce(
1996
517
  (acc, curr) => acc + curr.apy.apy * curr.usdValue,
1997
- 0
518
+ 0,
1998
519
  ) + extendedPositionSizeMultipliedByApy;
1999
- console.log(weightedAPYs);
2000
520
  const netAPY = weightedAPYs / totalHoldingsUSDValue;
2001
- console.log(netAPY);
521
+ logger.debug(
522
+ `${this.getTag()} netAPY: holdingsUsd=${totalHoldingsUSDValue}, weightedApy=${weightedAPYs}, net=${netAPY}`,
523
+ );
2002
524
  allPositions.push({
2003
- tokenInfo: usdcToken,
525
+ tokenInfo: this.usdcToken,
2004
526
  amount: new Web3Number(extendedPosition.size, 0),
2005
527
  usdValue: Number(extendedEquity),
2006
528
  apy: { apy: extendedApy, type: APYType.BASE },
@@ -2016,6 +538,10 @@ export class VesuExtendedMultiplierStrategy<
2016
538
  };
2017
539
  }
2018
540
 
541
+ /**
542
+ * Fetches the operator wallet's current holdings for USDC.e, USDC, and WBTC,
543
+ * returning each token's balance and USD value.
544
+ */
2019
545
  async getWalletHoldings(): Promise<
2020
546
  {
2021
547
  tokenInfo: TokenInfo;
@@ -2023,62 +549,47 @@ export class VesuExtendedMultiplierStrategy<
2023
549
  usdValue: number;
2024
550
  }[]
2025
551
  > {
2026
- const usdceToken = Global.getDefaultTokens().find(
2027
- (token) => token.symbol === "USDCe"
2028
- );
2029
- const wbtcToken = Global.getDefaultTokens().find(
2030
- (token) => token.symbol === "WBTC"
2031
- );
2032
- const usdcToken = Global.getDefaultTokens().find(
2033
- (token) => token.symbol === "USDC"
2034
- );
2035
- if (!usdceToken || !wbtcToken || !usdcToken) {
552
+ if (!this.usdceToken || !this.wbtcToken || !this.usdcToken) {
2036
553
  return [];
2037
554
  }
2038
555
  const walletAddress = this.metadata.additionalInfo.walletAddress;
2039
556
  const usdceWalletBalance = await new ERC20(this.config).balanceOf(
2040
- usdceToken.address,
557
+ this.usdceToken.address,
2041
558
  walletAddress,
2042
- usdceToken.decimals
559
+ this.usdceToken.decimals,
2043
560
  );
2044
561
  const usdcWalletBalance = await new ERC20(this.config).balanceOf(
2045
- usdcToken.address,
2046
- walletAddress,
2047
- usdcToken.decimals
2048
- );
2049
- const wbtcWalletBalance = await new ERC20(this.config).balanceOf(
2050
- wbtcToken.address,
562
+ this.usdcToken.address,
2051
563
  walletAddress,
2052
- wbtcToken.decimals
564
+ this.usdcToken.decimals,
2053
565
  );
2054
- const price = await this.pricer.getPrice(usdceToken.symbol);
2055
- const wbtcPrice = await this.pricer.getPrice(wbtcToken.symbol);
566
+ const price = await this.pricer.getPrice(this.usdceToken.symbol);
567
+ const wbtcPrice = await this.pricer.getPrice(this.wbtcToken.symbol);
2056
568
  const usdceUsdValue =
2057
- Number(usdceWalletBalance.toFixed(usdceToken.decimals)) * price.price;
569
+ Number(usdceWalletBalance.toFixed(this.usdceToken.decimals)) *
570
+ price.price;
2058
571
  const usdcUsdValue =
2059
- Number(usdcWalletBalance.toFixed(usdcToken.decimals)) * price.price;
2060
- const wbtcUsdValue =
2061
- Number(wbtcWalletBalance.toFixed(wbtcToken.decimals)) * wbtcPrice.price;
572
+ Number(usdcWalletBalance.toFixed(this.usdcToken.decimals)) * price.price;
2062
573
  return [
2063
574
  {
2064
- tokenInfo: usdceToken,
575
+ tokenInfo: this.usdceToken,
2065
576
  amount: usdceWalletBalance,
2066
577
  usdValue: usdceUsdValue,
2067
578
  },
2068
579
  {
2069
- tokenInfo: usdcToken,
580
+ tokenInfo: this.usdcToken,
2070
581
  amount: usdcWalletBalance,
2071
582
  usdValue: usdcUsdValue,
2072
- },
2073
- {
2074
- tokenInfo: wbtcToken,
2075
- amount: wbtcWalletBalance,
2076
- usdValue: wbtcUsdValue,
2077
- },
583
+ }
2078
584
  ];
2079
585
  }
2080
586
  }
2081
587
 
588
+ /**
589
+ * Configures all adapters (Vesu, Extended, Avnu, UsdcToUsdce, TokenTransfer)
590
+ * and registers their leaf adapters on the vault settings. This is the central
591
+ * wiring function that connects the strategy to its underlying protocol adapters.
592
+ */
2082
593
  function getLooperSettings(
2083
594
  lstSymbol: string,
2084
595
  underlyingSymbol: string,
@@ -2091,15 +602,18 @@ function getLooperSettings(
2091
602
  minimumVesuMovementAmount: number,
2092
603
  minimumExtendedRetriesDelayForOrderStatus: number,
2093
604
  minimumExtendedPriceDifferenceForSwapOpen: number,
2094
- maximumExtendedPriceDifferenceForSwapClosing: number
605
+ maximumExtendedPriceDifferenceForSwapClosing: number,
2095
606
  ) {
2096
607
  vaultSettings.leafAdapters = [];
2097
608
 
2098
609
  const wbtcToken = Global.getDefaultTokens().find(
2099
- (token) => token.symbol === lstSymbol
610
+ (token) => token.symbol === lstSymbol,
2100
611
  )!;
2101
612
  const usdcToken = Global.getDefaultTokens().find(
2102
- (token) => token.symbol === underlyingSymbol
613
+ (token) => token.symbol === underlyingSymbol,
614
+ )!;
615
+ const usdceToken = Global.getDefaultTokens().find(
616
+ (token) => token.symbol === "USDC.e",
2103
617
  )!;
2104
618
 
2105
619
  const baseAdapterConfig: BaseAdapterConfig = {
@@ -2126,9 +640,19 @@ function getLooperSettings(
2126
640
  maximumExtendedPriceDifferenceForSwapClosing,
2127
641
  });
2128
642
 
643
+ const usdcToUsdceAdapter = new UsdcToUsdceAdapter({
644
+ ...baseAdapterConfig,
645
+ supportedPositions: [
646
+ { asset: usdcToken, isDebt: true },
647
+ { asset: usdceToken, isDebt: false },
648
+ ],
649
+ });
650
+
2129
651
  const extendedAdapter = new ExtendedAdapter({
2130
652
  ...baseAdapterConfig,
2131
- supportedPositions: [{ asset: usdcToken, isDebt: true }],
653
+ supportedPositions: [
654
+ { asset: usdceToken, isDebt: false },
655
+ ],
2132
656
  vaultIdExtended: vaultIdExtended,
2133
657
  extendedContract: EXTENDED_CONTRACT,
2134
658
  extendedBackendWriteUrl: extendedBackendWriteUrl,
@@ -2147,6 +671,7 @@ function getLooperSettings(
2147
671
  poolId: pool1,
2148
672
  collateral: wbtcToken,
2149
673
  debt: usdcToken,
674
+ marginToken: usdcToken,
2150
675
  targetHealthFactor: vaultSettings.targetHealthFactor,
2151
676
  minHealthFactor: vaultSettings.minHealthFactor,
2152
677
  quoteAmountToFetchPrice: vaultSettings.quoteAmountToFetchPrice,
@@ -2158,8 +683,28 @@ function getLooperSettings(
2158
683
  minimumVesuMovementAmount: minimumVesuMovementAmount ?? 5, //5 usdc
2159
684
  });
2160
685
 
2161
- const unusedBalanceAdapter = new UnusedBalanceAdapter({
686
+ const vesuModifyPositionMaxLtv = VesuConfig.maxLtv;
687
+ const vesuModifyPositionTargetLtv = VesuConfig.targetLtv;
688
+ const vesuModifyPositionAdapter = new VesuModifyPositionAdapter({
689
+ poolId: pool1,
690
+ collateral: wbtcToken,
691
+ debt: usdcToken,
692
+ targetLtv: vesuModifyPositionTargetLtv,
693
+ maxLtv: vesuModifyPositionMaxLtv,
2162
694
  ...baseAdapterConfig,
695
+ supportedPositions: [
696
+ { asset: wbtcToken, isDebt: false },
697
+ { asset: usdcToken, isDebt: true },
698
+ ],
699
+ });
700
+
701
+ // Transfers USDC between the vault allocator (fromAddress) and the operator wallet (toAddress)
702
+ const usdceTransferAdapter = new TokenTransferAdapter({
703
+ ...baseAdapterConfig,
704
+ baseToken: usdceToken,
705
+ supportedPositions: [{ asset: usdceToken, isDebt: false }],
706
+ fromAddress: vaultSettings.vaultAllocator,
707
+ toAddress: ContractAddr.from(vaultSettings.walletAddress),
2163
708
  });
2164
709
 
2165
710
  vaultSettings.adapters.push({
@@ -2168,8 +713,18 @@ function getLooperSettings(
2168
713
  });
2169
714
 
2170
715
  vaultSettings.adapters.push({
2171
- id: `${unusedBalanceAdapter.name}_${wbtcToken.symbol}`,
2172
- adapter: unusedBalanceAdapter,
716
+ id: `${vesuModifyPositionAdapter.name}_${wbtcToken.symbol}_${usdcToken.symbol}`,
717
+ adapter: vesuModifyPositionAdapter,
718
+ });
719
+
720
+ vaultSettings.adapters.push({
721
+ id: `${usdceTransferAdapter.name}_${usdceToken.symbol}`,
722
+ adapter: usdceTransferAdapter,
723
+ });
724
+
725
+ vaultSettings.adapters.push({
726
+ id: `${usdcToUsdceAdapter.name}_${usdceToken.symbol}_${usdcToken.symbol}`,
727
+ adapter: usdcToUsdceAdapter,
2173
728
  });
2174
729
 
2175
730
  vaultSettings.adapters.push({
@@ -2192,26 +747,29 @@ function getLooperSettings(
2192
747
 
2193
748
  vaultSettings.leafAdapters.push(() => vesuMultiplyAdapter.getDepositLeaf());
2194
749
  vaultSettings.leafAdapters.push(() => vesuMultiplyAdapter.getWithdrawLeaf());
750
+ vaultSettings.leafAdapters.push(() => vesuModifyPositionAdapter.getDepositLeaf());
751
+ vaultSettings.leafAdapters.push(() => vesuModifyPositionAdapter.getWithdrawLeaf());
2195
752
  vaultSettings.leafAdapters.push(() => extendedAdapter.getDepositLeaf());
2196
- vaultSettings.leafAdapters.push(() =>
2197
- extendedAdapter.getSwapFromLegacyLeaf()
2198
- );
753
+ vaultSettings.leafAdapters.push(() => usdcToUsdceAdapter.getDepositLeaf());
754
+ vaultSettings.leafAdapters.push(() => usdcToUsdceAdapter.getWithdrawLeaf());
2199
755
  vaultSettings.leafAdapters.push(() => avnuAdapter.getDepositLeaf());
2200
756
  vaultSettings.leafAdapters.push(() => avnuAdapter.getWithdrawLeaf());
757
+ vaultSettings.leafAdapters.push(() => usdceTransferAdapter.getDepositLeaf());
758
+ vaultSettings.leafAdapters.push(() => usdceTransferAdapter.getWithdrawLeaf());
2201
759
  vaultSettings.leafAdapters.push(
2202
760
  commonAdapter
2203
761
  .getApproveAdapter(
2204
762
  usdcToken.address,
2205
763
  vaultSettings.vaultAddress,
2206
- UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY
764
+ UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY,
2207
765
  )
2208
- .bind(commonAdapter)
766
+ .bind(commonAdapter),
2209
767
  );
2210
768
 
2211
769
  vaultSettings.leafAdapters.push(
2212
770
  commonAdapter
2213
771
  .getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
2214
- .bind(commonAdapter)
772
+ .bind(commonAdapter),
2215
773
  );
2216
774
  return vaultSettings;
2217
775
  }
@@ -2222,7 +780,7 @@ function getDescription(tokenSymbol: string, underlyingSymbol: string) {
2222
780
 
2223
781
  export default function VaultDescription(
2224
782
  lstSymbol: string,
2225
- underlyingSymbol: string
783
+ underlyingSymbol: string,
2226
784
  ) {
2227
785
  const containerStyle = {
2228
786
  maxWidth: "800px",
@@ -2306,36 +864,37 @@ export default function VaultDescription(
2306
864
 
2307
865
  const re7UsdcPrimeDevansh: VesuExtendedStrategySettings = {
2308
866
  vaultAddress: ContractAddr.from(
2309
- "0x058905be22d6a81792df79425dc9641cf3e1b77f36748631b7d7e5d713a32b55"
867
+ "0x772d6cf5038c18ff5ab89f8945017bbf4d2c6959891339975c70a4f74ac6c8e",
2310
868
  ),
2311
869
  manager: ContractAddr.from(
2312
- "0x02648d703783feb2d967cf0520314cb5aa800d69a9426f3e3b317395af44de16"
870
+ "0x3340c9d7231838e2dccff72b9004f1598a74e65c74b954f07fe1ea19d04a625",
2313
871
  ),
2314
872
  vaultAllocator: ContractAddr.from(
2315
- "0x07d533c838eab6a4d854dd3aea96a55993fccd35821921970d00bde946b63b6f"
873
+ "0x537353b35eee5ca2d9a45eb646977baddd4e89ce870a231dcada79884117292",
2316
874
  ),
2317
875
  redeemRequestNFT: ContractAddr.from(
2318
- "0x01ef91f08fb99729c00f82fc6e0ece37917bcc43952596c19996259dc8adbbba"
876
+ "0x6117d1a8c72c0457948083757e1a17ee8c0833b969d5c959b629e5f8feb56ec",
2319
877
  ),
2320
878
  aumOracle: ContractAddr.from(
2321
- "0x030b6acfec162f5d6e72b8a4d2798aedce78fb39de78a8f549f7cd277ae8bc8d"
879
+ "0x6d7d68045bf5e0b5a4cec43241549851cb9645f7a73a20894152165dbe7083a",
2322
880
  ),
2323
881
  leafAdapters: [],
2324
882
  adapters: [],
2325
883
  targetHealthFactor: 1.4,
2326
884
  minHealthFactor: 1.05,
2327
885
  underlyingToken: Global.getDefaultTokens().find(
2328
- (token) => token.symbol === "USDC"
886
+ (token) => token.symbol === "USDC",
2329
887
  )!,
2330
888
  quoteAmountToFetchPrice: new Web3Number(
2331
889
  "0.001",
2332
- Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!.decimals
890
+ Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!
891
+ .decimals,
2333
892
  ),
2334
893
  borrowable_assets: [
2335
- Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!,
894
+ Global.getDefaultTokens().find((token) => token.symbol === "USDC")!,
2336
895
  ],
2337
896
  minimumWBTCDifferenceForAvnuSwap: MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP,
2338
- walletAddress: WALLET_ADDRESS,
897
+ walletAddress: '0x024b563C1C7d41B32BF4EFB9F38828508a65Be2d6e25268E9f63F22C5e9E51c5',
2339
898
  };
2340
899
 
2341
900
  export const VesuExtendedTestStrategies = (
@@ -2346,7 +905,7 @@ export const VesuExtendedTestStrategies = (
2346
905
  minimumVesuMovementAmount: number,
2347
906
  minimumExtendedRetriesDelayForOrderStatus: number,
2348
907
  minimumExtendedPriceDifferenceForSwapOpen: number,
2349
- maximumExtendedPriceDifferenceForSwapClosing: number
908
+ maximumExtendedPriceDifferenceForSwapClosing: number,
2350
909
  ): IStrategyMetadata<VesuExtendedStrategySettings>[] => {
2351
910
  return [
2352
911
  getStrategySettingsVesuExtended(
@@ -2362,11 +921,15 @@ export const VesuExtendedTestStrategies = (
2362
921
  minimumVesuMovementAmount,
2363
922
  minimumExtendedRetriesDelayForOrderStatus,
2364
923
  minimumExtendedPriceDifferenceForSwapOpen,
2365
- maximumExtendedPriceDifferenceForSwapClosing
924
+ maximumExtendedPriceDifferenceForSwapClosing,
2366
925
  ),
2367
926
  ];
2368
927
  };
2369
928
 
929
+ /**
930
+ * Constructs a complete IStrategyMetadata object for a Vesu-Extended strategy,
931
+ * including adapter wiring, risk configuration, FAQ, and UI description.
932
+ */
2370
933
  function getStrategySettingsVesuExtended(
2371
934
  lstSymbol: string,
2372
935
  underlyingSymbol: string,
@@ -2380,17 +943,22 @@ function getStrategySettingsVesuExtended(
2380
943
  minimumVesuMovementAmount: number,
2381
944
  minimumExtendedRetriesDelayForOrderStatus: number,
2382
945
  minimumExtendedPriceDifferenceForSwapOpen: number,
2383
- maximumExtendedPriceDifferenceForSwapClosing: number
946
+ maximumExtendedPriceDifferenceForSwapClosing: number,
2384
947
  ): IStrategyMetadata<VesuExtendedStrategySettings> {
2385
948
  return {
949
+ id: `extended_${underlyingSymbol.toLowerCase()}_test`,
2386
950
  name: `Extended Test ${underlyingSymbol}`,
2387
951
  description: getDescription(lstSymbol, underlyingSymbol),
2388
952
  address: addresses.vaultAddress,
2389
953
  launchBlock: 0,
2390
954
  type: "Other",
955
+ vaultType: {
956
+ type: VaultType.DELTA_NEUTRAL,
957
+ description: "Delta Neutral strategy using extended position on Vesu"
958
+ },
2391
959
  depositTokens: [
2392
960
  Global.getDefaultTokens().find(
2393
- (token) => token.symbol === underlyingSymbol
961
+ (token) => token.symbol === 'WBTC',
2394
962
  )!,
2395
963
  ],
2396
964
  additionalInfo: getLooperSettings(
@@ -2405,7 +973,7 @@ function getStrategySettingsVesuExtended(
2405
973
  minimumVesuMovementAmount,
2406
974
  minimumExtendedRetriesDelayForOrderStatus,
2407
975
  minimumExtendedPriceDifferenceForSwapOpen,
2408
- maximumExtendedPriceDifferenceForSwapClosing
976
+ maximumExtendedPriceDifferenceForSwapClosing,
2409
977
  ),
2410
978
  risk: {
2411
979
  riskFactor: _riskFactor,
@@ -2416,7 +984,6 @@ function getStrategySettingsVesuExtended(
2416
984
  },
2417
985
  auditUrl: AUDIT_URL,
2418
986
  protocols: [Protocols.ENDUR, Protocols.VESU],
2419
- maxTVL: Web3Number.fromWei(0, 18),
2420
987
  contractDetails: getContractDetails(addresses),
2421
988
  faqs: getFAQs(lstSymbol, underlyingSymbol, isLST),
2422
989
  investmentSteps: getInvestmentSteps(lstSymbol, underlyingSymbol),
@@ -2424,5 +991,27 @@ function getStrategySettingsVesuExtended(
2424
991
  apyMethodology: isLST
2425
992
  ? "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."
2426
993
  : "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.",
994
+ security: {
995
+ auditStatus: AuditStatus.NOT_AUDITED,
996
+ sourceCode: {
997
+ type: SourceCodeType.OPEN_SOURCE,
998
+ contractLink: AUDIT_URL,
999
+ },
1000
+ accessControl: {
1001
+ type: AccessControlType.MULTISIG_ACCOUNT,
1002
+ addresses: [addresses.vaultAddress],
1003
+ timeLock: "None",
1004
+ },
1005
+ },
1006
+ redemptionInfo: {
1007
+ instantWithdrawalVault: InstantWithdrawalVault.NO,
1008
+ redemptionsInfo: [{
1009
+ title: "Up to $500k",
1010
+ description: "1-24 hours",
1011
+ }],
1012
+ alerts: [],
1013
+ },
1014
+ usualTimeToEarnings: null,
1015
+ usualTimeToEarningsDescription: null,
2427
1016
  };
2428
1017
  }