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