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

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