@strkfarm/sdk 2.0.0-dev.9 → 2.0.0-staging.2

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 (64) hide show
  1. package/dist/index.browser.global.js +111371 -93151
  2. package/dist/index.browser.mjs +27815 -32690
  3. package/dist/index.d.ts +1095 -2011
  4. package/dist/index.js +27425 -32309
  5. package/dist/index.mjs +27590 -32452
  6. package/package.json +6 -5
  7. package/src/data/ekubo-price-fethcer.abi.json +265 -0
  8. package/src/data/universal-vault.abi.json +20 -135
  9. package/src/dataTypes/address.ts +0 -7
  10. package/src/dataTypes/index.ts +3 -2
  11. package/src/dataTypes/mynumber.ts +141 -0
  12. package/src/global.ts +296 -288
  13. package/src/index.browser.ts +6 -5
  14. package/src/interfaces/common.tsx +324 -184
  15. package/src/modules/apollo-client-config.ts +28 -0
  16. package/src/modules/avnu.ts +4 -17
  17. package/src/modules/ekubo-pricer.ts +79 -0
  18. package/src/modules/ekubo-quoter.ts +11 -88
  19. package/src/modules/erc20.ts +21 -67
  20. package/src/modules/harvests.ts +26 -15
  21. package/src/modules/index.ts +11 -13
  22. package/src/modules/lst-apr.ts +0 -36
  23. package/src/modules/pragma.ts +23 -8
  24. package/src/modules/pricer-from-api.ts +150 -14
  25. package/src/modules/pricer.ts +2 -1
  26. package/src/modules/pricerBase.ts +2 -1
  27. package/src/node/deployer.ts +36 -1
  28. package/src/node/pricer-redis.ts +2 -1
  29. package/src/strategies/autoCompounderStrk.ts +1 -1
  30. package/src/strategies/base-strategy.ts +5 -22
  31. package/src/strategies/ekubo-cl-vault.tsx +2904 -2175
  32. package/src/strategies/factory.ts +165 -0
  33. package/src/strategies/index.ts +10 -11
  34. package/src/strategies/registry.ts +268 -0
  35. package/src/strategies/sensei.ts +416 -292
  36. package/src/strategies/universal-adapters/adapter-utils.ts +1 -5
  37. package/src/strategies/universal-adapters/baseAdapter.ts +153 -181
  38. package/src/strategies/universal-adapters/common-adapter.ts +77 -98
  39. package/src/strategies/universal-adapters/index.ts +1 -5
  40. package/src/strategies/universal-adapters/vesu-adapter.ts +218 -220
  41. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +51 -58
  42. package/src/strategies/universal-lst-muliplier-strategy.tsx +1952 -992
  43. package/src/strategies/universal-strategy.tsx +1713 -1150
  44. package/src/strategies/vesu-rebalance.tsx +1189 -986
  45. package/src/utils/health-factor-math.ts +5 -11
  46. package/src/utils/index.ts +8 -9
  47. package/src/utils/strategy-utils.ts +57 -0
  48. package/src/data/extended-deposit.abi.json +0 -3613
  49. package/src/modules/ExtendedWrapperSDk/index.ts +0 -62
  50. package/src/modules/ExtendedWrapperSDk/types.ts +0 -311
  51. package/src/modules/ExtendedWrapperSDk/wrapper.ts +0 -395
  52. package/src/modules/midas.ts +0 -159
  53. package/src/modules/token-market-data.ts +0 -202
  54. package/src/strategies/svk-strategy.ts +0 -247
  55. package/src/strategies/universal-adapters/adapter-optimizer.ts +0 -65
  56. package/src/strategies/universal-adapters/avnu-adapter.ts +0 -413
  57. package/src/strategies/universal-adapters/extended-adapter.ts +0 -972
  58. package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
  59. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +0 -1306
  60. package/src/strategies/vesu-extended-strategy/services/operationService.ts +0 -34
  61. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +0 -77
  62. package/src/strategies/vesu-extended-strategy/utils/constants.ts +0 -49
  63. package/src/strategies/vesu-extended-strategy/utils/helper.ts +0 -370
  64. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +0 -1379
@@ -1,1173 +1,1736 @@
1
1
  import { ContractAddr, Web3Number } from "@/dataTypes";
2
- import { BaseStrategy, SingleActionAmount, SingleTokenInfo } from "./base-strategy";
2
+ import {
3
+ BaseStrategy,
4
+ SingleActionAmount,
5
+ SingleTokenInfo
6
+ } from "./base-strategy";
3
7
  import { PricerBase } from "@/modules/pricerBase";
4
- import { FAQ, getNoRiskTags, IConfig, IStrategyMetadata, Protocols, RiskFactor, RiskType, VaultPosition } from "@/interfaces";
5
- import { BlockIdentifier, Call, CallData, Contract, num, uint256 } from "starknet";
8
+ import {
9
+ FAQ,
10
+ getNoRiskTags,
11
+ IConfig,
12
+ IStrategyMetadata,
13
+ Protocols,
14
+ RiskFactor,
15
+ RiskType,
16
+ StrategyCategory,
17
+ StrategyTag,
18
+ StrategyLiveStatus,
19
+ StrategySettings,
20
+ VaultPosition,
21
+ AuditStatus,
22
+ SourceCodeType,
23
+ AccessControlType,
24
+ InstantWithdrawalVault
25
+ } from "@/interfaces";
26
+ import {
27
+ BlockIdentifier,
28
+ Call,
29
+ CallData,
30
+ Contract,
31
+ num,
32
+ uint256
33
+ } from "starknet";
6
34
  import { VesuRebalanceSettings } from "./vesu-rebalance";
7
35
  import { assert, LeafData, logger, StandardMerkleTree } from "@/utils";
8
- import UniversalVaultAbi from '../data/universal-vault.abi.json';
9
- import ManagerAbi from '../data/vault-manager.abi.json';
10
- import { ApproveCallParams, AvnuSwapCallParams, BaseAdapter, CommonAdapter, DepositParams, FlashloanCallParams, GenerateCallFn, LeafAdapterFn, ManageCall, WithdrawParams } from "./universal-adapters";
36
+ import UniversalVaultAbi from "../data/universal-vault.abi.json";
37
+ import ManagerAbi from "../data/vault-manager.abi.json";
38
+ import {
39
+ ApproveCallParams,
40
+ AvnuSwapCallParams,
41
+ BaseAdapter,
42
+ CommonAdapter,
43
+ FlashloanCallParams,
44
+ GenerateCallFn,
45
+ LeafAdapterFn,
46
+ ManageCall,
47
+ VesuAdapter,
48
+ VesuDefiSpringRewardsCallParams,
49
+ VesuModifyPositionCallParams,
50
+ VesuPools
51
+ } from "./universal-adapters";
11
52
  import { Global } from "@/global";
12
53
  import { AvnuWrapper, ERC20 } from "@/modules";
13
- import { AVNU_MIDDLEWARE, VESU_SINGLETON } from "./universal-adapters/adapter-utils";
14
- import { VesuHarvests } from "@/modules/harvests";
54
+ import {
55
+ AVNU_MIDDLEWARE,
56
+ VESU_SINGLETON
57
+ } from "./universal-adapters/adapter-utils";
58
+ import { HarvestInfo, VesuHarvests } from "@/modules/harvests";
15
59
 
16
60
  export interface UniversalManageCall {
17
- proofs: string[];
18
- manageCall: ManageCall;
19
- step: UNIVERSAL_MANAGE_IDS;
61
+ proofs: string[];
62
+ manageCall: ManageCall;
63
+ step: UNIVERSAL_MANAGE_IDS;
20
64
  }
21
65
 
22
66
  export interface UniversalStrategySettings {
23
- vaultAddress: ContractAddr,
24
- manager: ContractAddr,
25
- vaultAllocator: ContractAddr,
26
- redeemRequestNFT: ContractAddr,
27
-
28
- // Individual merkle tree leaves
29
- leafAdapters: LeafAdapterFn<any>[],
30
-
31
- // Useful for returning adapter class objects that can compute
32
- // certain things for us (e.g. positions, hfs)
33
- adapters: {id: string, adapter: BaseAdapter<DepositParams, WithdrawParams>}[]
67
+ vaultAddress: ContractAddr;
68
+ manager: ContractAddr;
69
+ vaultAllocator: ContractAddr;
70
+ redeemRequestNFT: ContractAddr;
71
+ aumOracle: ContractAddr;
72
+
73
+ // Individual merkle tree leaves
74
+ leafAdapters: LeafAdapterFn<any>[];
75
+
76
+ // Useful for returning adapter class objects that can compute
77
+ // certain things for us (e.g. positions, hfs)
78
+ adapters: { id: string; adapter: BaseAdapter }[];
79
+ targetHealthFactor: number;
80
+ minHealthFactor: number;
34
81
  }
35
82
 
36
83
  export enum AUMTypes {
37
- FINALISED = 'finalised',
38
- DEFISPRING = 'defispring'
84
+ FINALISED = "finalised",
85
+ DEFISPRING = "defispring"
39
86
  }
40
87
 
41
- // export class UniversalStrategy<
42
- // S extends UniversalStrategySettings
43
- // > extends BaseStrategy<
44
- // SingleTokenInfo,
45
- // SingleActionAmount
46
- // > {
47
-
48
- // /** Contract address of the strategy */
49
- // readonly address: ContractAddr;
50
- // /** Pricer instance for token price calculations */
51
- // readonly pricer: PricerBase;
52
- // /** Metadata containing strategy information */
53
- // readonly metadata: IStrategyMetadata<S>;
54
- // /** Contract instance for interacting with the strategy */
55
- // readonly contract: Contract;
56
- // readonly managerContract: Contract;
57
- // merkleTree: StandardMerkleTree | undefined;
58
-
59
- // constructor(
60
- // config: IConfig,
61
- // pricer: PricerBase,
62
- // metadata: IStrategyMetadata<S>
63
- // ) {
64
- // super(config);
65
- // this.pricer = pricer;
66
-
67
- // assert(
68
- // metadata.depositTokens.length === 1,
69
- // "VesuRebalance only supports 1 deposit token"
70
- // );
71
- // this.metadata = metadata;
72
- // this.address = metadata.address;
73
-
74
- // this.contract = new Contract({
75
- // abi: UniversalVaultAbi,
76
- // address: this.address.address,
77
- // providerOrAccount: this.config.provider
78
- // });
79
- // this.managerContract = new Contract({
80
- // abi: ManagerAbi,
81
- // address: this.metadata.additionalInfo.manager.address,
82
- // providerOrAccount: this.config.provider
83
- // });
84
- // }
85
-
86
- // getAllLeaves() {
87
- // const leaves = this.metadata.additionalInfo.leafAdapters.map((adapter, index) => {
88
- // return adapter()
89
- // });
90
- // const _leaves: LeafData[] = [];
91
- // leaves.forEach((_l) => {
92
- // _leaves.push(..._l.leaves);
93
- // })
94
- // return _leaves;
95
-
96
- // }
97
- // getMerkleTree() {
98
- // if (this.merkleTree) return this.merkleTree;
99
- // const _leaves = this.getAllLeaves();
100
-
101
- // const standardTree = StandardMerkleTree.of(_leaves);
102
- // this.merkleTree = standardTree;
103
- // return standardTree;
104
- // }
105
-
106
- // getMerkleRoot() {
107
- // return this.getMerkleTree().root;
108
- // }
109
-
110
- // getAdapter(id: string): BaseAdapter<DepositParams, WithdrawParams> {
111
- // const adapter = this.metadata.additionalInfo.adapters.find(adapter => adapter.id === id);
112
- // if (!adapter) {
113
- // throw new Error(`Adapter not found for ID: ${id}`);
114
- // }
115
- // return adapter.adapter;
116
- // }
117
-
118
- // asset() {
119
- // return this.metadata.depositTokens[0];
120
- // }
121
-
122
- // async depositCall(amountInfo: SingleActionAmount, receiver: ContractAddr): Promise<Call[]> {
123
- // // Technically its not erc4626 abi, but we just need approve call
124
- // // so, its ok to use it
125
- // assert(
126
- // amountInfo.tokenInfo.address.eq(this.asset().address),
127
- // "Deposit token mismatch"
128
- // );
129
- // const assetContract = new Contract({
130
- // abi: UniversalVaultAbi,
131
- // address: this.asset().address.address,
132
- // providerOrAccount: this.config.provider
133
- // });
134
- // const call1 = assetContract.populate("approve", [
135
- // this.address.address,
136
- // uint256.bnToUint256(amountInfo.amount.toWei())
137
- // ]);
138
- // const call2 = this.contract.populate("deposit", [
139
- // uint256.bnToUint256(amountInfo.amount.toWei()),
140
- // receiver.address
141
- // ]);
142
- // return [call1, call2];
143
- // }
144
-
145
- // async withdrawCall(amountInfo: SingleActionAmount, receiver: ContractAddr, owner: ContractAddr): Promise<Call[]> {
146
- // assert(
147
- // amountInfo.tokenInfo.address.eq(this.asset().address),
148
- // "Withdraw token mismatch"
149
- // );
150
- // const shares = await this.contract.call('convert_to_shares', [uint256.bnToUint256(amountInfo.amount.toWei())]);
151
- // const call = this.contract.populate("request_redeem", [
152
- // uint256.bnToUint256(shares.toString()),
153
- // receiver.address,
154
- // owner.address
155
- // ]);
156
- // return [call];
157
- // }
158
-
159
- // /**
160
- // * Calculates the Total Value Locked (TVL) for a specific user.
161
- // * @param user - Address of the user
162
- // * @returns Object containing the amount in token units and USD value
163
- // */
164
- // async getUserTVL(user: ContractAddr) {
165
- // const shares = await this.contract.balance_of(user.address);
166
- // const assets = await this.contract.convert_to_assets(
167
- // uint256.bnToUint256(shares)
168
- // );
169
- // const amount = Web3Number.fromWei(
170
- // assets.toString(),
171
- // this.metadata.depositTokens[0].decimals
172
- // );
173
- // let price = await this.pricer.getPrice(
174
- // this.metadata.depositTokens[0].symbol
175
- // );
176
- // const usdValue = Number(amount.toFixed(6)) * price.price;
177
- // return {
178
- // tokenInfo: this.asset(),
179
- // amount,
180
- // usdValue
181
- // };
182
- // }
183
-
184
- // /**
185
- // * Calculates the weighted average APY across all pools based on USD value.
186
- // * @returns {Promise<number>} The weighted average APY across all pools
187
- // */
188
- // async netAPY(): Promise<{ net: number, splits: { apy: number, id: string }[] }> {
189
- // if (this.metadata.isPreview) {
190
- // return { net: 0, splits: [{
191
- // apy: 0, id: 'base'
192
- // }, {
193
- // apy: 0, id: 'defispring'
194
- // }] };
195
- // }
196
-
197
- // const { positions, baseAPYs, rewardAPYs } = await this.getVesuAPYs();
198
-
199
- // const unusedBalanceAPY = await this.getUnusedBalanceAPY();
200
- // baseAPYs.push(...[unusedBalanceAPY.apy]);
201
- // rewardAPYs.push(0);
202
-
203
- // // Compute APy using weights
204
- // const weights = positions.map((p, index) => p.usdValue * (index % 2 == 0 ? 1 : -1));
205
- // weights.push(unusedBalanceAPY.weight);
206
-
207
- // const prevAUM = await this.getPrevAUM();
208
- // const price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
209
- // const prevAUMUSD = prevAUM.multipliedBy(price.price);
210
- // return this.returnNetAPY(baseAPYs, rewardAPYs, weights, prevAUMUSD);
211
- // }
212
-
213
- // protected async returnNetAPY(baseAPYs: number[], rewardAPYs: number[], weights: number[], prevAUMUSD: Web3Number) {
214
- // // If no positions, return 0
215
- // if (weights.every(p => p == 0)) {
216
- // return { net: 0, splits: [{
217
- // apy: 0, id: 'base'
218
- // }, {
219
- // apy: 0, id: 'defispring'
220
- // }]};
221
- // }
222
-
223
- // const baseAPY = this.computeAPY(baseAPYs, weights, prevAUMUSD);
224
- // const rewardAPY = this.computeAPY(rewardAPYs, weights, prevAUMUSD);
225
- // const netAPY = baseAPY + rewardAPY;
226
- // logger.verbose(`${this.metadata.name}::netAPY: net: ${netAPY}, baseAPY: ${baseAPY}, rewardAPY: ${rewardAPY}`);
227
- // return { net: netAPY, splits: [{
228
- // apy: baseAPY, id: 'base'
229
- // }, {
230
- // apy: rewardAPY, id: 'defispring'
231
- // }] };
232
- // }
233
-
234
- // protected async getUnusedBalanceAPY() {
235
- // return {
236
- // apy: 0, weight: 0
237
- // }
238
- // }
239
-
240
- // private computeAPY(apys: number[], weights: number[], currentAUM: Web3Number) {
241
- // assert(apys.length === weights.length, "APYs and weights length mismatch");
242
- // const weightedSum = apys.reduce((acc, apy, i) => acc + apy * weights[i], 0);
243
- // logger.verbose(`${this.getTag()} computeAPY: apys: ${JSON.stringify(apys)}, weights: ${JSON.stringify(weights)}, weightedSum: ${weightedSum}, currentAUM: ${currentAUM}`);
244
- // return weightedSum / currentAUM.toNumber();
245
- // }
246
-
247
- // /**
248
- // * Calculates the total TVL of the strategy.
249
- // * @returns Object containing the total amount in token units and USD value
250
- // */
251
- // async getTVL() {
252
- // const assets = await this.contract.total_assets();
253
- // const amount = Web3Number.fromWei(
254
- // assets.toString(),
255
- // this.metadata.depositTokens[0].decimals
256
- // );
257
- // let price = await this.pricer.getPrice(
258
- // this.metadata.depositTokens[0].symbol
259
- // );
260
- // const usdValue = Number(amount.toFixed(6)) * price.price;
261
- // return {
262
- // tokenInfo: this.asset(),
263
- // amount,
264
- // usdValue
265
- // };
266
- // }
267
-
268
- // async getUnusedBalance(): Promise<SingleTokenInfo> {
269
- // const balance = await (new ERC20(this.config)).balanceOf(this.asset().address, this.metadata.additionalInfo.vaultAllocator, this.asset().decimals);
270
- // const price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
271
- // const usdValue = Number(balance.toFixed(6)) * price.price;
272
- // return {
273
- // tokenInfo: this.asset(),
274
- // amount: balance,
275
- // usdValue
276
- // };
277
- // }
278
-
279
- // protected async getVesuAUM(adapter: VesuAdapter) {
280
- // const legAUM = await adapter.getPositions(this.config);
281
- // const underlying = this.asset();
282
- // let vesuAum = Web3Number.fromWei("0", underlying.decimals);
283
-
284
- // let tokenUnderlyingPrice = await this.pricer.getPrice(this.asset().symbol);
285
- // logger.verbose(`${this.getTag()} tokenUnderlyingPrice: ${tokenUnderlyingPrice.price}`);
286
-
287
- // // handle collateral
288
- // if (legAUM[0].token.address.eq(underlying.address)) {
289
- // vesuAum = vesuAum.plus(legAUM[0].amount);
290
- // } else {
291
- // vesuAum = vesuAum.plus(legAUM[0].usdValue / tokenUnderlyingPrice.price);
292
- // }
293
-
294
- // // handle debt
295
- // if (legAUM[1].token.address.eq(underlying.address)) {
296
- // vesuAum = vesuAum.minus(legAUM[1].amount);
297
- // } else {
298
- // vesuAum = vesuAum.minus(legAUM[1].usdValue / tokenUnderlyingPrice.price);
299
- // };
300
-
301
- // logger.verbose(`${this.getTag()} Vesu AUM: ${vesuAum}, legCollateral: ${legAUM[0].amount.toNumber()}, legDebt: ${legAUM[1].amount.toNumber()}`);
302
- // return vesuAum;
303
- // }
304
-
305
- // async getPrevAUM() {
306
- // const currentAUM: bigint = await this.contract.call('aum', []) as bigint;
307
- // const prevAum = Web3Number.fromWei(currentAUM.toString(), this.asset().decimals);
308
- // logger.verbose(`${this.getTag()} Prev AUM: ${prevAum}`);
309
- // return prevAum;
310
- // }
311
-
312
- // async getAUM(): Promise<{net: SingleTokenInfo, prevAum: Web3Number, splits: {id: string, aum: Web3Number}[]}> {
313
- // const prevAum = await this.getPrevAUM();
314
- // const token1Price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
315
-
316
- // // calculate vesu aum
317
- // const vesuAdapters = this.getVesuAdapters();
318
- // let vesuAum = Web3Number.fromWei("0", this.asset().decimals);
319
- // for (const adapter of vesuAdapters) {
320
- // vesuAum = vesuAum.plus(await this.getVesuAUM(adapter));
321
- // }
322
-
323
- // // account unused balance as aum as well (from vault allocator)
324
- // const balance = await this.getUnusedBalance();
325
- // logger.verbose(`${this.getTag()} unused balance: ${balance.amount.toNumber()}`);
326
-
327
- // // Initiate return values
328
- // const zeroAmt = Web3Number.fromWei('0', this.asset().decimals);
329
- // const net = {
330
- // tokenInfo: this.asset(),
331
- // amount: zeroAmt,
332
- // usdValue: 0
333
- // };
334
- // const aumToken = vesuAum.plus(balance.amount);
335
- // if (aumToken.isZero()) {
336
- // return { net, splits: [{
337
- // aum: zeroAmt, id: AUMTypes.FINALISED
338
- // }, {
339
- // aum: zeroAmt, id: AUMTypes.DEFISPRING
340
- // }], prevAum};
341
- // }
342
- // logger.verbose(`${this.getTag()} Actual AUM: ${aumToken}`);
343
-
344
- // // compute rewards contribution to AUM
345
- // const rewardAssets = await this.getRewardsAUM(prevAum);
346
-
347
- // // Sum up and return
348
- // const newAUM = aumToken.plus(rewardAssets);
349
- // logger.verbose(`${this.getTag()} New AUM: ${newAUM}`);
350
-
351
- // net.amount = newAUM;
352
- // net.usdValue = newAUM.multipliedBy(token1Price.price).toNumber();
353
- // const splits = [{
354
- // id: AUMTypes.FINALISED,
355
- // aum: aumToken
356
- // }, {
357
- // id: AUMTypes.DEFISPRING,
358
- // aum: rewardAssets
359
- // }];
360
- // return { net, splits, prevAum };
361
- // }
362
-
363
- // // account for future rewards (e.g. defispring rewards)
364
- // protected async getRewardsAUM(prevAum: Web3Number) {
365
- // const lastReportTime = await this.contract.call('last_report_timestamp', []);
366
- // // - calculate estimated growth from strk rewards
367
- // const netAPY = await this.netAPY();
368
- // // account only 80% of value
369
- // const defispringAPY = (netAPY.splits.find(s => s.id === 'defispring')?.apy || 0) * 0.8;
370
- // if (!defispringAPY) throw new Error('DefiSpring APY not found');
371
-
372
- // // compute rewards contribution to AUM
373
- // const timeDiff = (Math.round(Date.now() / 1000) - Number(lastReportTime));
374
- // const growthRate = timeDiff * defispringAPY / (365 * 24 * 60 * 60);
375
- // const rewardAssets = prevAum.multipliedBy(growthRate);
376
- // logger.verbose(`${this.getTag()} DefiSpring AUM time difference: ${timeDiff}`);
377
- // logger.verbose(`${this.getTag()} Current AUM: ${prevAum.toString()}`);
378
- // logger.verbose(`${this.getTag()} Net APY: ${JSON.stringify(netAPY)}`);
379
- // logger.verbose(`${this.getTag()} rewards AUM: ${rewardAssets}`);
380
-
381
- // return rewardAssets;
382
- // }
383
-
384
-
385
-
386
- // async getVaultPositions(): Promise<VaultPosition[]> {
387
- // const vesuPositions = await this.getVesuPositions();
388
- // const unusedBalance = await this.getUnusedBalance();
389
- // return [...vesuPositions, {
390
- // amount: unusedBalance.amount,
391
- // usdValue: unusedBalance.usdValue,
392
- // token: this.asset(),
393
- // remarks: "Unused Balance (may not include recent deposits)"
394
- // }];
395
- // }
396
-
397
- // getSetManagerCall(strategist: ContractAddr, root = this.getMerkleRoot()) {
398
- // return this.managerContract.populate('set_manage_root', [strategist.address, num.getHexString(root)]);
399
- // }
400
-
401
- // getManageCall(proofIds: string[], manageCalls: ManageCall[]) {
402
- // assert(proofIds.length == manageCalls.length, 'Proof IDs and Manage Calls length mismatch');
403
- // return this.managerContract.populate('manage_vault_with_merkle_verification', {
404
- // proofs: proofIds.map(id => this.getProofs(id).proofs),
405
- // decoder_and_sanitizers: manageCalls.map(call => call.sanitizer.address),
406
- // targets: manageCalls.map(call => call.call.contractAddress.address),
407
- // selectors: manageCalls.map(call => call.call.selector),
408
- // calldatas: manageCalls.map(call => call.call.calldata), // Calldata[]
409
- // });
410
- // }
411
-
412
- // getVesuModifyPositionCalls(params: {
413
- // isLeg1: boolean,
414
- // isDeposit: boolean,
415
- // depositAmount: Web3Number,
416
- // debtAmount: Web3Number
417
- // }): UniversalManageCall[] {
418
- // assert(params.depositAmount.gt(0) || params.debtAmount.gt(0), 'Either deposit or debt amount must be greater than 0');
419
- // // approve token
420
- // const isToken1 = params.isLeg1 == params.isDeposit; // XOR
421
- // const STEP1_ID = isToken1 ? UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1 :UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN2;
422
- // const manage4Info = this.getProofs<ApproveCallParams>(STEP1_ID);
423
- // const approveAmount = params.isDeposit ? params.depositAmount : params.debtAmount;
424
- // const manageCall4 = manage4Info.callConstructor({
425
- // amount: approveAmount
426
- // })
427
-
428
- // // deposit and borrow or repay and withdraw
429
- // const STEP2_ID = params.isLeg1 ? UNIVERSAL_MANAGE_IDS.VESU_LEG1 : UNIVERSAL_MANAGE_IDS.VESU_LEG2;
430
- // const manage5Info = this.getProofs<VesuModifyPositionCallParams>(STEP2_ID);
431
- // const manageCall5 = manage5Info.callConstructor(VesuAdapter.getDefaultModifyPositionCallParams({
432
- // collateralAmount: params.depositAmount,
433
- // isAddCollateral: params.isDeposit,
434
- // debtAmount: params.debtAmount,
435
- // isBorrow: params.isDeposit
436
- // }))
437
-
438
- // const output = [{
439
- // proofs: manage5Info.proofs,
440
- // manageCall: manageCall5,
441
- // step: STEP2_ID
442
- // }];
443
- // if (approveAmount.gt(0)) {
444
- // output.unshift({
445
- // proofs: manage4Info.proofs,
446
- // manageCall: manageCall4,
447
- // step: STEP1_ID
448
- // })
449
- // }
450
- // return output;
451
- // }
452
-
453
- // getTag() {
454
- // return `${UniversalStrategy.name}:${this.metadata.name}`;
455
- // }
456
-
457
- // /**
458
- // * Gets LST APR for the strategy's underlying asset from Endur API
459
- // * @returns Promise<number> The LST APR (not divided by 1e18)
460
- // */
461
- // async getLSTAPR(address: ContractAddr): Promise<number> {
462
- // return 0;
463
- // }
464
-
465
- // async getVesuHealthFactors(blockNumber: BlockIdentifier = 'latest') {
466
- // return await Promise.all(this.getVesuAdapters().map(v => v.getHealthFactor(blockNumber)));
467
- // }
468
-
469
- // async computeRebalanceConditionAndReturnCalls(): Promise<Call[]> {
470
- // const vesuAdapters = this.getVesuAdapters();
471
- // const healthFactors = await this.getVesuHealthFactors();
472
- // const leg1HealthFactor = healthFactors[0];
473
- // const leg2HealthFactor = healthFactors[1];
474
- // logger.verbose(`${this.getTag()}: HealthFactorLeg1: ${leg1HealthFactor}`);
475
- // logger.verbose(`${this.getTag()}: HealthFactorLeg2: ${leg2HealthFactor}`);
476
-
477
- // const minHf = this.metadata.additionalInfo.minHealthFactor;
478
- // const isRebalanceNeeded1 = leg1HealthFactor < minHf;
479
- // const isRebalanceNeeded2 = leg2HealthFactor < minHf;
480
- // if (!isRebalanceNeeded1 && !isRebalanceNeeded2) {
481
- // return [];
482
- // }
483
-
484
- // if (isRebalanceNeeded1) {
485
- // const amount = await this.getLegRebalanceAmount(vesuAdapters[0], leg1HealthFactor, false);
486
- // const leg2HF = await this.getNewHealthFactor(vesuAdapters[1], amount, true);
487
- // assert(leg2HF > minHf, `Rebalance Leg1 failed: Leg2 HF after rebalance would be too low: ${leg2HF}`);
488
- // return [await this.getRebalanceCall({
489
- // isLeg1toLeg2: false,
490
- // amount: amount
491
- // })];
492
- // } else {
493
- // const amount = await this.getLegRebalanceAmount(vesuAdapters[1], leg2HealthFactor, true);
494
- // const leg1HF = await this.getNewHealthFactor(vesuAdapters[0], amount, false);
495
- // assert(leg1HF > minHf, `Rebalance Leg2 failed: Leg1 HF after rebalance would be too low: ${leg1HF}`);
496
- // return [await this.getRebalanceCall({
497
- // isLeg1toLeg2: true,
498
- // amount: amount
499
- // })];
500
- // }
501
- // }
502
-
503
- // private async getNewHealthFactor(vesuAdapter: VesuAdapter, newAmount: Web3Number, isWithdraw: boolean) {
504
- // const {
505
- // collateralTokenAmount,
506
- // collateralUSDAmount,
507
- // collateralPrice,
508
- // debtTokenAmount,
509
- // debtUSDAmount,
510
- // debtPrice,
511
- // ltv
512
- // } = await vesuAdapter.getAssetPrices();
513
-
514
- // if (isWithdraw) {
515
- // const newHF = ((collateralTokenAmount.toNumber() - newAmount.toNumber()) * collateralPrice * ltv) / debtUSDAmount;
516
- // logger.verbose(`getNewHealthFactor:: HF: ${newHF}, amoutn: ${newAmount.toNumber()}, isDeposit`);
517
- // return newHF;
518
- // } else { // is borrow
519
- // const newHF = (collateralUSDAmount * ltv) / ((debtTokenAmount.toNumber() + newAmount.toNumber()) * debtPrice);
520
- // logger.verbose(`getNewHealthFactor:: HF: ${newHF}, amoutn: ${newAmount.toNumber()}, isRepay`);
521
- // return newHF;
522
- // }
523
- // }
524
-
525
- // /**
526
- // *
527
- // * @param vesuAdapter
528
- // * @param currentHf
529
- // * @param isDeposit if true, attempt by adding collateral, else by repaying
530
- // * @returns
531
- // */
532
- // private async getLegRebalanceAmount(vesuAdapter: VesuAdapter, currentHf: number, isDeposit: boolean) {
533
- // const {
534
- // collateralTokenAmount,
535
- // collateralUSDAmount,
536
- // collateralPrice,
537
- // debtTokenAmount,
538
- // debtUSDAmount,
539
- // debtPrice,
540
- // ltv
541
- // } = await vesuAdapter.getAssetPrices();
542
-
543
- // // debt is zero, nothing to rebalance
544
- // if(debtTokenAmount.isZero()) {
545
- // return Web3Number.fromWei(0, 0);
546
- // }
547
-
548
- // assert(collateralPrice > 0 && debtPrice > 0, "getRebalanceAmount: Invalid price");
549
-
550
- // // avoid calculating for too close
551
- // const targetHF = this.metadata.additionalInfo.targetHealthFactor;
552
- // if (currentHf > targetHF - 0.01)
553
- // throw new Error("getLegRebalanceAmount: Current health factor is healthy");
554
-
555
- // if (isDeposit) {
556
- // // TargetHF = (collAmount + newAmount) * price * ltv / debtUSD
557
- // const newAmount = targetHF * debtUSDAmount / (collateralPrice * ltv) - collateralTokenAmount.toNumber();
558
- // logger.verbose(`${this.getTag()}:: getLegRebalanceAmount: addCollateral, currentHf: ${currentHf}, targetHF: ${targetHF}, collAmount: ${collateralTokenAmount.toString()}, collUSD: ${collateralUSDAmount}, collPrice: ${collateralPrice}, debtAmount: ${debtTokenAmount.toString()}, debtUSD: ${debtUSDAmount}, debtPrice: ${debtPrice}, ltv: ${ltv}, newAmount: ${newAmount}`);
559
- // return new Web3Number(newAmount.toFixed(8), collateralTokenAmount.decimals);
560
- // } else {
561
- // // TargetHF = collUSD * ltv / (debtAmount - newAmount) * debtPrice
562
- // const newAmount = debtTokenAmount.toNumber() - collateralUSDAmount * ltv / (targetHF * debtPrice);
563
- // logger.verbose(`${this.getTag()}:: getLegRebalanceAmount: repayDebt, currentHf: ${currentHf}, targetHF: ${targetHF}, collAmount: ${collateralTokenAmount.toString()}, collUSD: ${collateralUSDAmount}, collPrice: ${collateralPrice}, debtAmount: ${debtTokenAmount.toString()}, debtUSD: ${debtUSDAmount}, debtPrice: ${debtPrice}, ltv: ${ltv}, newAmount: ${newAmount}`);
564
- // return new Web3Number(newAmount.toFixed(8), debtTokenAmount.decimals);
565
- // }
566
- // }
567
-
568
- // async getVesuModifyPositionCall(params: {
569
- // isDeposit: boolean,
570
- // leg1DepositAmount: Web3Number
571
- // }) {
572
- // const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
573
- // const leg1LTV = await vesuAdapter1.getLTVConfig(this.config);
574
- // const leg2LTV = await vesuAdapter2.getLTVConfig(this.config);
575
- // logger.verbose(`${this.getTag()}: LTVLeg1: ${leg1LTV}`);
576
- // logger.verbose(`${this.getTag()}: LTVLeg2: ${leg2LTV}`);
577
-
578
- // const token1Price = await this.pricer.getPrice(vesuAdapter1.config.collateral.symbol);
579
- // const token2Price = await this.pricer.getPrice(vesuAdapter2.config.collateral.symbol);
580
- // logger.verbose(`${this.getTag()}: Price${vesuAdapter1.config.collateral.symbol}: ${token1Price.price}`);
581
- // logger.verbose(`${this.getTag()}: Price${vesuAdapter2.config.collateral.symbol}: ${token2Price.price}`);
582
-
583
- // const TARGET_HF = this.metadata.additionalInfo.targetHealthFactor;
584
-
585
- // const k1 = token1Price.price * leg1LTV / token2Price.price / TARGET_HF;
586
- // const k2 = token1Price.price * TARGET_HF / token2Price.price / leg2LTV;
587
-
588
- // const borrow2Amount = new Web3Number(
589
- // params.leg1DepositAmount.multipliedBy(k1.toFixed(6)).dividedBy(k2 - k1).toFixed(6),
590
- // vesuAdapter2.config.debt.decimals
591
- // );
592
- // const borrow1Amount = new Web3Number(
593
- // borrow2Amount.multipliedBy(k2).toFixed(6),
594
- // vesuAdapter1.config.debt.decimals
595
- // );
596
- // logger.verbose(`${this.getTag()}:: leg1DepositAmount: ${params.leg1DepositAmount.toString()} ${vesuAdapter1.config.collateral.symbol}`);
597
- // logger.verbose(`${this.getTag()}:: borrow1Amount: ${borrow1Amount.toString()} ${vesuAdapter1.config.debt.symbol}`);
598
- // logger.verbose(`${this.getTag()}:: borrow2Amount: ${borrow2Amount.toString()} ${vesuAdapter2.config.debt.symbol}`);
599
-
600
- // let callSet1 = this.getVesuModifyPositionCalls({
601
- // isLeg1: true,
602
- // isDeposit: params.isDeposit,
603
- // depositAmount: params.leg1DepositAmount.plus(borrow2Amount),
604
- // debtAmount: borrow1Amount
605
- // });
606
-
607
- // let callSet2 = this.getVesuModifyPositionCalls({
608
- // isLeg1: false,
609
- // isDeposit: params.isDeposit,
610
- // depositAmount: borrow1Amount,
611
- // debtAmount: borrow2Amount
612
- // });
613
-
614
- // if (!params.isDeposit) {
615
- // let temp = callSet2;
616
- // callSet2 = [...callSet1];
617
- // callSet1 = [...temp];
618
- // }
619
- // const allActions = [...callSet1.map(i => i.manageCall), ...callSet2.map(i => i.manageCall)];
620
- // const flashloanCalldata = CallData.compile([
621
- // [...callSet1.map(i => i.proofs), ...callSet2.map(i => i.proofs)],
622
- // allActions.map(i => i.sanitizer.address),
623
- // allActions.map(i => i.call.contractAddress.address),
624
- // allActions.map(i => i.call.selector),
625
- // allActions.map(i => i.call.calldata)
626
- // ])
627
-
628
- // // flash loan
629
- // const STEP1_ID = UNIVERSAL_MANAGE_IDS.FLASH_LOAN;
630
- // const manage1Info = this.getProofs<FlashloanCallParams>(STEP1_ID);
631
- // const manageCall1 = manage1Info.callConstructor({
632
- // amount: borrow2Amount,
633
- // data: flashloanCalldata.map(i => BigInt(i))
634
- // })
635
- // const manageCall = this.getManageCall([UNIVERSAL_MANAGE_IDS.FLASH_LOAN], [manageCall1]);
636
- // return manageCall;
637
- // }
638
-
639
- // async getBringLiquidityCall(params: {
640
- // amount: Web3Number
641
- // }) {
642
- // const manage1Info = this.getProofs<ApproveCallParams>(UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY);
643
- // const manageCall1 = manage1Info.callConstructor({
644
- // amount: params.amount
645
- // });
646
- // const manage2Info = this.getProofs<ApproveCallParams>(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY);
647
- // const manageCall2 = manage2Info.callConstructor({
648
- // amount: params.amount
649
- // });
650
- // const manageCall = this.getManageCall([UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY, UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY], [manageCall1, manageCall2]);
651
- // return manageCall;
652
- // }
653
-
654
- // async getHarvestCall() {
655
- // const vesuHarvest = new VesuHarvests(this.config);
656
- // const harvestInfo = await vesuHarvest.getUnHarvestedRewards(this.metadata.additionalInfo.vaultAllocator);
657
- // if (harvestInfo.length != 1) {
658
- // throw new Error(`Expected 1 harvest info, got ${harvestInfo.length}`);
659
- // }
660
-
661
- // const amount = harvestInfo[0].claim.amount;
662
- // const actualReward = harvestInfo[0].actualReward;
663
- // const proofs = harvestInfo[0].proof;
664
- // if (actualReward.isZero()) {
665
- // throw new Error(`Expected non-zero actual reward, got ${harvestInfo[0].actualReward}`);
666
- // }
667
-
668
- // const manage1Info = this.getProofs<VesuDefiSpringRewardsCallParams>(UNIVERSAL_MANAGE_IDS.DEFISPRING_REWARDS);
669
- // const manageCall1 = manage1Info.callConstructor({
670
- // amount,
671
- // proofs
672
- // });
673
- // const proofIds: string[] = [UNIVERSAL_MANAGE_IDS.DEFISPRING_REWARDS];
674
- // const manageCalls: ManageCall[] = [manageCall1];
675
-
676
- // // swap rewards for underlying
677
- // const STRK = Global.getDefaultTokens().find(t => t.symbol === 'STRK')!;
678
- // if (this.asset().symbol != 'STRK') {
679
- // // approve
680
- // const manage2Info = this.getProofs<ApproveCallParams>(UNIVERSAL_MANAGE_IDS.APPROVE_SWAP_TOKEN1);
681
- // const manageCall2 = manage2Info.callConstructor({
682
- // amount: actualReward
683
- // });
684
-
685
- // // swap
686
- // const avnuModule = new AvnuWrapper();
687
- // const quote = await avnuModule.getQuotes(
688
- // STRK.address.address,
689
- // this.asset().address.address,
690
- // actualReward.toWei(),
691
- // this.address.address
692
- // );
693
- // const swapInfo = await avnuModule.getSwapInfo(quote, this.address.address, 0, this.address.address);
694
- // const manage3Info = this.getProofs<AvnuSwapCallParams>(UNIVERSAL_MANAGE_IDS.AVNU_SWAP_REWARDS);
695
- // const manageCall3 = manage3Info.callConstructor({
696
- // props: swapInfo
697
- // });
698
- // proofIds.push(UNIVERSAL_MANAGE_IDS.APPROVE_SWAP_TOKEN1);
699
- // proofIds.push(UNIVERSAL_MANAGE_IDS.AVNU_SWAP_REWARDS);
700
-
701
- // manageCalls.push(manageCall2);
702
- // manageCalls.push(manageCall3);
703
- // }
704
-
705
- // const manageCall = this.getManageCall(proofIds, manageCalls);
706
- // return { call: manageCall, reward: actualReward, tokenInfo: STRK };
707
- // }
708
-
709
- // async getRebalanceCall(params: {
710
- // isLeg1toLeg2: boolean,
711
- // amount: Web3Number
712
- // }) {
713
- // let callSet1 = this.getVesuModifyPositionCalls({
714
- // isLeg1: true,
715
- // isDeposit: params.isLeg1toLeg2,
716
- // depositAmount: Web3Number.fromWei(0, 0),
717
- // debtAmount: params.amount
718
- // });
719
-
720
- // let callSet2 = this.getVesuModifyPositionCalls({
721
- // isLeg1: false,
722
- // isDeposit: params.isLeg1toLeg2,
723
- // depositAmount: params.amount,
724
- // debtAmount: Web3Number.fromWei(0, 0)
725
- // });
726
-
727
- // if (params.isLeg1toLeg2) {
728
- // const manageCall = this.getManageCall([
729
- // ...callSet1.map(i => i.step), ...callSet2.map(i => i.step)
730
- // ], [...callSet1.map(i => i.manageCall), ...callSet2.map(i => i.manageCall)]);
731
- // return manageCall;
732
- // } else {
733
- // const manageCall = this.getManageCall([
734
- // ...callSet2.map(i => i.step), ...callSet1.map(i => i.step)
735
- // ], [...callSet2.map(i => i.manageCall), ...callSet1.map(i => i.manageCall)]);
736
- // return manageCall;
737
- // }
738
- // }
739
- // }
740
-
88
+ export class UniversalStrategy<
89
+ S extends UniversalStrategySettings
90
+ > extends BaseStrategy<SingleTokenInfo, SingleActionAmount> {
91
+ /** Contract address of the strategy */
92
+ readonly address: ContractAddr;
93
+ /** Pricer instance for token price calculations */
94
+ readonly pricer: PricerBase;
95
+ /** Metadata containing strategy information */
96
+ readonly metadata: IStrategyMetadata<S>;
97
+ /** Contract instance for interacting with the strategy */
98
+ readonly contract: Contract;
99
+ readonly managerContract: Contract;
100
+ merkleTree: StandardMerkleTree | undefined;
101
+
102
+ constructor(
103
+ config: IConfig,
104
+ pricer: PricerBase,
105
+ metadata: IStrategyMetadata<S>
106
+ ) {
107
+ super(config);
108
+ this.pricer = pricer;
109
+
110
+ assert(
111
+ metadata.depositTokens.length === 1,
112
+ "VesuRebalance only supports 1 deposit token"
113
+ );
114
+ this.metadata = metadata;
115
+ this.address = metadata.address;
116
+
117
+ this.contract = new Contract({
118
+ abi: UniversalVaultAbi,
119
+ address: this.address.address,
120
+ providerOrAccount: this.config.provider
121
+ });
122
+ this.managerContract = new Contract({
123
+ abi: ManagerAbi,
124
+ address: this.metadata.additionalInfo.manager.address,
125
+ providerOrAccount: this.config.provider
126
+ });
127
+ }
128
+
129
+ /**
130
+ * Returns the underlying asset token of the strategy.
131
+ * @returns The deposit token supported by this strategy
132
+ */
133
+ asset() {
134
+ return this.metadata.depositTokens[0];
135
+ }
136
+
137
+ getMerkleTree() {
138
+ if (this.merkleTree) return this.merkleTree;
139
+ const leaves = this.metadata.additionalInfo.leafAdapters.map(
140
+ (adapter, index) => {
141
+ return adapter();
142
+ }
143
+ );
144
+ const standardTree = StandardMerkleTree.of(leaves.map((l) => l.leaf));
145
+ this.merkleTree = standardTree;
146
+ return standardTree;
147
+ }
148
+
149
+ getMerkleRoot() {
150
+ return this.getMerkleTree().root;
151
+ }
152
+
153
+ getProofs<T>(id: string): {
154
+ proofs: string[];
155
+ callConstructor: GenerateCallFn<T>;
156
+ } {
157
+ const tree = this.getMerkleTree();
158
+ const leaves = this.metadata.additionalInfo.leafAdapters.map(
159
+ (adapter) => adapter()
160
+ );
161
+ let proofs: string[] = [];
162
+ let callConstructor: GenerateCallFn<T> | null = null;
163
+ for (const [i, v] of tree.entries()) {
164
+ if (v.readableId == id) {
165
+ proofs = tree.getProof(i);
166
+ callConstructor = leaves[i]
167
+ .callConstructor as GenerateCallFn<T>;
168
+ break;
169
+ }
170
+ }
171
+ if (proofs.length === 0 || !callConstructor) {
172
+ throw new Error(`Proof not found for ID: ${id}`);
173
+ }
174
+ return { proofs, callConstructor };
175
+ }
176
+
177
+ /**
178
+ * Calculates user realized APY based on trueSharesBasedAPY method.
179
+ * Returns the APY as a number.
180
+ */
181
+ async getUserRealizedAPY(
182
+ blockIdentifier: BlockIdentifier = "latest",
183
+ sinceBlocks = 600000
184
+ ): Promise<number> {
185
+ logger.verbose(
186
+ `${this.getTag()}: getUserRealizedAPY => starting with blockIdentifier=${blockIdentifier}, sinceBlocks=${sinceBlocks}`
187
+ );
188
+
189
+ // Determine current block number and timestamp
190
+ let blockNow =
191
+ typeof blockIdentifier === "number" ||
192
+ typeof blockIdentifier === "bigint"
193
+ ? Number(blockIdentifier)
194
+ : (await this.config.provider.getBlockLatestAccepted())
195
+ .block_number;
196
+ const blockNowTime =
197
+ typeof blockIdentifier === "number" ||
198
+ typeof blockIdentifier === "bigint"
199
+ ? (await this.config.provider.getBlockWithTxs(blockIdentifier))
200
+ .timestamp
201
+ : new Date().getTime() / 1000;
202
+
203
+ // Look back window, but never before launch block
204
+ const blockBefore = Math.max(
205
+ blockNow - sinceBlocks,
206
+ this.metadata.launchBlock
207
+ );
208
+
209
+ // TVL amounts (in underlying token units) and supply at current reference block
210
+ const assetsNowRaw: bigint = (await this.contract.call(
211
+ "total_assets",
212
+ [],
213
+ {
214
+ blockIdentifier
215
+ }
216
+ )) as bigint;
217
+ const amountNow = Web3Number.fromWei(
218
+ assetsNowRaw.toString(),
219
+ this.metadata.depositTokens[0].decimals
220
+ );
221
+
222
+ const supplyNowRaw: bigint = (await this.contract.call(
223
+ "total_supply",
224
+ [],
225
+ {
226
+ blockIdentifier
227
+ }
228
+ )) as bigint;
229
+ const supplyNow = Web3Number.fromWei(supplyNowRaw.toString(), 18);
230
+
231
+ // Historical TVL and supply
232
+ const assetsBeforeRaw: bigint = (await this.contract.call(
233
+ "total_assets",
234
+ [],
235
+ { blockIdentifier: blockBefore }
236
+ )) as bigint;
237
+ const amountBefore = Web3Number.fromWei(
238
+ assetsBeforeRaw.toString(),
239
+ this.metadata.depositTokens[0].decimals
240
+ );
241
+
242
+ const supplyBeforeRaw: bigint = (await this.contract.call(
243
+ "total_supply",
244
+ [],
245
+ { blockIdentifier: blockBefore }
246
+ )) as bigint;
247
+ const supplyBefore = Web3Number.fromWei(supplyBeforeRaw.toString(), 18);
248
+
249
+ const blockBeforeInfo = await this.config.provider.getBlockWithTxs(
250
+ blockBefore
251
+ );
252
+
253
+ // Calculate assets per share
254
+ const assetsPerShareNow = amountNow
255
+ .multipliedBy(1e18)
256
+ .dividedBy(supplyNow.toString());
257
+
258
+ const assetsPerShareBf = amountBefore
259
+ .multipliedBy(1e18)
260
+ .dividedBy(supplyBefore.toString());
261
+
262
+ const timeDiffSeconds = blockNowTime - blockBeforeInfo.timestamp;
263
+
264
+ logger.verbose(
265
+ `${this.getTag()} [getUserRealizedAPY] assetsNow: ${amountNow.toString()}`
266
+ );
267
+ logger.verbose(
268
+ `${this.getTag()} [getUserRealizedAPY] assetsBefore: ${amountBefore.toString()}`
269
+ );
270
+ logger.verbose(
271
+ `${this.getTag()} [getUserRealizedAPY] assetsPerShareNow: ${assetsPerShareNow.toString()}`
272
+ );
273
+ logger.verbose(
274
+ `${this.getTag()} [getUserRealizedAPY] assetsPerShareBf: ${assetsPerShareBf.toString()}`
275
+ );
276
+ logger.verbose(
277
+ `${this.getTag()} [getUserRealizedAPY] Supply before: ${supplyBefore.toString()}`
278
+ );
279
+ logger.verbose(
280
+ `${this.getTag()} [getUserRealizedAPY] Supply now: ${supplyNow.toString()}`
281
+ );
282
+ logger.verbose(
283
+ `${this.getTag()} [getUserRealizedAPY] Time diff in seconds: ${timeDiffSeconds}`
284
+ );
285
+
286
+ const apyForGivenBlocks =
287
+ Number(
288
+ assetsPerShareNow
289
+ .minus(assetsPerShareBf)
290
+ .multipliedBy(10000)
291
+ .dividedBy(assetsPerShareBf)
292
+ ) / 10000;
293
+
294
+ return (apyForGivenBlocks * (365 * 24 * 3600)) / timeDiffSeconds;
295
+ }
296
+
297
+ /**
298
+ * Calculates the total TVL of the strategy.
299
+ * @returns Object containing the total amount in token units and USD value
300
+ */
301
+ async getTVL(): Promise<SingleTokenInfo> {
302
+ const assets = await this.contract.total_assets();
303
+ const amount = Web3Number.fromWei(
304
+ assets.toString(),
305
+ this.metadata.depositTokens[0].decimals
306
+ );
307
+ let price = await this.pricer.getPrice(
308
+ this.metadata.depositTokens[0].symbol
309
+ );
310
+ const usdValue = Number(amount.toFixed(6)) * price.price;
311
+ return {
312
+ tokenInfo: this.asset(),
313
+ amount,
314
+ usdValue
315
+ };
316
+ }
317
+
318
+ async getUnusedBalance(): Promise<SingleTokenInfo> {
319
+ const balance = await new ERC20(this.config).balanceOf(
320
+ this.asset().address,
321
+ this.metadata.additionalInfo.vaultAllocator,
322
+ this.asset().decimals
323
+ );
324
+ const price = await this.pricer.getPrice(
325
+ this.metadata.depositTokens[0].symbol
326
+ );
327
+ const usdValue = Number(balance.toFixed(6)) * price.price;
328
+ return {
329
+ tokenInfo: this.asset(),
330
+ amount: balance,
331
+ usdValue
332
+ };
333
+ }
334
+
335
+ protected async getVesuAUM(adapter: VesuAdapter) {
336
+ const legAUM = await adapter.getPositions(this.config);
337
+ const underlying = this.asset();
338
+ let vesuAum = Web3Number.fromWei("0", underlying.decimals);
339
+
340
+ let tokenUnderlyingPrice = await this.pricer.getPrice(
341
+ this.asset().symbol
342
+ );
343
+ logger.verbose(
344
+ `${this.getTag()} tokenUnderlyingPrice: ${
345
+ tokenUnderlyingPrice.price
346
+ }`
347
+ );
348
+
349
+ // handle collateral
350
+ if (legAUM[0].token.address.eq(underlying.address)) {
351
+ vesuAum = vesuAum.plus(legAUM[0].amount);
352
+ } else {
353
+ vesuAum = vesuAum.plus(
354
+ legAUM[0].usdValue / tokenUnderlyingPrice.price
355
+ );
356
+ }
357
+
358
+ // handle debt
359
+ if (legAUM[1].token.address.eq(underlying.address)) {
360
+ vesuAum = vesuAum.minus(legAUM[1].amount);
361
+ } else {
362
+ vesuAum = vesuAum.minus(
363
+ legAUM[1].usdValue / tokenUnderlyingPrice.price
364
+ );
365
+ }
366
+
367
+ logger.verbose(
368
+ `${this.getTag()} Vesu AUM: ${vesuAum}, legCollateral: ${legAUM[0].amount.toNumber()}, legDebt: ${legAUM[1].amount.toNumber()}`
369
+ );
370
+ return vesuAum;
371
+ }
372
+
373
+ async getPrevAUM() {
374
+ const currentAUM: bigint = (await this.contract.call(
375
+ "aum",
376
+ []
377
+ )) as bigint;
378
+ const prevAum = Web3Number.fromWei(
379
+ currentAUM.toString(),
380
+ this.asset().decimals
381
+ );
382
+ logger.verbose(`${this.getTag()} Prev AUM: ${prevAum}`);
383
+ return prevAum;
384
+ }
385
+
386
+ async getAUM(): Promise<{
387
+ net: SingleTokenInfo;
388
+ prevAum: Web3Number;
389
+ splits: { id: string; aum: Web3Number }[];
390
+ }> {
391
+ const prevAum = await this.getPrevAUM();
392
+ const token1Price = await this.pricer.getPrice(
393
+ this.metadata.depositTokens[0].symbol
394
+ );
395
+
396
+ // calculate vesu aum
397
+ const vesuAdapters = this.getVesuAdapters();
398
+ let vesuAum = Web3Number.fromWei("0", this.asset().decimals);
399
+ for (const adapter of vesuAdapters) {
400
+ vesuAum = vesuAum.plus(await this.getVesuAUM(adapter));
401
+ }
402
+
403
+ // account unused balance as aum as well (from vault allocator)
404
+ const balance = await this.getUnusedBalance();
405
+ logger.verbose(
406
+ `${this.getTag()} unused balance: ${balance.amount.toNumber()}`
407
+ );
408
+
409
+ // Initiate return values
410
+ const zeroAmt = Web3Number.fromWei("0", this.asset().decimals);
411
+ const net = {
412
+ tokenInfo: this.asset(),
413
+ amount: zeroAmt,
414
+ usdValue: 0
415
+ };
416
+ const aumToken = vesuAum.plus(balance.amount);
417
+ if (aumToken.isZero()) {
418
+ return {
419
+ net,
420
+ splits: [
421
+ {
422
+ aum: zeroAmt,
423
+ id: AUMTypes.FINALISED
424
+ },
425
+ {
426
+ aum: zeroAmt,
427
+ id: AUMTypes.DEFISPRING
428
+ }
429
+ ],
430
+ prevAum
431
+ };
432
+ }
433
+ logger.verbose(`${this.getTag()} Actual AUM: ${aumToken}`);
434
+
435
+ // compute rewards contribution to AUM
436
+ const rewardAssets = await this.getRewardsAUM(prevAum);
437
+
438
+ // Sum up and return
439
+ const newAUM = aumToken.plus(rewardAssets);
440
+ logger.verbose(`${this.getTag()} New AUM: ${newAUM}`);
441
+
442
+ net.amount = newAUM;
443
+ net.usdValue = newAUM.multipliedBy(token1Price.price).toNumber();
444
+ const splits = [
445
+ {
446
+ id: AUMTypes.FINALISED,
447
+ aum: aumToken
448
+ },
449
+ {
450
+ id: AUMTypes.DEFISPRING,
451
+ aum: rewardAssets
452
+ }
453
+ ];
454
+ return { net, splits, prevAum };
455
+ }
456
+
457
+ // account for future rewards (e.g. defispring rewards)
458
+ protected async getRewardsAUM(prevAum: Web3Number) {
459
+ const lastReportTime = await this.contract.call(
460
+ "last_report_timestamp",
461
+ []
462
+ );
463
+ // - calculate estimated growth from strk rewards
464
+ const netAPY = await this.netAPY();
465
+ // account only 80% of value
466
+ const defispringAPY =
467
+ (netAPY.splits.find(
468
+ (s: { apy: number; id: string }) => s.id === "defispring"
469
+ )?.apy || 0) * 0.8;
470
+ if (!defispringAPY) throw new Error("DefiSpring APY not found");
471
+
472
+ // compute rewards contribution to AUM
473
+ const timeDiff = Math.round(Date.now() / 1000) - Number(lastReportTime);
474
+ const growthRate = (timeDiff * defispringAPY) / (365 * 24 * 60 * 60);
475
+ const rewardAssets = prevAum.multipliedBy(growthRate);
476
+ logger.verbose(
477
+ `${this.getTag()} DefiSpring AUM time difference: ${timeDiff}`
478
+ );
479
+ logger.verbose(`${this.getTag()} Current AUM: ${prevAum.toString()}`);
480
+ logger.verbose(`${this.getTag()} Net APY: ${JSON.stringify(netAPY)}`);
481
+ logger.verbose(`${this.getTag()} rewards AUM: ${rewardAssets}`);
482
+
483
+ return rewardAssets;
484
+ }
485
+
486
+ /**
487
+ * Gets an adapter by its ID from the metadata
488
+ */
489
+ getAdapter(id: string): BaseAdapter {
490
+ const adapterEntry = this.metadata.additionalInfo.adapters.find(
491
+ (a) => a.id === id
492
+ );
493
+ if (!adapterEntry) {
494
+ throw new Error(`Adapter with id ${id} not found`);
495
+ }
496
+ const adapter = adapterEntry.adapter;
497
+ (adapter as any).networkConfig = this.config;
498
+ (adapter as any).pricer = this.pricer;
499
+ return adapter;
500
+ }
501
+
502
+ /**
503
+ * Calculates net APY for the strategy
504
+ * @returns Promise with net APY and splits
505
+ */
506
+ async netAPY(): Promise<{
507
+ net: number;
508
+ splits: { apy: number; id: string }[];
509
+ }> {
510
+ // This is a base implementation that should be overridden by subclasses
511
+ // For now, return a default structure
512
+ return {
513
+ net: 0,
514
+ splits: []
515
+ };
516
+ }
517
+
518
+ getVesuAdapters() {
519
+ const vesuAdapter1 = this.getAdapter(
520
+ UNIVERSAL_ADAPTERS.VESU_LEG1
521
+ ) as VesuAdapter;
522
+ const vesuAdapter2 = this.getAdapter(
523
+ UNIVERSAL_ADAPTERS.VESU_LEG2
524
+ ) as VesuAdapter;
525
+ vesuAdapter1.pricer = this.pricer;
526
+ vesuAdapter2.pricer = this.pricer;
527
+ vesuAdapter1.networkConfig = this.config;
528
+ vesuAdapter2.networkConfig = this.config;
529
+
530
+ return [vesuAdapter1, vesuAdapter2];
531
+ }
532
+
533
+ async getVesuPositions(
534
+ blockNumber: BlockIdentifier = "latest"
535
+ ): Promise<VaultPosition[]> {
536
+ const adapters = this.getVesuAdapters();
537
+ const positions: VaultPosition[] = [];
538
+ for (const adapter of adapters) {
539
+ positions.push(
540
+ ...(await adapter.getPositions(this.config, blockNumber))
541
+ );
542
+ }
543
+ return positions;
544
+ }
545
+
546
+ async getVaultPositions(): Promise<VaultPosition[]> {
547
+ const vesuPositions = await this.getVesuPositions();
548
+ const unusedBalance = await this.getUnusedBalance();
549
+ return [
550
+ ...vesuPositions,
551
+ {
552
+ amount: unusedBalance.amount,
553
+ usdValue: unusedBalance.usdValue,
554
+ token: this.asset(),
555
+ remarks: "Unused Balance (may not include recent deposits)"
556
+ }
557
+ ];
558
+ }
559
+
560
+ getSetManagerCall(strategist: ContractAddr, root = this.getMerkleRoot()) {
561
+ return this.managerContract.populate("set_manage_root", [
562
+ strategist.address,
563
+ num.getHexString(root)
564
+ ]);
565
+ }
566
+
567
+ getManageCall(proofIds: string[], manageCalls: ManageCall[]) {
568
+ assert(
569
+ proofIds.length == manageCalls.length,
570
+ "Proof IDs and Manage Calls length mismatch"
571
+ );
572
+ return this.managerContract.populate(
573
+ "manage_vault_with_merkle_verification",
574
+ {
575
+ proofs: proofIds.map((id) => this.getProofs(id).proofs),
576
+ decoder_and_sanitizers: manageCalls.map(
577
+ (call) => call.sanitizer.address
578
+ ),
579
+ targets: manageCalls.map(
580
+ (call) => call.call.contractAddress.address
581
+ ),
582
+ selectors: manageCalls.map((call) => call.call.selector),
583
+ calldatas: manageCalls.map((call) => call.call.calldata) // Calldata[]
584
+ }
585
+ );
586
+ }
587
+
588
+ getVesuModifyPositionCalls(params: {
589
+ isLeg1: boolean;
590
+ isDeposit: boolean;
591
+ depositAmount: Web3Number;
592
+ debtAmount: Web3Number;
593
+ }): UniversalManageCall[] {
594
+ assert(
595
+ params.depositAmount.gt(0) || params.debtAmount.gt(0),
596
+ "Either deposit or debt amount must be greater than 0"
597
+ );
598
+ // approve token
599
+ const isToken1 = params.isLeg1 == params.isDeposit; // XOR
600
+ const STEP1_ID = isToken1
601
+ ? UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1
602
+ : UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN2;
603
+ const manage4Info = this.getProofs<ApproveCallParams>(STEP1_ID);
604
+ const approveAmount = params.isDeposit
605
+ ? params.depositAmount
606
+ : params.debtAmount;
607
+ const manageCall4 = manage4Info.callConstructor({
608
+ amount: approveAmount
609
+ });
610
+
611
+ // deposit and borrow or repay and withdraw
612
+ const STEP2_ID = params.isLeg1
613
+ ? UNIVERSAL_MANAGE_IDS.VESU_LEG1
614
+ : UNIVERSAL_MANAGE_IDS.VESU_LEG2;
615
+ const manage5Info =
616
+ this.getProofs<VesuModifyPositionCallParams>(STEP2_ID);
617
+ const manageCall5 = manage5Info.callConstructor(
618
+ VesuAdapter.getDefaultModifyPositionCallParams({
619
+ collateralAmount: params.depositAmount,
620
+ isAddCollateral: params.isDeposit,
621
+ debtAmount: params.debtAmount,
622
+ isBorrow: params.isDeposit
623
+ })
624
+ );
625
+
626
+ const output = [
627
+ {
628
+ proofs: manage5Info.proofs,
629
+ manageCall: manageCall5,
630
+ step: STEP2_ID
631
+ }
632
+ ];
633
+ if (approveAmount.gt(0)) {
634
+ output.unshift({
635
+ proofs: manage4Info.proofs,
636
+ manageCall: manageCall4,
637
+ step: STEP1_ID
638
+ });
639
+ }
640
+ return output;
641
+ }
642
+
643
+ getTag() {
644
+ return `${UniversalStrategy.name}:${this.metadata.name}`;
645
+ }
646
+
647
+ /**
648
+ * Gets LST APR for the strategy's underlying asset from Endur API
649
+ * @returns Promise<number> The LST APR (not divided by 1e18)
650
+ */
651
+ async getLSTAPR(address: ContractAddr): Promise<number> {
652
+ return 0;
653
+ }
654
+
655
+ async getVesuHealthFactors(blockNumber: BlockIdentifier = "latest") {
656
+ return await Promise.all(
657
+ this.getVesuAdapters().map((v) => v.getHealthFactor(blockNumber))
658
+ );
659
+ }
660
+
661
+ async computeRebalanceConditionAndReturnCalls(): Promise<Call[]> {
662
+ const vesuAdapters = this.getVesuAdapters();
663
+ const healthFactors = await this.getVesuHealthFactors();
664
+ const leg1HealthFactor = healthFactors[0];
665
+ const leg2HealthFactor = healthFactors[1];
666
+ logger.verbose(
667
+ `${this.getTag()}: HealthFactorLeg1: ${leg1HealthFactor}`
668
+ );
669
+ logger.verbose(
670
+ `${this.getTag()}: HealthFactorLeg2: ${leg2HealthFactor}`
671
+ );
672
+
673
+ const minHf = this.metadata.additionalInfo.minHealthFactor;
674
+ const isRebalanceNeeded1 = leg1HealthFactor < minHf;
675
+ const isRebalanceNeeded2 = leg2HealthFactor < minHf;
676
+ if (!isRebalanceNeeded1 && !isRebalanceNeeded2) {
677
+ return [];
678
+ }
679
+
680
+ if (isRebalanceNeeded1) {
681
+ const amount = await this.getLegRebalanceAmount(
682
+ vesuAdapters[0],
683
+ leg1HealthFactor,
684
+ false
685
+ );
686
+ const leg2HF = await this.getNewHealthFactor(
687
+ vesuAdapters[1],
688
+ amount,
689
+ true
690
+ );
691
+ assert(
692
+ leg2HF > minHf,
693
+ `Rebalance Leg1 failed: Leg2 HF after rebalance would be too low: ${leg2HF}`
694
+ );
695
+ return [
696
+ await this.getRebalanceCall({
697
+ isLeg1toLeg2: false,
698
+ amount: amount
699
+ })
700
+ ];
701
+ } else {
702
+ const amount = await this.getLegRebalanceAmount(
703
+ vesuAdapters[1],
704
+ leg2HealthFactor,
705
+ true
706
+ );
707
+ const leg1HF = await this.getNewHealthFactor(
708
+ vesuAdapters[0],
709
+ amount,
710
+ false
711
+ );
712
+ assert(
713
+ leg1HF > minHf,
714
+ `Rebalance Leg2 failed: Leg1 HF after rebalance would be too low: ${leg1HF}`
715
+ );
716
+ return [
717
+ await this.getRebalanceCall({
718
+ isLeg1toLeg2: true,
719
+ amount: amount
720
+ })
721
+ ];
722
+ }
723
+ }
724
+
725
+ private async getNewHealthFactor(
726
+ vesuAdapter: VesuAdapter,
727
+ newAmount: Web3Number,
728
+ isWithdraw: boolean
729
+ ) {
730
+ const {
731
+ collateralTokenAmount,
732
+ collateralUSDAmount,
733
+ collateralPrice,
734
+ debtTokenAmount,
735
+ debtUSDAmount,
736
+ debtPrice,
737
+ ltv
738
+ } = await vesuAdapter.getAssetPrices();
739
+
740
+ if (isWithdraw) {
741
+ const newHF =
742
+ ((collateralTokenAmount.toNumber() - newAmount.toNumber()) *
743
+ collateralPrice *
744
+ ltv) /
745
+ debtUSDAmount;
746
+ logger.verbose(
747
+ `getNewHealthFactor:: HF: ${newHF}, amoutn: ${newAmount.toNumber()}, isDeposit`
748
+ );
749
+ return newHF;
750
+ } else {
751
+ // is borrow
752
+ const newHF =
753
+ (collateralUSDAmount * ltv) /
754
+ ((debtTokenAmount.toNumber() + newAmount.toNumber()) *
755
+ debtPrice);
756
+ logger.verbose(
757
+ `getNewHealthFactor:: HF: ${newHF}, amoutn: ${newAmount.toNumber()}, isRepay`
758
+ );
759
+ return newHF;
760
+ }
761
+ }
762
+
763
+ /**
764
+ *
765
+ * @param vesuAdapter
766
+ * @param currentHf
767
+ * @param isDeposit if true, attempt by adding collateral, else by repaying
768
+ * @returns
769
+ */
770
+ private async getLegRebalanceAmount(
771
+ vesuAdapter: VesuAdapter,
772
+ currentHf: number,
773
+ isDeposit: boolean
774
+ ) {
775
+ const {
776
+ collateralTokenAmount,
777
+ collateralUSDAmount,
778
+ collateralPrice,
779
+ debtTokenAmount,
780
+ debtUSDAmount,
781
+ debtPrice,
782
+ ltv
783
+ } = await vesuAdapter.getAssetPrices();
784
+
785
+ // debt is zero, nothing to rebalance
786
+ if (debtTokenAmount.isZero()) {
787
+ return Web3Number.fromWei(0, 0);
788
+ }
789
+
790
+ assert(
791
+ collateralPrice > 0 && debtPrice > 0,
792
+ "getRebalanceAmount: Invalid price"
793
+ );
794
+
795
+ // avoid calculating for too close
796
+ const targetHF = this.metadata.additionalInfo.targetHealthFactor;
797
+ if (currentHf > targetHF - 0.01)
798
+ throw new Error(
799
+ "getLegRebalanceAmount: Current health factor is healthy"
800
+ );
801
+
802
+ if (isDeposit) {
803
+ // TargetHF = (collAmount + newAmount) * price * ltv / debtUSD
804
+ const newAmount =
805
+ (targetHF * debtUSDAmount) / (collateralPrice * ltv) -
806
+ collateralTokenAmount.toNumber();
807
+ logger.verbose(
808
+ `${this.getTag()}:: getLegRebalanceAmount: addCollateral, currentHf: ${currentHf}, targetHF: ${targetHF}, collAmount: ${collateralTokenAmount.toString()}, collUSD: ${collateralUSDAmount}, collPrice: ${collateralPrice}, debtAmount: ${debtTokenAmount.toString()}, debtUSD: ${debtUSDAmount}, debtPrice: ${debtPrice}, ltv: ${ltv}, newAmount: ${newAmount}`
809
+ );
810
+ return new Web3Number(
811
+ newAmount.toFixed(8),
812
+ collateralTokenAmount.decimals
813
+ );
814
+ } else {
815
+ // TargetHF = collUSD * ltv / (debtAmount - newAmount) * debtPrice
816
+ const newAmount =
817
+ debtTokenAmount.toNumber() -
818
+ (collateralUSDAmount * ltv) / (targetHF * debtPrice);
819
+ logger.verbose(
820
+ `${this.getTag()}:: getLegRebalanceAmount: repayDebt, currentHf: ${currentHf}, targetHF: ${targetHF}, collAmount: ${collateralTokenAmount.toString()}, collUSD: ${collateralUSDAmount}, collPrice: ${collateralPrice}, debtAmount: ${debtTokenAmount.toString()}, debtUSD: ${debtUSDAmount}, debtPrice: ${debtPrice}, ltv: ${ltv}, newAmount: ${newAmount}`
821
+ );
822
+ return new Web3Number(
823
+ newAmount.toFixed(8),
824
+ debtTokenAmount.decimals
825
+ );
826
+ }
827
+ }
828
+
829
+ async getVesuModifyPositionCall(params: {
830
+ isDeposit: boolean;
831
+ leg1DepositAmount: Web3Number;
832
+ }) {
833
+ const [vesuAdapter1, vesuAdapter2] = this.getVesuAdapters();
834
+ const leg1LTV = await vesuAdapter1.getLTVConfig(this.config);
835
+ const leg2LTV = await vesuAdapter2.getLTVConfig(this.config);
836
+ logger.verbose(`${this.getTag()}: LTVLeg1: ${leg1LTV}`);
837
+ logger.verbose(`${this.getTag()}: LTVLeg2: ${leg2LTV}`);
838
+
839
+ const token1Price = await this.pricer.getPrice(
840
+ vesuAdapter1.config.collateral.symbol
841
+ );
842
+ const token2Price = await this.pricer.getPrice(
843
+ vesuAdapter2.config.collateral.symbol
844
+ );
845
+ logger.verbose(
846
+ `${this.getTag()}: Price${vesuAdapter1.config.collateral.symbol}: ${
847
+ token1Price.price
848
+ }`
849
+ );
850
+ logger.verbose(
851
+ `${this.getTag()}: Price${vesuAdapter2.config.collateral.symbol}: ${
852
+ token2Price.price
853
+ }`
854
+ );
855
+
856
+ const TARGET_HF = this.metadata.additionalInfo.targetHealthFactor;
857
+
858
+ const k1 =
859
+ (token1Price.price * leg1LTV) / token2Price.price / TARGET_HF;
860
+ const k2 =
861
+ (token1Price.price * TARGET_HF) / token2Price.price / leg2LTV;
862
+
863
+ const borrow2Amount = new Web3Number(
864
+ params.leg1DepositAmount
865
+ .multipliedBy(k1.toFixed(6))
866
+ .dividedBy(k2 - k1)
867
+ .toFixed(6),
868
+ vesuAdapter2.config.debt.decimals
869
+ );
870
+ const borrow1Amount = new Web3Number(
871
+ borrow2Amount.multipliedBy(k2).toFixed(6),
872
+ vesuAdapter1.config.debt.decimals
873
+ );
874
+ logger.verbose(
875
+ `${this.getTag()}:: leg1DepositAmount: ${params.leg1DepositAmount.toString()} ${
876
+ vesuAdapter1.config.collateral.symbol
877
+ }`
878
+ );
879
+ logger.verbose(
880
+ `${this.getTag()}:: borrow1Amount: ${borrow1Amount.toString()} ${
881
+ vesuAdapter1.config.debt.symbol
882
+ }`
883
+ );
884
+ logger.verbose(
885
+ `${this.getTag()}:: borrow2Amount: ${borrow2Amount.toString()} ${
886
+ vesuAdapter2.config.debt.symbol
887
+ }`
888
+ );
889
+
890
+ let callSet1 = this.getVesuModifyPositionCalls({
891
+ isLeg1: true,
892
+ isDeposit: params.isDeposit,
893
+ depositAmount: params.leg1DepositAmount.plus(borrow2Amount),
894
+ debtAmount: borrow1Amount
895
+ });
896
+
897
+ let callSet2 = this.getVesuModifyPositionCalls({
898
+ isLeg1: false,
899
+ isDeposit: params.isDeposit,
900
+ depositAmount: borrow1Amount,
901
+ debtAmount: borrow2Amount
902
+ });
903
+
904
+ if (!params.isDeposit) {
905
+ let temp = callSet2;
906
+ callSet2 = [...callSet1];
907
+ callSet1 = [...temp];
908
+ }
909
+ const allActions = [
910
+ ...callSet1.map((i) => i.manageCall),
911
+ ...callSet2.map((i) => i.manageCall)
912
+ ];
913
+ const flashloanCalldata = CallData.compile([
914
+ [
915
+ ...callSet1.map((i) => i.proofs),
916
+ ...callSet2.map((i) => i.proofs)
917
+ ],
918
+ allActions.map((i) => i.sanitizer.address),
919
+ allActions.map((i) => i.call.contractAddress.address),
920
+ allActions.map((i) => i.call.selector),
921
+ allActions.map((i) => i.call.calldata)
922
+ ]);
923
+
924
+ // flash loan
925
+ const STEP1_ID = UNIVERSAL_MANAGE_IDS.FLASH_LOAN;
926
+ const manage1Info = this.getProofs<FlashloanCallParams>(STEP1_ID);
927
+ const manageCall1 = manage1Info.callConstructor({
928
+ amount: borrow2Amount,
929
+ data: flashloanCalldata.map((i) => BigInt(i))
930
+ });
931
+ const manageCall = this.getManageCall(
932
+ [UNIVERSAL_MANAGE_IDS.FLASH_LOAN],
933
+ [manageCall1]
934
+ );
935
+ return manageCall;
936
+ }
937
+
938
+ async getBringLiquidityCall(params: { amount: Web3Number }) {
939
+ const manage1Info = this.getProofs<ApproveCallParams>(
940
+ UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY
941
+ );
942
+ const manageCall1 = manage1Info.callConstructor({
943
+ amount: params.amount
944
+ });
945
+ const manage2Info = this.getProofs<ApproveCallParams>(
946
+ UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY
947
+ );
948
+ const manageCall2 = manage2Info.callConstructor({
949
+ amount: params.amount
950
+ });
951
+ const manageCall = this.getManageCall(
952
+ [
953
+ UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY,
954
+ UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY
955
+ ],
956
+ [manageCall1, manageCall2]
957
+ );
958
+ return manageCall;
959
+ }
960
+
961
+ async getPendingRewards(): Promise<HarvestInfo[]> {
962
+ const vesuHarvest = new VesuHarvests(this.config);
963
+ return await vesuHarvest.getUnHarvestedRewards(
964
+ this.metadata.additionalInfo.vaultAllocator
965
+ );
966
+ }
967
+
968
+ async getHarvestCall() {
969
+ const harvestInfo = await this.getPendingRewards();
970
+ if (harvestInfo.length == 0) {
971
+ throw new Error(`No pending rewards found`);
972
+ }
973
+ if (harvestInfo.length != 1) {
974
+ throw new Error(
975
+ `Expected 1 harvest info, got ${harvestInfo.length}`
976
+ );
977
+ }
978
+
979
+ const amount = harvestInfo[0].claim.amount;
980
+ const actualReward = harvestInfo[0].actualReward;
981
+ const proofs = harvestInfo[0].proof;
982
+ if (actualReward.isZero()) {
983
+ throw new Error(
984
+ `Expected non-zero actual reward, got ${harvestInfo[0].actualReward}`
985
+ );
986
+ }
987
+
988
+ const manage1Info = this.getProofs<VesuDefiSpringRewardsCallParams>(
989
+ UNIVERSAL_MANAGE_IDS.DEFISPRING_REWARDS
990
+ );
991
+ const manageCall1 = manage1Info.callConstructor({
992
+ amount,
993
+ proofs
994
+ });
995
+ const proofIds: string[] = [UNIVERSAL_MANAGE_IDS.DEFISPRING_REWARDS];
996
+ const manageCalls: ManageCall[] = [manageCall1];
997
+
998
+ // swap rewards for underlying
999
+ const STRK = Global.getDefaultTokens().find(
1000
+ (t) => t.symbol === "STRK"
1001
+ )!;
1002
+ if (this.asset().symbol != "STRK") {
1003
+ // approve
1004
+ const manage2Info = this.getProofs<ApproveCallParams>(
1005
+ UNIVERSAL_MANAGE_IDS.APPROVE_SWAP_TOKEN1
1006
+ );
1007
+ const manageCall2 = manage2Info.callConstructor({
1008
+ amount: actualReward
1009
+ });
1010
+
1011
+ // swap
1012
+ const avnuModule = new AvnuWrapper();
1013
+ const quote = await avnuModule.getQuotes(
1014
+ STRK.address.address,
1015
+ this.asset().address.address,
1016
+ actualReward.toWei(),
1017
+ this.address.address
1018
+ );
1019
+ const swapInfo = await avnuModule.getSwapInfo(
1020
+ quote,
1021
+ this.address.address,
1022
+ 0,
1023
+ this.address.address
1024
+ );
1025
+ const manage3Info = this.getProofs<AvnuSwapCallParams>(
1026
+ UNIVERSAL_MANAGE_IDS.AVNU_SWAP_REWARDS
1027
+ );
1028
+ const manageCall3 = manage3Info.callConstructor({
1029
+ props: swapInfo
1030
+ });
1031
+ proofIds.push(UNIVERSAL_MANAGE_IDS.APPROVE_SWAP_TOKEN1);
1032
+ proofIds.push(UNIVERSAL_MANAGE_IDS.AVNU_SWAP_REWARDS);
1033
+
1034
+ manageCalls.push(manageCall2);
1035
+ manageCalls.push(manageCall3);
1036
+ }
1037
+
1038
+ const manageCall = this.getManageCall(proofIds, manageCalls);
1039
+ return { call: manageCall, reward: actualReward, tokenInfo: STRK };
1040
+ }
1041
+
1042
+ async getRebalanceCall(params: {
1043
+ isLeg1toLeg2: boolean;
1044
+ amount: Web3Number;
1045
+ }) {
1046
+ let callSet1 = this.getVesuModifyPositionCalls({
1047
+ isLeg1: true,
1048
+ isDeposit: params.isLeg1toLeg2,
1049
+ depositAmount: Web3Number.fromWei(0, 0),
1050
+ debtAmount: params.amount
1051
+ });
1052
+
1053
+ let callSet2 = this.getVesuModifyPositionCalls({
1054
+ isLeg1: false,
1055
+ isDeposit: params.isLeg1toLeg2,
1056
+ depositAmount: params.amount,
1057
+ debtAmount: Web3Number.fromWei(0, 0)
1058
+ });
1059
+
1060
+ if (params.isLeg1toLeg2) {
1061
+ const manageCall = this.getManageCall(
1062
+ [
1063
+ ...callSet1.map((i) => i.step),
1064
+ ...callSet2.map((i) => i.step)
1065
+ ],
1066
+ [
1067
+ ...callSet1.map((i) => i.manageCall),
1068
+ ...callSet2.map((i) => i.manageCall)
1069
+ ]
1070
+ );
1071
+ return manageCall;
1072
+ } else {
1073
+ const manageCall = this.getManageCall(
1074
+ [
1075
+ ...callSet2.map((i) => i.step),
1076
+ ...callSet1.map((i) => i.step)
1077
+ ],
1078
+ [
1079
+ ...callSet2.map((i) => i.manageCall),
1080
+ ...callSet1.map((i) => i.manageCall)
1081
+ ]
1082
+ );
1083
+ return manageCall;
1084
+ }
1085
+ }
1086
+ }
741
1087
 
742
1088
  // useful to map readble names to proofs and calls
743
1089
  export enum UNIVERSAL_MANAGE_IDS {
744
- FLASH_LOAN = 'flash_loan_init',
745
- VESU_LEG1 = 'vesu_leg1',
746
- VESU_LEG2 = 'vesu_leg2',
747
- APPROVE_TOKEN1 = 'approve_token1',
748
- APPROVE_TOKEN2 = 'approve_token2',
749
- APPROVE_BRING_LIQUIDITY = 'approve_bring_liquidity',
750
- BRING_LIQUIDITY = 'bring_liquidity',
751
-
752
- // defi spring claim
753
- DEFISPRING_REWARDS = 'defispring_rewards',
754
-
755
- // avnu swaps
756
- APPROVE_SWAP_TOKEN1 = 'approve_swap_token1',
757
- AVNU_SWAP_REWARDS = 'avnu_swap_rewards'
1090
+ FLASH_LOAN = "flash_loan_init",
1091
+ VESU_LEG1 = "vesu_leg1",
1092
+ VESU_LEG2 = "vesu_leg2",
1093
+ APPROVE_TOKEN1 = "approve_token1",
1094
+ APPROVE_TOKEN2 = "approve_token2",
1095
+ APPROVE_BRING_LIQUIDITY = "approve_bring_liquidity",
1096
+ BRING_LIQUIDITY = "bring_liquidity",
1097
+
1098
+ // defi spring claim
1099
+ DEFISPRING_REWARDS = "defispring_rewards",
1100
+
1101
+ // avnu swaps
1102
+ APPROVE_SWAP_TOKEN1 = "approve_swap_token1",
1103
+ AVNU_SWAP_REWARDS = "avnu_swap_rewards"
1104
+ }
1105
+
1106
+ export enum UNIVERSAL_ADAPTERS {
1107
+ COMMON = "common_adapter",
1108
+ VESU_LEG1 = "vesu_leg1_adapter",
1109
+ VESU_LEG2 = "vesu_leg2_adapter"
1110
+ }
1111
+
1112
+ function getLooperSettings(
1113
+ token1Symbol: string,
1114
+ token2Symbol: string,
1115
+ vaultSettings: UniversalStrategySettings,
1116
+ pool1: ContractAddr,
1117
+ pool2: ContractAddr
1118
+ ) {
1119
+ const USDCToken = Global.getDefaultTokens().find(
1120
+ (token) => token.symbol === token1Symbol
1121
+ )!;
1122
+ const ETHToken = Global.getDefaultTokens().find(
1123
+ (token) => token.symbol === token2Symbol
1124
+ )!;
1125
+
1126
+ const commonAdapter = new CommonAdapter({
1127
+ manager: vaultSettings.manager,
1128
+ asset: USDCToken.address,
1129
+ id: UNIVERSAL_MANAGE_IDS.FLASH_LOAN,
1130
+ vaultAddress: vaultSettings.vaultAddress,
1131
+ vaultAllocator: vaultSettings.vaultAllocator
1132
+ });
1133
+ const vesuAdapterUSDCETH = new VesuAdapter({
1134
+ poolId: pool1,
1135
+ collateral: USDCToken,
1136
+ debt: ETHToken,
1137
+ vaultAllocator: vaultSettings.vaultAllocator,
1138
+ id: UNIVERSAL_MANAGE_IDS.VESU_LEG1
1139
+ });
1140
+ const vesuAdapterETHUSDC = new VesuAdapter({
1141
+ poolId: pool2,
1142
+ collateral: ETHToken,
1143
+ debt: USDCToken,
1144
+ vaultAllocator: vaultSettings.vaultAllocator,
1145
+ id: UNIVERSAL_MANAGE_IDS.VESU_LEG2
1146
+ });
1147
+ vaultSettings.adapters.push(
1148
+ ...[
1149
+ {
1150
+ id: UNIVERSAL_ADAPTERS.COMMON,
1151
+ adapter: commonAdapter
1152
+ },
1153
+ {
1154
+ id: UNIVERSAL_ADAPTERS.VESU_LEG1,
1155
+ adapter: vesuAdapterUSDCETH
1156
+ },
1157
+ {
1158
+ id: UNIVERSAL_ADAPTERS.VESU_LEG2,
1159
+ adapter: vesuAdapterETHUSDC
1160
+ }
1161
+ ]
1162
+ );
1163
+
1164
+ // vesu looping
1165
+ vaultSettings.leafAdapters.push(
1166
+ commonAdapter.getFlashloanAdapter.bind(commonAdapter)
1167
+ );
1168
+ vaultSettings.leafAdapters.push(
1169
+ vesuAdapterUSDCETH.getModifyPosition.bind(vesuAdapterUSDCETH)
1170
+ );
1171
+ vaultSettings.leafAdapters.push(
1172
+ vesuAdapterETHUSDC.getModifyPosition.bind(vesuAdapterETHUSDC)
1173
+ );
1174
+ vaultSettings.leafAdapters.push(
1175
+ commonAdapter
1176
+ .getApproveAdapter(
1177
+ USDCToken.address,
1178
+ VESU_SINGLETON,
1179
+ UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1
1180
+ )
1181
+ .bind(commonAdapter)
1182
+ );
1183
+ vaultSettings.leafAdapters.push(
1184
+ commonAdapter
1185
+ .getApproveAdapter(
1186
+ ETHToken.address,
1187
+ VESU_SINGLETON,
1188
+ UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN2
1189
+ )
1190
+ .bind(commonAdapter)
1191
+ );
1192
+
1193
+ // to bridge liquidity back to vault (used by bring_liquidity)
1194
+ vaultSettings.leafAdapters.push(
1195
+ commonAdapter
1196
+ .getApproveAdapter(
1197
+ USDCToken.address,
1198
+ vaultSettings.vaultAddress,
1199
+ UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY
1200
+ )
1201
+ .bind(commonAdapter)
1202
+ );
1203
+ vaultSettings.leafAdapters.push(
1204
+ commonAdapter
1205
+ .getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
1206
+ .bind(commonAdapter)
1207
+ );
1208
+
1209
+ // claim rewards
1210
+ vaultSettings.leafAdapters.push(
1211
+ vesuAdapterUSDCETH
1212
+ .getDefispringRewardsAdapter(
1213
+ UNIVERSAL_MANAGE_IDS.DEFISPRING_REWARDS
1214
+ )
1215
+ .bind(vesuAdapterUSDCETH)
1216
+ );
1217
+
1218
+ // avnu swap
1219
+ const STRKToken = Global.getDefaultTokens().find(
1220
+ (token) => token.symbol === "STRK"
1221
+ )!;
1222
+ vaultSettings.leafAdapters.push(
1223
+ commonAdapter
1224
+ .getApproveAdapter(
1225
+ STRKToken.address,
1226
+ AVNU_MIDDLEWARE,
1227
+ UNIVERSAL_MANAGE_IDS.APPROVE_SWAP_TOKEN1
1228
+ )
1229
+ .bind(commonAdapter)
1230
+ );
1231
+ vaultSettings.leafAdapters.push(
1232
+ commonAdapter
1233
+ .getAvnuAdapter(
1234
+ STRKToken.address,
1235
+ USDCToken.address,
1236
+ UNIVERSAL_MANAGE_IDS.AVNU_SWAP_REWARDS,
1237
+ true
1238
+ )
1239
+ .bind(commonAdapter)
1240
+ );
1241
+ return vaultSettings;
1242
+ }
1243
+
1244
+ const _riskFactor: RiskFactor[] = [
1245
+ {
1246
+ type: RiskType.SMART_CONTRACT_RISK,
1247
+ value: 0.5,
1248
+ weight: 25,
1249
+ reason: "Audited by Zellic"
1250
+ },
1251
+ {
1252
+ type: RiskType.LIQUIDATION_RISK,
1253
+ value: 1.5,
1254
+ weight: 50,
1255
+ reason: "Liquidation risk is mitigated by stable price feed on Starknet"
1256
+ },
1257
+ {
1258
+ type: RiskType.TECHNICAL_RISK,
1259
+ value: 1,
1260
+ weight: 50,
1261
+ reason: "Technical failures like risk monitoring failures"
1262
+ }
1263
+ ];
1264
+
1265
+ const usdcVaultSettings: UniversalStrategySettings = {
1266
+ vaultAddress: ContractAddr.from(
1267
+ "0x7e6498cf6a1bfc7e6fc89f1831865e2dacb9756def4ec4b031a9138788a3b5e"
1268
+ ),
1269
+ manager: ContractAddr.from(
1270
+ "0xf41a2b1f498a7f9629db0b8519259e66e964260a23d20003f3e42bb1997a07"
1271
+ ),
1272
+ vaultAllocator: ContractAddr.from(
1273
+ "0x228cca1005d3f2b55cbaba27cb291dacf1b9a92d1d6b1638195fbd3d0c1e3ba"
1274
+ ),
1275
+ redeemRequestNFT: ContractAddr.from(
1276
+ "0x906d03590010868cbf7590ad47043959d7af8e782089a605d9b22567b64fda"
1277
+ ),
1278
+ aumOracle: ContractAddr.from(
1279
+ "0x6faf45ed185dec13ef723c9ead4266cab98d06f2cb237e331b1fa5c2aa79afe"
1280
+ ),
1281
+ leafAdapters: [],
1282
+ adapters: [],
1283
+ targetHealthFactor: 1.3,
1284
+ minHealthFactor: 1.25
1285
+ };
1286
+
1287
+ const wbtcVaultSettings: UniversalStrategySettings = {
1288
+ vaultAddress: ContractAddr.from(
1289
+ "0x5a4c1651b913aa2ea7afd9024911603152a19058624c3e425405370d62bf80c"
1290
+ ),
1291
+ manager: ContractAddr.from(
1292
+ "0xef8a664ffcfe46a6af550766d27c28937bf1b77fb4ab54d8553e92bca5ba34"
1293
+ ),
1294
+ vaultAllocator: ContractAddr.from(
1295
+ "0x1e01c25f0d9494570226ad28a7fa856c0640505e809c366a9fab4903320e735"
1296
+ ),
1297
+ redeemRequestNFT: ContractAddr.from(
1298
+ "0x4fec59a12f8424281c1e65a80b5de51b4e754625c60cddfcd00d46941ec37b2"
1299
+ ),
1300
+ aumOracle: ContractAddr.from(
1301
+ "0x2edf4edbed3f839e7f07dcd913e92299898ff4cf0ba532f8c572c66c5b331b2"
1302
+ ),
1303
+ leafAdapters: [],
1304
+ adapters: [],
1305
+ targetHealthFactor: 1.3,
1306
+ minHealthFactor: 1.25
1307
+ };
1308
+
1309
+ const ethVaultSettings: UniversalStrategySettings = {
1310
+ vaultAddress: ContractAddr.from(
1311
+ "0x446c22d4d3f5cb52b4950ba832ba1df99464c6673a37c092b1d9622650dbd8"
1312
+ ),
1313
+ manager: ContractAddr.from(
1314
+ "0x494888b37206616bd09d759dcda61e5118470b9aa7f58fb84f21c778a7b8f97"
1315
+ ),
1316
+ vaultAllocator: ContractAddr.from(
1317
+ "0x4acc0ad6bea58cb578d60ff7c31f06f44369a7a9a7bbfffe4701f143e427bd"
1318
+ ),
1319
+ redeemRequestNFT: ContractAddr.from(
1320
+ "0x2e6cd71e5060a254d4db00655e420db7bf89da7755bb0d5f922e2f00c76ac49"
1321
+ ),
1322
+ aumOracle: ContractAddr.from(
1323
+ "0x4b747f2e75c057bed9aa2ce46fbdc2159dc684c15bd32d4f95983a6ecf39a05"
1324
+ ),
1325
+ leafAdapters: [],
1326
+ adapters: [],
1327
+ targetHealthFactor: 1.3,
1328
+ minHealthFactor: 1.25
1329
+ };
1330
+
1331
+ const strkVaultSettings: UniversalStrategySettings = {
1332
+ vaultAddress: ContractAddr.from(
1333
+ "0x55d012f57e58c96e0a5c7ebbe55853989d01e6538b15a95e7178aca4af05c21"
1334
+ ),
1335
+ manager: ContractAddr.from(
1336
+ "0xcc6a5153ca56293405506eb20826a379d982cd738008ef7e808454d318fb81"
1337
+ ),
1338
+ vaultAllocator: ContractAddr.from(
1339
+ "0xf29d2f82e896c0ed74c9eff220af34ac148e8b99846d1ace9fbb02c9191d01"
1340
+ ),
1341
+ redeemRequestNFT: ContractAddr.from(
1342
+ "0x46902423bd632c428376b84fcee9cac5dbe016214e93a8103bcbde6e1de656b"
1343
+ ),
1344
+ aumOracle: ContractAddr.from(
1345
+ "0x6d7dbfad4bb51715da211468389a623da00c0625f8f6efbea822ee5ac5231f4"
1346
+ ),
1347
+ leafAdapters: [],
1348
+ adapters: [],
1349
+ targetHealthFactor: 1.3,
1350
+ minHealthFactor: 1.25
1351
+ };
1352
+
1353
+ const usdtVaultSettings: UniversalStrategySettings = {
1354
+ vaultAddress: ContractAddr.from(
1355
+ "0x1c4933d1880c6778585e597154eaca7b428579d72f3aae425ad2e4d26c6bb3"
1356
+ ),
1357
+ manager: ContractAddr.from(
1358
+ "0x39bb9843503799b552b7ed84b31c06e4ff10c0537edcddfbf01fe944b864029"
1359
+ ),
1360
+ vaultAllocator: ContractAddr.from(
1361
+ "0x56437d18c43727ac971f6c7086031cad7d9d6ccb340f4f3785a74cc791c931a"
1362
+ ),
1363
+ redeemRequestNFT: ContractAddr.from(
1364
+ "0x5af0c2a657eaa8e23ed78e855dac0c51e4f69e2cf91a18c472041a1f75bb41f"
1365
+ ),
1366
+ aumOracle: ContractAddr.from(
1367
+ "0x7018f8040c8066a4ab929e6760ae52dd43b6a3a289172f514750a61fcc565cc"
1368
+ ),
1369
+ leafAdapters: [],
1370
+ adapters: [],
1371
+ targetHealthFactor: 1.3,
1372
+ minHealthFactor: 1.25
1373
+ };
1374
+
1375
+ type AllowedSources = "vesu" | "endur" | "extended";
1376
+ export default function MetaVaultDescription(allowedSources: AllowedSources[]) {
1377
+ const logos: any = {
1378
+ vesu: Protocols.VESU.logo,
1379
+ endur: Protocols.ENDUR.logo,
1380
+ extended: Protocols.EXTENDED.logo,
1381
+ ekubo: "https://dummyimage.com/64x64/ffffff/000000&text=K"
1382
+ };
1383
+ const _sources = [
1384
+ {
1385
+ key: "vesu",
1386
+ name: "Vesu",
1387
+ status: "Live",
1388
+ description:
1389
+ "Integrated liquidity venue used for automated routing to capture the best available yield."
1390
+ },
1391
+ {
1392
+ key: "endur",
1393
+ name: "Endur",
1394
+ status: "Coming soon",
1395
+ description:
1396
+ "Planned integration to tap into STRK staking–backed yields via our LST pipeline."
1397
+ },
1398
+ {
1399
+ key: "extended",
1400
+ name: "Extended",
1401
+ status: "Coming soon",
1402
+ description:
1403
+ "Expanding coverage to additional money markets and vaults across the ecosystem."
1404
+ }
1405
+ // { key: "ekubo", name: "Ekubo", status: "Coming soon", description: "Concentrated liquidity strategies targeted for optimized fee APR harvesting." },
1406
+ ];
1407
+ const sources = _sources.filter((s) =>
1408
+ allowedSources.includes(s.key as any)
1409
+ );
1410
+
1411
+ const containerStyle = {
1412
+ maxWidth: "800px",
1413
+ margin: "0 auto",
1414
+ backgroundColor: "#111",
1415
+ color: "#eee",
1416
+ fontFamily: "Arial, sans-serif",
1417
+ borderRadius: "12px"
1418
+ };
1419
+
1420
+ const cardStyle = {
1421
+ border: "1px solid #333",
1422
+ borderRadius: "10px",
1423
+ padding: "12px",
1424
+ marginBottom: "12px",
1425
+ backgroundColor: "#1a1a1a"
1426
+ };
1427
+
1428
+ const logoStyle = {
1429
+ width: "24px",
1430
+ height: "24px",
1431
+ borderRadius: "8px",
1432
+ border: "1px solid #444",
1433
+ backgroundColor: "#222"
1434
+ };
1435
+
1436
+ return (
1437
+ <div style={containerStyle}>
1438
+ <h1 style={{ fontSize: "18px", marginBottom: "10px" }}>
1439
+ Meta Vault — Automated Yield Router
1440
+ </h1>
1441
+ <p
1442
+ style={{
1443
+ fontSize: "14px",
1444
+ lineHeight: "1.5",
1445
+ marginBottom: "16px"
1446
+ }}
1447
+ >
1448
+ This Evergreen vault is a tokenized Meta Vault, auto-compounding
1449
+ strategy that continuously allocates your deposited asset to the
1450
+ best available yield source in the ecosystem. Depositors receive
1451
+ vault shares that represent a proportional claim on the
1452
+ underlying assets and accrued yield. Allocation shifts are
1453
+ handled programmatically based on on-chain signals and risk
1454
+ filters, minimizing idle capital and maximizing net APY.
1455
+ </p>
1456
+
1457
+ <div
1458
+ style={{
1459
+ backgroundColor: "#222",
1460
+ padding: "10px",
1461
+ borderRadius: "8px",
1462
+ marginBottom: "20px",
1463
+ border: "1px solid #444"
1464
+ }}
1465
+ >
1466
+ <p style={{ fontSize: "13px", color: "#ccc" }}>
1467
+ <strong>Withdrawals:</strong> Requests can take up to{" "}
1468
+ <strong>1-2 hours</strong> to process as the vault unwinds
1469
+ and settles routing.
1470
+ </p>
1471
+ </div>
1472
+
1473
+ <h2 style={{ fontSize: "18px", marginBottom: "10px" }}>
1474
+ Supported Yield Sources
1475
+ </h2>
1476
+ {sources.map((s) => (
1477
+ <div key={s.key} style={cardStyle}>
1478
+ <div
1479
+ style={{
1480
+ display: "flex",
1481
+ gap: "10px",
1482
+ alignItems: "flex-start"
1483
+ }}
1484
+ >
1485
+ <img
1486
+ src={logos[s.key]}
1487
+ alt={`${s.name} logo`}
1488
+ style={logoStyle}
1489
+ />
1490
+ <div style={{ flex: 1 }}>
1491
+ <div
1492
+ style={{
1493
+ display: "flex",
1494
+ justifyContent: "space-between",
1495
+ alignItems: "center"
1496
+ }}
1497
+ >
1498
+ <h3 style={{ fontSize: "15px", margin: 0 }}>
1499
+ {s.name}
1500
+ </h3>
1501
+ <StatusBadge status={s.status} />
1502
+ </div>
1503
+ {/* <p style={{ fontSize: "13px", color: "#ccc", marginTop: "4px" }}>{s.description}</p> */}
1504
+ </div>
1505
+ </div>
1506
+ </div>
1507
+ ))}
1508
+ </div>
1509
+ );
1510
+ }
1511
+
1512
+ function StatusBadge({ status }: { status: string }) {
1513
+ const isSoon = String(status).toLowerCase() !== "live";
1514
+ const badgeStyle = {
1515
+ fontSize: "12px",
1516
+ padding: "2px 6px",
1517
+ borderRadius: "12px",
1518
+ border: "1px solid",
1519
+ color: isSoon ? "rgb(196 196 195)" : "#6aff7d",
1520
+ borderColor: isSoon ? "#484848" : "#6aff7d",
1521
+ backgroundColor: isSoon ? "#424242" : "#002b1a"
1522
+ };
1523
+ return <span style={badgeStyle}>{status}</span>;
1524
+ }
1525
+
1526
+ function getDescription(tokenSymbol: string, allowedSources: AllowedSources[]) {
1527
+ return MetaVaultDescription(allowedSources);
1528
+ }
1529
+
1530
+ function getFAQs(): FAQ[] {
1531
+ return [
1532
+ {
1533
+ question: "What is the Meta Vault?",
1534
+ answer: "The Meta Vault is a tokenized strategy that automatically allocates your deposited assets to the best available yield source in the ecosystem. It optimizes returns while minimizing idle capital."
1535
+ },
1536
+ {
1537
+ question: "How does yield allocation work?",
1538
+ answer: "The vault continuously monitors supported protocols and routes liquidity to the source offering the highest net yield after accounting for fees, slippage, and gas costs. Reallocations are performed automatically."
1539
+ },
1540
+ {
1541
+ question: "Which yield sources are supported?",
1542
+ answer: (
1543
+ <span>
1544
+ Currently, <strong>Vesu</strong> is live. Future
1545
+ integrations may include <strong>Endur</strong>,{" "}
1546
+ <strong>Extended</strong>, and <strong>Ekubo</strong> (all
1547
+ coming soon).
1548
+ </span>
1549
+ )
1550
+ },
1551
+ {
1552
+ question: "What do I receive when I deposit?",
1553
+ answer: "Depositors receive vault tokens representing their proportional share of the vault. These tokens entitle holders to both the principal and accrued yield."
1554
+ },
1555
+ {
1556
+ question: "How long do withdrawals take?",
1557
+ answer: "Withdrawals may take up to 1-2 hours to process, as the vault unwinds and settles liquidity routing across integrated protocols."
1558
+ },
1559
+ {
1560
+ question: "Is the Meta Vault non-custodial?",
1561
+ answer: "Yes. The Meta Vault operates entirely on-chain. Users always maintain control of their vault tokens, and the strategy is fully transparent."
1562
+ },
1563
+ {
1564
+ question: "How is risk managed?",
1565
+ answer: "Integrations are supported with active risk monitoring like liquidation risk. Only vetted protocols are included to balance yield with safety. However, usual Defi risks like smart contract risk, extreme volatile market risk, etc. are still present."
1566
+ },
1567
+ {
1568
+ question: "Are there any fees?",
1569
+ answer: "Troves charges a performance of 10% on the yield generated. The APY shown is net of this fee. This fee is only applied to the profits earned, ensuring that users retain their initial capital."
1570
+ }
1571
+ ];
758
1572
  }
759
1573
 
760
- // export enum UNIVERSAL_ADAPTERS {
761
- // COMMON = 'common_adapter',
762
- // VESU_LEG1 = 'vesu_leg1_adapter',
763
- // VESU_LEG2 = 'vesu_leg2_adapter'
764
- // }
765
-
766
- // function getLooperSettings(
767
- // token1Symbol: string,
768
- // token2Symbol: string,
769
- // vaultSettings: UniversalStrategySettings,
770
- // pool1: ContractAddr,
771
- // pool2: ContractAddr,
772
- // ) {
773
- // const USDCToken = Global.getDefaultTokens().find(token => token.symbol === token1Symbol)!;
774
- // const ETHToken = Global.getDefaultTokens().find(token => token.symbol === token2Symbol)!;
775
-
776
- // const commonAdapter = new CommonAdapter({
777
- // manager: vaultSettings.manager,
778
- // asset: USDCToken.address,
779
- // id: UNIVERSAL_MANAGE_IDS.FLASH_LOAN,
780
- // vaultAddress: vaultSettings.vaultAddress,
781
- // vaultAllocator: vaultSettings.vaultAllocator,
782
- // })
783
- // const vesuAdapterUSDCETH = new VesuAdapter({
784
- // poolId: pool1,
785
- // collateral: USDCToken,
786
- // debt: ETHToken,
787
- // vaultAllocator: vaultSettings.vaultAllocator,
788
- // id: UNIVERSAL_MANAGE_IDS.VESU_LEG1
789
- // })
790
- // const vesuAdapterETHUSDC = new VesuAdapter({
791
- // poolId: pool2,
792
- // collateral: ETHToken,
793
- // debt: USDCToken,
794
- // vaultAllocator: vaultSettings.vaultAllocator,
795
- // id: UNIVERSAL_MANAGE_IDS.VESU_LEG2
796
- // })
797
- // vaultSettings.adapters.push(...[{
798
- // id: UNIVERSAL_ADAPTERS.COMMON,
799
- // adapter: commonAdapter
800
- // }, {
801
- // id: UNIVERSAL_ADAPTERS.VESU_LEG1,
802
- // adapter: vesuAdapterUSDCETH
803
- // }, {
804
- // id: UNIVERSAL_ADAPTERS.VESU_LEG2,
805
- // adapter: vesuAdapterETHUSDC
806
- // }])
807
-
808
- // // vesu looping
809
- // vaultSettings.leafAdapters.push(commonAdapter.getFlashloanAdapter.bind(commonAdapter));
810
- // vaultSettings.leafAdapters.push(vesuAdapterUSDCETH.getModifyPosition.bind(vesuAdapterUSDCETH));
811
- // vaultSettings.leafAdapters.push(vesuAdapterETHUSDC.getModifyPosition.bind(vesuAdapterETHUSDC));
812
- // vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(USDCToken.address, VESU_SINGLETON, UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN1).bind(commonAdapter));
813
- // vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(ETHToken.address, VESU_SINGLETON, UNIVERSAL_MANAGE_IDS.APPROVE_TOKEN2).bind(commonAdapter));
814
-
815
- // // to bridge liquidity back to vault (used by bring_liquidity)
816
- // vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(USDCToken.address, vaultSettings.vaultAddress, UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY).bind(commonAdapter));
817
- // vaultSettings.leafAdapters.push(commonAdapter.getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY).bind(commonAdapter));
818
-
819
- // // claim rewards
820
- // vaultSettings.leafAdapters.push(vesuAdapterUSDCETH.getDefispringRewardsAdapter(UNIVERSAL_MANAGE_IDS.DEFISPRING_REWARDS).bind(vesuAdapterUSDCETH));
821
-
822
- // // avnu swap
823
- // const STRKToken = Global.getDefaultTokens().find(token => token.symbol === 'STRK')!;
824
- // vaultSettings.leafAdapters.push(commonAdapter.getApproveAdapter(STRKToken.address, AVNU_MIDDLEWARE, UNIVERSAL_MANAGE_IDS.APPROVE_SWAP_TOKEN1).bind(commonAdapter));
825
- // vaultSettings.leafAdapters.push(commonAdapter.getAvnuAdapter(STRKToken.address, USDCToken.address, UNIVERSAL_MANAGE_IDS.AVNU_SWAP_REWARDS, true).bind(commonAdapter));
826
- // return vaultSettings;
827
- // }
828
-
829
- // const _riskFactor: RiskFactor[] = [
830
- // { type: RiskType.SMART_CONTRACT_RISK, value: 0.5, weight: 25, reason: "Audited by Zellic" },
831
- // { type: RiskType.LIQUIDATION_RISK, value: 1.5, weight: 50, reason: "Liquidation risk is mitigated by stable price feed on Starknet" },
832
- // { type: RiskType.TECHNICAL_RISK, value: 1, weight: 50, reason: "Technical failures like risk monitoring failures" }
833
- // ];
834
-
835
- // const usdcVaultSettings: UniversalStrategySettings = {
836
- // vaultAddress: ContractAddr.from('0x7e6498cf6a1bfc7e6fc89f1831865e2dacb9756def4ec4b031a9138788a3b5e'),
837
- // manager: ContractAddr.from('0xf41a2b1f498a7f9629db0b8519259e66e964260a23d20003f3e42bb1997a07'),
838
- // vaultAllocator: ContractAddr.from('0x228cca1005d3f2b55cbaba27cb291dacf1b9a92d1d6b1638195fbd3d0c1e3ba'),
839
- // redeemRequestNFT: ContractAddr.from('0x906d03590010868cbf7590ad47043959d7af8e782089a605d9b22567b64fda'),
840
- // aumOracle: ContractAddr.from("0x6faf45ed185dec13ef723c9ead4266cab98d06f2cb237e331b1fa5c2aa79afe"),
841
- // leafAdapters: [],
842
- // adapters: [],
843
- // targetHealthFactor: 1.3,
844
- // minHealthFactor: 1.25
845
- // }
846
-
847
- // const wbtcVaultSettings: UniversalStrategySettings = {
848
- // vaultAddress: ContractAddr.from('0x5a4c1651b913aa2ea7afd9024911603152a19058624c3e425405370d62bf80c'),
849
- // manager: ContractAddr.from('0xef8a664ffcfe46a6af550766d27c28937bf1b77fb4ab54d8553e92bca5ba34'),
850
- // vaultAllocator: ContractAddr.from('0x1e01c25f0d9494570226ad28a7fa856c0640505e809c366a9fab4903320e735'),
851
- // redeemRequestNFT: ContractAddr.from('0x4fec59a12f8424281c1e65a80b5de51b4e754625c60cddfcd00d46941ec37b2'),
852
- // aumOracle: ContractAddr.from("0x2edf4edbed3f839e7f07dcd913e92299898ff4cf0ba532f8c572c66c5b331b2"),
853
- // leafAdapters: [],
854
- // adapters: [],
855
- // targetHealthFactor: 1.3,
856
- // minHealthFactor: 1.25
857
- // }
858
-
859
- // const ethVaultSettings: UniversalStrategySettings = {
860
- // vaultAddress: ContractAddr.from('0x446c22d4d3f5cb52b4950ba832ba1df99464c6673a37c092b1d9622650dbd8'),
861
- // manager: ContractAddr.from('0x494888b37206616bd09d759dcda61e5118470b9aa7f58fb84f21c778a7b8f97'),
862
- // vaultAllocator: ContractAddr.from('0x4acc0ad6bea58cb578d60ff7c31f06f44369a7a9a7bbfffe4701f143e427bd'),
863
- // redeemRequestNFT: ContractAddr.from('0x2e6cd71e5060a254d4db00655e420db7bf89da7755bb0d5f922e2f00c76ac49'),
864
- // aumOracle: ContractAddr.from("0x4b747f2e75c057bed9aa2ce46fbdc2159dc684c15bd32d4f95983a6ecf39a05"),
865
- // leafAdapters: [],
866
- // adapters: [],
867
- // targetHealthFactor: 1.3,
868
- // minHealthFactor: 1.25
869
- // }
870
-
871
- // const strkVaultSettings: UniversalStrategySettings = {
872
- // vaultAddress: ContractAddr.from('0x55d012f57e58c96e0a5c7ebbe55853989d01e6538b15a95e7178aca4af05c21'),
873
- // manager: ContractAddr.from('0xcc6a5153ca56293405506eb20826a379d982cd738008ef7e808454d318fb81'),
874
- // vaultAllocator: ContractAddr.from('0xf29d2f82e896c0ed74c9eff220af34ac148e8b99846d1ace9fbb02c9191d01'),
875
- // redeemRequestNFT: ContractAddr.from('0x46902423bd632c428376b84fcee9cac5dbe016214e93a8103bcbde6e1de656b'),
876
- // aumOracle: ContractAddr.from("0x6d7dbfad4bb51715da211468389a623da00c0625f8f6efbea822ee5ac5231f4"),
877
- // leafAdapters: [],
878
- // adapters: [],
879
- // targetHealthFactor: 1.3,
880
- // minHealthFactor: 1.25
881
- // }
882
-
883
- // const usdtVaultSettings: UniversalStrategySettings = {
884
- // vaultAddress: ContractAddr.from('0x1c4933d1880c6778585e597154eaca7b428579d72f3aae425ad2e4d26c6bb3'),
885
- // manager: ContractAddr.from('0x39bb9843503799b552b7ed84b31c06e4ff10c0537edcddfbf01fe944b864029'),
886
- // vaultAllocator: ContractAddr.from('0x56437d18c43727ac971f6c7086031cad7d9d6ccb340f4f3785a74cc791c931a'),
887
- // redeemRequestNFT: ContractAddr.from('0x5af0c2a657eaa8e23ed78e855dac0c51e4f69e2cf91a18c472041a1f75bb41f'),
888
- // aumOracle: ContractAddr.from("0x7018f8040c8066a4ab929e6760ae52dd43b6a3a289172f514750a61fcc565cc"),
889
- // leafAdapters: [],
890
- // adapters: [],
891
- // targetHealthFactor: 1.3,
892
- // minHealthFactor: 1.25
893
- // }
894
-
895
- // type AllowedSources = 'vesu' | 'endur' | 'extended';
896
- // export default function MetaVaultDescription(allowedSources: AllowedSources[]) {
897
- // const logos: any = {
898
- // vesu: Protocols.VESU.logo,
899
- // endur: Protocols.ENDUR.logo,
900
- // extended: Protocols.EXTENDED.logo,
901
- // ekubo: "https://dummyimage.com/64x64/ffffff/000000&text=K",
902
- // };
903
- // const _sources = [
904
- // { key: "vesu", name: "Vesu", status: "Live", description: "Integrated liquidity venue used for automated routing to capture the best available yield." },
905
- // { key: "endur", name: "Endur", status: "Coming soon", description: "Planned integration to tap into STRK staking–backed yields via our LST pipeline." },
906
- // { key: "extended", name: "Extended", status: "Coming soon", description: "Expanding coverage to additional money markets and vaults across the ecosystem." },
907
- // // { key: "ekubo", name: "Ekubo", status: "Coming soon", description: "Concentrated liquidity strategies targeted for optimized fee APR harvesting." },
908
- // ];
909
- // const sources = _sources.filter(s => allowedSources.includes(s.key as any));
910
-
911
- // const containerStyle = {
912
- // maxWidth: "800px",
913
- // margin: "0 auto",
914
- // backgroundColor: "#111",
915
- // color: "#eee",
916
- // fontFamily: "Arial, sans-serif",
917
- // borderRadius: "12px",
918
- // };
919
-
920
- // const cardStyle = {
921
- // border: "1px solid #333",
922
- // borderRadius: "10px",
923
- // padding: "12px",
924
- // marginBottom: "12px",
925
- // backgroundColor: "#1a1a1a",
926
- // };
927
-
928
- // const logoStyle = {
929
- // width: "24px",
930
- // height: "24px",
931
- // borderRadius: "8px",
932
- // border: "1px solid #444",
933
- // backgroundColor: "#222",
934
- // };
935
-
936
- // return (
937
- // <div style={containerStyle}>
938
- // <h1 style={{ fontSize: "18px", marginBottom: "10px" }}>Meta Vault — Automated Yield Router</h1>
939
- // <p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
940
- // This Evergreen vault is a tokenized Meta Vault, auto-compounding strategy that continuously allocates your deposited
941
- // asset to the best available yield source in the ecosystem. Depositors receive vault shares that
942
- // represent a proportional claim on the underlying assets and accrued yield. Allocation shifts are
943
- // handled programmatically based on on-chain signals and risk filters, minimizing idle capital and
944
- // maximizing net APY.
945
- // </p>
946
-
947
- // <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
948
- // <p style={{ fontSize: "13px", color: "#ccc" }}>
949
- // <strong>Withdrawals:</strong> Requests can take up to <strong>1-2 hours</strong> to process as the vault unwinds and settles routing.
950
- // </p>
951
- // </div>
952
-
953
- // <h2 style={{ fontSize: "18px", marginBottom: "10px" }}>Supported Yield Sources</h2>
954
- // {sources.map((s) => (
955
- // <div key={s.key} style={cardStyle}>
956
- // <div style={{ display: "flex", gap: "10px", alignItems: "flex-start" }}>
957
- // <img src={logos[s.key]} alt={`${s.name} logo`} style={logoStyle} />
958
- // <div style={{ flex: 1 }}>
959
- // <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
960
- // <h3 style={{ fontSize: "15px", margin: 0 }}>{s.name}</h3>
961
- // <StatusBadge status={s.status} />
962
- // </div>
963
- // {/* <p style={{ fontSize: "13px", color: "#ccc", marginTop: "4px" }}>{s.description}</p> */}
964
- // </div>
965
- // </div>
966
- // </div>
967
- // ))}
968
- // </div>
969
- // );
970
- // }
971
-
972
- // function StatusBadge({ status }: { status: string }) {
973
- // const isSoon = String(status).toLowerCase() !== "live";
974
- // const badgeStyle = {
975
- // fontSize: "12px",
976
- // padding: "2px 6px",
977
- // borderRadius: "12px",
978
- // border: "1px solid",
979
- // color: isSoon ? "rgb(196 196 195)" : "#6aff7d",
980
- // borderColor: isSoon ? "#484848" : "#6aff7d",
981
- // backgroundColor: isSoon ? "#424242" : "#002b1a",
982
- // };
983
- // return <span style={badgeStyle}>{status}</span>;
984
- // }
985
-
986
- // function getDescription(tokenSymbol: string, allowedSources: AllowedSources[]) {
987
- // return MetaVaultDescription(allowedSources);
988
- // }
989
-
990
- // function getFAQs(): FAQ[] {
991
- // return [
992
- // {
993
- // question: "What is the Meta Vault?",
994
- // answer:
995
- // "The Meta Vault is a tokenized strategy that automatically allocates your deposited assets to the best available yield source in the ecosystem. It optimizes returns while minimizing idle capital.",
996
- // },
997
- // {
998
- // question: "How does yield allocation work?",
999
- // answer:
1000
- // "The vault continuously monitors supported protocols and routes liquidity to the source offering the highest net yield after accounting for fees, slippage, and gas costs. Reallocations are performed automatically.",
1001
- // },
1002
- // {
1003
- // question: "Which yield sources are supported?",
1004
- // answer: (
1005
- // <span>
1006
- // Currently, <strong>Vesu</strong> is live. Future integrations may include{" "}
1007
- // <strong>Endur</strong>, <strong>Extended</strong>, and{" "}
1008
- // <strong>Ekubo</strong> (all coming soon).
1009
- // </span>
1010
- // ),
1011
- // },
1012
- // {
1013
- // question: "What do I receive when I deposit?",
1014
- // answer:
1015
- // "Depositors receive vault tokens representing their proportional share of the vault. These tokens entitle holders to both the principal and accrued yield.",
1016
- // },
1017
- // {
1018
- // question: "How long do withdrawals take?",
1019
- // answer:
1020
- // "Withdrawals may take up to 1-2 hours to process, as the vault unwinds and settles liquidity routing across integrated protocols.",
1021
- // },
1022
- // {
1023
- // question: "Is the Meta Vault non-custodial?",
1024
- // answer:
1025
- // "Yes. The Meta Vault operates entirely on-chain. Users always maintain control of their vault tokens, and the strategy is fully transparent.",
1026
- // },
1027
- // {
1028
- // question: "How is risk managed?",
1029
- // answer:
1030
- // "Integrations are supported with active risk monitoring like liquidation risk. Only vetted protocols are included to balance yield with safety. However, usual Defi risks like smart contract risk, extreme volatile market risk, etc. are still present.",
1031
- // },
1032
- // {
1033
- // question: "Are there any fees?",
1034
- // answer:
1035
- // "Troves charges a performance of 10% on the yield generated. The APY shown is net of this fee. This fee is only applied to the profits earned, ensuring that users retain their initial capital.",
1036
- // },
1037
- // ];
1038
- // }
1039
-
1040
- export function getContractDetails(settings: UniversalStrategySettings & { aumOracle?: ContractAddr }): {address: ContractAddr, name: string}[] {
1041
- const contracts = [
1042
- { address: settings.vaultAddress, name: "Vault" },
1043
- { address: settings.manager, name: "Vault Manager" },
1044
- { address: settings.vaultAllocator, name: "Vault Allocator" },
1045
- { address: settings.redeemRequestNFT, name: "Redeem Request NFT" },
1046
- ];
1047
- if (settings.aumOracle) {
1048
- contracts.push({ address: settings.aumOracle, name: "AUM Oracle" });
1049
- }
1050
- return contracts;
1574
+ export function getContractDetails(
1575
+ settings: UniversalStrategySettings
1576
+ ): { address: ContractAddr; name: string }[] {
1577
+ return [
1578
+ { address: settings.vaultAddress, name: "Vault" },
1579
+ { address: settings.manager, name: "Vault Manager" },
1580
+ { address: settings.vaultAllocator, name: "Vault Allocator" },
1581
+ { address: settings.redeemRequestNFT, name: "Redeem Request NFT" }
1582
+ // { address: settings.aumOracle, name: "AUM Oracle" },
1583
+ ];
1051
1584
  }
1052
1585
 
1053
- // const investmentSteps: string[] = [
1054
- // "Deposit funds into the vault",
1055
- // "Vault manager allocates funds to optimal yield sources",
1056
- // "Vault manager reports asset under management (AUM) regularly to the vault",
1057
- // "Request withdrawal and vault manager processes it in 1-2 hours"
1058
- // ]
1059
-
1060
- // const AUDIT_URL = 'https://docs.troves.fi/p/security#starknet-vault-kit'
1061
- // export const UniversalStrategies: IStrategyMetadata<UniversalStrategySettings>[] =
1062
- // [
1063
- // {
1064
- // name: "USDC Evergreen",
1065
- // description: getDescription('USDC', ['vesu', 'extended']),
1066
- // address: ContractAddr.from('0x7e6498cf6a1bfc7e6fc89f1831865e2dacb9756def4ec4b031a9138788a3b5e'),
1067
- // launchBlock: 0,
1068
- // type: 'ERC4626',
1069
- // depositTokens: [Global.getDefaultTokens().find(token => token.symbol === 'USDC')!],
1070
- // additionalInfo: getLooperSettings('USDC', 'ETH', usdcVaultSettings, VesuPools.Genesis, VesuPools.Genesis),
1071
- // risk: {
1072
- // riskFactor: _riskFactor,
1073
- // netRisk:
1074
- // _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
1075
- // _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
1076
- // notARisks: getNoRiskTags(_riskFactor)
1077
- // },
1078
- // auditUrl: AUDIT_URL,
1079
- // protocols: [Protocols.VESU],
1080
- // maxTVL: Web3Number.fromWei(0, 6),
1081
- // contractDetails: getContractDetails(usdcVaultSettings),
1082
- // faqs: getFAQs(),
1083
- // investmentSteps: investmentSteps,
1084
- // },
1085
- // {
1086
- // name: "WBTC Evergreen",
1087
- // description: getDescription('WBTC', ['vesu', 'endur', 'extended']),
1088
- // address: ContractAddr.from('0x5a4c1651b913aa2ea7afd9024911603152a19058624c3e425405370d62bf80c'),
1089
- // launchBlock: 0,
1090
- // type: 'ERC4626',
1091
- // depositTokens: [Global.getDefaultTokens().find(token => token.symbol === 'WBTC')!],
1092
- // additionalInfo: getLooperSettings('WBTC', 'ETH', wbtcVaultSettings, VesuPools.Genesis, VesuPools.Genesis),
1093
- // risk: {
1094
- // riskFactor: _riskFactor,
1095
- // netRisk:
1096
- // _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
1097
- // _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
1098
- // notARisks: getNoRiskTags(_riskFactor)
1099
- // },
1100
- // protocols: [Protocols.VESU],
1101
- // maxTVL: Web3Number.fromWei(0, 8),
1102
- // contractDetails: getContractDetails(wbtcVaultSettings),
1103
- // faqs: getFAQs(),
1104
- // investmentSteps: investmentSteps,
1105
- // auditUrl: AUDIT_URL,
1106
- // },
1107
- // {
1108
- // name: "ETH Evergreen",
1109
- // description: getDescription('ETH', ['vesu', 'extended']),
1110
- // address: ContractAddr.from('0x446c22d4d3f5cb52b4950ba832ba1df99464c6673a37c092b1d9622650dbd8'),
1111
- // launchBlock: 0,
1112
- // type: 'ERC4626',
1113
- // depositTokens: [Global.getDefaultTokens().find(token => token.symbol === 'ETH')!],
1114
- // additionalInfo: getLooperSettings('ETH', 'WBTC', ethVaultSettings, VesuPools.Genesis, VesuPools.Genesis),
1115
- // risk: {
1116
- // riskFactor: _riskFactor,
1117
- // netRisk:
1118
- // _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
1119
- // _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
1120
- // notARisks: getNoRiskTags(_riskFactor)
1121
- // },
1122
- // protocols: [Protocols.VESU],
1123
- // maxTVL: Web3Number.fromWei(0, 18),
1124
- // contractDetails: getContractDetails(ethVaultSettings),
1125
- // faqs: getFAQs(),
1126
- // investmentSteps: investmentSteps,
1127
- // auditUrl: AUDIT_URL,
1128
- // },
1129
- // {
1130
- // name: "STRK Evergreen",
1131
- // description: getDescription('STRK', ['vesu', 'endur', 'extended']),
1132
- // address: ContractAddr.from('0x55d012f57e58c96e0a5c7ebbe55853989d01e6538b15a95e7178aca4af05c21'),
1133
- // launchBlock: 0,
1134
- // type: 'ERC4626',
1135
- // depositTokens: [Global.getDefaultTokens().find(token => token.symbol === 'STRK')!],
1136
- // additionalInfo: getLooperSettings('STRK', 'ETH', strkVaultSettings, VesuPools.Genesis, VesuPools.Genesis),
1137
- // risk: {
1138
- // riskFactor: _riskFactor,
1139
- // netRisk:
1140
- // _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
1141
- // _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
1142
- // notARisks: getNoRiskTags(_riskFactor)
1143
- // },
1144
- // protocols: [Protocols.VESU],
1145
- // maxTVL: Web3Number.fromWei(0, 18),
1146
- // contractDetails: getContractDetails(strkVaultSettings),
1147
- // faqs: getFAQs(),
1148
- // investmentSteps: investmentSteps,
1149
- // auditUrl: AUDIT_URL,
1150
- // },
1151
- // {
1152
- // name: "USDT Evergreen",
1153
- // description: getDescription('USDT', ['vesu']),
1154
- // address: ContractAddr.from('0x1c4933d1880c6778585e597154eaca7b428579d72f3aae425ad2e4d26c6bb3'),
1155
- // launchBlock: 0,
1156
- // type: 'ERC4626',
1157
- // depositTokens: [Global.getDefaultTokens().find(token => token.symbol === 'USDT')!],
1158
- // additionalInfo: getLooperSettings('USDT', 'ETH', usdtVaultSettings, VesuPools.Genesis, VesuPools.Genesis),
1159
- // risk: {
1160
- // riskFactor: _riskFactor,
1161
- // netRisk:
1162
- // _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
1163
- // _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
1164
- // notARisks: getNoRiskTags(_riskFactor)
1165
- // },
1166
- // protocols: [Protocols.VESU],
1167
- // maxTVL: Web3Number.fromWei(0, 6),
1168
- // contractDetails: getContractDetails(usdtVaultSettings),
1169
- // faqs: getFAQs(),
1170
- // investmentSteps: investmentSteps,
1171
- // auditUrl: AUDIT_URL,
1172
- // }
1173
- // ]
1586
+ const investmentSteps: string[] = [
1587
+ "Deposit funds into the vault",
1588
+ "Vault manager allocates funds to optimal yield sources",
1589
+ "Vault manager reports asset under management (AUM) regularly to the vault",
1590
+ "Request withdrawal and vault manager processes it in 1-2 hours"
1591
+ ];
1592
+
1593
+ const AUDIT_URL = "https://docs.troves.fi/p/security#starknet-vault-kit";
1594
+
1595
+ // Helper to create common risk object
1596
+ const getUniversalRisk = () => ({
1597
+ riskFactor: _riskFactor,
1598
+ netRisk:
1599
+ _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
1600
+ _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
1601
+ notARisks: getNoRiskTags(_riskFactor)
1602
+ });
1603
+
1604
+ // Helper to create Universal strategy settings
1605
+ const createUniversalSettings = (
1606
+ tokenSymbol: string,
1607
+ maxTVLDecimals: number
1608
+ ): StrategySettings => ({
1609
+ maxTVL: Web3Number.fromWei(0, maxTVLDecimals),
1610
+ isAudited: true,
1611
+ liveStatus: StrategyLiveStatus.ACTIVE,
1612
+ isPaused: false,
1613
+ isInstantWithdrawal: false,
1614
+ hideHarvestInfo: true,
1615
+ quoteToken: Global.getDefaultTokens().find(
1616
+ (token) => token.symbol === tokenSymbol
1617
+ )!,
1618
+ alerts: [
1619
+ {
1620
+ tab: "withdraw" as const,
1621
+ text: "On withdrawal, you will receive an NFT representing your withdrawal request. The funds will be automatically sent to your wallet (NFT owner) in 1-2 hours. You can monitor the status in transactions tab.",
1622
+ type: "info" as const
1623
+ }
1624
+ ],
1625
+ showWithdrawalWarningModal: true
1626
+ });
1627
+
1628
+ const EVERGREEN_SECURITY = {
1629
+ auditStatus: AuditStatus.AUDITED,
1630
+ sourceCode: {
1631
+ type: SourceCodeType.CLOSED_SOURCE,
1632
+ contractLink: "https://github.com/trovesfi/troves-contracts"
1633
+ },
1634
+ accessControl: {
1635
+ type: AccessControlType.STANDARD_ACCOUNT,
1636
+ addresses: [ContractAddr.from("0x0")],
1637
+ timeLock: "2 Days"
1638
+ }
1639
+ };
1640
+
1641
+ const EVERGREEN_REDEMPTION_INFO = {
1642
+ instantWithdrawalVault: InstantWithdrawalVault.NO,
1643
+ expectedRedemptionTime: {
1644
+ upto1M: "1-2hrs",
1645
+ upto10M: "24hrs",
1646
+ above10M: "2-3 Days"
1647
+ }
1648
+ };
1649
+
1650
+ // Helper to create a Universal strategy
1651
+ const createUniversalStrategy = (
1652
+ tokenSymbol: string,
1653
+ address: string,
1654
+ vaultSettings: UniversalStrategySettings,
1655
+ token1Symbol: string,
1656
+ token2Symbol: string,
1657
+ maxTVLDecimals: number,
1658
+ allowedSources: AllowedSources[]
1659
+ ): IStrategyMetadata<UniversalStrategySettings> => ({
1660
+ id: `evergreen_${tokenSymbol.toLowerCase()}`,
1661
+ name: `${tokenSymbol} Evergreen`,
1662
+ description: getDescription(tokenSymbol, allowedSources),
1663
+ address: ContractAddr.from(address),
1664
+ launchBlock: 0,
1665
+ type: "ERC4626" as const,
1666
+ depositTokens: [
1667
+ Global.getDefaultTokens().find((token) => token.symbol === tokenSymbol)!
1668
+ ],
1669
+ additionalInfo: getLooperSettings(
1670
+ token1Symbol,
1671
+ token2Symbol,
1672
+ vaultSettings,
1673
+ VesuPools.Genesis,
1674
+ VesuPools.Genesis
1675
+ ),
1676
+ risk: getUniversalRisk(),
1677
+ auditUrl: AUDIT_URL,
1678
+ protocols: [Protocols.VESU],
1679
+ settings: createUniversalSettings(tokenSymbol, maxTVLDecimals),
1680
+ contractDetails: getContractDetails(vaultSettings),
1681
+ faqs: getFAQs(),
1682
+ investmentSteps: investmentSteps,
1683
+ category: StrategyCategory.META_VAULTS,
1684
+ tags: [StrategyTag.EVERGREEN],
1685
+ security: EVERGREEN_SECURITY,
1686
+ redemptionInfo: EVERGREEN_REDEMPTION_INFO
1687
+ });
1688
+
1689
+ export const UniversalStrategies: IStrategyMetadata<UniversalStrategySettings>[] =
1690
+ [
1691
+ createUniversalStrategy(
1692
+ "USDC",
1693
+ "0x7e6498cf6a1bfc7e6fc89f1831865e2dacb9756def4ec4b031a9138788a3b5e",
1694
+ usdcVaultSettings,
1695
+ "USDC",
1696
+ "ETH",
1697
+ 6,
1698
+ ["vesu", "extended"]
1699
+ ),
1700
+ createUniversalStrategy(
1701
+ "WBTC",
1702
+ "0x5a4c1651b913aa2ea7afd9024911603152a19058624c3e425405370d62bf80c",
1703
+ wbtcVaultSettings,
1704
+ "WBTC",
1705
+ "ETH",
1706
+ 8,
1707
+ ["vesu", "endur", "extended"]
1708
+ ),
1709
+ createUniversalStrategy(
1710
+ "ETH",
1711
+ "0x446c22d4d3f5cb52b4950ba832ba1df99464c6673a37c092b1d9622650dbd8",
1712
+ ethVaultSettings,
1713
+ "ETH",
1714
+ "WBTC",
1715
+ 18,
1716
+ ["vesu", "extended"]
1717
+ ),
1718
+ createUniversalStrategy(
1719
+ "STRK",
1720
+ "0x55d012f57e58c96e0a5c7ebbe55853989d01e6538b15a95e7178aca4af05c21",
1721
+ strkVaultSettings,
1722
+ "STRK",
1723
+ "ETH",
1724
+ 18,
1725
+ ["vesu", "endur", "extended"]
1726
+ ),
1727
+ createUniversalStrategy(
1728
+ "USDT",
1729
+ "0x1c4933d1880c6778585e597154eaca7b428579d72f3aae425ad2e4d26c6bb3",
1730
+ usdtVaultSettings,
1731
+ "USDT",
1732
+ "ETH",
1733
+ 6,
1734
+ ["vesu"]
1735
+ )
1736
+ ];