@strkfarm/sdk 1.0.37 → 1.0.39

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.
@@ -1,258 +1,402 @@
1
1
  import { ContractAddr, Web3Number } from "@/dataTypes";
2
- import { FlowChartColors, getNoRiskTags, IConfig, IInvestmentFlow, IProtocol, IStrategyMetadata, RiskFactor, RiskType } from "@/interfaces";
2
+ import {
3
+ FAQ,
4
+ FlowChartColors,
5
+ getNoRiskTags,
6
+ IConfig,
7
+ IInvestmentFlow,
8
+ IProtocol,
9
+ IStrategyMetadata,
10
+ RiskFactor,
11
+ RiskType
12
+ } from "@/interfaces";
3
13
  import { AvnuWrapper, Pricer, SwapInfo } from "@/modules";
4
14
  import { Account, CairoCustomEnum, Contract, num, uint256 } from "starknet";
5
- import VesuRebalanceAbi from '@/data/vesu-rebalance.abi.json';
6
- import { Global, logger } from "@/global";
15
+ import VesuRebalanceAbi from "@/data/vesu-rebalance.abi.json";
16
+ import { Global } from "@/global";
7
17
  import { assert } from "@/utils";
18
+ import { logger } from "@/utils/logger";
8
19
  import axios from "axios";
9
20
  import { PricerBase } from "@/modules/pricerBase";
10
- import { BaseStrategy, SingleActionAmount, SingleTokenInfo } from "./base-strategy";
21
+ import {
22
+ BaseStrategy,
23
+ SingleActionAmount,
24
+ SingleTokenInfo
25
+ } from "./base-strategy";
11
26
  import { getAPIUsingHeadlessBrowser } from "@/node/headless";
12
27
  import { VesuHarvests } from "@/modules/harvests";
13
28
  import VesuPoolIDs from "@/data/vesu_pools.json";
14
29
 
15
30
  interface PoolProps {
16
- pool_id: ContractAddr;
17
- max_weight: number;
18
- v_token: ContractAddr;
31
+ pool_id: ContractAddr;
32
+ max_weight: number;
33
+ v_token: ContractAddr;
19
34
  }
20
35
 
21
36
  interface Change {
22
- pool_id: ContractAddr;
23
- changeAmt: Web3Number;
24
- finalAmt: Web3Number;
25
- isDeposit: boolean;
37
+ pool_id: ContractAddr;
38
+ changeAmt: Web3Number;
39
+ finalAmt: Web3Number;
40
+ isDeposit: boolean;
26
41
  }
27
42
 
28
43
  export interface VesuRebalanceSettings {
29
- feeBps: number;
44
+ feeBps: number;
30
45
  }
31
46
 
32
47
  interface PoolInfoFull {
33
- pool_id: ContractAddr;
34
- pool_name: string | undefined;
35
- max_weight: number;
36
- current_weight: number;
37
- v_token: ContractAddr;
38
- amount: Web3Number;
39
- usdValue: Web3Number;
40
- APY: {
41
- baseApy: number;
42
- defiSpringApy: number;
43
- netApy: number;
44
- };
45
- currentUtilization: number;
46
- maxUtilization: number;
48
+ pool_id: ContractAddr;
49
+ pool_name: string | undefined;
50
+ max_weight: number;
51
+ current_weight: number;
52
+ v_token: ContractAddr;
53
+ amount: Web3Number;
54
+ usdValue: Web3Number;
55
+ APY: {
56
+ baseApy: number;
57
+ defiSpringApy: number;
58
+ netApy: number;
59
+ };
60
+ currentUtilization: number;
61
+ maxUtilization: number;
47
62
  }
48
63
  /**
49
64
  * Represents a VesuRebalance strategy.
50
65
  * This class implements an automated rebalancing strategy for Vesu pools,
51
66
  * managing deposits and withdrawals while optimizing yield through STRK rewards.
52
67
  */
53
- export class VesuRebalance extends BaseStrategy<SingleTokenInfo, SingleActionAmount> {
54
- /** Contract address of the strategy */
55
- readonly address: ContractAddr;
56
- /** Pricer instance for token price calculations */
57
- readonly pricer: PricerBase;
58
- /** Metadata containing strategy information */
59
- readonly metadata: IStrategyMetadata<VesuRebalanceSettings>;
60
- /** Contract instance for interacting with the strategy */
61
- readonly contract: Contract;
62
- readonly BASE_WEIGHT = 10000; // 10000 bps = 100%
63
-
64
- /**
65
- * Creates a new VesuRebalance strategy instance.
66
- * @param config - Configuration object containing provider and other settings
67
- * @param pricer - Pricer instance for token price calculations
68
- * @param metadata - Strategy metadata including deposit tokens and address
69
- * @throws {Error} If more than one deposit token is specified
70
- */
71
- constructor(config: IConfig, pricer: PricerBase, metadata: IStrategyMetadata<VesuRebalanceSettings>) {
72
- super(config);
73
- this.pricer = pricer;
74
-
75
- assert(metadata.depositTokens.length === 1, 'VesuRebalance only supports 1 deposit token');
76
- this.metadata = metadata;
77
- this.address = metadata.address;
78
-
79
- this.contract = new Contract(VesuRebalanceAbi, this.address.address, this.config.provider);
80
- }
68
+ export class VesuRebalance extends BaseStrategy<
69
+ SingleTokenInfo,
70
+ SingleActionAmount
71
+ > {
72
+ /** Contract address of the strategy */
73
+ readonly address: ContractAddr;
74
+ /** Pricer instance for token price calculations */
75
+ readonly pricer: PricerBase;
76
+ /** Metadata containing strategy information */
77
+ readonly metadata: IStrategyMetadata<VesuRebalanceSettings>;
78
+ /** Contract instance for interacting with the strategy */
79
+ readonly contract: Contract;
80
+ readonly BASE_WEIGHT = 10000; // 10000 bps = 100%
81
81
 
82
- /**
83
- * Creates a deposit call to the strategy contract.
84
- * @param assets - Amount of assets to deposit
85
- * @param receiver - Address that will receive the strategy tokens
86
- * @returns Populated contract call for deposit
87
- */
88
- async depositCall(amountInfo: SingleActionAmount, receiver: ContractAddr) {
89
- // Technically its not erc4626 abi, but we just need approve call
90
- // so, its ok to use it
91
- assert(amountInfo.tokenInfo.address.eq(this.asset().address), 'Deposit token mismatch');
92
- const assetContract = new Contract(VesuRebalanceAbi, this.asset().address.address, this.config.provider);
93
- const call1 = assetContract.populate('approve', [this.address.address, uint256.bnToUint256(amountInfo.amount.toWei())]);
94
- const call2 = this.contract.populate('deposit', [uint256.bnToUint256(amountInfo.amount.toWei()), receiver.address]);
95
- return [call1, call2];
96
- }
82
+ /**
83
+ * Creates a new VesuRebalance strategy instance.
84
+ * @param config - Configuration object containing provider and other settings
85
+ * @param pricer - Pricer instance for token price calculations
86
+ * @param metadata - Strategy metadata including deposit tokens and address
87
+ * @throws {Error} If more than one deposit token is specified
88
+ */
89
+ constructor(
90
+ config: IConfig,
91
+ pricer: PricerBase,
92
+ metadata: IStrategyMetadata<VesuRebalanceSettings>
93
+ ) {
94
+ super(config);
95
+ this.pricer = pricer;
97
96
 
98
- /**
99
- * Creates a withdrawal call to the strategy contract.
100
- * @param assets - Amount of assets to withdraw
101
- * @param receiver - Address that will receive the withdrawn assets
102
- * @param owner - Address that owns the strategy tokens
103
- * @returns Populated contract call for withdrawal
104
- */
105
- async withdrawCall(amountInfo: SingleActionAmount, receiver: ContractAddr, owner: ContractAddr) {
106
- return [this.contract.populate('withdraw', [uint256.bnToUint256(amountInfo.amount.toWei()), receiver.address, owner.address])];
107
- }
97
+ assert(
98
+ metadata.depositTokens.length === 1,
99
+ "VesuRebalance only supports 1 deposit token"
100
+ );
101
+ this.metadata = metadata;
102
+ this.address = metadata.address;
108
103
 
109
- /**
110
- * Returns the underlying asset token of the strategy.
111
- * @returns The deposit token supported by this strategy
112
- */
113
- asset() {
114
- return this.metadata.depositTokens[0];
115
- }
104
+ this.contract = new Contract(
105
+ VesuRebalanceAbi,
106
+ this.address.address,
107
+ this.config.provider
108
+ );
109
+ }
116
110
 
117
- /**
118
- * Returns the number of decimals used by the strategy token.
119
- * @returns Number of decimals (same as the underlying token)
120
- */
121
- decimals() {
122
- return this.metadata.depositTokens[0].decimals; // same as underlying token
123
- }
111
+ /**
112
+ * Creates a deposit call to the strategy contract.
113
+ * @param assets - Amount of assets to deposit
114
+ * @param receiver - Address that will receive the strategy tokens
115
+ * @returns Populated contract call for deposit
116
+ */
117
+ async depositCall(amountInfo: SingleActionAmount, receiver: ContractAddr) {
118
+ // Technically its not erc4626 abi, but we just need approve call
119
+ // so, its ok to use it
120
+ assert(
121
+ amountInfo.tokenInfo.address.eq(this.asset().address),
122
+ "Deposit token mismatch"
123
+ );
124
+ const assetContract = new Contract(
125
+ VesuRebalanceAbi,
126
+ this.asset().address.address,
127
+ this.config.provider
128
+ );
129
+ const call1 = assetContract.populate("approve", [
130
+ this.address.address,
131
+ uint256.bnToUint256(amountInfo.amount.toWei())
132
+ ]);
133
+ const call2 = this.contract.populate("deposit", [
134
+ uint256.bnToUint256(amountInfo.amount.toWei()),
135
+ receiver.address
136
+ ]);
137
+ return [call1, call2];
138
+ }
124
139
 
125
- /**
126
- * Calculates the Total Value Locked (TVL) for a specific user.
127
- * @param user - Address of the user
128
- * @returns Object containing the amount in token units and USD value
129
- */
130
- async getUserTVL(user: ContractAddr) {
131
- const shares = await this.contract.balanceOf(user.address);
132
- const assets = await this.contract.convert_to_assets(uint256.bnToUint256(shares));
133
- const amount = Web3Number.fromWei(assets.toString(), this.metadata.depositTokens[0].decimals);
134
- let price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
135
- const usdValue = Number(amount.toFixed(6)) * price.price;
136
- return {
137
- tokenInfo: this.asset(),
138
- amount,
139
- usdValue
140
- }
141
- }
140
+ /**
141
+ * Creates a withdrawal call to the strategy contract.
142
+ * @param assets - Amount of assets to withdraw
143
+ * @param receiver - Address that will receive the withdrawn assets
144
+ * @param owner - Address that owns the strategy tokens
145
+ * @returns Populated contract call for withdrawal
146
+ */
147
+ async withdrawCall(
148
+ amountInfo: SingleActionAmount,
149
+ receiver: ContractAddr,
150
+ owner: ContractAddr
151
+ ) {
152
+ return [
153
+ this.contract.populate("withdraw", [
154
+ uint256.bnToUint256(amountInfo.amount.toWei()),
155
+ receiver.address,
156
+ owner.address
157
+ ])
158
+ ];
159
+ }
142
160
 
143
- /**
144
- * Calculates the total TVL of the strategy.
145
- * @returns Object containing the total amount in token units and USD value
146
- */
147
- async getTVL() {
148
- const assets = await this.contract.total_assets();
149
- const amount = Web3Number.fromWei(assets.toString(), this.metadata.depositTokens[0].decimals);
150
- let price = await this.pricer.getPrice(this.metadata.depositTokens[0].symbol);
151
- const usdValue = Number(amount.toFixed(6)) * price.price;
152
- return {
153
- tokenInfo: this.asset(),
154
- amount,
155
- usdValue
156
- }
157
- }
161
+ /**
162
+ * Returns the underlying asset token of the strategy.
163
+ * @returns The deposit token supported by this strategy
164
+ */
165
+ asset() {
166
+ return this.metadata.depositTokens[0];
167
+ }
158
168
 
159
- static async getAllPossibleVerifiedPools(asset: ContractAddr) {
160
- const data = await getAPIUsingHeadlessBrowser('https://api.vesu.xyz/pools');
161
- const verifiedPools =data.data.filter((d: any) => d.isVerified);
162
- const pools = verifiedPools.map((p: any) => {
163
- const hasMyAsset = p.assets.find((a: any) => asset.eqString(a.address));
164
- if (hasMyAsset) {
165
- return {
166
- pool_id: ContractAddr.from(p.id),
167
- max_weight: 10000,
168
- v_token: ContractAddr.from(hasMyAsset.vToken.address),
169
- name: p.name,
170
- }
171
- }
172
- return null;
173
- }).filter((p: PoolProps | null) => p !== null);
174
- return pools;
175
- }
169
+ /**
170
+ * Returns the number of decimals used by the strategy token.
171
+ * @returns Number of decimals (same as the underlying token)
172
+ */
173
+ decimals() {
174
+ return this.metadata.depositTokens[0].decimals; // same as underlying token
175
+ }
176
176
 
177
- async getPoolInfo(
178
- p: PoolProps,
179
- pools: any[],
180
- vesuPositions: any[],
181
- totalAssets: Web3Number,
182
- isErrorPositionsAPI: boolean,
183
- isErrorPoolsAPI: boolean,
184
- ) {
185
- const vesuPosition = vesuPositions.find((d: any) => d.pool.id.toString() === num.getDecimalString(p.pool_id.address.toString()));
186
- const _pool = pools.find((d: any) => {
187
- logger.verbose(`pool check: ${d.id == num.getDecimalString(p.pool_id.address.toString())}, id: ${d.id}, pool_id: ${num.getDecimalString(p.pool_id.address.toString())}`);
188
- return d.id == num.getDecimalString(p.pool_id.address.toString());
189
- });
190
- logger.verbose(`pool: ${JSON.stringify(_pool)}`);
191
- logger.verbose(typeof _pool);
192
- logger.verbose(`name: ${_pool?.name}`);
193
- const name = _pool?.name;
194
- logger.verbose(`name2: ${name}, ${!name ? true : false}, ${name?.length}, ${typeof name}`);
195
- const assetInfo = _pool?.assets.find((d: any) => this.asset().address.eqString(d.address));
196
- if (!name) {
197
- logger.verbose(`Pool not found`);
198
- throw new Error(`Pool name ${p.pool_id.address.toString()} not found`);
199
- }
200
- if (!assetInfo) {
201
- throw new Error(`Asset ${this.asset().address.toString()} not found in pool ${p.pool_id.address.toString()}`);
202
- }
203
- let vTokenContract = new Contract(VesuRebalanceAbi, p.v_token.address, this.config.provider);
204
- const bal = await vTokenContract.balanceOf(this.address.address);
205
- const assets = await vTokenContract.convert_to_assets(uint256.bnToUint256(bal.toString()));
206
- logger.verbose(`Collateral: ${JSON.stringify(vesuPosition?.collateral)}`);
207
- logger.verbose(`supplyApy: ${JSON.stringify(assetInfo?.stats.supplyApy)}`);
208
- logger.verbose(`defiSpringSupplyApr: ${JSON.stringify(assetInfo?.stats.defiSpringSupplyApr)}`);
209
- logger.verbose(`currentUtilization: ${JSON.stringify(assetInfo?.stats.currentUtilization)}`);
210
- logger.verbose(`maxUtilization: ${JSON.stringify(assetInfo?.config.maxUtilization)}`);
211
- const item = {
212
- pool_id: p.pool_id,
213
- pool_name: _pool?.name,
214
- max_weight: p.max_weight,
215
- current_weight: isErrorPositionsAPI || !vesuPosition ? 0 : Number(Web3Number.fromWei(vesuPosition.collateral.value, this.decimals()).dividedBy(totalAssets.toString()).toFixed(6)),
216
- v_token: p.v_token,
217
- amount: Web3Number.fromWei(assets.toString(), this.decimals()),
218
- usdValue: isErrorPositionsAPI || !vesuPosition ? Web3Number.fromWei("0", this.decimals()) : Web3Number.fromWei(vesuPosition.collateral.usdPrice.value, vesuPosition.collateral.usdPrice.decimals),
219
- APY: isErrorPoolsAPI || !assetInfo ? {
220
- baseApy: 0,
221
- defiSpringApy: 0,
222
- netApy: 0,
223
- } : {
224
- baseApy: Number(Web3Number.fromWei(assetInfo.stats.supplyApy.value, assetInfo.stats.supplyApy.decimals).toFixed(6)),
225
- defiSpringApy: assetInfo.stats.defiSpringSupplyApr ? Number(Web3Number.fromWei(assetInfo.stats.defiSpringSupplyApr.value, assetInfo.stats.defiSpringSupplyApr.decimals).toFixed(6)) : 0,
226
- netApy: 0,
227
- },
228
- currentUtilization: isErrorPoolsAPI || !assetInfo ? 0 : Number(Web3Number.fromWei(assetInfo.stats.currentUtilization.value, assetInfo.stats.currentUtilization.decimals).toFixed(6)),
229
- maxUtilization: isErrorPoolsAPI || !assetInfo ? 0 : Number(Web3Number.fromWei(assetInfo.config.maxUtilization.value, assetInfo.config.maxUtilization.decimals).toFixed(6)),
177
+ /**
178
+ * Calculates the Total Value Locked (TVL) for a specific user.
179
+ * @param user - Address of the user
180
+ * @returns Object containing the amount in token units and USD value
181
+ */
182
+ async getUserTVL(user: ContractAddr) {
183
+ const shares = await this.contract.balanceOf(user.address);
184
+ const assets = await this.contract.convert_to_assets(
185
+ uint256.bnToUint256(shares)
186
+ );
187
+ const amount = Web3Number.fromWei(
188
+ assets.toString(),
189
+ this.metadata.depositTokens[0].decimals
190
+ );
191
+ let price = await this.pricer.getPrice(
192
+ this.metadata.depositTokens[0].symbol
193
+ );
194
+ const usdValue = Number(amount.toFixed(6)) * price.price;
195
+ return {
196
+ tokenInfo: this.asset(),
197
+ amount,
198
+ usdValue
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Calculates the total TVL of the strategy.
204
+ * @returns Object containing the total amount in token units and USD value
205
+ */
206
+ async getTVL() {
207
+ const assets = await this.contract.total_assets();
208
+ const amount = Web3Number.fromWei(
209
+ assets.toString(),
210
+ this.metadata.depositTokens[0].decimals
211
+ );
212
+ let price = await this.pricer.getPrice(
213
+ this.metadata.depositTokens[0].symbol
214
+ );
215
+ const usdValue = Number(amount.toFixed(6)) * price.price;
216
+ return {
217
+ tokenInfo: this.asset(),
218
+ amount,
219
+ usdValue
220
+ };
221
+ }
222
+
223
+ static async getAllPossibleVerifiedPools(asset: ContractAddr) {
224
+ const data = await getAPIUsingHeadlessBrowser("https://api.vesu.xyz/pools");
225
+ const verifiedPools = data.data.filter((d: any) => d.isVerified);
226
+ const pools = verifiedPools
227
+ .map((p: any) => {
228
+ const hasMyAsset = p.assets.find((a: any) => asset.eqString(a.address));
229
+ if (hasMyAsset) {
230
+ return {
231
+ pool_id: ContractAddr.from(p.id),
232
+ max_weight: 10000,
233
+ v_token: ContractAddr.from(hasMyAsset.vToken.address),
234
+ name: p.name
235
+ };
230
236
  }
231
- item.APY.netApy = item.APY.baseApy + item.APY.defiSpringApy;
232
- return item;
237
+ return null;
238
+ })
239
+ .filter((p: PoolProps | null) => p !== null);
240
+ return pools;
241
+ }
242
+
243
+ async getPoolInfo(
244
+ p: PoolProps,
245
+ pools: any[],
246
+ vesuPositions: any[],
247
+ totalAssets: Web3Number,
248
+ isErrorPositionsAPI: boolean,
249
+ isErrorPoolsAPI: boolean
250
+ ) {
251
+ const vesuPosition = vesuPositions.find(
252
+ (d: any) =>
253
+ d.pool.id.toString() ===
254
+ num.getDecimalString(p.pool_id.address.toString())
255
+ );
256
+ const _pool = pools.find((d: any) => {
257
+ logger.verbose(
258
+ `pool check: ${
259
+ d.id == num.getDecimalString(p.pool_id.address.toString())
260
+ }, id: ${d.id}, pool_id: ${num.getDecimalString(
261
+ p.pool_id.address.toString()
262
+ )}`
263
+ );
264
+ return d.id == num.getDecimalString(p.pool_id.address.toString());
265
+ });
266
+ logger.verbose(`pool: ${JSON.stringify(_pool)}`);
267
+ logger.verbose(typeof _pool);
268
+ logger.verbose(`name: ${_pool?.name}`);
269
+ const name = _pool?.name;
270
+ logger.verbose(
271
+ `name2: ${name}, ${!name ? true : false}, ${name?.length}, ${typeof name}`
272
+ );
273
+ const assetInfo = _pool?.assets.find((d: any) =>
274
+ this.asset().address.eqString(d.address)
275
+ );
276
+ if (!name) {
277
+ logger.verbose(`Pool not found`);
278
+ throw new Error(`Pool name ${p.pool_id.address.toString()} not found`);
279
+ }
280
+ if (!assetInfo) {
281
+ throw new Error(
282
+ `Asset ${this.asset().address.toString()} not found in pool ${p.pool_id.address.toString()}`
283
+ );
233
284
  }
285
+ let vTokenContract = new Contract(
286
+ VesuRebalanceAbi,
287
+ p.v_token.address,
288
+ this.config.provider
289
+ );
290
+ const bal = await vTokenContract.balanceOf(this.address.address);
291
+ const assets = await vTokenContract.convert_to_assets(
292
+ uint256.bnToUint256(bal.toString())
293
+ );
294
+ logger.verbose(`Collateral: ${JSON.stringify(vesuPosition?.collateral)}`);
295
+ logger.verbose(`supplyApy: ${JSON.stringify(assetInfo?.stats.supplyApy)}`);
296
+ logger.verbose(
297
+ `defiSpringSupplyApr: ${JSON.stringify(
298
+ assetInfo?.stats.defiSpringSupplyApr
299
+ )}`
300
+ );
301
+ logger.verbose(
302
+ `currentUtilization: ${JSON.stringify(
303
+ assetInfo?.stats.currentUtilization
304
+ )}`
305
+ );
306
+ logger.verbose(
307
+ `maxUtilization: ${JSON.stringify(assetInfo?.config.maxUtilization)}`
308
+ );
309
+ const item = {
310
+ pool_id: p.pool_id,
311
+ pool_name: _pool?.name,
312
+ max_weight: p.max_weight,
313
+ current_weight:
314
+ isErrorPositionsAPI || !vesuPosition
315
+ ? 0
316
+ : Number(
317
+ Web3Number.fromWei(vesuPosition.collateral.value, this.decimals())
318
+ .dividedBy(totalAssets.toString())
319
+ .toFixed(6)
320
+ ),
321
+ v_token: p.v_token,
322
+ amount: Web3Number.fromWei(assets.toString(), this.decimals()),
323
+ usdValue:
324
+ isErrorPositionsAPI || !vesuPosition
325
+ ? Web3Number.fromWei("0", this.decimals())
326
+ : Web3Number.fromWei(
327
+ vesuPosition.collateral.usdPrice.value,
328
+ vesuPosition.collateral.usdPrice.decimals
329
+ ),
330
+ APY:
331
+ isErrorPoolsAPI || !assetInfo
332
+ ? {
333
+ baseApy: 0,
334
+ defiSpringApy: 0,
335
+ netApy: 0
336
+ }
337
+ : {
338
+ baseApy: Number(
339
+ Web3Number.fromWei(
340
+ assetInfo.stats.supplyApy.value,
341
+ assetInfo.stats.supplyApy.decimals
342
+ ).toFixed(6)
343
+ ),
344
+ defiSpringApy: assetInfo.stats.defiSpringSupplyApr
345
+ ? Number(
346
+ Web3Number.fromWei(
347
+ assetInfo.stats.defiSpringSupplyApr.value,
348
+ assetInfo.stats.defiSpringSupplyApr.decimals
349
+ ).toFixed(6)
350
+ )
351
+ : 0,
352
+ netApy: 0
353
+ },
354
+ currentUtilization:
355
+ isErrorPoolsAPI || !assetInfo
356
+ ? 0
357
+ : Number(
358
+ Web3Number.fromWei(
359
+ assetInfo.stats.currentUtilization.value,
360
+ assetInfo.stats.currentUtilization.decimals
361
+ ).toFixed(6)
362
+ ),
363
+ maxUtilization:
364
+ isErrorPoolsAPI || !assetInfo
365
+ ? 0
366
+ : Number(
367
+ Web3Number.fromWei(
368
+ assetInfo.config.maxUtilization.value,
369
+ assetInfo.config.maxUtilization.decimals
370
+ ).toFixed(6)
371
+ )
372
+ };
373
+ item.APY.netApy = item.APY.baseApy + item.APY.defiSpringApy;
374
+ return item;
375
+ }
234
376
 
235
- /**
236
- * Retrieves the list of allowed pools and their detailed information from multiple sources:
237
- * 1. Contract's allowed pools
238
- * 2. Vesu positions API for current positions
239
- * 3. Vesu pools API for APY and utilization data
240
- *
241
- * @returns {Promise<{
242
- * data: Array<PoolInfoFull>,
243
- * isErrorPositionsAPI: boolean
244
- * }>} Object containing:
245
- * - data: Array of pool information including IDs, weights, amounts, APYs and utilization
246
- * - isErrorPositionsAPI: Boolean indicating if there was an error fetching position data
247
- */
248
- async getPools() {
249
- const allowedPools: PoolProps[] = (await this.contract.get_allowed_pools()).map((p: any) => ({
250
- pool_id: ContractAddr.from(p.pool_id),
251
- max_weight: Number(p.max_weight) / this.BASE_WEIGHT,
252
- v_token: ContractAddr.from(p.v_token),
253
- }));
254
-
255
- /*
377
+ /**
378
+ * Retrieves the list of allowed pools and their detailed information from multiple sources:
379
+ * 1. Contract's allowed pools
380
+ * 2. Vesu positions API for current positions
381
+ * 3. Vesu pools API for APY and utilization data
382
+ *
383
+ * @returns {Promise<{
384
+ * data: Array<PoolInfoFull>,
385
+ * isErrorPositionsAPI: boolean
386
+ * }>} Object containing:
387
+ * - data: Array of pool information including IDs, weights, amounts, APYs and utilization
388
+ * - isErrorPositionsAPI: Boolean indicating if there was an error fetching position data
389
+ */
390
+ async getPools() {
391
+ const allowedPools: PoolProps[] = (
392
+ await this.contract.get_allowed_pools()
393
+ ).map((p: any) => ({
394
+ pool_id: ContractAddr.from(p.pool_id),
395
+ max_weight: Number(p.max_weight) / this.BASE_WEIGHT,
396
+ v_token: ContractAddr.from(p.v_token)
397
+ }));
398
+
399
+ /*
256
400
  Vesu Positions API Response Schema (/positions?walletAddress=):
257
401
  {
258
402
  "data": [{
@@ -299,442 +443,630 @@ export class VesuRebalance extends BaseStrategy<SingleTokenInfo, SingleActionAmo
299
443
  }]
300
444
  }
301
445
  */
302
- let isErrorPositionsAPI = false;
303
- let vesuPositions: any[] = [];
304
- try {
305
- const data = await getAPIUsingHeadlessBrowser(`https://api.vesu.xyz/positions?walletAddress=${this.address.address}`)
306
- vesuPositions = data.data;
307
- } catch (e) {
308
- console.error(`${VesuRebalance.name}: Error fetching positions for ${this.address.address}`, e);
309
- isErrorPositionsAPI = true;
310
- }
311
-
446
+ let isErrorPositionsAPI = false;
447
+ let vesuPositions: any[] = [];
448
+ try {
449
+ const data = await getAPIUsingHeadlessBrowser(
450
+ `https://api.vesu.xyz/positions?walletAddress=${this.address.address}`
451
+ );
452
+ vesuPositions = data.data;
453
+ } catch (e) {
454
+ console.error(
455
+ `${VesuRebalance.name}: Error fetching positions for ${this.address.address}`,
456
+ e
457
+ );
458
+ isErrorPositionsAPI = true;
459
+ }
312
460
 
313
- let { pools, isErrorPoolsAPI } = await this.getVesuPools();
461
+ let { pools, isErrorPoolsAPI } = await this.getVesuPools();
314
462
 
315
- const totalAssets = (await this.getTVL()).amount;
463
+ const totalAssets = (await this.getTVL()).amount;
464
+
465
+ const info = allowedPools.map((p) =>
466
+ this.getPoolInfo(
467
+ p,
468
+ pools,
469
+ vesuPositions,
470
+ totalAssets,
471
+ isErrorPositionsAPI,
472
+ isErrorPoolsAPI
473
+ )
474
+ );
475
+ const data = await Promise.all(info);
476
+ return {
477
+ data,
478
+ isErrorPositionsAPI,
479
+ isErrorPoolsAPI,
480
+ isError: isErrorPositionsAPI || isErrorPoolsAPI
481
+ };
482
+ }
316
483
 
317
- const info = allowedPools.map(p => this.getPoolInfo(p, pools, vesuPositions, totalAssets, isErrorPositionsAPI, isErrorPoolsAPI));
318
- const data = await Promise.all(info);
319
- return {
320
- data,
321
- isErrorPositionsAPI,
322
- isErrorPoolsAPI,
323
- isError: isErrorPositionsAPI || isErrorPoolsAPI,
484
+ async getVesuPools(
485
+ retry = 0
486
+ ): Promise<{ pools: any[]; isErrorPoolsAPI: boolean }> {
487
+ let isErrorPoolsAPI = false;
488
+ let pools: any[] = [];
489
+ try {
490
+ const data = await getAPIUsingHeadlessBrowser(
491
+ "https://api.vesu.xyz/pools"
492
+ );
493
+ pools = data.data;
494
+
495
+ // Vesu API is unstable sometimes, some Pools may be missing sometimes
496
+ for (const pool of VesuPoolIDs.data) {
497
+ const found = pools.find((d: any) => d.id === pool.id);
498
+ if (!found) {
499
+ logger.verbose(`VesuRebalance: pools: ${JSON.stringify(pools)}`);
500
+ logger.verbose(
501
+ `VesuRebalance: Pool ${pool.id} not found in Vesu API, using hardcoded data`
502
+ );
503
+ throw new Error("pool not found [sanity check]");
324
504
  }
505
+ }
506
+ } catch (e) {
507
+ logger.error(
508
+ `${VesuRebalance.name}: Error fetching pools for ${this.address.address}, retry ${retry}`,
509
+ e
510
+ );
511
+ isErrorPoolsAPI = true;
512
+ if (retry < 10) {
513
+ await new Promise((resolve) => setTimeout(resolve, 5000 * (retry + 1)));
514
+ return await this.getVesuPools(retry + 1);
515
+ }
325
516
  }
326
517
 
327
- async getVesuPools(retry = 0): Promise<{pools: any[], isErrorPoolsAPI: boolean}> {
328
- let isErrorPoolsAPI = false;
329
- let pools: any[] = [];
330
- try {
331
- const data = await getAPIUsingHeadlessBrowser('https://api.vesu.xyz/pools');
332
- pools = data.data;
333
-
334
- // Vesu API is unstable sometimes, some Pools may be missing sometimes
335
- for (const pool of VesuPoolIDs.data) {
336
- const found = pools.find((d: any) => d.id === pool.id);
337
- if (!found) {
338
- logger.verbose(`VesuRebalance: pools: ${JSON.stringify(pools)}`);
339
- logger.verbose(`VesuRebalance: Pool ${pool.id} not found in Vesu API, using hardcoded data`);
340
- throw new Error('pool not found [sanity check]')
341
- }
342
- }
343
- } catch (e) {
344
- logger.error(`${VesuRebalance.name}: Error fetching pools for ${this.address.address}, retry ${retry}`, e);
345
- isErrorPoolsAPI = true;
346
- if (retry < 10) {
347
- await new Promise((resolve) => setTimeout(resolve, 5000 * (retry + 1)));
348
- return await this.getVesuPools(retry + 1);
349
- }
350
- }
518
+ return { pools, isErrorPoolsAPI };
519
+ }
351
520
 
352
- return { pools, isErrorPoolsAPI };
353
- }
521
+ /**
522
+ * Calculates the weighted average APY across all pools based on USD value.
523
+ * @returns {Promise<number>} The weighted average APY across all pools
524
+ */
525
+ async netAPY(): Promise<number> {
526
+ const { data: pools } = await this.getPools();
527
+ return this.netAPYGivenPools(pools);
528
+ }
354
529
 
355
- /**
356
- * Calculates the weighted average APY across all pools based on USD value.
357
- * @returns {Promise<number>} The weighted average APY across all pools
358
- */
359
- async netAPY(): Promise<number> {
360
- const { data: pools } = await this.getPools();
361
- return this.netAPYGivenPools(pools);
362
- }
530
+ /**
531
+ * Calculates the weighted average APY across all pools based on USD value.
532
+ * @returns {Promise<number>} The weighted average APY across all pools
533
+ */
534
+ async netAPYGivenPools(pools: PoolInfoFull[]): Promise<number> {
535
+ const weightedApyNumerator = pools.reduce((acc: number, curr) => {
536
+ const weight = curr.current_weight;
537
+ return acc + curr.APY.netApy * Number(curr.amount.toString());
538
+ }, 0);
539
+ const totalAssets = (await this.getTVL()).amount;
540
+ const weightedApy = weightedApyNumerator / Number(totalAssets.toString());
541
+ return weightedApy * (1 - this.metadata.additionalInfo.feeBps / 10000);
542
+ }
363
543
 
364
- /**
365
- * Calculates the weighted average APY across all pools based on USD value.
366
- * @returns {Promise<number>} The weighted average APY across all pools
367
- */
368
- async netAPYGivenPools(pools: PoolInfoFull[]): Promise<number> {
369
- const weightedApyNumerator = pools.reduce((acc: number, curr) => {
370
- const weight = curr.current_weight;
371
- return acc + (curr.APY.netApy * Number(curr.amount.toString()));
372
- }, 0);
373
- const totalAssets = (await this.getTVL()).amount;
374
- const weightedApy = weightedApyNumerator / Number(totalAssets.toString());
375
- return weightedApy * (1 - (this.metadata.additionalInfo.feeBps / 10000));
544
+ /**
545
+ * Calculates optimal position changes to maximize APY while respecting max weights.
546
+ * The algorithm:
547
+ * 1. Sorts pools by APY (highest first)
548
+ * 2. Calculates target amounts based on max weights
549
+ * 3. For each pool that needs more funds:
550
+ * - Takes funds from lowest APY pools that are over their target
551
+ * 4. Validates that total assets remain constant
552
+ *
553
+ * @returns {Promise<{
554
+ * changes: Change[],
555
+ * finalPools: PoolInfoFull[],
556
+ * isAnyPoolOverMaxWeight: boolean
557
+ * }>} Object containing:
558
+ * - changes: Array of position changes
559
+ * - finalPools: Array of pool information after rebalance
560
+ * @throws Error if rebalance is not possible while maintaining constraints
561
+ */
562
+ async getRebalancedPositions(_pools?: PoolInfoFull[]) {
563
+ logger.verbose(`VesuRebalance: getRebalancedPositions`);
564
+ if (!_pools) {
565
+ const { data: _pools2 } = await this.getPools();
566
+ _pools = _pools2;
376
567
  }
568
+ const feeDeductions = await this.getFee(_pools);
569
+ logger.verbose(
570
+ `VesuRebalance: feeDeductions: ${JSON.stringify(feeDeductions)}`
571
+ );
377
572
 
378
- /**
379
- * Calculates optimal position changes to maximize APY while respecting max weights.
380
- * The algorithm:
381
- * 1. Sorts pools by APY (highest first)
382
- * 2. Calculates target amounts based on max weights
383
- * 3. For each pool that needs more funds:
384
- * - Takes funds from lowest APY pools that are over their target
385
- * 4. Validates that total assets remain constant
386
- *
387
- * @returns {Promise<{
388
- * changes: Change[],
389
- * finalPools: PoolInfoFull[],
390
- * isAnyPoolOverMaxWeight: boolean
391
- * }>} Object containing:
392
- * - changes: Array of position changes
393
- * - finalPools: Array of pool information after rebalance
394
- * @throws Error if rebalance is not possible while maintaining constraints
395
- */
396
- async getRebalancedPositions(_pools?: PoolInfoFull[]) {
397
- logger.verbose(`VesuRebalance: getRebalancedPositions`);
398
- if (!_pools) {
399
- const { data: _pools2 } = await this.getPools();
400
- _pools = _pools2;
401
- }
402
- const feeDeductions = await this.getFee(_pools);
403
- logger.verbose(`VesuRebalance: feeDeductions: ${JSON.stringify(feeDeductions)}`);
404
-
405
- // remove fee from pools
406
- const pools = _pools.map((p) => {
407
- const fee = feeDeductions.find((f) => p.v_token.eq(f.vToken))?.fee || Web3Number.fromWei("0", this.decimals());
408
- logger.verbose(`FeeAdjustment: ${p.pool_id} => ${fee.toString()}, amt: ${p.amount.toString()}`);
409
- return {
410
- ...p,
411
- amount: p.amount.minus(fee),
412
- }
413
- });
414
- let totalAssets = (await this.getTVL()).amount;
415
- if (totalAssets.eq(0)) return {
416
- changes: [],
417
- finalPools: [],
418
- }
419
- // deduct fee from total assets
420
- feeDeductions.forEach((f) => {
421
- totalAssets = totalAssets.minus(f.fee);
422
- });
423
-
424
-
425
- // assert sum of pools.amount <= totalAssets
426
- const sumPools = pools.reduce((acc, curr) => acc.plus(curr.amount.toString()), Web3Number.fromWei("0", this.decimals()));
427
- logger.verbose(`Sum of pools: ${sumPools.toString()}`);
428
- logger.verbose(`Total assets: ${totalAssets.toString()}`);
429
- assert(sumPools.lte(totalAssets.multipliedBy(1.00001).toString()), 'Sum of pools.amount must be less than or equal to totalAssets');
430
-
431
- // Sort pools by APY and calculate target amounts
432
- const sortedPools = [...pools].sort((a, b) => b.APY.netApy - a.APY.netApy);
433
- const targetAmounts: Record<string, Web3Number> = {};
434
- let remainingAssets = totalAssets;
435
- logger.verbose(`Remaining assets: ${remainingAssets.toString()}`);
436
-
437
- // First pass: Allocate to high APY pools up to their max weight
438
- let isAnyPoolOverMaxWeight = false;
439
- for (const pool of sortedPools) {
440
- const maxAmount = totalAssets.multipliedBy(pool.max_weight * 0.98); // some tolerance
441
- const targetAmount = remainingAssets.gte(maxAmount) ? maxAmount : remainingAssets;
442
- logger.verbose(`Target amount: ${targetAmount.toString()}`);
443
- logger.verbose(`Remaining assets: ${remainingAssets.toString()}`);
444
- logger.verbose(`Max amount: ${maxAmount.toString()}`);
445
- logger.verbose(`pool.max_weight: ${pool.max_weight}`);
446
- targetAmounts[pool.pool_id.address.toString()] = targetAmount;
447
- remainingAssets = remainingAssets.minus(targetAmount.toString());
448
- if (pool.current_weight > pool.max_weight) {
449
- isAnyPoolOverMaxWeight = true;
450
- }
451
- }
573
+ // remove fee from pools
574
+ const pools = _pools.map((p) => {
575
+ const fee =
576
+ feeDeductions.find((f) => p.v_token.eq(f.vToken))?.fee ||
577
+ Web3Number.fromWei("0", this.decimals());
578
+ logger.verbose(
579
+ `FeeAdjustment: ${
580
+ p.pool_id
581
+ } => ${fee.toString()}, amt: ${p.amount.toString()}`
582
+ );
583
+ return {
584
+ ...p,
585
+ amount: p.amount.minus(fee)
586
+ };
587
+ });
588
+ let totalAssets = (await this.getTVL()).amount;
589
+ if (totalAssets.eq(0))
590
+ return {
591
+ changes: [],
592
+ finalPools: []
593
+ };
594
+ // deduct fee from total assets
595
+ feeDeductions.forEach((f) => {
596
+ totalAssets = totalAssets.minus(f.fee);
597
+ });
452
598
 
453
- assert(remainingAssets.lt(0.00001), 'Remaining assets must be 0');
454
-
455
- // Calculate required changes
456
- const changes: Change[] = sortedPools.map(pool => {
457
- const target = targetAmounts[pool.pool_id.address.toString()] || Web3Number.fromWei("0", this.decimals());
458
- const change = Web3Number.fromWei(target.minus(pool.amount.toString()).toWei(), this.decimals());
459
- return {
460
- pool_id: pool.pool_id,
461
- changeAmt: change,
462
- finalAmt: target,
463
- isDeposit: change.gt(0)
464
- };
465
- });
466
-
467
- logger.verbose(`Changes: ${JSON.stringify(changes)}`);
468
- // Validate changes
469
- const sumChanges = changes.reduce((sum, c) => sum.plus(c.changeAmt.toString()), Web3Number.fromWei("0", this.decimals()));
470
- const sumFinal = changes.reduce((sum, c) => sum.plus(c.finalAmt.toString()), Web3Number.fromWei("0", this.decimals()));
471
- const hasChanges = changes.some(c => !c.changeAmt.eq(0));
472
-
473
- logger.verbose(`Sum of changes: ${sumChanges.toString()}`);
474
- if (!sumChanges.eq(0)) throw new Error('Sum of changes must be zero');
475
- logger.verbose(`Sum of final: ${sumFinal.toString()}`);
476
- logger.verbose(`Total assets: ${totalAssets.toString()}`);
477
- if (!sumFinal.eq(totalAssets.toString())) throw new Error('Sum of final amounts must equal total assets');
478
- if (!hasChanges) throw new Error('No changes required');
479
-
480
- const finalPools: PoolInfoFull[] = pools.map((p) => {
481
- const target = targetAmounts[p.pool_id.address.toString()] || Web3Number.fromWei("0", this.decimals());
482
- return {
483
- ...p,
484
- amount: target,
485
- usdValue: Web3Number.fromWei("0", this.decimals()),
486
- }
487
- });
488
- return {
489
- changes,
490
- finalPools,
491
- isAnyPoolOverMaxWeight,
492
- }
493
- }
599
+ // assert sum of pools.amount <= totalAssets
600
+ const sumPools = pools.reduce(
601
+ (acc, curr) => acc.plus(curr.amount.toString()),
602
+ Web3Number.fromWei("0", this.decimals())
603
+ );
604
+ logger.verbose(`Sum of pools: ${sumPools.toString()}`);
605
+ logger.verbose(`Total assets: ${totalAssets.toString()}`);
606
+ assert(
607
+ sumPools.lte(totalAssets.multipliedBy(1.00001).toString()),
608
+ "Sum of pools.amount must be less than or equal to totalAssets"
609
+ );
494
610
 
495
- /**
496
- * Creates a rebalance Call object for the strategy contract
497
- * @param pools - Array of pool information including IDs, weights, amounts, APYs and utilization
498
- * @returns Populated contract call for rebalance
499
- */
500
- async getRebalanceCall(
501
- pools: Awaited<ReturnType<typeof this.getRebalancedPositions>>['changes'],
502
- isOverWeightAdjustment: boolean // here, yield increase doesnt matter
503
- ) {
504
- const actions: any[] = [];
505
- // sort to put withdrawals first
506
- pools.sort((a, b) => b.isDeposit ? -1 : 1);
507
- pools.forEach((p) => {
508
- if (p.changeAmt.eq(0)) return null;
509
- actions.push({
510
- pool_id: p.pool_id.address,
511
- feature: new CairoCustomEnum(p.isDeposit ? {DEPOSIT: {}} : {WITHDRAW: {}}),
512
- token: this.asset().address.address,
513
- amount: uint256.bnToUint256(p.changeAmt.multipliedBy(p.isDeposit ? 1 : -1).toWei()),
514
- });
515
- });
516
- if (actions.length === 0) return null;
517
- if (isOverWeightAdjustment) {
518
- return this.contract.populate('rebalance_weights', [actions]);
519
- }
520
- return this.contract.populate('rebalance', [actions]);
611
+ // Sort pools by APY and calculate target amounts
612
+ const sortedPools = [...pools].sort((a, b) => b.APY.netApy - a.APY.netApy);
613
+ const targetAmounts: Record<string, Web3Number> = {};
614
+ let remainingAssets = totalAssets;
615
+ logger.verbose(`Remaining assets: ${remainingAssets.toString()}`);
616
+
617
+ // First pass: Allocate to high APY pools up to their max weight
618
+ let isAnyPoolOverMaxWeight = false;
619
+ for (const pool of sortedPools) {
620
+ const maxAmount = totalAssets.multipliedBy(pool.max_weight * 0.98); // some tolerance
621
+ const targetAmount = remainingAssets.gte(maxAmount)
622
+ ? maxAmount
623
+ : remainingAssets;
624
+ logger.verbose(`Target amount: ${targetAmount.toString()}`);
625
+ logger.verbose(`Remaining assets: ${remainingAssets.toString()}`);
626
+ logger.verbose(`Max amount: ${maxAmount.toString()}`);
627
+ logger.verbose(`pool.max_weight: ${pool.max_weight}`);
628
+ targetAmounts[pool.pool_id.address.toString()] = targetAmount;
629
+ remainingAssets = remainingAssets.minus(targetAmount.toString());
630
+ if (pool.current_weight > pool.max_weight) {
631
+ isAnyPoolOverMaxWeight = true;
632
+ }
521
633
  }
522
634
 
523
- async getInvestmentFlows(pools: PoolInfoFull[]) {
524
- const netYield = await this.netAPYGivenPools(pools);
525
-
526
- const baseFlow: IInvestmentFlow = {
527
- title: "Your Deposit",
528
- subItems: [{key: `Net yield`, value: `${(netYield * 100).toFixed(2)}%`}, {key: `Performance Fee`, value: `${(this.metadata.additionalInfo.feeBps / 100).toFixed(2)}%`}],
529
- linkedFlows: [],
530
- style: {backgroundColor: FlowChartColors.Purple.valueOf()},
531
- };
532
-
533
- let _pools = [...pools];
534
- _pools = _pools.sort((a, b) => Number(b.amount.toString()) - Number(a.amount.toString()));
535
- _pools.forEach((p) => {
536
- const flow: IInvestmentFlow = {
537
- title: `Pool name: ${p.pool_name}`,
538
- subItems: [
539
- {key: `APY`, value: `${(p.APY.netApy * 100).toFixed(2)}%`},
540
- {key: 'Weight', value: `${(p.current_weight * 100).toFixed(2)} / ${(p.max_weight * 100).toFixed(2)}%`}
541
- ],
542
- linkedFlows: [],
543
- style: p.amount.greaterThan(0) ? {backgroundColor: FlowChartColors.Blue.valueOf()} : {color: 'gray'},
544
- };
545
- baseFlow.linkedFlows.push(flow);
546
- });
547
- return [baseFlow];
635
+ assert(remainingAssets.lt(0.00001), "Remaining assets must be 0");
636
+
637
+ // Calculate required changes
638
+ const changes: Change[] = sortedPools.map((pool) => {
639
+ const target =
640
+ targetAmounts[pool.pool_id.address.toString()] ||
641
+ Web3Number.fromWei("0", this.decimals());
642
+ const change = Web3Number.fromWei(
643
+ target.minus(pool.amount.toString()).toWei(),
644
+ this.decimals()
645
+ );
646
+ return {
647
+ pool_id: pool.pool_id,
648
+ changeAmt: change,
649
+ finalAmt: target,
650
+ isDeposit: change.gt(0)
651
+ };
652
+ });
653
+
654
+ logger.verbose(`Changes: ${JSON.stringify(changes)}`);
655
+ // Validate changes
656
+ const sumChanges = changes.reduce(
657
+ (sum, c) => sum.plus(c.changeAmt.toString()),
658
+ Web3Number.fromWei("0", this.decimals())
659
+ );
660
+ const sumFinal = changes.reduce(
661
+ (sum, c) => sum.plus(c.finalAmt.toString()),
662
+ Web3Number.fromWei("0", this.decimals())
663
+ );
664
+ const hasChanges = changes.some((c) => !c.changeAmt.eq(0));
665
+
666
+ logger.verbose(`Sum of changes: ${sumChanges.toString()}`);
667
+ if (!sumChanges.eq(0)) throw new Error("Sum of changes must be zero");
668
+ logger.verbose(`Sum of final: ${sumFinal.toString()}`);
669
+ logger.verbose(`Total assets: ${totalAssets.toString()}`);
670
+ if (!sumFinal.eq(totalAssets.toString()))
671
+ throw new Error("Sum of final amounts must equal total assets");
672
+ if (!hasChanges) throw new Error("No changes required");
673
+
674
+ const finalPools: PoolInfoFull[] = pools.map((p) => {
675
+ const target =
676
+ targetAmounts[p.pool_id.address.toString()] ||
677
+ Web3Number.fromWei("0", this.decimals());
678
+ return {
679
+ ...p,
680
+ amount: target,
681
+ usdValue: Web3Number.fromWei("0", this.decimals())
682
+ };
683
+ });
684
+ return {
685
+ changes,
686
+ finalPools,
687
+ isAnyPoolOverMaxWeight
688
+ };
689
+ }
690
+
691
+ /**
692
+ * Creates a rebalance Call object for the strategy contract
693
+ * @param pools - Array of pool information including IDs, weights, amounts, APYs and utilization
694
+ * @returns Populated contract call for rebalance
695
+ */
696
+ async getRebalanceCall(
697
+ pools: Awaited<ReturnType<typeof this.getRebalancedPositions>>["changes"],
698
+ isOverWeightAdjustment: boolean // here, yield increase doesnt matter
699
+ ) {
700
+ const actions: any[] = [];
701
+ // sort to put withdrawals first
702
+ pools.sort((a, b) => (b.isDeposit ? -1 : 1));
703
+ pools.forEach((p) => {
704
+ if (p.changeAmt.eq(0)) return null;
705
+ actions.push({
706
+ pool_id: p.pool_id.address,
707
+ feature: new CairoCustomEnum(
708
+ p.isDeposit ? { DEPOSIT: {} } : { WITHDRAW: {} }
709
+ ),
710
+ token: this.asset().address.address,
711
+ amount: uint256.bnToUint256(
712
+ p.changeAmt.multipliedBy(p.isDeposit ? 1 : -1).toWei()
713
+ )
714
+ });
715
+ });
716
+ if (actions.length === 0) return null;
717
+ if (isOverWeightAdjustment) {
718
+ return this.contract.populate("rebalance_weights", [actions]);
548
719
  }
720
+ return this.contract.populate("rebalance", [actions]);
721
+ }
549
722
 
550
- async harvest(acc: Account) {
551
- const vesuHarvest = new VesuHarvests(this.config);
552
- const harvests = await vesuHarvest.getUnHarvestedRewards(this.address);
553
- const harvest = harvests[0];
554
- const avnu = new AvnuWrapper();
555
- let swapInfo: SwapInfo = {
556
- token_from_address: harvest.token.address,
557
- token_from_amount: uint256.bnToUint256(harvest.actualReward.toWei()),
558
- token_to_address: this.asset().address.address,
559
- token_to_amount: uint256.bnToUint256(0),
560
- token_to_min_amount: uint256.bnToUint256(0),
561
- beneficiary: this.address.address,
562
- integrator_fee_amount_bps: 0,
563
- integrator_fee_recipient: this.address.address,
564
- routes: []
565
- }
566
- if (!this.asset().address.eqString(harvest.token.address)) {
567
- const quote = await avnu.getQuotes(
568
- harvest.token.address,
569
- this.asset().address.address,
570
- harvest.actualReward.toWei(),
571
- this.address.address
572
- );
573
- swapInfo = await avnu.getSwapInfo(quote, this.address.address, 0, this.address.address);
723
+ async getInvestmentFlows(pools: PoolInfoFull[]) {
724
+ const netYield = await this.netAPYGivenPools(pools);
725
+
726
+ const baseFlow: IInvestmentFlow = {
727
+ title: "Your Deposit",
728
+ subItems: [
729
+ { key: `Net yield`, value: `${(netYield * 100).toFixed(2)}%` },
730
+ {
731
+ key: `Performance Fee`,
732
+ value: `${(this.metadata.additionalInfo.feeBps / 100).toFixed(2)}%`
574
733
  }
575
-
576
- return [
577
- this.contract.populate('harvest', [
578
- harvest.rewardsContract.address,
579
- {
580
- id: harvest.claim.id,
581
- amount: harvest.claim.amount.toWei(),
582
- claimee: harvest.claim.claimee.address
583
- },
584
- harvest.proof,
585
- swapInfo
586
- ])
587
- ]
734
+ ],
735
+ linkedFlows: [],
736
+ style: { backgroundColor: FlowChartColors.Purple.valueOf() }
737
+ };
738
+
739
+ let _pools = [...pools];
740
+ _pools = _pools.sort(
741
+ (a, b) => Number(b.amount.toString()) - Number(a.amount.toString())
742
+ );
743
+ _pools.forEach((p) => {
744
+ const flow: IInvestmentFlow = {
745
+ title: `Pool name: ${p.pool_name}`,
746
+ subItems: [
747
+ { key: `APY`, value: `${(p.APY.netApy * 100).toFixed(2)}%` },
748
+ {
749
+ key: "Weight",
750
+ value: `${(p.current_weight * 100).toFixed(2)} / ${(
751
+ p.max_weight * 100
752
+ ).toFixed(2)}%`
753
+ }
754
+ ],
755
+ linkedFlows: [],
756
+ style: p.amount.greaterThan(0)
757
+ ? { backgroundColor: FlowChartColors.Blue.valueOf() }
758
+ : { color: "gray" }
759
+ };
760
+ baseFlow.linkedFlows.push(flow);
761
+ });
762
+ return [baseFlow];
763
+ }
764
+
765
+ async harvest(acc: Account) {
766
+ const vesuHarvest = new VesuHarvests(this.config);
767
+ const harvests = await vesuHarvest.getUnHarvestedRewards(this.address);
768
+ const harvest = harvests[0];
769
+ const avnu = new AvnuWrapper();
770
+ let swapInfo: SwapInfo = {
771
+ token_from_address: harvest.token.address,
772
+ token_from_amount: uint256.bnToUint256(harvest.actualReward.toWei()),
773
+ token_to_address: this.asset().address.address,
774
+ token_to_amount: uint256.bnToUint256(0),
775
+ token_to_min_amount: uint256.bnToUint256(0),
776
+ beneficiary: this.address.address,
777
+ integrator_fee_amount_bps: 0,
778
+ integrator_fee_recipient: this.address.address,
779
+ routes: []
780
+ };
781
+ if (!this.asset().address.eqString(harvest.token.address)) {
782
+ const quote = await avnu.getQuotes(
783
+ harvest.token.address,
784
+ this.asset().address.address,
785
+ harvest.actualReward.toWei(),
786
+ this.address.address
787
+ );
788
+ swapInfo = await avnu.getSwapInfo(
789
+ quote,
790
+ this.address.address,
791
+ 0,
792
+ this.address.address
793
+ );
588
794
  }
589
795
 
590
- /**
591
- * Calculates the fees deducted in different vTokens based on the current and previous state.
592
- * @param previousTotalSupply - The total supply of the strategy token before the transaction
593
- * @returns {Promise<Array<{ vToken: ContractAddr, fee: Web3Number }>>} Array of fees deducted in different vTokens
594
- */
595
- async getFee(allowedPools: Array<PoolInfoFull>): Promise<Array<{ vToken: ContractAddr, fee: Web3Number }>> {
596
- const assets = Web3Number.fromWei((await this.contract.total_assets()).toString(), this.asset().decimals);
597
- const totalSupply = Web3Number.fromWei((await this.contract.total_supply()).toString(), this.asset().decimals);
598
- const prevIndex = Web3Number.fromWei((await this.contract.get_previous_index()).toString(), 18);
599
- const currIndex = (new Web3Number(1, 18)).multipliedBy(assets).dividedBy(totalSupply);
600
-
601
- logger.verbose(`Previous index: ${prevIndex.toString()}`);
602
- logger.verbose(`Assets: ${assets.toString()}`);
603
- logger.verbose(`Total supply: ${totalSupply.toString()}`);
604
- logger.verbose(`Current index: ${currIndex.toNumber()}`);
605
-
606
- if (currIndex.lt(prevIndex)) {
607
- logger.verbose(`getFee::Current index is less than previous index, no fees to be deducted`);
608
- return [];
609
- }
796
+ return [
797
+ this.contract.populate("harvest", [
798
+ harvest.rewardsContract.address,
799
+ {
800
+ id: harvest.claim.id,
801
+ amount: harvest.claim.amount.toWei(),
802
+ claimee: harvest.claim.claimee.address
803
+ },
804
+ harvest.proof,
805
+ swapInfo
806
+ ])
807
+ ];
808
+ }
610
809
 
611
- const indexDiff = currIndex.minus(prevIndex);
612
- logger.verbose(`Index diff: ${indexDiff.toString()}`);
613
- const numerator = totalSupply.multipliedBy(indexDiff).multipliedBy(this.metadata.additionalInfo.feeBps);
614
- const denominator = 10000;
615
- let fee = numerator.dividedBy(denominator);
616
- logger.verbose(`Fee: ${fee.toString()}`);
810
+ /**
811
+ * Calculates the fees deducted in different vTokens based on the current and previous state.
812
+ * @param previousTotalSupply - The total supply of the strategy token before the transaction
813
+ * @returns {Promise<Array<{ vToken: ContractAddr, fee: Web3Number }>>} Array of fees deducted in different vTokens
814
+ */
815
+ async getFee(
816
+ allowedPools: Array<PoolInfoFull>
817
+ ): Promise<Array<{ vToken: ContractAddr; fee: Web3Number }>> {
818
+ const assets = Web3Number.fromWei(
819
+ (await this.contract.total_assets()).toString(),
820
+ this.asset().decimals
821
+ );
822
+ const totalSupply = Web3Number.fromWei(
823
+ (await this.contract.total_supply()).toString(),
824
+ this.asset().decimals
825
+ );
826
+ const prevIndex = Web3Number.fromWei(
827
+ (await this.contract.get_previous_index()).toString(),
828
+ 18
829
+ );
830
+ const currIndex = new Web3Number(1, 18)
831
+ .multipliedBy(assets)
832
+ .dividedBy(totalSupply);
617
833
 
618
- if (fee.lte(0)) {
619
- return [];
620
- }
834
+ logger.verbose(`Previous index: ${prevIndex.toString()}`);
835
+ logger.verbose(`Assets: ${assets.toString()}`);
836
+ logger.verbose(`Total supply: ${totalSupply.toString()}`);
837
+ logger.verbose(`Current index: ${currIndex.toNumber()}`);
838
+
839
+ if (currIndex.lt(prevIndex)) {
840
+ logger.verbose(
841
+ `getFee::Current index is less than previous index, no fees to be deducted`
842
+ );
843
+ return [];
844
+ }
621
845
 
622
- const fees: Array<{ vToken: ContractAddr, fee: Web3Number }> = [];
623
- let remainingFee = fee.plus(Web3Number.fromWei("100", this.asset().decimals));
846
+ const indexDiff = currIndex.minus(prevIndex);
847
+ logger.verbose(`Index diff: ${indexDiff.toString()}`);
848
+ const numerator = totalSupply
849
+ .multipliedBy(indexDiff)
850
+ .multipliedBy(this.metadata.additionalInfo.feeBps);
851
+ const denominator = 10000;
852
+ let fee = numerator.dividedBy(denominator);
853
+ logger.verbose(`Fee: ${fee.toString()}`);
624
854
 
625
- for (const pool of allowedPools) {
626
- const vToken = pool.v_token;
627
- const balance = pool.amount;
855
+ if (fee.lte(0)) {
856
+ return [];
857
+ }
628
858
 
629
- if (remainingFee.lte(balance)) {
630
- fees.push({ vToken, fee: remainingFee });
631
- break;
632
- } else {
633
- fees.push({ vToken, fee: Web3Number.fromWei(balance.toString(), 18) });
634
- remainingFee = remainingFee.minus(Web3Number.fromWei(balance.toString(), 18));
635
- }
636
- }
859
+ const fees: Array<{ vToken: ContractAddr; fee: Web3Number }> = [];
860
+ let remainingFee = fee.plus(
861
+ Web3Number.fromWei("100", this.asset().decimals)
862
+ );
637
863
 
638
- logger.verbose(`Fees: ${JSON.stringify(fees)}`);
864
+ for (const pool of allowedPools) {
865
+ const vToken = pool.v_token;
866
+ const balance = pool.amount;
639
867
 
640
- return fees;
868
+ if (remainingFee.lte(balance)) {
869
+ fees.push({ vToken, fee: remainingFee });
870
+ break;
871
+ } else {
872
+ fees.push({ vToken, fee: Web3Number.fromWei(balance.toString(), 18) });
873
+ remainingFee = remainingFee.minus(
874
+ Web3Number.fromWei(balance.toString(), 18)
875
+ );
876
+ }
641
877
  }
878
+
879
+ logger.verbose(`Fees: ${JSON.stringify(fees)}`);
880
+
881
+ return fees;
882
+ }
642
883
  }
643
884
 
644
- const _description = "Automatically diversify {{TOKEN}} holdings into different Vesu pools while reducing risk and maximizing yield. Defi spring STRK Rewards are auto-compounded as well."
645
- const _protocol: IProtocol = {name: 'Vesu', logo: 'https://static-assets-8zct.onrender.com/integrations/vesu/logo.png'}
885
+ const _description =
886
+ "Automatically diversify {{TOKEN}} holdings into different Vesu pools while reducing risk and maximizing yield. Defi spring STRK Rewards are auto-compounded as well.";
887
+ const _protocol: IProtocol = {
888
+ name: "Vesu",
889
+ logo: "https://static-assets-8zct.onrender.com/integrations/vesu/logo.png"
890
+ };
646
891
  // need to fine tune better
647
892
  const _riskFactor: RiskFactor[] = [
648
- {type: RiskType.SMART_CONTRACT_RISK, value: 0.5, weight: 25},
649
- {type: RiskType.COUNTERPARTY_RISK, value: 1, weight: 50},
650
- {type: RiskType.ORACLE_RISK, value: 0.5, weight: 25},
651
- ]
652
- const AUDIT_URL = 'https://assets.strkfarm.com/strkfarm/audit_report_vesu_and_ekubo_strats.pdf';
893
+ { type: RiskType.SMART_CONTRACT_RISK, value: 0.5, weight: 25 },
894
+ { type: RiskType.COUNTERPARTY_RISK, value: 1, weight: 50 },
895
+ { type: RiskType.ORACLE_RISK, value: 0.5, weight: 25 }
896
+ ];
897
+ const AUDIT_URL =
898
+ "https://assets.strkfarm.com/strkfarm/audit_report_vesu_and_ekubo_strats.pdf";
899
+
900
+ const faqs: FAQ[] = [
901
+ {
902
+ question: "What is the Vesu Rebalancing Strategy?",
903
+ answer:
904
+ "The Vesu Rebalancing Strategy is an automated investment strategy that diversifies your holdings across multiple Vesu pools. It optimizes yield by rebalancing assets based on pool performance while adhering to risk constraints."
905
+ },
906
+ {
907
+ question: "Will I earn Vesu points?",
908
+ answer: "Yes, of course! You will earn Vesu points for your deposits."
909
+ },
910
+ {
911
+ question: "How does the strategy optimize yield?",
912
+ answer:
913
+ "The strategy calculates the weighted average APY across all pools and reallocates assets to maximize returns. It prioritizes high-performing pools while ensuring compliance with maximum weight constraints."
914
+ },
915
+ {
916
+ question: "What are the risks associated with this strategy?",
917
+ answer:
918
+ "The strategy involves usual DeFi risks such as smart contract vulnerabilities, counterparty risks, and oracle inaccuracies. However, we try our best to reduce these risks through audits and careful pool selection."
919
+ },
920
+ {
921
+ question: "How are fees calculated and deducted?",
922
+ answer:
923
+ "Fees are calculated based on the performance of the strategy and deducted proportionally from the total assets. We charge a 10% performance fee and is already accounted in the APY shown."
924
+ },
925
+ {
926
+ question: "What happens if a pool exceeds its maximum weight?",
927
+ answer:
928
+ "If a pool exceeds its maximum weight, the strategy rebalances by withdrawing excess funds and reallocating them to other pools with available capacity."
929
+ },
930
+ {
931
+ question: "Can I withdraw my assets at any time?",
932
+ answer:
933
+ "Yes, you can withdraw your assets at any time. In rare circumstances, if debt utilisation is high for certain pools on Vesu, it may not be possible to withdraw until markets restore balance."
934
+ },
935
+ {
936
+ question: "What happens to my Defi Spring STRK rewards?",
937
+ answer:
938
+ "STRK rewards are automatically harvested and reinvested into the strategy every week to maximize compounding returns."
939
+ },
940
+ {
941
+ question: "Is the strategy audited?",
942
+ answer:
943
+ <div>Yes, the strategy has been audited. You can review the audit report in our docs <a href="https://docs.strkfarm.com/p/strategies/vesu-fusion-rebalancing-vaults#technical-details" style={{textDecoration: 'underline', marginLeft: '5px'}}>Here</a>.</div>
944
+ }
945
+ ];
946
+
653
947
  /**
654
948
  * Represents the Vesu Rebalance Strategies.
655
949
  */
656
- export const VesuRebalanceStrategies: IStrategyMetadata<VesuRebalanceSettings>[] = [{
657
- name: 'Vesu Fusion STRK',
658
- description: _description.replace('{{TOKEN}}', 'STRK'),
659
- address: ContractAddr.from('0x7fb5bcb8525954a60fde4e8fb8220477696ce7117ef264775a1770e23571929'),
660
- type: 'ERC4626',
661
- depositTokens: [Global.getDefaultTokens().find(t => t.symbol === 'STRK')!],
662
- protocols: [_protocol],
663
- auditUrl: AUDIT_URL,
664
- maxTVL: Web3Number.fromWei('0', 18),
665
- risk: {
950
+ export const VesuRebalanceStrategies: IStrategyMetadata<VesuRebalanceSettings>[] =
951
+ [
952
+ {
953
+ name: "Vesu Fusion STRK",
954
+ description: _description.replace("{{TOKEN}}", "STRK"),
955
+ address: ContractAddr.from(
956
+ "0x7fb5bcb8525954a60fde4e8fb8220477696ce7117ef264775a1770e23571929"
957
+ ),
958
+ launchBlock: 0,
959
+ type: "ERC4626",
960
+ depositTokens: [
961
+ Global.getDefaultTokens().find((t) => t.symbol === "STRK")!
962
+ ],
963
+ protocols: [_protocol],
964
+ auditUrl: AUDIT_URL,
965
+ maxTVL: Web3Number.fromWei("0", 18),
966
+ risk: {
666
967
  riskFactor: _riskFactor,
667
- netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
968
+ netRisk:
969
+ _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
970
+ _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
668
971
  notARisks: getNoRiskTags(_riskFactor)
972
+ },
973
+ additionalInfo: {
974
+ feeBps: 1000
975
+ },
976
+ faqs
669
977
  },
670
- additionalInfo: {
671
- feeBps: 1000,
672
- },
673
- }, {
674
- name: 'Vesu Fusion ETH',
675
- description: _description.replace('{{TOKEN}}', 'ETH'),
676
- address: ContractAddr.from('0x5eaf5ee75231cecf79921ff8ded4b5ffe96be718bcb3daf206690ad1a9ad0ca'),
677
- type: 'ERC4626',
678
- auditUrl: AUDIT_URL,
679
- depositTokens: [Global.getDefaultTokens().find(t => t.symbol === 'ETH')!],
680
- protocols: [_protocol],
681
- maxTVL: Web3Number.fromWei('0', 18),
682
- risk: {
978
+ {
979
+ name: "Vesu Fusion ETH",
980
+ description: _description.replace("{{TOKEN}}", "ETH"),
981
+ address: ContractAddr.from(
982
+ "0x5eaf5ee75231cecf79921ff8ded4b5ffe96be718bcb3daf206690ad1a9ad0ca"
983
+ ),
984
+ launchBlock: 0,
985
+ type: "ERC4626",
986
+ auditUrl: AUDIT_URL,
987
+ depositTokens: [
988
+ Global.getDefaultTokens().find((t) => t.symbol === "ETH")!
989
+ ],
990
+ protocols: [_protocol],
991
+ maxTVL: Web3Number.fromWei("0", 18),
992
+ risk: {
683
993
  riskFactor: _riskFactor,
684
- netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
994
+ netRisk:
995
+ _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
996
+ _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
685
997
  notARisks: getNoRiskTags(_riskFactor)
998
+ },
999
+ additionalInfo: {
1000
+ feeBps: 1000
1001
+ },
1002
+ faqs
686
1003
  },
687
- additionalInfo: {
688
- feeBps: 1000,
689
- },
690
- }, {
691
- name: 'Vesu Fusion USDC',
692
- description: _description.replace('{{TOKEN}}', 'USDC'),
693
- address: ContractAddr.from('0xa858c97e9454f407d1bd7c57472fc8d8d8449a777c822b41d18e387816f29c'),
694
- type: 'ERC4626',
695
- auditUrl: AUDIT_URL,
696
- depositTokens: [Global.getDefaultTokens().find(t => t.symbol === 'USDC')!],
697
- protocols: [_protocol],
698
- maxTVL: Web3Number.fromWei('0', 6),
699
- risk: {
1004
+ {
1005
+ name: "Vesu Fusion USDC",
1006
+ description: _description.replace("{{TOKEN}}", "USDC"),
1007
+ address: ContractAddr.from(
1008
+ "0xa858c97e9454f407d1bd7c57472fc8d8d8449a777c822b41d18e387816f29c"
1009
+ ),
1010
+ launchBlock: 0,
1011
+ type: "ERC4626",
1012
+ auditUrl: AUDIT_URL,
1013
+ depositTokens: [
1014
+ Global.getDefaultTokens().find((t) => t.symbol === "USDC")!
1015
+ ],
1016
+ protocols: [_protocol],
1017
+ maxTVL: Web3Number.fromWei("0", 6),
1018
+ risk: {
700
1019
  riskFactor: _riskFactor,
701
- netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
1020
+ netRisk:
1021
+ _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
1022
+ _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
702
1023
  notARisks: getNoRiskTags(_riskFactor)
1024
+ },
1025
+ additionalInfo: {
1026
+ feeBps: 1000
1027
+ },
1028
+ faqs
703
1029
  },
704
- additionalInfo: {
705
- feeBps: 1000,
706
- },
707
- }, {
708
- name: 'Vesu Fusion USDT',
709
- description: _description.replace('{{TOKEN}}', 'USDT'),
710
- address: ContractAddr.from('0x115e94e722cfc4c77a2f15c4aefb0928c1c0029e5a57570df24c650cb7cec2c'),
711
- type: 'ERC4626',
712
- depositTokens: [Global.getDefaultTokens().find(t => t.symbol === 'USDT')!],
713
- auditUrl: AUDIT_URL,
714
- protocols: [_protocol],
715
- maxTVL: Web3Number.fromWei('0', 6),
716
- risk: {
1030
+ {
1031
+ name: "Vesu Fusion USDT",
1032
+ description: _description.replace("{{TOKEN}}", "USDT"),
1033
+ address: ContractAddr.from(
1034
+ "0x115e94e722cfc4c77a2f15c4aefb0928c1c0029e5a57570df24c650cb7cec2c"
1035
+ ),
1036
+ launchBlock: 0,
1037
+ type: "ERC4626",
1038
+ depositTokens: [
1039
+ Global.getDefaultTokens().find((t) => t.symbol === "USDT")!
1040
+ ],
1041
+ auditUrl: AUDIT_URL,
1042
+ protocols: [_protocol],
1043
+ maxTVL: Web3Number.fromWei("0", 6),
1044
+ risk: {
717
1045
  riskFactor: _riskFactor,
718
- netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
1046
+ netRisk:
1047
+ _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
1048
+ _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
719
1049
  notARisks: getNoRiskTags(_riskFactor)
720
- },
721
- additionalInfo: {
722
- feeBps: 1000,
723
- },
724
- // }, {
725
- // name: 'Vesu Fusion WBTC',
726
- // description: _description.replace('{{TOKEN}}', 'WBTC'),
727
- // address: ContractAddr.from('0x778007f8136a5b827325d21613803e796bda4d676fbe1e34aeab0b2a2ec027f'),
728
- // type: 'ERC4626',
729
- // depositTokens: [Global.getDefaultTokens().find(t => t.symbol === 'WBTC')!],
730
- // auditUrl: AUDIT_URL,
731
- // protocols: [_protocol],
732
- // maxTVL: Web3Number.fromWei('0', 8),
733
- // risk: {
734
- // riskFactor: _riskFactor,
735
- // netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
736
- // },
737
- // additionalInfo: {
738
- // feeBps: 1000,
739
- // },
740
- }]
1050
+ },
1051
+ additionalInfo: {
1052
+ feeBps: 1000
1053
+ },
1054
+ faqs
1055
+ // }, {
1056
+ // name: 'Vesu Fusion WBTC',
1057
+ // description: _description.replace('{{TOKEN}}', 'WBTC'),
1058
+ // address: ContractAddr.from('0x778007f8136a5b827325d21613803e796bda4d676fbe1e34aeab0b2a2ec027f'),
1059
+ // type: 'ERC4626',
1060
+ // depositTokens: [Global.getDefaultTokens().find(t => t.symbol === 'WBTC')!],
1061
+ // auditUrl: AUDIT_URL,
1062
+ // protocols: [_protocol],
1063
+ // maxTVL: Web3Number.fromWei('0', 8),
1064
+ // risk: {
1065
+ // riskFactor: _riskFactor,
1066
+ // netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
1067
+ // },
1068
+ // additionalInfo: {
1069
+ // feeBps: 1000,
1070
+ // },
1071
+ }
1072
+ ];