@strkfarm/sdk 1.0.36 → 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.
- package/dist/cli.js +236 -112
- package/dist/cli.mjs +241 -113
- package/dist/index.browser.global.js +15678 -7543
- package/dist/index.browser.mjs +870 -309
- package/dist/index.d.ts +9 -4
- package/dist/index.js +865 -308
- package/dist/index.mjs +870 -309
- package/package.json +1 -2
- package/src/interfaces/common.ts +111 -98
- package/src/strategies/ekubo-cl-vault.tsx +1434 -923
- package/src/strategies/vesu-rebalance.tsx +937 -610
- package/src/node/headless.node.ts +0 -36
|
@@ -1,258 +1,401 @@
|
|
|
1
1
|
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
2
|
-
import {
|
|
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
|
|
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 {
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
30
|
+
pool_id: ContractAddr;
|
|
31
|
+
max_weight: number;
|
|
32
|
+
v_token: ContractAddr;
|
|
19
33
|
}
|
|
20
34
|
|
|
21
35
|
interface Change {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
36
|
+
pool_id: ContractAddr;
|
|
37
|
+
changeAmt: Web3Number;
|
|
38
|
+
finalAmt: Web3Number;
|
|
39
|
+
isDeposit: boolean;
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
export interface VesuRebalanceSettings {
|
|
29
|
-
|
|
43
|
+
feeBps: number;
|
|
30
44
|
}
|
|
31
45
|
|
|
32
46
|
interface PoolInfoFull {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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<
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
103
|
+
this.contract = new Contract(
|
|
104
|
+
VesuRebalanceAbi,
|
|
105
|
+
this.address.address,
|
|
106
|
+
this.config.provider
|
|
107
|
+
);
|
|
108
|
+
}
|
|
116
109
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
232
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
460
|
+
let { pools, isErrorPoolsAPI } = await this.getVesuPools();
|
|
314
461
|
|
|
315
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
328
|
-
|
|
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
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
) {
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
|
|
619
|
-
|
|
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
|
-
|
|
623
|
-
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
854
|
+
if (fee.lte(0)) {
|
|
855
|
+
return [];
|
|
856
|
+
}
|
|
628
857
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
|
|
863
|
+
for (const pool of allowedPools) {
|
|
864
|
+
const vToken = pool.v_token;
|
|
865
|
+
const balance = pool.amount;
|
|
639
866
|
|
|
640
|
-
|
|
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 =
|
|
645
|
-
|
|
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
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
]
|
|
652
|
-
const AUDIT_URL =
|
|
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
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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:
|
|
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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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:
|
|
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
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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:
|
|
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
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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:
|
|
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
|
-
|
|
722
|
-
feeBps: 1000
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
//
|
|
726
|
-
//
|
|
727
|
-
//
|
|
728
|
-
//
|
|
729
|
-
//
|
|
730
|
-
//
|
|
731
|
-
//
|
|
732
|
-
//
|
|
733
|
-
//
|
|
734
|
-
//
|
|
735
|
-
//
|
|
736
|
-
//
|
|
737
|
-
//
|
|
738
|
-
//
|
|
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
|
+
];
|