@strkfarm/sdk 1.0.37 → 1.0.38

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