@strkfarm/sdk 2.0.0-dev.26 → 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 -49354
  4. package/dist/index.browser.mjs +18039 -11431
  5. package/dist/index.d.ts +2869 -898
  6. package/dist/index.js +19036 -12207
  7. package/dist/index.mjs +18942 -12158
  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 +3 -1
  61. package/src/strategies/vesu-extended-strategy/utils/helper.ts +158 -124
  62. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +377 -1781
  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,1633 +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 availableForWithdrawal = parseFloat(
368
- usdcBalanceOnExtended.availableForWithdrawal
369
- );
370
- if (
371
- !Number.isFinite(availableForWithdrawal) ||
372
- availableForWithdrawal < 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
- /** The LIMIT_BALANCE is the bffer amount to keep in the investing Cycle */
391
- const amountToInvest = new Web3Number(
392
- balance.usdValue,
393
- USDC_TOKEN_DECIMALS
394
- )
395
- .plus(usdcBalanceOnExtended?.availableForTrade ?? 0)
396
- .multipliedBy(1 - LIMIT_BALANCE);
397
-
398
- const amountToInvestNumber = amountToInvest.toNumber();
399
- if (!Number.isFinite(amountToInvestNumber)) {
400
- logger.error(
401
- `Invalid amountToInvest calculation result: ${amountToInvestNumber}. Calculation may have produced NaN or Infinity.`
402
- );
403
- return {
404
- shouldInvest: false,
405
- vesuAmount: new Web3Number(0, 0),
406
- extendedAmount: new Web3Number(0, 0),
407
- extendedLeverage: 0,
408
- collateralPrice: 0,
409
- debtPrice: 0,
410
- vesuLeverage: 0,
411
- debtAmountToBeRepaid: new Web3Number(0, 0),
412
- };
413
- }
414
-
415
- logger.info(
416
- `${VesuExtendedMultiplierStrategy.name}::shouldInvest amountToInvest: ${amountToInvestNumber}`
417
- );
418
-
419
- if (amountToInvest.lessThan(LIMIT_BALANCE_VALUE)) {
420
- return {
421
- shouldInvest: false,
422
- vesuAmount: new Web3Number(0, 0),
423
- extendedAmount: new Web3Number(0, 0),
424
- extendedLeverage: 0,
425
- collateralPrice: 0,
426
- debtPrice: 0,
427
- vesuLeverage: 0,
428
- debtAmountToBeRepaid: new Web3Number(0, 0),
429
- };
430
- }
431
-
432
- const extendedPositon = await extendedAdapter.getAllOpenPositions();
433
- if (!extendedPositon) {
434
- logger.error("error getting extended position to decide move assets");
435
- return {
436
- shouldInvest: false,
437
- vesuAmount: new Web3Number(0, 0),
438
- extendedAmount: new Web3Number(0, 0),
439
- extendedLeverage: 0,
440
- collateralPrice: 0,
441
- debtPrice: 0,
442
- vesuLeverage: 0,
443
- debtAmountToBeRepaid: new Web3Number(0, 0),
444
- };
445
- }
446
- const { collateralTokenAmount, debtTokenAmount } =
447
- await vesuAdapter.vesuAdapter.getAssetPrices();
448
-
449
- const { collateralPrice, debtPrice } = await this.getAssetPrices();
450
-
451
- if (
452
- !Number.isFinite(collateralPrice.price) ||
453
- collateralPrice.price <= 0
454
- ) {
455
- logger.error(
456
- `Invalid collateralPrice: ${collateralPrice.price}. Expected a finite, positive number.`
457
- );
458
- return {
459
- shouldInvest: false,
460
- vesuAmount: new Web3Number(0, 0),
461
- extendedAmount: new Web3Number(0, 0),
462
- extendedLeverage: 0,
463
- collateralPrice: 0,
464
- debtPrice: 0,
465
- vesuLeverage: 0,
466
- debtAmountToBeRepaid: new Web3Number(0, 0),
467
- };
468
- }
469
- if (!Number.isFinite(debtPrice.price) || debtPrice.price <= 0) {
470
- logger.error(
471
- `Invalid debtPrice: ${debtPrice.price}. Expected a finite, positive number.`
472
- );
473
- return {
474
- shouldInvest: false,
475
- vesuAmount: new Web3Number(0, 0),
476
- extendedAmount: new Web3Number(0, 0),
477
- extendedLeverage: 0,
478
- collateralPrice: 0,
479
- debtPrice: 0,
480
- vesuLeverage: 0,
481
- debtAmountToBeRepaid: new Web3Number(0, 0),
482
- };
483
- }
484
-
485
- const debtAmountToBeRepaid = calculateDeltaDebtAmount(
486
- MAX_LTV_BTC_USDC,
487
- collateralTokenAmount,
488
- debtTokenAmount,
489
- collateralPrice.price,
490
- debtPrice.price,
491
- this.metadata.additionalInfo.targetHealthFactor
492
- );
493
- if (!debtAmountToBeRepaid) {
494
- logger.error("error calculating debt amount to be repaid");
495
- return {
496
- shouldInvest: false,
497
- vesuAmount: new Web3Number(0, 0),
498
- extendedAmount: new Web3Number(0, 0),
499
- extendedLeverage: 0,
500
- collateralPrice: 0,
501
- debtPrice: 0,
502
- vesuLeverage: 0,
503
- debtAmountToBeRepaid: new Web3Number(0, 0),
504
- };
505
- }
506
- logger.info(
507
- `${
508
- VesuExtendedMultiplierStrategy.name
509
- }::shouldInvest debtAmountToBeRepaid: ${debtAmountToBeRepaid.toNumber()}`
510
- );
511
- /**
512
- * Since the debt amount is negative, we need to add it to the amount to invest
513
- * to maintain the ltv
514
- */
515
- const amountToInvestAfterRepayingDebt =
516
- amountToInvest.plus(debtAmountToBeRepaid);
517
- const { vesu_amount, extended_amount, extended_leverage, vesu_leverage } =
518
- await calculateAmountDistribution(
519
- amountToInvestAfterRepayingDebt.toNumber(),
520
- extendedAdapter.client,
521
- extendedAdapter.config.extendedMarketName,
522
- collateralPrice.price,
523
- debtPrice.price,
524
- collateralTokenAmount,
525
- extendedPositon
526
- );
527
- if (
528
- !vesu_amount ||
529
- !extended_amount ||
530
- !extended_leverage ||
531
- !vesu_leverage
532
- ) {
533
- logger.error(
534
- `Not enough amount to invest: vesu_amount=${vesu_amount}, extended_amount=${extended_amount}`
535
- );
536
- return {
537
- shouldInvest: false,
538
- vesuAmount: new Web3Number(0, 0),
539
- extendedAmount: new Web3Number(0, 0),
540
- extendedLeverage: 0,
541
- collateralPrice: 0,
542
- debtPrice: 0,
543
- vesuLeverage: 0,
544
- debtAmountToBeRepaid: new Web3Number(0, 0),
545
- };
546
- }
547
- logger.info(
548
- `${
549
- VesuExtendedMultiplierStrategy.name
550
- }::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.`
551
223
  );
552
- return {
553
- shouldInvest: true,
554
- vesuAmount: vesu_amount,
555
- extendedAmount: extended_amount,
556
- extendedLeverage: extended_leverage,
557
- vesuLeverage: vesu_leverage,
558
- collateralPrice: collateralPrice.price,
559
- debtPrice: debtPrice.price,
560
- debtAmountToBeRepaid: debtAmountToBeRepaid,
561
- };
562
- } catch (err) {
563
- logger.error(`error deciding invest: ${err}`);
564
- return {
565
- shouldInvest: false,
566
- vesuAmount: new Web3Number(0, 0),
567
- extendedAmount: new Web3Number(0, 0),
568
- extendedLeverage: 0,
569
- collateralPrice: 0,
570
- debtPrice: 0,
571
- vesuLeverage: 0,
572
- debtAmountToBeRepaid: new Web3Number(0, 0),
573
- };
574
224
  }
225
+ return avnuAdapter.adapter as AvnuAdapter;
575
226
  }
576
227
 
577
- async shouldMoveAssets(
578
- extendedAmount: Web3Number,
579
- vesuAmount: Web3Number
580
- ): Promise<TransactionResult[]> {
581
- try {
582
- const vesuAdapter = await this.getVesuAdapter();
583
- const extendedAdapter = await this.getExtendedAdapter();
584
- if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
585
- logger.error(
586
- `vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
587
- );
588
- return [];
589
- }
590
-
591
- const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
592
- if (!extendedHoldings) {
593
- logger.error(`error getting extended holdings: ${extendedHoldings}`);
594
- return [];
595
- }
596
- const usdcAmountInWallet = (await this.getUnusedBalance()).amount;
597
- /**
598
- * Trade is the correct metric, since we can close position for some vesu amount, otherwise underutilisation of funds will be a huge issue
599
- */
600
- const usdcAmountOnExtendedAvailableForTrade = parseFloat(
601
- extendedHoldings.availableForTrade
602
- );
603
-
604
- logger.info(
605
- `${
606
- VesuExtendedMultiplierStrategy.name
607
- }::shouldMoveAssets calculating movements - Extended current: ${usdcAmountOnExtendedAvailableForTrade}, Wallet: ${usdcAmountInWallet.toNumber()}, Target Extended: ${extendedAmount.toNumber()}, Target Vesu: ${vesuAmount.toNumber()}`
608
- );
609
-
610
- let totalExtendedWithdrawal = new Web3Number(0, USDC_TOKEN_DECIMALS);
611
- let totalExtendedDeposit = new Web3Number(0, USDC_TOKEN_DECIMALS);
612
-
613
- if (
614
- extendedAmount.isNegative() &&
615
- extendedAmount
616
- .abs()
617
- .greaterThan(extendedAdapter.minimumExtendedMovementAmount)
618
- ) {
619
- totalExtendedWithdrawal = totalExtendedWithdrawal.plus(
620
- extendedAmount.abs()
621
- );
622
- }
623
-
624
- // Calculate remaining Extended difference (target vs current)
625
- // If extendedAmount was negative, we've already accounted for that withdrawal
626
- // So we calculate based on what Extended will be after that withdrawal
627
- const extendedTargetAmount = extendedAmount.abs(); // Use absolute value as target
628
- let projectedExtendedBalance = usdcAmountOnExtendedAvailableForTrade;
629
-
630
- if (extendedAmount.isNegative()) {
631
- projectedExtendedBalance =
632
- projectedExtendedBalance - extendedAmount.abs().toNumber();
633
- }
634
-
635
- const extendedAmountDifference = extendedTargetAmount.minus(
636
- projectedExtendedBalance
637
- );
638
- const extendedAmountDifferenceAbs = extendedAmountDifference.abs();
639
-
640
- // Track additional Extended movements
641
- if (extendedAmountDifference.lessThan(0)) {
642
- totalExtendedWithdrawal = totalExtendedWithdrawal.plus(
643
- extendedAmountDifferenceAbs
644
- );
645
- } else if (extendedAmountDifference.greaterThan(0)) {
646
- totalExtendedDeposit = totalExtendedDeposit.plus(
647
- extendedAmountDifference
648
- );
649
- }
650
-
651
- const vesuTargetAmount = vesuAmount.abs();
652
- const projectedWalletBalance = usdcAmountInWallet
653
- .plus(totalExtendedWithdrawal)
654
- .minus(totalExtendedDeposit);
655
-
656
- let vesuAmountDifference = vesuTargetAmount.minus(projectedWalletBalance);
657
- const vesuAmountDifferenceAbs = vesuAmountDifference.abs();
658
-
659
- logger.info(
660
- `${
661
- VesuExtendedMultiplierStrategy.name
662
- }::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.`
663
235
  );
664
- let transactionResults: TransactionResult[] = [];
665
-
666
- // Handle negative extendedAmount (initial withdrawal needed)
667
- if (
668
- extendedAmount.isNegative() &&
669
- extendedAmount
670
- .abs()
671
- .greaterThan(extendedAdapter.minimumExtendedMovementAmount)
672
- ) {
673
- try {
674
- const {
675
- calls: extendedCalls,
676
- status: extendedStatus,
677
- transactionMetadata: extendedTransactionMetadata,
678
- } = await this.moveAssets(
679
- {
680
- to: Protocols.VAULT.name,
681
- from: Protocols.EXTENDED.name,
682
- amount: extendedAmount.abs(),
683
- cycleType: CycleType.INVESTMENT,
684
- },
685
- extendedAdapter,
686
- vesuAdapter
687
- );
688
- if (extendedStatus) {
689
- transactionResults.push({
690
- status: extendedStatus,
691
- calls: extendedCalls,
692
- transactionMetadata: {
693
- ...extendedTransactionMetadata,
694
- transactionType: "DEPOSIT",
695
- },
696
- });
697
- } else {
698
- return [
699
- this.createTransactionResult(
700
- [],
701
- false,
702
- {
703
- from: Protocols.EXTENDED.name,
704
- to: Protocols.VAULT.name,
705
- amount: extendedAmount.abs(),
706
- },
707
- "NONE",
708
- CycleType.INVESTMENT
709
- ),
710
- ];
711
- }
712
- } catch (err) {
713
- logger.error(`Failed moving assets to vault: ${err}`);
714
- return [
715
- this.createTransactionResult(
716
- [],
717
- false,
718
- {
719
- from: Protocols.EXTENDED.name,
720
- to: Protocols.VAULT.name,
721
- amount: extendedAmount.abs(),
722
- },
723
- "NONE",
724
- CycleType.INVESTMENT
725
- ),
726
- ];
727
- }
728
- }
729
-
730
- if (
731
- vesuAmount.isNegative() &&
732
- vesuAmount.abs().greaterThan(vesuAdapter.minimumVesuMovementAmount)
733
- ) {
734
- try {
735
- const {
736
- calls: vesuCalls,
737
- status: vesuStatus,
738
- transactionMetadata: vesuTransactionMetadata,
739
- } = await this.moveAssets(
740
- {
741
- to: Protocols.EXTENDED.name,
742
- from: Protocols.VESU.name,
743
- amount: vesuAmount.abs(),
744
- cycleType: CycleType.INVESTMENT,
745
- },
746
- extendedAdapter,
747
- vesuAdapter
748
- );
749
- if (!vesuStatus) {
750
- return [
751
- this.createTransactionResult(
752
- [],
753
- false,
754
- {
755
- from: Protocols.VESU.name,
756
- to: Protocols.EXTENDED.name,
757
- amount: vesuAmount.abs(),
758
- },
759
- "NONE",
760
- CycleType.INVESTMENT
761
- ),
762
- ];
763
- }
764
- transactionResults.push({
765
- status: vesuStatus,
766
- calls: vesuCalls,
767
- transactionMetadata: {
768
- ...vesuTransactionMetadata,
769
- transactionType: "DEPOSIT",
770
- },
771
- });
772
- } catch (err) {
773
- logger.error(
774
- `Failed moving assets to extended via vault allocator: ${err}`
775
- );
776
- return [
777
- this.createTransactionResult(
778
- [],
779
- false,
780
- {
781
- from: Protocols.VESU.name,
782
- to: Protocols.EXTENDED.name,
783
- amount: vesuAmount.abs(),
784
- },
785
- "NONE",
786
- CycleType.INVESTMENT
787
- ),
788
- ];
789
- }
790
- }
791
-
792
- // Handle Extended adjustments based on calculated difference
793
- if (
794
- extendedAmountDifferenceAbs.greaterThan(
795
- extendedAdapter.minimumExtendedMovementAmount
796
- )
797
- ) {
798
- if (extendedAmountDifference.greaterThan(0)) {
799
- try {
800
- const {
801
- calls: extendedCalls,
802
- status: extendedStatus,
803
- transactionMetadata: extendedTransactionMetadata,
804
- } = await this.moveAssets(
805
- {
806
- to: Protocols.EXTENDED.name,
807
- from: Protocols.VAULT.name,
808
- amount: extendedAmountDifference,
809
- cycleType: CycleType.INVESTMENT,
810
- },
811
- extendedAdapter,
812
- vesuAdapter
813
- );
814
- if (extendedStatus) {
815
- transactionResults.push({
816
- status: extendedStatus,
817
- calls: extendedCalls,
818
- transactionMetadata: extendedTransactionMetadata,
819
- });
820
- } else {
821
- logger.error(
822
- `Failed to move assets to extended - operation returned false status`
823
- );
824
- return [
825
- this.createTransactionResult(
826
- [],
827
- false,
828
- {
829
- from: Protocols.VAULT.name,
830
- to: Protocols.EXTENDED.name,
831
- amount: extendedAmountDifference,
832
- },
833
- "NONE",
834
- CycleType.INVESTMENT
835
- ),
836
- ];
837
- }
838
- } catch (err) {
839
- logger.error(`Failed moving assets to extended: ${err}`);
840
- return [
841
- this.createTransactionResult(
842
- [],
843
- false,
844
- {
845
- from: Protocols.VAULT.name,
846
- to: Protocols.EXTENDED.name,
847
- amount: extendedAmountDifference,
848
- },
849
- "NONE",
850
- CycleType.INVESTMENT
851
- ),
852
- ];
853
- }
854
- } else if (extendedAmountDifference.lessThan(0)) {
855
- try {
856
- const {
857
- calls: extendedCalls,
858
- status: extendedStatus,
859
- transactionMetadata: extendedTransactionMetadata,
860
- } = await this.moveAssets(
861
- {
862
- to: Protocols.VAULT.name,
863
- from: Protocols.EXTENDED.name,
864
- amount: extendedAmountDifferenceAbs,
865
- cycleType: CycleType.INVESTMENT,
866
- },
867
- extendedAdapter,
868
- vesuAdapter
869
- );
870
- if (extendedStatus) {
871
- transactionResults.push({
872
- status: extendedStatus,
873
- calls: extendedCalls,
874
- transactionMetadata: {
875
- ...extendedTransactionMetadata,
876
- transactionType: "DEPOSIT",
877
- },
878
- });
879
- } else {
880
- logger.error(
881
- `Failed to withdraw from extended - operation returned false status`
882
- );
883
- return [
884
- this.createTransactionResult(
885
- [],
886
- false,
887
- {
888
- from: Protocols.EXTENDED.name,
889
- to: Protocols.VAULT.name,
890
- amount: extendedAmountDifferenceAbs,
891
- },
892
- "NONE",
893
- CycleType.INVESTMENT
894
- ),
895
- ];
896
- }
897
- } catch (err) {
898
- logger.error(`Failed moving assets from extended to vault: ${err}`);
899
- return [
900
- this.createTransactionResult(
901
- [],
902
- false,
903
- {
904
- from: Protocols.EXTENDED.name,
905
- to: Protocols.VAULT.name,
906
- amount: extendedAmountDifferenceAbs,
907
- },
908
- "NONE",
909
- CycleType.INVESTMENT
910
- ),
911
- ];
912
- }
913
- }
914
- }
915
-
916
- // Handle Vesu adjustments based on calculated difference (already adjusted for Extended movements)
917
- if (
918
- vesuAmountDifferenceAbs.greaterThan(
919
- vesuAdapter.minimumVesuMovementAmount
920
- )
921
- ) {
922
- if (vesuAmountDifference.lessThanOrEqualTo(0)) {
923
- logger.warn(
924
- `Vesu amount difference is negative or zero: ${vesuAmountDifference.toNumber()}. Skipping operation.`
925
- );
926
- } else {
927
- // Move assets from Extended to Vault (which will then go to Vesu)
928
- try {
929
- const {
930
- calls: vesuCalls,
931
- status: vesuStatus,
932
- transactionMetadata: vesuTransactionMetadata,
933
- } = await this.moveAssets(
934
- {
935
- to: Protocols.VAULT.name,
936
- from: Protocols.EXTENDED.name,
937
- amount: vesuAmountDifference,
938
- cycleType: CycleType.INVESTMENT,
939
- },
940
- extendedAdapter,
941
- vesuAdapter
942
- );
943
- if (!vesuStatus) {
944
- logger.error(
945
- `Failed to move assets to vesu - operation returned false status`
946
- );
947
- return [
948
- this.createTransactionResult(
949
- [],
950
- false,
951
- {
952
- from: Protocols.EXTENDED.name,
953
- to: Protocols.VAULT.name,
954
- amount: vesuAmountDifference,
955
- },
956
- "NONE",
957
- CycleType.INVESTMENT
958
- ),
959
- ];
960
- }
961
- transactionResults.push({
962
- status: vesuStatus,
963
- calls: vesuCalls,
964
- transactionMetadata: {
965
- ...vesuTransactionMetadata,
966
- transactionType: "DEPOSIT",
967
- },
968
- });
969
- } catch (err) {
970
- logger.error(`Failed moving assets to vault: ${err}`);
971
- return [
972
- this.createTransactionResult(
973
- [],
974
- false,
975
- {
976
- from: Protocols.EXTENDED.name,
977
- to: Protocols.VAULT.name,
978
- amount: vesuAmountDifference,
979
- },
980
- "NONE",
981
- CycleType.INVESTMENT
982
- ),
983
- ];
984
- }
985
- }
986
- }
987
- return transactionResults;
988
- } catch (err) {
989
- logger.error(`Failed moving assets to vesu: ${err}`);
990
- return [
991
- this.createTransactionResult(
992
- [],
993
- false,
994
- {
995
- from: Protocols.EXTENDED.name,
996
- to: Protocols.VAULT.name,
997
- amount: new Web3Number(0, USDC_TOKEN_DECIMALS),
998
- },
999
- "NONE",
1000
- CycleType.INVESTMENT
1001
- ),
1002
- ];
1003
- }
1004
- }
1005
-
1006
- /**
1007
- * Helper method to create transaction result with metadata
1008
- */
1009
- private createTransactionResult(
1010
- calls: Call[],
1011
- status: boolean,
1012
- params: { from: string; to: string; amount: Web3Number },
1013
- transactionType: "DEPOSIT" | "WITHDRAWAL" | "NONE",
1014
- cycleType: CycleType
1015
- ): TransactionResult {
1016
- if (status) {
1017
- return {
1018
- calls,
1019
- status: status,
1020
- transactionMetadata: {
1021
- protocolFrom: params.from,
1022
- protocolTo: params.to,
1023
- transactionType: transactionType,
1024
- usdAmount: params.amount.abs().toFixed(),
1025
- status: "PENDING",
1026
- cycleType: cycleType,
1027
- },
1028
- };
1029
236
  }
1030
- return {
1031
- calls: [],
1032
- status: false,
1033
- transactionMetadata: {
1034
- protocolFrom: "",
1035
- protocolTo: "",
1036
- transactionType: "DEPOSIT",
1037
- usdAmount: "0",
1038
- status: "FAILED",
1039
- cycleType: cycleType,
1040
- },
1041
- };
1042
- }
1043
-
1044
- /**
1045
- * This method is used to move assets between protocols
1046
- * @param params - The parameters for the move assets operation
1047
- * @param extendedAdapter - The extended adapter
1048
- * @param vesuAdapter - The vesu adapter
1049
- * @returns The transaction result
1050
- * If Extended amount is greater than amount of withdrawal from extended, then we need to open a long position
1051
- * so that the amount of withdrawal from extended is fullfilled
1052
- */
1053
- async moveAssets(
1054
- params: {
1055
- amount: Web3Number;
1056
- from: string;
1057
- to: string;
1058
- cycleType: CycleType;
1059
- },
1060
- extendedAdapter: ExtendedAdapter,
1061
- vesuAdapter: VesuMultiplyAdapter
1062
- ): Promise<TransactionResult> {
1063
- try {
1064
- // Validate amount is positive before starting operations
1065
- if (params.amount.lessThanOrEqualTo(0)) {
1066
- logger.error(
1067
- `Invalid amount for moveAssets: ${params.amount.toNumber()}. Amount must be positive.`
1068
- );
1069
- return this.createTransactionResult(
1070
- [],
1071
- false,
1072
- params,
1073
- "NONE",
1074
- params.cycleType
1075
- );
1076
- }
1077
-
1078
- // Check minimum movement amounts before starting operations
1079
- // const amountAbs = params.amount.abs();
1080
- // if (params.from === Protocols.EXTENDED.name || params.to === Protocols.EXTENDED.name) {
1081
- // if (amountAbs.lessThanOrEqualTo(extendedAdapter.minimumExtendedMovementAmount)) {
1082
- // logger.warn(
1083
- // `Amount ${amountAbs.toNumber()} is below minimum Extended movement amount ${extendedAdapter.minimumExtendedMovementAmount}. Skipping operation.`
1084
- // );
1085
- // return this.createTransactionResult([], false, params, "NONE", params.cycleType);
1086
- // }
1087
- // }
1088
- // if (params.from === Protocols.VESU.name || params.to === Protocols.VESU.name) {
1089
- // if (amountAbs.lessThanOrEqualTo(vesuAdapter.minimumVesuMovementAmount)) {
1090
- // logger.warn(
1091
- // `Amount ${amountAbs.toNumber()} is below minimum Vesu movement amount ${vesuAdapter.minimumVesuMovementAmount}. Skipping operation.`
1092
- // );
1093
- // return this.createTransactionResult([], false, params, "NONE", params.cycleType);
1094
- // }
1095
- // }
1096
-
1097
- const avnuAdapter = await this.getAvnuAdapter();
1098
- if (!avnuAdapter) {
1099
- logger.error(`avnu adapter not found: ${avnuAdapter}`);
1100
- return this.createTransactionResult(
1101
- [],
1102
- false,
1103
- params,
1104
- "NONE",
1105
- params.cycleType
1106
- );
1107
- }
1108
- logger.info(`moveAssets params, ${JSON.stringify(params)}`);
1109
- const collateralToken = vesuAdapter.config.supportedPositions[0].asset;
1110
- const { collateralPrice } = await this.getAssetPrices();
1111
-
1112
- if (
1113
- params.to === Protocols.EXTENDED.name &&
1114
- params.from === Protocols.VAULT.name
1115
- ) {
1116
- const proofsInfo = extendedAdapter.getProofs(
1117
- true,
1118
- this.getMerkleTree()
1119
- );
1120
- const calls = [];
1121
- const proofGroups = proofsInfo.proofs;
1122
- const call = this.getManageCall(
1123
- proofGroups,
1124
- await proofsInfo.callConstructor({ amount: params.amount })
1125
- );
1126
- calls.push(call);
1127
- return this.createTransactionResult(
1128
- calls,
1129
- true,
1130
- params,
1131
- "DEPOSIT",
1132
- params.cycleType
1133
- );
1134
- } else if (
1135
- params.to === Protocols.VAULT.name &&
1136
- params.from === Protocols.EXTENDED.name
1137
- ) {
1138
- const extendedLeverage = calculateExtendedLevergae();
1139
- const extendedHoldings =
1140
- await extendedAdapter.getExtendedDepositAmount();
1141
- if (!extendedHoldings) {
1142
- logger.error(`error getting extended holdings: ${extendedHoldings}`);
1143
- return this.createTransactionResult(
1144
- [],
1145
- false,
1146
- params,
1147
- "NONE",
1148
- params.cycleType
1149
- );
1150
- }
1151
- const extendedHoldingAmount = new Web3Number(
1152
- extendedHoldings.availableForWithdrawal,
1153
- USDC_TOKEN_DECIMALS
1154
- );
1155
- logger.info(
1156
- `${
1157
- VesuExtendedMultiplierStrategy.name
1158
- }::moveAssets extendedHoldingAmount: ${extendedHoldingAmount.toNumber()}`
1159
- );
1160
- const extendedPositions = await extendedAdapter.getAllOpenPositions();
1161
- if (!extendedPositions) {
1162
- logger.error(
1163
- `error getting extended positions: ${extendedPositions} while moving assets from extended to vault`
1164
- );
1165
- return this.createTransactionResult(
1166
- [],
1167
- false,
1168
- params,
1169
- "NONE",
1170
- params.cycleType
1171
- );
1172
- }
1173
- if (params.amount.abs().greaterThan(extendedHoldingAmount)) {
1174
- const leftAmountAfterWithdrawalAmountInAccount = new Web3Number(
1175
- Math.ceil(
1176
- params.amount.abs().minus(extendedHoldingAmount).toNumber()
1177
- ),
1178
- USDC_TOKEN_DECIMALS
1179
- );
1180
- const positionAmountToClose =
1181
- await calculatePositionToCloseToWithdrawAmount(
1182
- extendedHoldings,
1183
- extendedPositions[0],
1184
- params.amount
1185
- );
1186
- logger.info(
1187
- `positionAmountToClose: ${positionAmountToClose} this is without leverage`
1188
- );
1189
- logger.info(
1190
- `${
1191
- VesuExtendedMultiplierStrategy.name
1192
- }::moveAssets leftAmountAfterWithdrawalAmountInAccount: ${leftAmountAfterWithdrawalAmountInAccount.toNumber()}`
1193
- );
1194
- let priceOfBTC;
1195
- const { ask, bid, status } =
1196
- await extendedAdapter.fetchOrderBookBTCUSDC();
1197
- const price = ask.plus(bid).dividedBy(2);
1198
- if (status) {
1199
- priceOfBTC = price;
1200
- } else {
1201
- logger.error(`error fetching order book btc usdc: ${status}`);
1202
- priceOfBTC = collateralPrice.price;
1203
- }
1204
- const btcAmount = positionAmountToClose.dividedBy(priceOfBTC);
1205
- /**
1206
- * If amount for withdrawal is greater than the amount in extended available for withdrawal,
1207
- * then we need to open a long position depending on the difference between the two
1208
- */
1209
- const openLongPosition = btcAmount
1210
- .multipliedBy(3)
1211
- .greaterThan(MINIMUM_EXTENDED_POSITION_SIZE)
1212
- ? await extendedAdapter.createOrder(
1213
- extendedLeverage.toString(),
1214
- btcAmount.toNumber(),
1215
- OrderSide.BUY
1216
- )
1217
- : await extendedAdapter.createOrder(
1218
- extendedLeverage.toString(),
1219
- 0.000034, // just in case amount falls short then we need to create a withdrawal
1220
- OrderSide.BUY
1221
- );
1222
- if (!openLongPosition) {
1223
- logger.error(`error opening long position: ${openLongPosition}`);
1224
- }
1225
- const updatedHoldings =
1226
- await extendedAdapter.getExtendedDepositAmount();
1227
- if (
1228
- !updatedHoldings ||
1229
- new Web3Number(
1230
- updatedHoldings.availableForWithdrawal,
1231
- USDC_TOKEN_DECIMALS
1232
- ).lessThan(params.amount.abs())
1233
- ) {
1234
- logger.error(
1235
- `Insufficient balance after opening position. Available: ${
1236
- updatedHoldings?.availableForWithdrawal
1237
- }, Needed: ${params.amount.abs()}`
1238
- );
1239
- return this.createTransactionResult(
1240
- [],
1241
- false,
1242
- params,
1243
- "NONE",
1244
- params.cycleType
1245
- );
1246
- }
1247
- }
1248
- const {
1249
- status: withdrawalFromExtendedStatus,
1250
- receivedTxnHash: withdrawalFromExtendedTxnHash,
1251
- } = await extendedAdapter.withdrawFromExtended(params.amount);
1252
- /**
1253
- * This logic needs fixing
1254
- */
1255
- logger.info(
1256
- `withdrawalFromExtendedStatus: ${withdrawalFromExtendedStatus}, withdrawalFromExtendedTxnHash: ${withdrawalFromExtendedTxnHash}`
1257
- );
1258
- if (withdrawalFromExtendedStatus && withdrawalFromExtendedTxnHash) {
1259
- /**
1260
- * We need to move assets from my wallet back to vault contract
1261
- */
1262
- const extendedHoldings =
1263
- await extendedAdapter.getExtendedDepositAmount();
1264
- logger.info(
1265
- `extendedHoldings after withdrawal ${extendedHoldings?.availableForWithdrawal}`
1266
- );
1267
- await new Promise((resolve) => setTimeout(resolve, 5000));
1268
- const { calls, status } = await this.moveAssetsToVaultAllocator(
1269
- params.amount,
1270
- extendedAdapter
1271
- );
1272
- if (calls.length > 0 && status) {
1273
- return this.createTransactionResult(
1274
- calls,
1275
- true,
1276
- params,
1277
- "WITHDRAWAL",
1278
- params.cycleType
1279
- );
1280
- } else {
1281
- /**
1282
- * This is a fallback scenario, where the funds were withdrawn from extended, but didn't get transferred to the wallet
1283
- * We need to return a successful transaction result, but with no calls
1284
- * Db update will be handled by the risk engine for this specific case
1285
- */
1286
- return this.createTransactionResult(
1287
- [],
1288
- true,
1289
- params,
1290
- "WITHDRAWAL",
1291
- params.cycleType
1292
- );
1293
- }
1294
- } else if (
1295
- withdrawalFromExtendedStatus &&
1296
- !withdrawalFromExtendedTxnHash
1297
- ) {
1298
- logger.error(
1299
- "withdrawal from extended successful, but funds didn't get transferred to the wallet"
1300
- );
1301
- return this.createTransactionResult(
1302
- [],
1303
- true,
1304
- params,
1305
- "WITHDRAWAL",
1306
- params.cycleType
1307
- );
1308
- } else {
1309
- logger.error("withdrawal from extended failed");
1310
- return this.createTransactionResult(
1311
- [],
1312
- false,
1313
- params,
1314
- "NONE",
1315
- params.cycleType
1316
- );
1317
- }
1318
- } else if (
1319
- params.to === Protocols.VAULT.name &&
1320
- params.from === Protocols.VESU.name
1321
- ) {
1322
- const isPriceDifferenceBetweenAvnuAndExtended =
1323
- await this.checkPriceDifferenceBetweenAvnuAndExtended(
1324
- extendedAdapter,
1325
- vesuAdapter,
1326
- avnuAdapter,
1327
- PositionTypeAvnuExtended.CLOSE
1328
- );
1329
- if (
1330
- !isPriceDifferenceBetweenAvnuAndExtended &&
1331
- params.cycleType === CycleType.WITHDRAWAL
1332
- ) {
1333
- logger.warn(
1334
- `price difference between avnu and extended doesn't fit the range for close position, ${avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing}`
1335
- );
1336
- return this.createTransactionResult(
1337
- [],
1338
- false,
1339
- params,
1340
- "NONE",
1341
- params.cycleType
1342
- );
1343
- }
1344
- //withdraw from vesu
1345
- const vesuAmountInBTC = new Web3Number(
1346
- params.amount
1347
- .dividedBy(collateralPrice.price)
1348
- .toFixed(WBTC_TOKEN_DECIMALS),
1349
- collateralToken.decimals
1350
- );
1351
- const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
1352
- const calls = [];
1353
- const proofGroups = proofsInfo.proofs;
1354
- const call = this.getManageCall(
1355
- proofGroups,
1356
- await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
1357
- );
1358
- calls.push(call);
1359
- const swapProofsInfo = avnuAdapter.getProofs(
1360
- false,
1361
- this.getMerkleTree()
1362
- );
1363
- const swapProofGroups = swapProofsInfo.proofs;
1364
- const swapCall = this.getManageCall(
1365
- swapProofGroups,
1366
- await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
1367
- );
1368
- calls.push(swapCall);
1369
- return this.createTransactionResult(
1370
- calls,
1371
- true,
1372
- params,
1373
- "WITHDRAWAL",
1374
- params.cycleType
1375
- );
1376
- } else if (
1377
- params.to === Protocols.EXTENDED.name &&
1378
- params.from === Protocols.VESU.name
1379
- ) {
1380
- const isPriceDifferenceBetweenAvnuAndExtended =
1381
- await this.checkPriceDifferenceBetweenAvnuAndExtended(
1382
- extendedAdapter,
1383
- vesuAdapter,
1384
- avnuAdapter,
1385
- PositionTypeAvnuExtended.CLOSE
1386
- );
1387
- if (!isPriceDifferenceBetweenAvnuAndExtended) {
1388
- logger.warn(
1389
- `price difference between avnu and extended doesn't fit the range for close position, ${avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing}`
1390
- );
1391
- return this.createTransactionResult(
1392
- [],
1393
- false,
1394
- params,
1395
- "NONE",
1396
- params.cycleType
1397
- );
1398
- }
1399
- const vesuAmountInBTC = new Web3Number(
1400
- params.amount.dividedBy(collateralPrice.price).toNumber(),
1401
- collateralToken.decimals
1402
- );
1403
- const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
1404
- const calls = [];
1405
- const proofGroups = proofsInfo.proofs;
1406
- const call = this.getManageCall(
1407
- proofGroups,
1408
- await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
1409
- );
1410
- calls.push(call);
1411
- const swapProofsInfo = avnuAdapter.getProofs(
1412
- false,
1413
- this.getMerkleTree()
1414
- );
1415
- const swapProofGroups = swapProofsInfo.proofs;
1416
- const swapCall = this.getManageCall(
1417
- swapProofGroups,
1418
- await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
1419
- );
1420
- calls.push(swapCall);
1421
- const proofsInfoDeposit = extendedAdapter.getProofs(
1422
- true,
1423
- this.getMerkleTree()
1424
- );
1425
- //Deposit Amount would still be in usdc
1426
- const proofGroupsDeposit = proofsInfoDeposit.proofs;
1427
- const callDeposit = this.getManageCall(
1428
- proofGroupsDeposit,
1429
- await proofsInfoDeposit.callConstructor({ amount: params.amount })
1430
- );
1431
- calls.push(callDeposit);
1432
- return this.createTransactionResult(
1433
- calls,
1434
- true,
1435
- params,
1436
- "DEPOSIT",
1437
- params.cycleType
1438
- );
1439
- }
1440
- logger.error(
1441
- `Unsupported assets movement: ${params.from} to ${params.to}`
1442
- );
1443
- return this.createTransactionResult(
1444
- [],
1445
- false,
1446
- params,
1447
- "NONE",
1448
- params.cycleType
1449
- );
1450
- } catch (err) {
1451
- logger.error(`error moving assets: ${err}`);
1452
- return this.createTransactionResult(
1453
- [],
1454
- false,
1455
- params,
1456
- "NONE",
1457
- params.cycleType
237
+ if (!extendedAdapter.adapter || !(extendedAdapter.adapter as ExtendedAdapter).client) {
238
+ throw new Error(
239
+ `${this.getTag()} Extended adapter client not initialized.`
1458
240
  );
1459
241
  }
242
+ return extendedAdapter.adapter as ExtendedAdapter;
1460
243
  }
1461
244
 
1462
- async handleDeposit(): Promise<TransactionResult> {
1463
- try {
1464
- /**
1465
- * Just a demo function, not used in the risk engine
1466
- */
1467
- return this.createTransactionResult(
1468
- [],
1469
- false,
1470
- {
1471
- from: Protocols.VAULT.name,
1472
- to: Protocols.VAULT.name,
1473
- amount: new Web3Number(0, 0),
1474
- },
1475
- "NONE",
1476
- CycleType.INVESTMENT
1477
- );
1478
- } catch (err) {
1479
- logger.error(`error handling deposit: ${err}`);
1480
- return this.createTransactionResult(
1481
- [],
1482
- false,
1483
- {
1484
- from: Protocols.VAULT.name,
1485
- to: Protocols.VAULT.name,
1486
- amount: new Web3Number(0, 0),
1487
- },
1488
- "NONE",
1489
- 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.`
1490
252
  );
1491
253
  }
254
+ return usdcToUsdceAdapter.adapter as UsdcToUsdceAdapter;
1492
255
  }
1493
256
 
1494
257
  /**
1495
- * Check if the price difference between avnu and extended is within the acceptable range to enhance the position size or close the position
1496
- * @param extendedAdapter - the extended adapter
1497
- * @param vesuAdapter - the vesu adapter
1498
- * @param avnuAdapter - the avnu adapter
1499
- * @param positionType - the position type (open or close)
1500
- * @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%)
1501
265
  */
1502
- async checkPriceDifferenceBetweenAvnuAndExtended(
1503
- extendedAdapter: ExtendedAdapter,
1504
- vesuAdapter: VesuMultiplyAdapter,
1505
- avnuAdapter: AvnuAdapter,
1506
- positionType: PositionTypeAvnuExtended
1507
- ): Promise<boolean> {
1508
- const { ask, bid } = await extendedAdapter.fetchOrderBookBTCUSDC();
1509
- const price = ask.plus(bid).dividedBy(2);
1510
- const btcToken = vesuAdapter.config.supportedPositions[0].asset;
1511
- const btcPriceAvnu = await avnuAdapter.getPriceOfToken(
1512
- btcToken.address.toString()
1513
- );
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
+ };
1514
309
 
1515
- if (!btcPriceAvnu) {
1516
- logger.error(`error getting btc price avnu: ${btcPriceAvnu}`);
1517
- return false;
1518
- }
1519
- logger.info(`price: ${price}`);
1520
- logger.info(`btcPriceAvnu: ${btcPriceAvnu}`);
1521
- const priceDifference = new Web3Number(
1522
- price.minus(btcPriceAvnu).toFixed(2),
1523
- 0
1524
- );
1525
- logger.info(`priceDifference: ${priceDifference}`);
1526
- if (priceDifference.isNegative()) {
1527
- return false;
1528
- }
1529
- if (positionType === PositionTypeAvnuExtended.OPEN) {
1530
- logger.info(
1531
- `price difference between avnu and extended for open position: ${priceDifference.toNumber()}, minimumExtendedPriceDifferenceForSwapOpen: ${
1532
- avnuAdapter.config.minimumExtendedPriceDifferenceForSwapOpen
1533
- }`
1534
- );
1535
- const result = priceDifference.greaterThanOrEqualTo(
1536
- avnuAdapter.config.minimumExtendedPriceDifferenceForSwapOpen
1537
- ); // 500 for now
1538
- logger.info(`result: ${result}`);
1539
- return result;
1540
- } else {
1541
- logger.info(
1542
- `price difference between avnu and extended for close position: ${priceDifference.toNumber()}, maximumExtendedPriceDifferenceForSwapClosing: ${
1543
- avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing
1544
- }`
1545
- );
1546
- const result = priceDifference.lessThanOrEqualTo(
1547
- avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing
1548
- ); // 1000 for now
1549
- logger.info(`result: ${result}`);
1550
- return result;
1551
- }
310
+ return new ExecutionService(executionConfig);
1552
311
  }
1553
312
 
1554
313
  /**
1555
- * Handle the withdrawal of assets from the vault
1556
- * @param amount - the amount to withdraw in USDC
1557
- * @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.
1558
318
  */
1559
- async handleWithdraw(amount: Web3Number): Promise<TransactionResult[]> {
1560
- try {
1561
- const usdcBalanceVaultAllocator = await this.getUnusedBalance();
1562
- const usdcBalanceDifference = amount
1563
- .plus(BUFFER_USDC_IN_WITHDRAWAL)
1564
- .minus(usdcBalanceVaultAllocator.usdValue);
1565
- logger.info(`usdcBalanceDifference, ${usdcBalanceDifference.toNumber()}`);
1566
- let calls: Call[] = [];
1567
- let status: boolean = true;
1568
- if (usdcBalanceDifference.lessThan(0)) {
1569
- const withdrawCall = await this.getBringLiquidityCall({
1570
- amount: usdcBalanceVaultAllocator.amount,
1571
- });
1572
- calls.push(withdrawCall);
1573
- return [
1574
- this.createTransactionResult(
1575
- calls,
1576
- true,
1577
- {
1578
- from: Protocols.VAULT.name,
1579
- to: Protocols.NONE.name,
1580
- amount: amount,
1581
- },
1582
- "WITHDRAWAL",
1583
- CycleType.WITHDRAWAL
1584
- ),
1585
- ];
1586
- }
1587
- const vesuAdapter = await this.getVesuAdapter();
1588
- const extendedAdapter = await this.getExtendedAdapter();
1589
- if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
1590
- status = false;
1591
- logger.error(
1592
- `vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
1593
- );
1594
- return [
1595
- this.createTransactionResult(
1596
- calls,
1597
- status,
1598
- {
1599
- from: Protocols.VAULT.name,
1600
- to: Protocols.NONE.name,
1601
- amount: amount,
1602
- },
1603
- "NONE",
1604
- CycleType.WITHDRAWAL
1605
- ),
1606
- ];
1607
- }
1608
- let transactionResults: TransactionResult[] = [];
1609
- const { collateralTokenAmount } =
1610
- await vesuAdapter.vesuAdapter.getAssetPrices();
1611
- const { collateralPrice } = await this.getAssetPrices();
1612
- const extendedPositon = await extendedAdapter.getAllOpenPositions();
1613
- if (!extendedPositon) {
1614
- status = false;
1615
- logger.error("error getting extended position", extendedPositon);
1616
- return [
1617
- this.createTransactionResult(
1618
- calls,
1619
- status,
1620
- {
1621
- from: Protocols.VAULT.name,
1622
- to: Protocols.NONE.name,
1623
- amount: amount,
1624
- },
1625
- "NONE",
1626
- CycleType.WITHDRAWAL
1627
- ),
1628
- ];
1629
- }
1630
- const amountDistributionForWithdrawal =
1631
- await calculateAmountDistributionForWithdrawal(
1632
- usdcBalanceDifference,
1633
- collateralPrice.price,
1634
- collateralTokenAmount,
1635
- extendedPositon
1636
- );
1637
- if (!amountDistributionForWithdrawal) {
1638
- status = false;
1639
- logger.error(
1640
- `error calculating amount distribution for withdrawal: ${amountDistributionForWithdrawal}`
1641
- );
1642
- return [
1643
- this.createTransactionResult(
1644
- calls,
1645
- status,
1646
- {
1647
- from: Protocols.VAULT.name,
1648
- to: Protocols.NONE.name,
1649
- amount: amount,
1650
- },
1651
- "NONE",
1652
- CycleType.WITHDRAWAL
1653
- ),
1654
- ];
1655
- }
1656
- const { vesu_amount, extended_amount } = amountDistributionForWithdrawal;
1657
-
1658
- if (status && vesu_amount.greaterThan(0)) {
1659
- const {
1660
- calls: vesuCalls,
1661
- status: vesuStatus,
1662
- transactionMetadata: vesuTransactionMetadata,
1663
- } = await this.moveAssets(
1664
- {
1665
- amount: vesu_amount,
1666
- from: Protocols.VESU.name,
1667
- to: Protocols.VAULT.name,
1668
- cycleType: CycleType.WITHDRAWAL,
1669
- },
1670
- extendedAdapter,
1671
- vesuAdapter
1672
- );
1673
- status = vesuStatus;
1674
- transactionResults.push({
1675
- status: vesuStatus,
1676
- calls: vesuCalls,
1677
- transactionMetadata: vesuTransactionMetadata,
1678
- });
1679
- }
1680
- if (status && extended_amount.greaterThan(0)) {
1681
- const {
1682
- calls: extendedCalls,
1683
- status: extendedStatus,
1684
- transactionMetadata: extendedTransactionMetadata,
1685
- } = await this.moveAssets(
1686
- {
1687
- amount: extended_amount,
1688
- from: Protocols.EXTENDED.name,
1689
- to: Protocols.VAULT.name,
1690
- cycleType: CycleType.WITHDRAWAL,
1691
- },
1692
- extendedAdapter,
1693
- vesuAdapter
1694
- );
1695
- status = extendedStatus;
1696
- if (status) {
1697
- transactionResults.push({
1698
- status: extendedStatus,
1699
- calls: extendedCalls,
1700
- transactionMetadata: extendedTransactionMetadata,
1701
- });
1702
- } else {
1703
- logger.error(
1704
- "error moving assets to vault: extendedStatus: ${extendedStatus}"
1705
- );
1706
- return [
1707
- this.createTransactionResult(
1708
- [],
1709
- status,
1710
- {
1711
- from: Protocols.VAULT.name,
1712
- to: Protocols.NONE.name,
1713
- amount: amount,
1714
- },
1715
- "NONE",
1716
- CycleType.WITHDRAWAL
1717
- ),
1718
- ];
1719
- }
1720
- }
1721
- const withdrawCall = await this.getBringLiquidityCall({
1722
- amount: amount,
1723
- });
1724
- logger.info("withdraw call", withdrawCall);
1725
- transactionResults.push({
1726
- status: status,
1727
- calls: [withdrawCall],
1728
- transactionMetadata: {
1729
- protocolFrom: Protocols.VAULT.name,
1730
- protocolTo: Protocols.NONE.name,
1731
- transactionType: "WITHDRAWAL",
1732
- usdAmount: amount.toFixed(),
1733
- status: "PENDING",
1734
- cycleType: CycleType.WITHDRAWAL,
1735
- },
1736
- });
1737
- return transactionResults;
1738
- } catch (err) {
1739
- logger.error(`error handling withdrawal: ${err}`);
1740
- return [
1741
- this.createTransactionResult(
1742
- [],
1743
- false,
1744
- {
1745
- from: Protocols.VAULT.name,
1746
- to: Protocols.NONE.name,
1747
- amount: amount,
1748
- },
1749
- "NONE",
1750
- CycleType.WITHDRAWAL
1751
- ),
1752
- ];
1753
- }
1754
- }
1755
-
1756
319
  async getAUM(): Promise<{
1757
320
  net: SingleTokenInfo;
1758
321
  prevAum: Web3Number;
@@ -1760,10 +323,14 @@ export class VesuExtendedMultiplierStrategy<
1760
323
  }> {
1761
324
  const allPositions: PositionInfo[] = [];
1762
325
  for (let adapter of this.metadata.additionalInfo.adapters) {
1763
- const positions = await adapter.adapter.getPositions();
1764
- 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
+ }
1765
333
  }
1766
-
1767
334
  const assetPrice = await this.pricer.getPrice(this.asset().symbol);
1768
335
  let netAUM = new Web3Number(0, this.asset().decimals);
1769
336
  for (let position of allPositions) {
@@ -1774,6 +341,16 @@ export class VesuExtendedMultiplierStrategy<
1774
341
  }
1775
342
  }
1776
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
+
1777
354
  const prevAum = await this.getPrevAUM();
1778
355
  const realAUM: PositionInfo = {
1779
356
  tokenInfo: this.asset(),
@@ -1804,140 +381,101 @@ export class VesuExtendedMultiplierStrategy<
1804
381
  };
1805
382
  }
1806
383
 
1807
- async processTransactionDataFromSDK(
1808
- txnData: TransactionResult<any>[]
1809
- ): Promise<{
1810
- callsToBeExecutedFinal: Call[];
1811
- txnMetadata: TransactionMetadata[];
1812
- } | null> {
1813
- try {
1814
- const txnsToBeExecuted = txnData.filter((txn) => {
1815
- return (
1816
- txn.transactionMetadata.transactionType !== "NONE" &&
1817
- txn.transactionMetadata.protocolFrom !== "" &&
1818
- txn.transactionMetadata.protocolTo !== ""
1819
- );
1820
- });
1821
- const callsToBeExecutedFinal = txnsToBeExecuted.flatMap(
1822
- (txn) => txn.calls
1823
- );
1824
- const txnMetadata = txnsToBeExecuted.map(
1825
- (txn) => txn.transactionMetadata
1826
- );
1827
- return { callsToBeExecutedFinal, txnMetadata };
1828
- } catch (err) {
1829
- logger.error(`error processing transaction data from SDK: ${err}`);
1830
- return null;
1831
- }
1832
- }
1833
-
1834
- async processTransactionMetadata(
1835
- txnMetadata: TransactionMetadata[],
1836
- extendedIntentFulfilled: boolean
1837
- ): Promise<TransactionMetadata[] | null> {
1838
- try {
1839
- const txnMetadataNew = txnMetadata.map((txn) => {
1840
- const isExtendedProtocol =
1841
- txn.protocolFrom === Protocols.EXTENDED.name ||
1842
- txn.protocolTo === Protocols.EXTENDED.name;
1843
- // Only update status for extended protocol transactions since thsoe only cause delays
1844
- if (isExtendedProtocol) {
1845
- txn.status = extendedIntentFulfilled ? "COMPLETED" : "PENDING";
1846
- } else {
1847
- txn.status = "COMPLETED";
1848
- }
1849
- return txn;
1850
- });
1851
- return txnMetadataNew;
1852
- } catch (err) {
1853
- logger.error(`error processing transaction data from SDK: ${err}`);
1854
- return null;
1855
- }
1856
- }
1857
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
+ */
1858
391
  async getMaxBorrowableAmount(): Promise<Web3Number> {
1859
- const vesuAdapter = await this.getVesuAdapter();
392
+ const vesuAdapter = await this.getVesuModifyPositionAdapter();
1860
393
  const extendedAdapter = await this.getExtendedAdapter();
1861
- if (!vesuAdapter || !extendedAdapter) {
1862
- return new Web3Number(0, 0);
1863
- }
1864
394
  const extendedFundingRate = new Web3Number(
1865
395
  (await extendedAdapter.getNetAPY()).toFixed(4),
1866
- 0
396
+ 0,
1867
397
  );
1868
398
  const extendedPositions = await extendedAdapter.getAllOpenPositions();
1869
399
  if (!extendedPositions || extendedPositions.length === 0) {
1870
- logger.info(`no extended positions found`);
400
+ logger.warn(`${this.getTag()} getMaxBorrowableAmount: no extended positions found`);
1871
401
  return new Web3Number(0, 0);
1872
402
  }
1873
403
  const extendePositionSizeUSD = new Web3Number(
1874
404
  extendedPositions[0].value || 0,
1875
- 0
405
+ 0,
1876
406
  );
1877
407
  const vesuPositions = await vesuAdapter.getPositions();
1878
408
  const vesuSupplyApy = vesuPositions[0].apy.apy;
1879
409
  const vesuCollateralSizeUSD = new Web3Number(
1880
410
  vesuPositions[0].usdValue.toFixed(USDC_TOKEN_DECIMALS),
1881
- USDC_TOKEN_DECIMALS
411
+ USDC_TOKEN_DECIMALS,
1882
412
  );
1883
413
  const vesuDebtSizeUSD = new Web3Number(
1884
414
  vesuPositions[1].usdValue.toFixed(USDC_TOKEN_DECIMALS),
1885
- USDC_TOKEN_DECIMALS
415
+ USDC_TOKEN_DECIMALS,
1886
416
  );
1887
417
  const num1 = extendePositionSizeUSD.multipliedBy(extendedFundingRate);
1888
418
  const num2 = vesuCollateralSizeUSD.multipliedBy(vesuSupplyApy);
1889
419
  const num3 = vesuDebtSizeUSD.abs();
1890
420
  const maxBorrowApy = num1.plus(num2).minus(0.1).dividedBy(num3);
1891
- const vesuMaxBorrowableAmount =
1892
- await vesuAdapter.vesuAdapter.getMaxBorrowableByInterestRate(
421
+ const vesuMaxBorrowableResult =
422
+ await vesuAdapter._vesuAdapter.getMaxBorrowableByInterestRate(
1893
423
  this.config,
1894
424
  vesuAdapter.config.debt,
1895
- maxBorrowApy.toNumber()
425
+ maxBorrowApy.toNumber(),
1896
426
  );
1897
427
  return new Web3Number(
1898
- vesuMaxBorrowableAmount.toFixed(USDC_TOKEN_DECIMALS),
1899
- USDC_TOKEN_DECIMALS
428
+ vesuMaxBorrowableResult.maxDebtToHave.toFixed(USDC_TOKEN_DECIMALS),
429
+ USDC_TOKEN_DECIMALS,
1900
430
  );
1901
431
  }
1902
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
+ */
1903
438
  async getVesuHealthFactors(): Promise<number[]> {
1904
- const vesuAdapter = await this.getVesuAdapter();
439
+ const vesuAdapter = await this.getVesuModifyPositionAdapter();
1905
440
  const extendedAdapter = await this.getExtendedAdapter();
1906
- if (!vesuAdapter || !extendedAdapter) {
1907
- return [0, 0];
1908
- }
1909
441
  const vesuPositions = await vesuAdapter.getPositions();
1910
442
  const vesuCollateralSizeUSD = new Web3Number(
1911
443
  vesuPositions[0].usdValue.toFixed(USDC_TOKEN_DECIMALS),
1912
- 0
444
+ 0,
1913
445
  );
1914
446
  const vesuDebtSizeUSD = new Web3Number(
1915
447
  vesuPositions[1].usdValue.toFixed(USDC_TOKEN_DECIMALS),
1916
- 0
448
+ 0,
1917
449
  );
1918
450
  const actualLtv = vesuDebtSizeUSD.dividedBy(vesuCollateralSizeUSD).abs();
1919
- logger.info(`actualLtv: ${actualLtv.toNumber()}`);
451
+ logger.debug(`${this.getTag()} getVesuHealthFactors: actualLtv=${actualLtv.toNumber()}`);
1920
452
  const maxLtv = new Web3Number(
1921
- await vesuAdapter.vesuAdapter.getLTVConfig(this.config),
1922
- 4
453
+ await vesuAdapter._vesuAdapter.getLTVConfig(this.config),
454
+ 4,
1923
455
  );
1924
456
  const healthFactor = new Web3Number(
1925
457
  maxLtv.dividedBy(actualLtv).toFixed(4),
1926
- 4
458
+ 4,
1927
459
  );
1928
- logger.info(`healthFactor: ${healthFactor.toNumber()}`);
460
+ logger.debug(`${this.getTag()} getVesuHealthFactors: healthFactor=${healthFactor.toNumber()}`);
1929
461
  const extendedBalance = await extendedAdapter.getExtendedDepositAmount();
1930
462
  if (!extendedBalance) {
1931
463
  return [0, 0];
1932
464
  }
1933
465
  const extendedLeverage = new Web3Number(
1934
466
  (Number(extendedBalance.marginRatio) * 100).toFixed(4),
1935
- 4
467
+ 4,
1936
468
  );
1937
- logger.info(`extendedLeverage: ${extendedLeverage.toNumber()}`);
469
+ logger.debug(`${this.getTag()} getVesuHealthFactors: extendedLeverage=${extendedLeverage.toNumber()}`);
1938
470
  return [healthFactor.toNumber(), extendedLeverage.toNumber()];
1939
471
  }
1940
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
+ */
1941
479
  async netAPY(): Promise<{
1942
480
  net: number;
1943
481
  splits: { apy: number; id: string }[];
@@ -1952,23 +490,14 @@ export class VesuExtendedMultiplierStrategy<
1952
490
  }
1953
491
  }
1954
492
  const extendedAdapter = await this.getExtendedAdapter();
1955
- if (!extendedAdapter) {
1956
- return {
1957
- net: 0,
1958
- splits: [],
1959
- };
1960
- }
1961
493
  let vesuPositions = allPositions.filter(
1962
- (item) => item.protocol === Protocols.VESU
494
+ (item) => item.protocol === Protocols.VESU,
1963
495
  );
1964
496
  vesuPositions.map((item) => {
1965
497
  item.apy.apy = item.apy.apy * 0.1;
1966
498
  });
1967
499
  const extendedPositions = await extendedAdapter.getAllOpenPositions();
1968
- const usdcToken = Global.getDefaultTokens().find(
1969
- (token) => token.symbol === "USDC"
1970
- );
1971
- if (!extendedPositions || !usdcToken) {
500
+ if (!extendedPositions || !this.usdcToken) {
1972
501
  return {
1973
502
  net: 0,
1974
503
  splits: [],
@@ -1981,19 +510,19 @@ export class VesuExtendedMultiplierStrategy<
1981
510
  const totalHoldingsUSDValue =
1982
511
  allPositions.reduce((acc, curr) => acc + curr.usdValue, 0) +
1983
512
  Number(extendedEquity);
1984
- console.log(totalHoldingsUSDValue);
1985
513
  const extendedPositionSizeMultipliedByApy =
1986
514
  Number(extendedPosition.value) * extendedApy;
1987
515
  let weightedAPYs =
1988
516
  allPositions.reduce(
1989
517
  (acc, curr) => acc + curr.apy.apy * curr.usdValue,
1990
- 0
518
+ 0,
1991
519
  ) + extendedPositionSizeMultipliedByApy;
1992
- console.log(weightedAPYs);
1993
520
  const netAPY = weightedAPYs / totalHoldingsUSDValue;
1994
- console.log(netAPY);
521
+ logger.debug(
522
+ `${this.getTag()} netAPY: holdingsUsd=${totalHoldingsUSDValue}, weightedApy=${weightedAPYs}, net=${netAPY}`,
523
+ );
1995
524
  allPositions.push({
1996
- tokenInfo: usdcToken,
525
+ tokenInfo: this.usdcToken,
1997
526
  amount: new Web3Number(extendedPosition.size, 0),
1998
527
  usdValue: Number(extendedEquity),
1999
528
  apy: { apy: extendedApy, type: APYType.BASE },
@@ -2009,6 +538,10 @@ export class VesuExtendedMultiplierStrategy<
2009
538
  };
2010
539
  }
2011
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
+ */
2012
545
  async getWalletHoldings(): Promise<
2013
546
  {
2014
547
  tokenInfo: TokenInfo;
@@ -2016,62 +549,47 @@ export class VesuExtendedMultiplierStrategy<
2016
549
  usdValue: number;
2017
550
  }[]
2018
551
  > {
2019
- const usdceToken = Global.getDefaultTokens().find(
2020
- (token) => token.symbol === "USDCe"
2021
- );
2022
- const wbtcToken = Global.getDefaultTokens().find(
2023
- (token) => token.symbol === "WBTC"
2024
- );
2025
- const usdcToken = Global.getDefaultTokens().find(
2026
- (token) => token.symbol === "USDC"
2027
- );
2028
- if (!usdceToken || !wbtcToken || !usdcToken) {
552
+ if (!this.usdceToken || !this.wbtcToken || !this.usdcToken) {
2029
553
  return [];
2030
554
  }
2031
555
  const walletAddress = this.metadata.additionalInfo.walletAddress;
2032
556
  const usdceWalletBalance = await new ERC20(this.config).balanceOf(
2033
- usdceToken.address,
557
+ this.usdceToken.address,
2034
558
  walletAddress,
2035
- usdceToken.decimals
559
+ this.usdceToken.decimals,
2036
560
  );
2037
561
  const usdcWalletBalance = await new ERC20(this.config).balanceOf(
2038
- usdcToken.address,
2039
- walletAddress,
2040
- usdcToken.decimals
2041
- );
2042
- const wbtcWalletBalance = await new ERC20(this.config).balanceOf(
2043
- wbtcToken.address,
562
+ this.usdcToken.address,
2044
563
  walletAddress,
2045
- wbtcToken.decimals
564
+ this.usdcToken.decimals,
2046
565
  );
2047
- const price = await this.pricer.getPrice(usdceToken.symbol);
2048
- 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);
2049
568
  const usdceUsdValue =
2050
- Number(usdceWalletBalance.toFixed(usdceToken.decimals)) * price.price;
569
+ Number(usdceWalletBalance.toFixed(this.usdceToken.decimals)) *
570
+ price.price;
2051
571
  const usdcUsdValue =
2052
- Number(usdcWalletBalance.toFixed(usdcToken.decimals)) * price.price;
2053
- const wbtcUsdValue =
2054
- Number(wbtcWalletBalance.toFixed(wbtcToken.decimals)) * wbtcPrice.price;
572
+ Number(usdcWalletBalance.toFixed(this.usdcToken.decimals)) * price.price;
2055
573
  return [
2056
574
  {
2057
- tokenInfo: usdceToken,
575
+ tokenInfo: this.usdceToken,
2058
576
  amount: usdceWalletBalance,
2059
577
  usdValue: usdceUsdValue,
2060
578
  },
2061
579
  {
2062
- tokenInfo: usdcToken,
580
+ tokenInfo: this.usdcToken,
2063
581
  amount: usdcWalletBalance,
2064
582
  usdValue: usdcUsdValue,
2065
- },
2066
- {
2067
- tokenInfo: wbtcToken,
2068
- amount: wbtcWalletBalance,
2069
- usdValue: wbtcUsdValue,
2070
- },
583
+ }
2071
584
  ];
2072
585
  }
2073
586
  }
2074
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
+ */
2075
593
  function getLooperSettings(
2076
594
  lstSymbol: string,
2077
595
  underlyingSymbol: string,
@@ -2084,15 +602,18 @@ function getLooperSettings(
2084
602
  minimumVesuMovementAmount: number,
2085
603
  minimumExtendedRetriesDelayForOrderStatus: number,
2086
604
  minimumExtendedPriceDifferenceForSwapOpen: number,
2087
- maximumExtendedPriceDifferenceForSwapClosing: number
605
+ maximumExtendedPriceDifferenceForSwapClosing: number,
2088
606
  ) {
2089
607
  vaultSettings.leafAdapters = [];
2090
608
 
2091
609
  const wbtcToken = Global.getDefaultTokens().find(
2092
- (token) => token.symbol === lstSymbol
610
+ (token) => token.symbol === lstSymbol,
2093
611
  )!;
2094
612
  const usdcToken = Global.getDefaultTokens().find(
2095
- (token) => token.symbol === underlyingSymbol
613
+ (token) => token.symbol === underlyingSymbol,
614
+ )!;
615
+ const usdceToken = Global.getDefaultTokens().find(
616
+ (token) => token.symbol === "USDC.e",
2096
617
  )!;
2097
618
 
2098
619
  const baseAdapterConfig: BaseAdapterConfig = {
@@ -2119,9 +640,19 @@ function getLooperSettings(
2119
640
  maximumExtendedPriceDifferenceForSwapClosing,
2120
641
  });
2121
642
 
643
+ const usdcToUsdceAdapter = new UsdcToUsdceAdapter({
644
+ ...baseAdapterConfig,
645
+ supportedPositions: [
646
+ { asset: usdcToken, isDebt: true },
647
+ { asset: usdceToken, isDebt: false },
648
+ ],
649
+ });
650
+
2122
651
  const extendedAdapter = new ExtendedAdapter({
2123
652
  ...baseAdapterConfig,
2124
- supportedPositions: [{ asset: usdcToken, isDebt: true }],
653
+ supportedPositions: [
654
+ { asset: usdceToken, isDebt: false },
655
+ ],
2125
656
  vaultIdExtended: vaultIdExtended,
2126
657
  extendedContract: EXTENDED_CONTRACT,
2127
658
  extendedBackendWriteUrl: extendedBackendWriteUrl,
@@ -2140,6 +671,7 @@ function getLooperSettings(
2140
671
  poolId: pool1,
2141
672
  collateral: wbtcToken,
2142
673
  debt: usdcToken,
674
+ marginToken: usdcToken,
2143
675
  targetHealthFactor: vaultSettings.targetHealthFactor,
2144
676
  minHealthFactor: vaultSettings.minHealthFactor,
2145
677
  quoteAmountToFetchPrice: vaultSettings.quoteAmountToFetchPrice,
@@ -2151,8 +683,28 @@ function getLooperSettings(
2151
683
  minimumVesuMovementAmount: minimumVesuMovementAmount ?? 5, //5 usdc
2152
684
  });
2153
685
 
2154
- 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,
2155
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),
2156
708
  });
2157
709
 
2158
710
  vaultSettings.adapters.push({
@@ -2161,8 +713,18 @@ function getLooperSettings(
2161
713
  });
2162
714
 
2163
715
  vaultSettings.adapters.push({
2164
- id: `${unusedBalanceAdapter.name}_${wbtcToken.symbol}`,
2165
- 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,
2166
728
  });
2167
729
 
2168
730
  vaultSettings.adapters.push({
@@ -2185,26 +747,29 @@ function getLooperSettings(
2185
747
 
2186
748
  vaultSettings.leafAdapters.push(() => vesuMultiplyAdapter.getDepositLeaf());
2187
749
  vaultSettings.leafAdapters.push(() => vesuMultiplyAdapter.getWithdrawLeaf());
750
+ vaultSettings.leafAdapters.push(() => vesuModifyPositionAdapter.getDepositLeaf());
751
+ vaultSettings.leafAdapters.push(() => vesuModifyPositionAdapter.getWithdrawLeaf());
2188
752
  vaultSettings.leafAdapters.push(() => extendedAdapter.getDepositLeaf());
2189
- vaultSettings.leafAdapters.push(() =>
2190
- extendedAdapter.getSwapFromLegacyLeaf()
2191
- );
753
+ vaultSettings.leafAdapters.push(() => usdcToUsdceAdapter.getDepositLeaf());
754
+ vaultSettings.leafAdapters.push(() => usdcToUsdceAdapter.getWithdrawLeaf());
2192
755
  vaultSettings.leafAdapters.push(() => avnuAdapter.getDepositLeaf());
2193
756
  vaultSettings.leafAdapters.push(() => avnuAdapter.getWithdrawLeaf());
757
+ vaultSettings.leafAdapters.push(() => usdceTransferAdapter.getDepositLeaf());
758
+ vaultSettings.leafAdapters.push(() => usdceTransferAdapter.getWithdrawLeaf());
2194
759
  vaultSettings.leafAdapters.push(
2195
760
  commonAdapter
2196
761
  .getApproveAdapter(
2197
762
  usdcToken.address,
2198
763
  vaultSettings.vaultAddress,
2199
- UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY
764
+ UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY,
2200
765
  )
2201
- .bind(commonAdapter)
766
+ .bind(commonAdapter),
2202
767
  );
2203
768
 
2204
769
  vaultSettings.leafAdapters.push(
2205
770
  commonAdapter
2206
771
  .getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
2207
- .bind(commonAdapter)
772
+ .bind(commonAdapter),
2208
773
  );
2209
774
  return vaultSettings;
2210
775
  }
@@ -2215,7 +780,7 @@ function getDescription(tokenSymbol: string, underlyingSymbol: string) {
2215
780
 
2216
781
  export default function VaultDescription(
2217
782
  lstSymbol: string,
2218
- underlyingSymbol: string
783
+ underlyingSymbol: string,
2219
784
  ) {
2220
785
  const containerStyle = {
2221
786
  maxWidth: "800px",
@@ -2299,36 +864,37 @@ export default function VaultDescription(
2299
864
 
2300
865
  const re7UsdcPrimeDevansh: VesuExtendedStrategySettings = {
2301
866
  vaultAddress: ContractAddr.from(
2302
- "0x058905be22d6a81792df79425dc9641cf3e1b77f36748631b7d7e5d713a32b55"
867
+ "0x772d6cf5038c18ff5ab89f8945017bbf4d2c6959891339975c70a4f74ac6c8e",
2303
868
  ),
2304
869
  manager: ContractAddr.from(
2305
- "0x02648d703783feb2d967cf0520314cb5aa800d69a9426f3e3b317395af44de16"
870
+ "0x3340c9d7231838e2dccff72b9004f1598a74e65c74b954f07fe1ea19d04a625",
2306
871
  ),
2307
872
  vaultAllocator: ContractAddr.from(
2308
- "0x07d533c838eab6a4d854dd3aea96a55993fccd35821921970d00bde946b63b6f"
873
+ "0x537353b35eee5ca2d9a45eb646977baddd4e89ce870a231dcada79884117292",
2309
874
  ),
2310
875
  redeemRequestNFT: ContractAddr.from(
2311
- "0x01ef91f08fb99729c00f82fc6e0ece37917bcc43952596c19996259dc8adbbba"
876
+ "0x6117d1a8c72c0457948083757e1a17ee8c0833b969d5c959b629e5f8feb56ec",
2312
877
  ),
2313
878
  aumOracle: ContractAddr.from(
2314
- "0x030b6acfec162f5d6e72b8a4d2798aedce78fb39de78a8f549f7cd277ae8bc8d"
879
+ "0x6d7d68045bf5e0b5a4cec43241549851cb9645f7a73a20894152165dbe7083a",
2315
880
  ),
2316
881
  leafAdapters: [],
2317
882
  adapters: [],
2318
883
  targetHealthFactor: 1.4,
2319
884
  minHealthFactor: 1.05,
2320
885
  underlyingToken: Global.getDefaultTokens().find(
2321
- (token) => token.symbol === "USDC"
886
+ (token) => token.symbol === "USDC",
2322
887
  )!,
2323
888
  quoteAmountToFetchPrice: new Web3Number(
2324
889
  "0.001",
2325
- Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!.decimals
890
+ Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!
891
+ .decimals,
2326
892
  ),
2327
893
  borrowable_assets: [
2328
- Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!,
894
+ Global.getDefaultTokens().find((token) => token.symbol === "USDC")!,
2329
895
  ],
2330
896
  minimumWBTCDifferenceForAvnuSwap: MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP,
2331
- walletAddress: WALLET_ADDRESS,
897
+ walletAddress: '0x024b563C1C7d41B32BF4EFB9F38828508a65Be2d6e25268E9f63F22C5e9E51c5',
2332
898
  };
2333
899
 
2334
900
  export const VesuExtendedTestStrategies = (
@@ -2339,7 +905,7 @@ export const VesuExtendedTestStrategies = (
2339
905
  minimumVesuMovementAmount: number,
2340
906
  minimumExtendedRetriesDelayForOrderStatus: number,
2341
907
  minimumExtendedPriceDifferenceForSwapOpen: number,
2342
- maximumExtendedPriceDifferenceForSwapClosing: number
908
+ maximumExtendedPriceDifferenceForSwapClosing: number,
2343
909
  ): IStrategyMetadata<VesuExtendedStrategySettings>[] => {
2344
910
  return [
2345
911
  getStrategySettingsVesuExtended(
@@ -2355,11 +921,15 @@ export const VesuExtendedTestStrategies = (
2355
921
  minimumVesuMovementAmount,
2356
922
  minimumExtendedRetriesDelayForOrderStatus,
2357
923
  minimumExtendedPriceDifferenceForSwapOpen,
2358
- maximumExtendedPriceDifferenceForSwapClosing
924
+ maximumExtendedPriceDifferenceForSwapClosing,
2359
925
  ),
2360
926
  ];
2361
927
  };
2362
928
 
929
+ /**
930
+ * Constructs a complete IStrategyMetadata object for a Vesu-Extended strategy,
931
+ * including adapter wiring, risk configuration, FAQ, and UI description.
932
+ */
2363
933
  function getStrategySettingsVesuExtended(
2364
934
  lstSymbol: string,
2365
935
  underlyingSymbol: string,
@@ -2373,17 +943,22 @@ function getStrategySettingsVesuExtended(
2373
943
  minimumVesuMovementAmount: number,
2374
944
  minimumExtendedRetriesDelayForOrderStatus: number,
2375
945
  minimumExtendedPriceDifferenceForSwapOpen: number,
2376
- maximumExtendedPriceDifferenceForSwapClosing: number
946
+ maximumExtendedPriceDifferenceForSwapClosing: number,
2377
947
  ): IStrategyMetadata<VesuExtendedStrategySettings> {
2378
948
  return {
949
+ id: `extended_${underlyingSymbol.toLowerCase()}_test`,
2379
950
  name: `Extended Test ${underlyingSymbol}`,
2380
951
  description: getDescription(lstSymbol, underlyingSymbol),
2381
952
  address: addresses.vaultAddress,
2382
953
  launchBlock: 0,
2383
954
  type: "Other",
955
+ vaultType: {
956
+ type: VaultType.DELTA_NEUTRAL,
957
+ description: "Delta Neutral strategy using extended position on Vesu"
958
+ },
2384
959
  depositTokens: [
2385
960
  Global.getDefaultTokens().find(
2386
- (token) => token.symbol === underlyingSymbol
961
+ (token) => token.symbol === 'WBTC',
2387
962
  )!,
2388
963
  ],
2389
964
  additionalInfo: getLooperSettings(
@@ -2398,7 +973,7 @@ function getStrategySettingsVesuExtended(
2398
973
  minimumVesuMovementAmount,
2399
974
  minimumExtendedRetriesDelayForOrderStatus,
2400
975
  minimumExtendedPriceDifferenceForSwapOpen,
2401
- maximumExtendedPriceDifferenceForSwapClosing
976
+ maximumExtendedPriceDifferenceForSwapClosing,
2402
977
  ),
2403
978
  risk: {
2404
979
  riskFactor: _riskFactor,
@@ -2409,7 +984,6 @@ function getStrategySettingsVesuExtended(
2409
984
  },
2410
985
  auditUrl: AUDIT_URL,
2411
986
  protocols: [Protocols.ENDUR, Protocols.VESU],
2412
- maxTVL: Web3Number.fromWei(0, 18),
2413
987
  contractDetails: getContractDetails(addresses),
2414
988
  faqs: getFAQs(lstSymbol, underlyingSymbol, isLST),
2415
989
  investmentSteps: getInvestmentSteps(lstSymbol, underlyingSymbol),
@@ -2417,5 +991,27 @@ function getStrategySettingsVesuExtended(
2417
991
  apyMethodology: isLST
2418
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."
2419
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,
2420
1016
  };
2421
1017
  }