@strkfarm/sdk 2.0.0-dev.9 → 2.0.0-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.global.js +111371 -93151
- package/dist/index.browser.mjs +27815 -32690
- package/dist/index.d.ts +1095 -2011
- package/dist/index.js +27425 -32309
- package/dist/index.mjs +27590 -32452
- package/package.json +6 -5
- package/src/data/ekubo-price-fethcer.abi.json +265 -0
- package/src/data/universal-vault.abi.json +20 -135
- package/src/dataTypes/address.ts +0 -7
- package/src/dataTypes/index.ts +3 -2
- package/src/dataTypes/mynumber.ts +141 -0
- package/src/global.ts +296 -288
- package/src/index.browser.ts +6 -5
- package/src/interfaces/common.tsx +324 -184
- package/src/modules/apollo-client-config.ts +28 -0
- package/src/modules/avnu.ts +4 -17
- package/src/modules/ekubo-pricer.ts +79 -0
- package/src/modules/ekubo-quoter.ts +11 -88
- package/src/modules/erc20.ts +21 -67
- package/src/modules/harvests.ts +26 -15
- package/src/modules/index.ts +11 -13
- package/src/modules/lst-apr.ts +0 -36
- package/src/modules/pragma.ts +23 -8
- package/src/modules/pricer-from-api.ts +150 -14
- package/src/modules/pricer.ts +2 -1
- package/src/modules/pricerBase.ts +2 -1
- package/src/node/deployer.ts +36 -1
- package/src/node/pricer-redis.ts +2 -1
- package/src/strategies/autoCompounderStrk.ts +1 -1
- package/src/strategies/base-strategy.ts +5 -22
- package/src/strategies/ekubo-cl-vault.tsx +2904 -2175
- package/src/strategies/factory.ts +165 -0
- package/src/strategies/index.ts +10 -11
- package/src/strategies/registry.ts +268 -0
- package/src/strategies/sensei.ts +416 -292
- package/src/strategies/universal-adapters/adapter-utils.ts +1 -5
- package/src/strategies/universal-adapters/baseAdapter.ts +153 -181
- package/src/strategies/universal-adapters/common-adapter.ts +77 -98
- package/src/strategies/universal-adapters/index.ts +1 -5
- package/src/strategies/universal-adapters/vesu-adapter.ts +218 -220
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +51 -58
- package/src/strategies/universal-lst-muliplier-strategy.tsx +1952 -992
- package/src/strategies/universal-strategy.tsx +1713 -1150
- package/src/strategies/vesu-rebalance.tsx +1189 -986
- package/src/utils/health-factor-math.ts +5 -11
- package/src/utils/index.ts +8 -9
- package/src/utils/strategy-utils.ts +57 -0
- package/src/data/extended-deposit.abi.json +0 -3613
- package/src/modules/ExtendedWrapperSDk/index.ts +0 -62
- package/src/modules/ExtendedWrapperSDk/types.ts +0 -311
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +0 -395
- package/src/modules/midas.ts +0 -159
- package/src/modules/token-market-data.ts +0 -202
- package/src/strategies/svk-strategy.ts +0 -247
- package/src/strategies/universal-adapters/adapter-optimizer.ts +0 -65
- package/src/strategies/universal-adapters/avnu-adapter.ts +0 -413
- package/src/strategies/universal-adapters/extended-adapter.ts +0 -972
- package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +0 -1306
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +0 -34
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +0 -77
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +0 -49
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +0 -370
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +0 -1379
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
FAQ,
|
|
4
|
+
FlowChartColors,
|
|
5
|
+
getNoRiskTags,
|
|
6
|
+
highlightTextWithLinks,
|
|
7
|
+
IConfig,
|
|
8
|
+
IInvestmentFlow,
|
|
9
|
+
IProtocol,
|
|
10
|
+
IStrategyMetadata,
|
|
11
|
+
RiskFactor,
|
|
12
|
+
RiskType,
|
|
13
|
+
StrategyCategory,
|
|
14
|
+
StrategyTag,
|
|
15
|
+
StrategyLiveStatus,
|
|
16
|
+
StrategySettings,
|
|
17
|
+
AuditStatus,
|
|
18
|
+
SourceCodeType,
|
|
19
|
+
AccessControlType,
|
|
20
|
+
InstantWithdrawalVault
|
|
13
21
|
} from "@/interfaces";
|
|
14
22
|
import { AvnuWrapper, Pricer, SwapInfo } from "@/modules";
|
|
15
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
Account,
|
|
25
|
+
CairoCustomEnum,
|
|
26
|
+
Contract,
|
|
27
|
+
num,
|
|
28
|
+
uint256,
|
|
29
|
+
BlockIdentifier
|
|
30
|
+
} from "starknet";
|
|
16
31
|
import VesuRebalanceAbi from "@/data/vesu-rebalance.abi.json";
|
|
17
32
|
import { Global } from "@/global";
|
|
18
33
|
import { assert } from "@/utils";
|
|
@@ -20,48 +35,47 @@ import { logger } from "@/utils/logger";
|
|
|
20
35
|
import axios from "axios";
|
|
21
36
|
import { PricerBase } from "@/modules/pricerBase";
|
|
22
37
|
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
SingleTokenInfo
|
|
38
|
+
BaseStrategy,
|
|
39
|
+
SingleActionAmount,
|
|
40
|
+
SingleTokenInfo
|
|
27
41
|
} from "./base-strategy";
|
|
28
42
|
import { getAPIUsingHeadlessBrowser } from "@/node/headless";
|
|
29
|
-
import {
|
|
43
|
+
import { HarvestInfo, VesuHarvests } from "@/modules/harvests";
|
|
30
44
|
import VesuPoolIDs from "@/data/vesu_pools.json";
|
|
31
45
|
import { COMMON_CONTRACTS, ENDPOINTS } from "./constants";
|
|
32
46
|
|
|
33
47
|
interface PoolProps {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
48
|
+
pool_id: ContractAddr;
|
|
49
|
+
max_weight: number;
|
|
50
|
+
v_token: ContractAddr;
|
|
37
51
|
}
|
|
38
52
|
|
|
39
53
|
interface Change {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
54
|
+
pool_id: ContractAddr;
|
|
55
|
+
changeAmt: Web3Number;
|
|
56
|
+
finalAmt: Web3Number;
|
|
57
|
+
isDeposit: boolean;
|
|
44
58
|
}
|
|
45
59
|
|
|
46
60
|
export interface VesuRebalanceSettings {
|
|
47
|
-
|
|
61
|
+
feeBps: number;
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
interface PoolInfoFull {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
pool_id: ContractAddr;
|
|
66
|
+
pool_name: string | undefined;
|
|
67
|
+
max_weight: number;
|
|
68
|
+
current_weight: number;
|
|
69
|
+
v_token: ContractAddr;
|
|
70
|
+
amount: Web3Number;
|
|
71
|
+
usdValue: Web3Number;
|
|
72
|
+
APY: {
|
|
73
|
+
baseApy: number;
|
|
74
|
+
defiSpringApy: number;
|
|
75
|
+
netApy: number;
|
|
76
|
+
};
|
|
77
|
+
currentUtilization: number;
|
|
78
|
+
maxUtilization: number;
|
|
65
79
|
}
|
|
66
80
|
/**
|
|
67
81
|
* Represents a VesuRebalance strategy.
|
|
@@ -69,336 +83,510 @@ interface PoolInfoFull {
|
|
|
69
83
|
* managing deposits and withdrawals while optimizing yield through STRK rewards.
|
|
70
84
|
*/
|
|
71
85
|
export class VesuRebalance extends BaseStrategy<
|
|
72
|
-
|
|
73
|
-
|
|
86
|
+
SingleTokenInfo,
|
|
87
|
+
SingleActionAmount
|
|
74
88
|
> {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Creates a deposit call to the strategy contract.
|
|
116
|
-
* @param assets - Amount of assets to deposit
|
|
117
|
-
* @param receiver - Address that will receive the strategy tokens
|
|
118
|
-
* @returns Populated contract call for deposit
|
|
119
|
-
*/
|
|
120
|
-
async depositCall(amountInfo: SingleActionAmount, receiver: ContractAddr) {
|
|
121
|
-
// Technically its not erc4626 abi, but we just need approve call
|
|
122
|
-
// so, its ok to use it
|
|
123
|
-
assert(
|
|
124
|
-
amountInfo.tokenInfo.address.eq(this.asset().address),
|
|
125
|
-
"Deposit token mismatch"
|
|
126
|
-
);
|
|
127
|
-
const assetContract = new Contract({
|
|
128
|
-
abi: VesuRebalanceAbi,
|
|
129
|
-
address: this.asset().address.address,
|
|
130
|
-
providerOrAccount: this.config.provider
|
|
131
|
-
});
|
|
132
|
-
const call1 = assetContract.populate("approve", [
|
|
133
|
-
this.address.address,
|
|
134
|
-
uint256.bnToUint256(amountInfo.amount.toWei())
|
|
135
|
-
]);
|
|
136
|
-
const call2 = this.contract.populate("deposit", [
|
|
137
|
-
uint256.bnToUint256(amountInfo.amount.toWei()),
|
|
138
|
-
receiver.address
|
|
139
|
-
]);
|
|
140
|
-
return [call1, call2];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Creates a withdrawal call to the strategy contract.
|
|
145
|
-
* @param assets - Amount of assets to withdraw
|
|
146
|
-
* @param receiver - Address that will receive the withdrawn assets
|
|
147
|
-
* @param owner - Address that owns the strategy tokens
|
|
148
|
-
* @returns Populated contract call for withdrawal
|
|
149
|
-
*/
|
|
150
|
-
async withdrawCall(
|
|
151
|
-
amountInfo: SingleActionAmount,
|
|
152
|
-
receiver: ContractAddr,
|
|
153
|
-
owner: ContractAddr
|
|
154
|
-
) {
|
|
155
|
-
return [
|
|
156
|
-
this.contract.populate("withdraw", [
|
|
157
|
-
uint256.bnToUint256(amountInfo.amount.toWei()),
|
|
158
|
-
receiver.address,
|
|
159
|
-
owner.address
|
|
160
|
-
])
|
|
161
|
-
];
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Returns the underlying asset token of the strategy.
|
|
166
|
-
* @returns The deposit token supported by this strategy
|
|
167
|
-
*/
|
|
168
|
-
asset() {
|
|
169
|
-
return this.metadata.depositTokens[0];
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Returns the number of decimals used by the strategy token.
|
|
174
|
-
* @returns Number of decimals (same as the underlying token)
|
|
175
|
-
*/
|
|
176
|
-
decimals() {
|
|
177
|
-
return this.metadata.depositTokens[0].decimals; // same as underlying token
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Calculates the Total Value Locked (TVL) for a specific user.
|
|
182
|
-
* @param user - Address of the user
|
|
183
|
-
* @returns Object containing the amount in token units and USD value
|
|
184
|
-
*/
|
|
185
|
-
async getUserTVL(user: ContractAddr) {
|
|
186
|
-
const shares = await this.contract.balance_of(user.address);
|
|
187
|
-
const assets = await this.contract.convert_to_assets(
|
|
188
|
-
uint256.bnToUint256(shares)
|
|
189
|
-
);
|
|
190
|
-
const amount = Web3Number.fromWei(
|
|
191
|
-
assets.toString(),
|
|
192
|
-
this.metadata.depositTokens[0].decimals
|
|
193
|
-
);
|
|
194
|
-
let price = await this.pricer.getPrice(
|
|
195
|
-
this.metadata.depositTokens[0].symbol
|
|
196
|
-
);
|
|
197
|
-
const usdValue = Number(amount.toFixed(6)) * price.price;
|
|
198
|
-
return {
|
|
199
|
-
tokenInfo: this.asset(),
|
|
200
|
-
amount,
|
|
201
|
-
usdValue
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Calculates the total TVL of the strategy.
|
|
207
|
-
* @returns Object containing the total amount in token units and USD value
|
|
208
|
-
*/
|
|
209
|
-
async getTVL() {
|
|
210
|
-
const assets = await this.contract.total_assets();
|
|
211
|
-
const amount = Web3Number.fromWei(
|
|
212
|
-
assets.toString(),
|
|
213
|
-
this.metadata.depositTokens[0].decimals
|
|
214
|
-
);
|
|
215
|
-
let price = await this.pricer.getPrice(
|
|
216
|
-
this.metadata.depositTokens[0].symbol
|
|
217
|
-
);
|
|
218
|
-
const usdValue = Number(amount.toFixed(6)) * price.price;
|
|
219
|
-
return {
|
|
220
|
-
tokenInfo: this.asset(),
|
|
221
|
-
amount,
|
|
222
|
-
usdValue
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
static async getAllPossibleVerifiedPools(asset: ContractAddr) {
|
|
227
|
-
const data = await getAPIUsingHeadlessBrowser(`${ENDPOINTS.VESU_BASE_STAGING}/pools`);
|
|
228
|
-
const verifiedPools = data.data.filter((d: any) => d.isVerified);
|
|
229
|
-
const pools = verifiedPools
|
|
230
|
-
.map((p: any) => {
|
|
231
|
-
const hasMyAsset = p.assets.find((a: any) => asset.eqString(a.address));
|
|
232
|
-
if (hasMyAsset) {
|
|
233
|
-
return {
|
|
234
|
-
pool_id: ContractAddr.from(p.id),
|
|
235
|
-
max_weight: 10000,
|
|
236
|
-
v_token: ContractAddr.from(hasMyAsset.vToken.address),
|
|
237
|
-
name: p.name
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
return null;
|
|
241
|
-
})
|
|
242
|
-
.filter((p: PoolProps | null) => p !== null);
|
|
243
|
-
return pools;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async getPoolInfo(
|
|
247
|
-
p: PoolProps,
|
|
248
|
-
pools: any[],
|
|
249
|
-
vesuPositions: any[],
|
|
250
|
-
totalAssets: Web3Number,
|
|
251
|
-
isErrorPositionsAPI: boolean,
|
|
252
|
-
isErrorPoolsAPI: boolean
|
|
253
|
-
) {
|
|
254
|
-
const vesuPosition = vesuPositions.find(
|
|
255
|
-
(d: any) =>
|
|
256
|
-
ContractAddr.from(d.id).eq(p.pool_id)
|
|
257
|
-
);
|
|
258
|
-
const _pool = pools.find((d: any) => {
|
|
259
|
-
logger.verbose(
|
|
260
|
-
`pool check: ${
|
|
261
|
-
ContractAddr.from(d.id).eq(p.pool_id)
|
|
262
|
-
}, id: ${d.id}, pool_id: ${num.getDecimalString(
|
|
263
|
-
p.pool_id.address.toString()
|
|
264
|
-
)}`
|
|
265
|
-
);
|
|
266
|
-
return ContractAddr.from(d.id).eq(p.pool_id);
|
|
267
|
-
});
|
|
268
|
-
logger.verbose(`pool: ${JSON.stringify(_pool)}`);
|
|
269
|
-
logger.verbose(typeof _pool);
|
|
270
|
-
logger.verbose(`name: ${_pool?.name}`);
|
|
271
|
-
const name = _pool?.name;
|
|
272
|
-
logger.verbose(
|
|
273
|
-
`name2: ${name}, ${!name ? true : false}, ${name?.length}, ${typeof name}`
|
|
274
|
-
);
|
|
275
|
-
const assetInfo = _pool?.assets.find((d: any) =>
|
|
276
|
-
this.asset().address.eqString(d.address)
|
|
277
|
-
);
|
|
278
|
-
if (!name) {
|
|
279
|
-
logger.verbose(`Pool not found`);
|
|
280
|
-
throw new Error(`Pool name ${p.pool_id.address.toString()} not found`);
|
|
89
|
+
/** Contract address of the strategy */
|
|
90
|
+
readonly address: ContractAddr;
|
|
91
|
+
/** Pricer instance for token price calculations */
|
|
92
|
+
readonly pricer: PricerBase;
|
|
93
|
+
/** Metadata containing strategy information */
|
|
94
|
+
readonly metadata: IStrategyMetadata<VesuRebalanceSettings>;
|
|
95
|
+
/** Contract instance for interacting with the strategy */
|
|
96
|
+
readonly contract: Contract;
|
|
97
|
+
readonly BASE_WEIGHT = 10000; // 10000 bps = 100%
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Creates a new VesuRebalance strategy instance.
|
|
101
|
+
* @param config - Configuration object containing provider and other settings
|
|
102
|
+
* @param pricer - Pricer instance for token price calculations
|
|
103
|
+
* @param metadata - Strategy metadata including deposit tokens and address
|
|
104
|
+
* @throws {Error} If more than one deposit token is specified
|
|
105
|
+
*/
|
|
106
|
+
constructor(
|
|
107
|
+
config: IConfig,
|
|
108
|
+
pricer: PricerBase,
|
|
109
|
+
metadata: IStrategyMetadata<VesuRebalanceSettings>
|
|
110
|
+
) {
|
|
111
|
+
super(config);
|
|
112
|
+
this.pricer = pricer;
|
|
113
|
+
|
|
114
|
+
assert(
|
|
115
|
+
metadata.depositTokens.length === 1,
|
|
116
|
+
"VesuRebalance only supports 1 deposit token"
|
|
117
|
+
);
|
|
118
|
+
this.metadata = metadata;
|
|
119
|
+
this.address = metadata.address;
|
|
120
|
+
|
|
121
|
+
this.contract = new Contract({
|
|
122
|
+
abi: VesuRebalanceAbi,
|
|
123
|
+
address: this.address.address,
|
|
124
|
+
providerOrAccount: this.config.provider
|
|
125
|
+
});
|
|
281
126
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Creates a deposit call to the strategy contract.
|
|
130
|
+
* @param assets - Amount of assets to deposit
|
|
131
|
+
* @param receiver - Address that will receive the strategy tokens
|
|
132
|
+
* @returns Populated contract call for deposit
|
|
133
|
+
*/
|
|
134
|
+
async depositCall(amountInfo: SingleActionAmount, receiver: ContractAddr) {
|
|
135
|
+
// Technically its not erc4626 abi, but we just need approve call
|
|
136
|
+
// so, its ok to use it
|
|
137
|
+
assert(
|
|
138
|
+
amountInfo.tokenInfo.address.eq(this.asset().address),
|
|
139
|
+
"Deposit token mismatch"
|
|
140
|
+
);
|
|
141
|
+
const assetContract = new Contract({
|
|
142
|
+
abi: VesuRebalanceAbi,
|
|
143
|
+
address: this.asset().address.address,
|
|
144
|
+
providerOrAccount: this.config.provider
|
|
145
|
+
});
|
|
146
|
+
const call1 = assetContract.populate("approve", [
|
|
147
|
+
this.address.address,
|
|
148
|
+
uint256.bnToUint256(amountInfo.amount.toWei())
|
|
149
|
+
]);
|
|
150
|
+
const call2 = this.contract.populate("deposit", [
|
|
151
|
+
uint256.bnToUint256(amountInfo.amount.toWei()),
|
|
152
|
+
receiver.address
|
|
153
|
+
]);
|
|
154
|
+
return [call1, call2];
|
|
286
155
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Creates a withdrawal call to the strategy contract.
|
|
159
|
+
* @param assets - Amount of assets to withdraw
|
|
160
|
+
* @param receiver - Address that will receive the withdrawn assets
|
|
161
|
+
* @param owner - Address that owns the strategy tokens
|
|
162
|
+
* @returns Populated contract call for withdrawal
|
|
163
|
+
*/
|
|
164
|
+
async withdrawCall(
|
|
165
|
+
amountInfo: SingleActionAmount,
|
|
166
|
+
receiver: ContractAddr,
|
|
167
|
+
owner: ContractAddr
|
|
168
|
+
) {
|
|
169
|
+
return [
|
|
170
|
+
this.contract.populate("withdraw", [
|
|
171
|
+
uint256.bnToUint256(amountInfo.amount.toWei()),
|
|
172
|
+
receiver.address,
|
|
173
|
+
owner.address
|
|
174
|
+
])
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Returns the underlying asset token of the strategy.
|
|
180
|
+
* @returns The deposit token supported by this strategy
|
|
181
|
+
*/
|
|
182
|
+
asset() {
|
|
183
|
+
return this.metadata.depositTokens[0];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Returns the number of decimals used by the strategy token.
|
|
188
|
+
* @returns Number of decimals (same as the underlying token)
|
|
189
|
+
*/
|
|
190
|
+
decimals() {
|
|
191
|
+
return this.metadata.depositTokens[0].decimals; // same as underlying token
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Calculates the Total Value Locked (TVL) for a specific user.
|
|
196
|
+
* @param user - Address of the user
|
|
197
|
+
* @param blockIdentifier - Block identifier for the query
|
|
198
|
+
* @returns Object containing the amount in token units and USD value
|
|
199
|
+
*/
|
|
200
|
+
async getUserTVL(
|
|
201
|
+
user: ContractAddr,
|
|
202
|
+
blockIdentifier: BlockIdentifier = "latest"
|
|
203
|
+
) {
|
|
204
|
+
const shares: any = await this.contract.call(
|
|
205
|
+
"balanceOf",
|
|
206
|
+
[user.address],
|
|
207
|
+
{ blockIdentifier }
|
|
208
|
+
);
|
|
209
|
+
const assets: any = await this.contract.call(
|
|
210
|
+
"convert_to_assets",
|
|
211
|
+
[uint256.bnToUint256(shares)],
|
|
212
|
+
{ blockIdentifier }
|
|
213
|
+
);
|
|
214
|
+
const amount = Web3Number.fromWei(
|
|
215
|
+
assets.toString(),
|
|
216
|
+
this.metadata.depositTokens[0].decimals
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Convert blockIdentifier to block number for pricer if it's a number
|
|
220
|
+
const blockNumber =
|
|
221
|
+
typeof blockIdentifier === "number" ||
|
|
222
|
+
typeof blockIdentifier === "bigint"
|
|
223
|
+
? Number(blockIdentifier)
|
|
224
|
+
: undefined;
|
|
225
|
+
|
|
226
|
+
let price = await this.pricer.getPrice(
|
|
227
|
+
this.metadata.depositTokens[0].symbol,
|
|
228
|
+
blockNumber
|
|
229
|
+
);
|
|
230
|
+
const usdValue = Number(amount.toFixed(6)) * price.price;
|
|
231
|
+
return {
|
|
232
|
+
tokenInfo: this.asset(),
|
|
233
|
+
amount,
|
|
234
|
+
usdValue
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Calculates user realized APY based on trueSharesBasedAPY method.
|
|
240
|
+
* Returns the APY as a number.
|
|
241
|
+
*/
|
|
242
|
+
async getUserRealizedAPY(
|
|
243
|
+
blockIdentifier: BlockIdentifier = "latest",
|
|
244
|
+
sinceBlocks = 600000
|
|
245
|
+
): Promise<number> {
|
|
246
|
+
logger.verbose(
|
|
247
|
+
`${VesuRebalance.name}: getUserRealizedAPY => starting with blockIdentifier=${blockIdentifier}, sinceBlocks=${sinceBlocks}`
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// Determine current block number and timestamp
|
|
251
|
+
let blockNow =
|
|
252
|
+
typeof blockIdentifier === "number" ||
|
|
253
|
+
typeof blockIdentifier === "bigint"
|
|
254
|
+
? Number(blockIdentifier)
|
|
255
|
+
: (await this.config.provider.getBlockLatestAccepted())
|
|
256
|
+
.block_number;
|
|
257
|
+
const blockNowTime =
|
|
258
|
+
typeof blockIdentifier === "number" ||
|
|
259
|
+
typeof blockIdentifier === "bigint"
|
|
260
|
+
? (await this.config.provider.getBlockWithTxs(blockIdentifier))
|
|
261
|
+
.timestamp
|
|
262
|
+
: new Date().getTime() / 1000;
|
|
263
|
+
|
|
264
|
+
// Look back window, but never before launch block
|
|
265
|
+
const blockBefore = Math.max(
|
|
266
|
+
blockNow - sinceBlocks,
|
|
267
|
+
this.metadata.launchBlock
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
// TVL amounts (in underlying token units) and supply at current reference block
|
|
271
|
+
const assetsNowRaw: bigint = (await this.contract.call(
|
|
272
|
+
"total_assets",
|
|
273
|
+
[],
|
|
274
|
+
{
|
|
275
|
+
blockIdentifier
|
|
338
276
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
),
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
277
|
+
)) as bigint;
|
|
278
|
+
const amountNow = Web3Number.fromWei(
|
|
279
|
+
assetsNowRaw.toString(),
|
|
280
|
+
this.metadata.depositTokens[0].decimals
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
const supplyNowRaw: bigint = (await this.contract.call(
|
|
284
|
+
"total_supply",
|
|
285
|
+
[],
|
|
286
|
+
{
|
|
287
|
+
blockIdentifier
|
|
288
|
+
}
|
|
289
|
+
)) as bigint;
|
|
290
|
+
const supplyNow = Web3Number.fromWei(
|
|
291
|
+
supplyNowRaw.toString(),
|
|
292
|
+
this.metadata.depositTokens[0].decimals
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
// Historical TVL and supply
|
|
296
|
+
const assetsBeforeRaw: bigint = (await this.contract.call(
|
|
297
|
+
"total_assets",
|
|
298
|
+
[],
|
|
299
|
+
{ blockIdentifier: blockBefore }
|
|
300
|
+
)) as bigint;
|
|
301
|
+
const amountBefore = Web3Number.fromWei(
|
|
302
|
+
assetsBeforeRaw.toString(),
|
|
303
|
+
this.metadata.depositTokens[0].decimals
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
const supplyBeforeRaw: bigint = (await this.contract.call(
|
|
307
|
+
"total_supply",
|
|
308
|
+
[],
|
|
309
|
+
{ blockIdentifier: blockBefore }
|
|
310
|
+
)) as bigint;
|
|
311
|
+
const supplyBefore = Web3Number.fromWei(
|
|
312
|
+
supplyBeforeRaw.toString(),
|
|
313
|
+
this.metadata.depositTokens[0].decimals
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
const blockBeforeInfo = await this.config.provider.getBlockWithTxs(
|
|
317
|
+
blockBefore
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const assetsPerShareNow = amountNow
|
|
321
|
+
.multipliedBy(1e18)
|
|
322
|
+
.dividedBy(supplyNow.toString());
|
|
323
|
+
|
|
324
|
+
const assetsPerShareBf = amountBefore
|
|
325
|
+
.multipliedBy(1e18)
|
|
326
|
+
.dividedBy(supplyBefore.toString());
|
|
327
|
+
|
|
328
|
+
const timeDiffSeconds = blockNowTime - blockBeforeInfo.timestamp;
|
|
329
|
+
|
|
330
|
+
logger.verbose(
|
|
331
|
+
`${VesuRebalance.name}:${
|
|
332
|
+
this.metadata.name
|
|
333
|
+
} [getUserRealizedAPY] assetsNow: ${amountNow.toString()}`
|
|
334
|
+
);
|
|
335
|
+
logger.verbose(
|
|
336
|
+
`${VesuRebalance.name}:${
|
|
337
|
+
this.metadata.name
|
|
338
|
+
} [getUserRealizedAPY] assetsBefore: ${amountBefore.toString()}`
|
|
339
|
+
);
|
|
340
|
+
logger.verbose(
|
|
341
|
+
`${VesuRebalance.name}:${
|
|
342
|
+
this.metadata.name
|
|
343
|
+
} [getUserRealizedAPY] assetsPerShareNow: ${assetsPerShareNow.toString()}`
|
|
344
|
+
);
|
|
345
|
+
logger.verbose(
|
|
346
|
+
`${VesuRebalance.name}:${
|
|
347
|
+
this.metadata.name
|
|
348
|
+
} [getUserRealizedAPY] assetsPerShareBf: ${assetsPerShareBf.toString()}`
|
|
349
|
+
);
|
|
350
|
+
logger.verbose(
|
|
351
|
+
`${VesuRebalance.name}:${
|
|
352
|
+
this.metadata.name
|
|
353
|
+
} [getUserRealizedAPY] Supply before: ${supplyBefore.toString()}`
|
|
354
|
+
);
|
|
355
|
+
logger.verbose(
|
|
356
|
+
`${VesuRebalance.name}:${
|
|
357
|
+
this.metadata.name
|
|
358
|
+
} [getUserRealizedAPY] Supply now: ${supplyNow.toString()}`
|
|
359
|
+
);
|
|
360
|
+
logger.verbose(
|
|
361
|
+
`${VesuRebalance.name}:${this.metadata.name} [getUserRealizedAPY] Time diff in seconds: ${timeDiffSeconds}`
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const apyForGivenBlocks =
|
|
365
|
+
Number(
|
|
366
|
+
assetsPerShareNow
|
|
367
|
+
.minus(assetsPerShareBf)
|
|
368
|
+
.multipliedBy(10000)
|
|
369
|
+
.dividedBy(assetsPerShareBf)
|
|
370
|
+
) / 10000;
|
|
371
|
+
|
|
372
|
+
return (apyForGivenBlocks * (365 * 24 * 3600)) / timeDiffSeconds;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Calculates the total TVL of the strategy.
|
|
377
|
+
* @returns Object containing the total amount in token units and USD value
|
|
378
|
+
*/
|
|
379
|
+
async getTVL(): Promise<SingleTokenInfo> {
|
|
380
|
+
const assets = await this.contract.total_assets();
|
|
381
|
+
const amount = Web3Number.fromWei(
|
|
382
|
+
assets.toString(),
|
|
383
|
+
this.metadata.depositTokens[0].decimals
|
|
384
|
+
);
|
|
385
|
+
let price = await this.pricer.getPrice(
|
|
386
|
+
this.metadata.depositTokens[0].symbol
|
|
387
|
+
);
|
|
388
|
+
const usdValue = Number(amount.toFixed(6)) * price.price;
|
|
389
|
+
return {
|
|
390
|
+
tokenInfo: this.asset(),
|
|
391
|
+
amount,
|
|
392
|
+
usdValue
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
static async getAllPossibleVerifiedPools(asset: ContractAddr) {
|
|
397
|
+
const data = await getAPIUsingHeadlessBrowser(
|
|
398
|
+
`${ENDPOINTS.VESU_BASE_STAGING}/pools`
|
|
399
|
+
);
|
|
400
|
+
const verifiedPools = data.data.filter((d: any) => d.isVerified);
|
|
401
|
+
const pools = verifiedPools
|
|
402
|
+
.map((p: any) => {
|
|
403
|
+
const hasMyAsset = p.assets.find((a: any) =>
|
|
404
|
+
asset.eqString(a.address)
|
|
405
|
+
);
|
|
406
|
+
if (hasMyAsset) {
|
|
407
|
+
return {
|
|
408
|
+
pool_id: ContractAddr.from(p.id),
|
|
409
|
+
max_weight: 10000,
|
|
410
|
+
v_token: ContractAddr.from(hasMyAsset.vToken.address),
|
|
411
|
+
name: p.name
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
})
|
|
416
|
+
.filter((p: PoolProps | null) => p !== null);
|
|
417
|
+
return pools;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async getPoolInfo(
|
|
421
|
+
p: PoolProps,
|
|
422
|
+
pools: any[],
|
|
423
|
+
vesuPositions: any[],
|
|
424
|
+
totalAssets: Web3Number,
|
|
425
|
+
isErrorPositionsAPI: boolean,
|
|
426
|
+
isErrorPoolsAPI: boolean
|
|
427
|
+
) {
|
|
428
|
+
const vesuPosition = vesuPositions.find((d: any) =>
|
|
429
|
+
ContractAddr.from(d.id).eq(p.pool_id)
|
|
430
|
+
);
|
|
431
|
+
const _pool = pools.find((d: any) => {
|
|
432
|
+
logger.verbose(
|
|
433
|
+
`pool check: ${ContractAddr.from(d.id).eq(p.pool_id)}, id: ${
|
|
434
|
+
d.id
|
|
435
|
+
}, pool_id: ${num.getDecimalString(
|
|
436
|
+
p.pool_id.address.toString()
|
|
437
|
+
)}`
|
|
438
|
+
);
|
|
439
|
+
return ContractAddr.from(d.id).eq(p.pool_id);
|
|
440
|
+
});
|
|
441
|
+
logger.verbose(`pool: ${JSON.stringify(_pool)}`);
|
|
442
|
+
logger.verbose(typeof _pool);
|
|
443
|
+
logger.verbose(`name: ${_pool?.name}`);
|
|
444
|
+
const name = _pool?.name;
|
|
445
|
+
logger.verbose(
|
|
446
|
+
`name2: ${name}, ${!name ? true : false}, ${
|
|
447
|
+
name?.length
|
|
448
|
+
}, ${typeof name}`
|
|
449
|
+
);
|
|
450
|
+
const assetInfo = _pool?.assets.find((d: any) =>
|
|
451
|
+
this.asset().address.eqString(d.address)
|
|
452
|
+
);
|
|
453
|
+
if (!name) {
|
|
454
|
+
logger.verbose(`Pool not found`);
|
|
455
|
+
throw new Error(
|
|
456
|
+
`Pool name ${p.pool_id.address.toString()} not found`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
if (!assetInfo) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
`Asset ${this.asset().address.toString()} not found in pool ${p.pool_id.address.toString()}`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
let vTokenContract = new Contract({
|
|
465
|
+
abi: VesuRebalanceAbi,
|
|
466
|
+
address: p.v_token.address,
|
|
467
|
+
providerOrAccount: this.config.provider
|
|
468
|
+
});
|
|
469
|
+
const bal = await vTokenContract.balanceOf(this.address.address);
|
|
470
|
+
const assets = await vTokenContract.convert_to_assets(
|
|
471
|
+
uint256.bnToUint256(bal.toString())
|
|
472
|
+
);
|
|
473
|
+
logger.verbose(
|
|
474
|
+
`Collateral: ${JSON.stringify(vesuPosition?.collateral)}`
|
|
475
|
+
);
|
|
476
|
+
logger.verbose(
|
|
477
|
+
`supplyApy: ${JSON.stringify(assetInfo?.stats.supplyApy)}`
|
|
478
|
+
);
|
|
479
|
+
logger.verbose(
|
|
480
|
+
`defiSpringSupplyApr: ${JSON.stringify(
|
|
481
|
+
assetInfo?.stats.defiSpringSupplyApr
|
|
482
|
+
)}`
|
|
483
|
+
);
|
|
484
|
+
logger.verbose(
|
|
485
|
+
`currentUtilization: ${JSON.stringify(
|
|
486
|
+
assetInfo?.stats.currentUtilization
|
|
487
|
+
)}`
|
|
488
|
+
);
|
|
489
|
+
logger.verbose(
|
|
490
|
+
`maxUtilization: ${JSON.stringify(
|
|
491
|
+
assetInfo?.config.maxUtilization
|
|
492
|
+
)}`
|
|
493
|
+
);
|
|
494
|
+
const item = {
|
|
495
|
+
pool_id: p.pool_id,
|
|
496
|
+
pool_name: _pool?.name,
|
|
497
|
+
max_weight: p.max_weight,
|
|
498
|
+
current_weight:
|
|
499
|
+
isErrorPositionsAPI || !vesuPosition
|
|
500
|
+
? 0
|
|
501
|
+
: Number(
|
|
502
|
+
Web3Number.fromWei(
|
|
503
|
+
vesuPosition.collateral.value,
|
|
504
|
+
this.decimals()
|
|
505
|
+
)
|
|
506
|
+
.dividedBy(totalAssets.toString())
|
|
507
|
+
.toFixed(6)
|
|
508
|
+
),
|
|
509
|
+
v_token: p.v_token,
|
|
510
|
+
amount: Web3Number.fromWei(assets.toString(), this.decimals()),
|
|
511
|
+
usdValue:
|
|
512
|
+
isErrorPositionsAPI || !vesuPosition
|
|
513
|
+
? Web3Number.fromWei("0", this.decimals())
|
|
514
|
+
: Web3Number.fromWei(
|
|
515
|
+
vesuPosition.collateral.usdPrice.value,
|
|
516
|
+
vesuPosition.collateral.usdPrice.decimals
|
|
517
|
+
),
|
|
518
|
+
APY:
|
|
519
|
+
isErrorPoolsAPI || !assetInfo
|
|
520
|
+
? {
|
|
521
|
+
baseApy: 0,
|
|
522
|
+
defiSpringApy: 0,
|
|
523
|
+
netApy: 0
|
|
524
|
+
}
|
|
525
|
+
: {
|
|
526
|
+
baseApy: Number(
|
|
527
|
+
Web3Number.fromWei(
|
|
528
|
+
assetInfo.stats.supplyApy.value,
|
|
529
|
+
assetInfo.stats.supplyApy.decimals
|
|
530
|
+
).toFixed(6)
|
|
531
|
+
),
|
|
532
|
+
defiSpringApy: assetInfo.stats.defiSpringSupplyApr
|
|
533
|
+
? Number(
|
|
534
|
+
Web3Number.fromWei(
|
|
535
|
+
assetInfo.stats.defiSpringSupplyApr
|
|
536
|
+
.value,
|
|
537
|
+
assetInfo.stats.defiSpringSupplyApr
|
|
538
|
+
.decimals
|
|
539
|
+
).toFixed(6)
|
|
540
|
+
)
|
|
541
|
+
: 0,
|
|
542
|
+
netApy: 0
|
|
543
|
+
},
|
|
544
|
+
currentUtilization:
|
|
545
|
+
isErrorPoolsAPI || !assetInfo
|
|
546
|
+
? 0
|
|
547
|
+
: Number(
|
|
548
|
+
Web3Number.fromWei(
|
|
549
|
+
assetInfo.stats.currentUtilization.value,
|
|
550
|
+
assetInfo.stats.currentUtilization.decimals
|
|
551
|
+
).toFixed(6)
|
|
552
|
+
),
|
|
553
|
+
maxUtilization:
|
|
554
|
+
isErrorPoolsAPI || !assetInfo
|
|
555
|
+
? 0
|
|
556
|
+
: Number(
|
|
557
|
+
Web3Number.fromWei(
|
|
558
|
+
assetInfo.config.maxUtilization.value,
|
|
559
|
+
assetInfo.config.maxUtilization.decimals
|
|
560
|
+
).toFixed(6)
|
|
561
|
+
)
|
|
562
|
+
};
|
|
563
|
+
item.APY.netApy = item.APY.baseApy + item.APY.defiSpringApy;
|
|
564
|
+
return item;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Retrieves the list of allowed pools and their detailed information from multiple sources:
|
|
569
|
+
* 1. Contract's allowed pools
|
|
570
|
+
* 2. Vesu positions API for current positions
|
|
571
|
+
* 3. Vesu pools API for APY and utilization data
|
|
572
|
+
*
|
|
573
|
+
* @returns {Promise<{
|
|
574
|
+
* data: Array<PoolInfoFull>,
|
|
575
|
+
* isErrorPositionsAPI: boolean
|
|
576
|
+
* }>} Object containing:
|
|
577
|
+
* - data: Array of pool information including IDs, weights, amounts, APYs and utilization
|
|
578
|
+
* - isErrorPositionsAPI: Boolean indicating if there was an error fetching position data
|
|
579
|
+
*/
|
|
580
|
+
async getPools() {
|
|
581
|
+
const allowedPools: PoolProps[] = (
|
|
582
|
+
await this.contract.get_allowed_pools()
|
|
583
|
+
).map((p: any) => ({
|
|
584
|
+
pool_id: ContractAddr.from(p.pool_id),
|
|
585
|
+
max_weight: Number(p.max_weight) / this.BASE_WEIGHT,
|
|
586
|
+
v_token: ContractAddr.from(p.v_token)
|
|
587
|
+
}));
|
|
588
|
+
|
|
589
|
+
/*
|
|
402
590
|
Vesu Positions API Response Schema (/positions?walletAddress=):
|
|
403
591
|
{
|
|
404
592
|
"data": [{
|
|
@@ -445,674 +633,689 @@ export class VesuRebalance extends BaseStrategy<
|
|
|
445
633
|
}]
|
|
446
634
|
}
|
|
447
635
|
*/
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
636
|
+
let isErrorPositionsAPI = false;
|
|
637
|
+
let vesuPositions: any[] = [];
|
|
638
|
+
try {
|
|
639
|
+
const data = await getAPIUsingHeadlessBrowser(
|
|
640
|
+
`https://staging.api.vesu.xyz/positions?walletAddress=${this.address.address}`
|
|
641
|
+
);
|
|
642
|
+
vesuPositions = data.data;
|
|
643
|
+
} catch (e) {
|
|
644
|
+
console.error(
|
|
645
|
+
`${VesuRebalance.name}: Error fetching positions for ${this.address.address}`,
|
|
646
|
+
e
|
|
647
|
+
);
|
|
648
|
+
isErrorPositionsAPI = true;
|
|
649
|
+
}
|
|
462
650
|
|
|
463
|
-
|
|
651
|
+
let { pools, isErrorPoolsAPI } = await this.getVesuPools();
|
|
464
652
|
|
|
465
|
-
|
|
653
|
+
const totalAssets = (await this.getTVL()).amount;
|
|
466
654
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
655
|
+
const info = allowedPools.map((p) =>
|
|
656
|
+
this.getPoolInfo(
|
|
657
|
+
p,
|
|
658
|
+
pools,
|
|
659
|
+
vesuPositions,
|
|
660
|
+
totalAssets,
|
|
661
|
+
isErrorPositionsAPI,
|
|
662
|
+
isErrorPoolsAPI
|
|
663
|
+
)
|
|
664
|
+
);
|
|
665
|
+
const data = await Promise.all(info);
|
|
666
|
+
return {
|
|
667
|
+
data,
|
|
668
|
+
isErrorPositionsAPI,
|
|
669
|
+
isErrorPoolsAPI,
|
|
670
|
+
isError: isErrorPositionsAPI || isErrorPoolsAPI
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
async getVesuPools(
|
|
675
|
+
retry = 0
|
|
676
|
+
): Promise<{ pools: any[]; isErrorPoolsAPI: boolean }> {
|
|
677
|
+
let isErrorPoolsAPI = false;
|
|
678
|
+
let pools: any[] = [];
|
|
679
|
+
try {
|
|
680
|
+
const data = await getAPIUsingHeadlessBrowser(
|
|
681
|
+
`${ENDPOINTS.VESU_BASE_STAGING}/pools`
|
|
682
|
+
);
|
|
683
|
+
pools = data.data;
|
|
684
|
+
|
|
685
|
+
// Vesu API is unstable sometimes, some Pools may be missing sometimes
|
|
686
|
+
for (const pool of VesuPoolIDs.data) {
|
|
687
|
+
const found = pools.find((d: any) =>
|
|
688
|
+
ContractAddr.from(d.id).eqString(pool.id)
|
|
689
|
+
);
|
|
690
|
+
if (!found) {
|
|
691
|
+
logger.verbose(
|
|
692
|
+
`VesuRebalance: pools: ${JSON.stringify(pools)}`
|
|
693
|
+
);
|
|
694
|
+
logger.verbose(
|
|
695
|
+
`VesuRebalance: Pool ${pool.id} not found in Vesu API, using hardcoded data`
|
|
696
|
+
);
|
|
697
|
+
throw new Error(
|
|
698
|
+
`pool not found [sanity check]: ${pool.id}`
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
} catch (e) {
|
|
703
|
+
logger.error(
|
|
704
|
+
`${VesuRebalance.name}: Error fetching pools for ${this.address.address}, retry ${retry}`,
|
|
705
|
+
e
|
|
706
|
+
);
|
|
707
|
+
isErrorPoolsAPI = true;
|
|
708
|
+
if (retry < 10) {
|
|
709
|
+
await new Promise((resolve) =>
|
|
710
|
+
setTimeout(resolve, 5000 * (retry + 1))
|
|
711
|
+
);
|
|
712
|
+
return await this.getVesuPools(retry + 1);
|
|
713
|
+
}
|
|
506
714
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
logger.error(
|
|
510
|
-
`${VesuRebalance.name}: Error fetching pools for ${this.address.address}, retry ${retry}`,
|
|
511
|
-
e
|
|
512
|
-
);
|
|
513
|
-
isErrorPoolsAPI = true;
|
|
514
|
-
if (retry < 10) {
|
|
515
|
-
await new Promise((resolve) => setTimeout(resolve, 5000 * (retry + 1)));
|
|
516
|
-
return await this.getVesuPools(retry + 1);
|
|
517
|
-
}
|
|
715
|
+
|
|
716
|
+
return { pools, isErrorPoolsAPI };
|
|
518
717
|
}
|
|
519
718
|
|
|
520
|
-
|
|
521
|
-
|
|
719
|
+
/**
|
|
720
|
+
* Calculates the weighted average APY across all pools based on USD value.
|
|
721
|
+
* @returns {Promise<number>} The weighted average APY across all pools
|
|
722
|
+
*/
|
|
723
|
+
async netAPY(): Promise<number> {
|
|
724
|
+
const { data: pools } = await this.getPools();
|
|
725
|
+
return this.netAPYGivenPools(pools);
|
|
726
|
+
}
|
|
522
727
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
* Calculates the weighted average APY across all pools based on USD value.
|
|
537
|
-
* @returns {Promise<number>} The weighted average APY across all pools
|
|
538
|
-
*/
|
|
539
|
-
async netAPYGivenPools(pools: PoolInfoFull[]): Promise<number> {
|
|
540
|
-
const weightedApyNumerator = pools.reduce((acc: number, curr) => {
|
|
541
|
-
const weight = curr.current_weight;
|
|
542
|
-
return acc + curr.APY.netApy * Number(curr.amount.toString());
|
|
543
|
-
}, 0);
|
|
544
|
-
const totalAssets = (await this.getTVL()).amount;
|
|
545
|
-
const weightedApy = weightedApyNumerator / Number(totalAssets.toString());
|
|
546
|
-
return weightedApy * (1 - this.metadata.additionalInfo.feeBps / 10000);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
/**
|
|
550
|
-
* Calculates optimal position changes to maximize APY while respecting max weights.
|
|
551
|
-
* The algorithm:
|
|
552
|
-
* 1. Sorts pools by APY (highest first)
|
|
553
|
-
* 2. Calculates target amounts based on max weights
|
|
554
|
-
* 3. For each pool that needs more funds:
|
|
555
|
-
* - Takes funds from lowest APY pools that are over their target
|
|
556
|
-
* 4. Validates that total assets remain constant
|
|
557
|
-
*
|
|
558
|
-
* @returns {Promise<{
|
|
559
|
-
* changes: Change[],
|
|
560
|
-
* finalPools: PoolInfoFull[],
|
|
561
|
-
* isAnyPoolOverMaxWeight: boolean
|
|
562
|
-
* }>} Object containing:
|
|
563
|
-
* - changes: Array of position changes
|
|
564
|
-
* - finalPools: Array of pool information after rebalance
|
|
565
|
-
* @throws Error if rebalance is not possible while maintaining constraints
|
|
566
|
-
*/
|
|
567
|
-
async getRebalancedPositions(_pools?: PoolInfoFull[]) {
|
|
568
|
-
logger.verbose(`VesuRebalance: getRebalancedPositions`);
|
|
569
|
-
if (!_pools) {
|
|
570
|
-
const { data: _pools2 } = await this.getPools();
|
|
571
|
-
_pools = _pools2;
|
|
728
|
+
/**
|
|
729
|
+
* Calculates the weighted average APY across all pools based on USD value.
|
|
730
|
+
* @returns {Promise<number>} The weighted average APY across all pools
|
|
731
|
+
*/
|
|
732
|
+
async netAPYGivenPools(pools: PoolInfoFull[]): Promise<number> {
|
|
733
|
+
const weightedApyNumerator = pools.reduce((acc: number, curr) => {
|
|
734
|
+
const weight = curr.current_weight;
|
|
735
|
+
return acc + curr.APY.netApy * Number(curr.amount.toString());
|
|
736
|
+
}, 0);
|
|
737
|
+
const totalAssets = (await this.getTVL()).amount;
|
|
738
|
+
const weightedApy =
|
|
739
|
+
weightedApyNumerator / Number(totalAssets.toString());
|
|
740
|
+
return weightedApy * (1 - this.metadata.additionalInfo.feeBps / 10000);
|
|
572
741
|
}
|
|
573
|
-
const feeDeductions = await this.getFee(_pools);
|
|
574
|
-
logger.verbose(
|
|
575
|
-
`VesuRebalance: feeDeductions: ${JSON.stringify(feeDeductions)}`
|
|
576
|
-
);
|
|
577
742
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
743
|
+
/**
|
|
744
|
+
* Calculates optimal position changes to maximize APY while respecting max weights.
|
|
745
|
+
* The algorithm:
|
|
746
|
+
* 1. Sorts pools by APY (highest first)
|
|
747
|
+
* 2. Calculates target amounts based on max weights
|
|
748
|
+
* 3. For each pool that needs more funds:
|
|
749
|
+
* - Takes funds from lowest APY pools that are over their target
|
|
750
|
+
* 4. Validates that total assets remain constant
|
|
751
|
+
*
|
|
752
|
+
* @returns {Promise<{
|
|
753
|
+
* changes: Change[],
|
|
754
|
+
* finalPools: PoolInfoFull[],
|
|
755
|
+
* isAnyPoolOverMaxWeight: boolean
|
|
756
|
+
* }>} Object containing:
|
|
757
|
+
* - changes: Array of position changes
|
|
758
|
+
* - finalPools: Array of pool information after rebalance
|
|
759
|
+
* @throws Error if rebalance is not possible while maintaining constraints
|
|
760
|
+
*/
|
|
761
|
+
async getRebalancedPositions(_pools?: PoolInfoFull[]) {
|
|
762
|
+
logger.verbose(`VesuRebalance: getRebalancedPositions`);
|
|
763
|
+
if (!_pools) {
|
|
764
|
+
const { data: _pools2 } = await this.getPools();
|
|
765
|
+
_pools = _pools2;
|
|
766
|
+
}
|
|
767
|
+
const feeDeductions = await this.getFee(_pools);
|
|
768
|
+
logger.verbose(
|
|
769
|
+
`VesuRebalance: feeDeductions: ${JSON.stringify(feeDeductions)}`
|
|
770
|
+
);
|
|
771
|
+
|
|
772
|
+
// remove fee from pools
|
|
773
|
+
const pools = _pools.map((p) => {
|
|
774
|
+
const fee =
|
|
775
|
+
feeDeductions.find((f) => p.v_token.eq(f.vToken))?.fee ||
|
|
776
|
+
Web3Number.fromWei("0", this.decimals());
|
|
777
|
+
logger.verbose(
|
|
778
|
+
`FeeAdjustment: ${
|
|
779
|
+
p.pool_id
|
|
780
|
+
} => ${fee.toString()}, amt: ${p.amount.toString()}`
|
|
781
|
+
);
|
|
782
|
+
return {
|
|
783
|
+
...p,
|
|
784
|
+
amount: p.amount.minus(fee)
|
|
785
|
+
};
|
|
786
|
+
});
|
|
787
|
+
let totalAssets = (await this.getTVL()).amount;
|
|
788
|
+
if (totalAssets.eq(0))
|
|
789
|
+
return {
|
|
790
|
+
changes: [],
|
|
791
|
+
finalPools: []
|
|
792
|
+
};
|
|
793
|
+
// deduct fee from total assets
|
|
794
|
+
feeDeductions.forEach((f) => {
|
|
795
|
+
totalAssets = totalAssets.minus(f.fee);
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
// assert sum of pools.amount <= totalAssets
|
|
799
|
+
const sumPools = pools.reduce(
|
|
800
|
+
(acc, curr) => acc.plus(curr.amount.toString()),
|
|
801
|
+
Web3Number.fromWei("0", this.decimals())
|
|
802
|
+
);
|
|
803
|
+
logger.verbose(`Sum of pools: ${sumPools.toString()}`);
|
|
804
|
+
logger.verbose(`Total assets: ${totalAssets.toString()}`);
|
|
805
|
+
assert(
|
|
806
|
+
sumPools.lte(totalAssets.multipliedBy(1.00001).toString()),
|
|
807
|
+
"Sum of pools.amount must be less than or equal to totalAssets"
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
// Sort pools by APY and calculate target amounts
|
|
811
|
+
const sortedPools = [...pools].sort(
|
|
812
|
+
(a, b) => b.APY.netApy - a.APY.netApy
|
|
813
|
+
);
|
|
814
|
+
const targetAmounts: Record<string, Web3Number> = {};
|
|
815
|
+
let remainingAssets = totalAssets;
|
|
816
|
+
logger.verbose(`Remaining assets: ${remainingAssets.toString()}`);
|
|
817
|
+
|
|
818
|
+
// First pass: Allocate to high APY pools up to their max weight
|
|
819
|
+
let isAnyPoolOverMaxWeight = false;
|
|
820
|
+
for (const pool of sortedPools) {
|
|
821
|
+
const maxAmount = totalAssets.multipliedBy(pool.max_weight * 0.98); // some tolerance
|
|
822
|
+
const targetAmount = remainingAssets.gte(maxAmount)
|
|
823
|
+
? maxAmount
|
|
824
|
+
: remainingAssets;
|
|
825
|
+
logger.verbose(`Target amount: ${targetAmount.toString()}`);
|
|
826
|
+
logger.verbose(`Remaining assets: ${remainingAssets.toString()}`);
|
|
827
|
+
logger.verbose(`Max amount: ${maxAmount.toString()}`);
|
|
828
|
+
logger.verbose(`pool.max_weight: ${pool.max_weight}`);
|
|
829
|
+
targetAmounts[pool.pool_id.address.toString()] = targetAmount;
|
|
830
|
+
remainingAssets = remainingAssets.minus(targetAmount.toString());
|
|
831
|
+
if (pool.current_weight > pool.max_weight) {
|
|
832
|
+
isAnyPoolOverMaxWeight = true;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
assert(remainingAssets.lt(0.00001), "Remaining assets must be 0");
|
|
837
|
+
|
|
838
|
+
// Calculate required changes
|
|
839
|
+
const changes: Change[] = sortedPools.map((pool) => {
|
|
840
|
+
const target =
|
|
841
|
+
targetAmounts[pool.pool_id.address.toString()] ||
|
|
842
|
+
Web3Number.fromWei("0", this.decimals());
|
|
843
|
+
const change = Web3Number.fromWei(
|
|
844
|
+
target.minus(pool.amount.toString()).toWei(),
|
|
845
|
+
this.decimals()
|
|
846
|
+
);
|
|
847
|
+
return {
|
|
848
|
+
pool_id: pool.pool_id,
|
|
849
|
+
changeAmt: change,
|
|
850
|
+
finalAmt: target,
|
|
851
|
+
isDeposit: change.gt(0)
|
|
852
|
+
};
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
logger.verbose(`Changes: ${JSON.stringify(changes)}`);
|
|
856
|
+
// Validate changes
|
|
857
|
+
const sumChanges = changes.reduce(
|
|
858
|
+
(sum, c) => sum.plus(c.changeAmt.toString()),
|
|
859
|
+
Web3Number.fromWei("0", this.decimals())
|
|
860
|
+
);
|
|
861
|
+
const sumFinal = changes.reduce(
|
|
862
|
+
(sum, c) => sum.plus(c.finalAmt.toString()),
|
|
863
|
+
Web3Number.fromWei("0", this.decimals())
|
|
864
|
+
);
|
|
865
|
+
const hasChanges = changes.some((c) => !c.changeAmt.eq(0));
|
|
615
866
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
867
|
+
logger.verbose(`Sum of changes: ${sumChanges.toString()}`);
|
|
868
|
+
if (!sumChanges.eq(0)) throw new Error("Sum of changes must be zero");
|
|
869
|
+
logger.verbose(`Sum of final: ${sumFinal.toString()}`);
|
|
870
|
+
logger.verbose(`Total assets: ${totalAssets.toString()}`);
|
|
871
|
+
if (!sumFinal.eq(totalAssets.toString()))
|
|
872
|
+
throw new Error("Sum of final amounts must equal total assets");
|
|
873
|
+
if (!hasChanges) throw new Error("No changes required");
|
|
874
|
+
|
|
875
|
+
const finalPools: PoolInfoFull[] = pools.map((p) => {
|
|
876
|
+
const target =
|
|
877
|
+
targetAmounts[p.pool_id.address.toString()] ||
|
|
878
|
+
Web3Number.fromWei("0", this.decimals());
|
|
879
|
+
return {
|
|
880
|
+
...p,
|
|
881
|
+
amount: target,
|
|
882
|
+
usdValue: Web3Number.fromWei("0", this.decimals())
|
|
883
|
+
};
|
|
884
|
+
});
|
|
885
|
+
return {
|
|
886
|
+
changes,
|
|
887
|
+
finalPools,
|
|
888
|
+
isAnyPoolOverMaxWeight
|
|
889
|
+
};
|
|
638
890
|
}
|
|
639
891
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
if (!sumChanges.eq(0)) throw new Error("Sum of changes must be zero");
|
|
673
|
-
logger.verbose(`Sum of final: ${sumFinal.toString()}`);
|
|
674
|
-
logger.verbose(`Total assets: ${totalAssets.toString()}`);
|
|
675
|
-
if (!sumFinal.eq(totalAssets.toString()))
|
|
676
|
-
throw new Error("Sum of final amounts must equal total assets");
|
|
677
|
-
if (!hasChanges) throw new Error("No changes required");
|
|
678
|
-
|
|
679
|
-
const finalPools: PoolInfoFull[] = pools.map((p) => {
|
|
680
|
-
const target =
|
|
681
|
-
targetAmounts[p.pool_id.address.toString()] ||
|
|
682
|
-
Web3Number.fromWei("0", this.decimals());
|
|
683
|
-
return {
|
|
684
|
-
...p,
|
|
685
|
-
amount: target,
|
|
686
|
-
usdValue: Web3Number.fromWei("0", this.decimals())
|
|
687
|
-
};
|
|
688
|
-
});
|
|
689
|
-
return {
|
|
690
|
-
changes,
|
|
691
|
-
finalPools,
|
|
692
|
-
isAnyPoolOverMaxWeight
|
|
693
|
-
};
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
/**
|
|
697
|
-
* Creates a rebalance Call object for the strategy contract
|
|
698
|
-
* @param pools - Array of pool information including IDs, weights, amounts, APYs and utilization
|
|
699
|
-
* @returns Populated contract call for rebalance
|
|
700
|
-
*/
|
|
701
|
-
async getRebalanceCall(
|
|
702
|
-
pools: Awaited<ReturnType<typeof this.getRebalancedPositions>>["changes"],
|
|
703
|
-
isOverWeightAdjustment: boolean // here, yield increase doesnt matter
|
|
704
|
-
) {
|
|
705
|
-
const actions: any[] = [];
|
|
706
|
-
// sort to put withdrawals first
|
|
707
|
-
pools.sort((a, b) => (b.isDeposit ? -1 : 1));
|
|
708
|
-
pools.forEach((p) => {
|
|
709
|
-
if (p.changeAmt.eq(0)) return null;
|
|
710
|
-
actions.push({
|
|
711
|
-
pool_id: p.pool_id.address,
|
|
712
|
-
feature: new CairoCustomEnum(
|
|
713
|
-
p.isDeposit ? { DEPOSIT: {} } : { WITHDRAW: {} }
|
|
714
|
-
),
|
|
715
|
-
token: this.asset().address.address,
|
|
716
|
-
amount: uint256.bnToUint256(
|
|
717
|
-
p.changeAmt.multipliedBy(p.isDeposit ? 1 : -1).toWei()
|
|
718
|
-
)
|
|
719
|
-
});
|
|
720
|
-
});
|
|
721
|
-
if (actions.length === 0) return null;
|
|
722
|
-
if (isOverWeightAdjustment) {
|
|
723
|
-
return this.contract.populate("rebalance_weights", [actions]);
|
|
892
|
+
/**
|
|
893
|
+
* Creates a rebalance Call object for the strategy contract
|
|
894
|
+
* @param pools - Array of pool information including IDs, weights, amounts, APYs and utilization
|
|
895
|
+
* @returns Populated contract call for rebalance
|
|
896
|
+
*/
|
|
897
|
+
async getRebalanceCall(
|
|
898
|
+
pools: Awaited<
|
|
899
|
+
ReturnType<typeof this.getRebalancedPositions>
|
|
900
|
+
>["changes"],
|
|
901
|
+
isOverWeightAdjustment: boolean // here, yield increase doesnt matter
|
|
902
|
+
) {
|
|
903
|
+
const actions: any[] = [];
|
|
904
|
+
// sort to put withdrawals first
|
|
905
|
+
pools.sort((a, b) => (b.isDeposit ? -1 : 1));
|
|
906
|
+
pools.forEach((p) => {
|
|
907
|
+
if (p.changeAmt.eq(0)) return null;
|
|
908
|
+
actions.push({
|
|
909
|
+
pool_id: p.pool_id.address,
|
|
910
|
+
feature: new CairoCustomEnum(
|
|
911
|
+
p.isDeposit ? { DEPOSIT: {} } : { WITHDRAW: {} }
|
|
912
|
+
),
|
|
913
|
+
token: this.asset().address.address,
|
|
914
|
+
amount: uint256.bnToUint256(
|
|
915
|
+
p.changeAmt.multipliedBy(p.isDeposit ? 1 : -1).toWei()
|
|
916
|
+
)
|
|
917
|
+
});
|
|
918
|
+
});
|
|
919
|
+
if (actions.length === 0) return null;
|
|
920
|
+
if (isOverWeightAdjustment) {
|
|
921
|
+
return this.contract.populate("rebalance_weights", [actions]);
|
|
922
|
+
}
|
|
923
|
+
return this.contract.populate("rebalance", [actions]);
|
|
724
924
|
}
|
|
725
|
-
return this.contract.populate("rebalance", [actions]);
|
|
726
|
-
}
|
|
727
925
|
|
|
728
|
-
|
|
729
|
-
|
|
926
|
+
async getInvestmentFlows(pools: PoolInfoFull[]) {
|
|
927
|
+
const netYield = await this.netAPYGivenPools(pools);
|
|
730
928
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
929
|
+
const baseFlow: IInvestmentFlow = {
|
|
930
|
+
title: "Your Deposit",
|
|
931
|
+
subItems: [
|
|
932
|
+
{ key: `Net yield`, value: `${(netYield * 100).toFixed(2)}%` },
|
|
933
|
+
{
|
|
934
|
+
key: `Performance Fee`,
|
|
935
|
+
value: `${(
|
|
936
|
+
this.metadata.additionalInfo.feeBps / 100
|
|
937
|
+
).toFixed(2)}%`
|
|
938
|
+
}
|
|
939
|
+
],
|
|
940
|
+
linkedFlows: [],
|
|
941
|
+
style: { backgroundColor: FlowChartColors.Purple.valueOf() }
|
|
942
|
+
};
|
|
743
943
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
const vesuHarvest = new VesuHarvests(this.config);
|
|
772
|
-
const harvests = await vesuHarvest.getUnHarvestedRewards(this.address, endpoint);
|
|
773
|
-
const harvest = harvests[0];
|
|
774
|
-
const avnu = new AvnuWrapper();
|
|
775
|
-
let swapInfo: SwapInfo = {
|
|
776
|
-
token_from_address: harvest.token.address,
|
|
777
|
-
token_from_amount: uint256.bnToUint256(harvest.actualReward.toWei()),
|
|
778
|
-
token_to_address: this.asset().address.address,
|
|
779
|
-
token_to_amount: uint256.bnToUint256(0),
|
|
780
|
-
token_to_min_amount: uint256.bnToUint256(0),
|
|
781
|
-
beneficiary: this.address.address,
|
|
782
|
-
integrator_fee_amount_bps: 0,
|
|
783
|
-
integrator_fee_recipient: this.address.address,
|
|
784
|
-
routes: []
|
|
785
|
-
};
|
|
786
|
-
if (!this.asset().address.eqString(harvest.token.address)) {
|
|
787
|
-
const quote = await avnu.getQuotes(
|
|
788
|
-
harvest.token.address,
|
|
789
|
-
this.asset().address.address,
|
|
790
|
-
harvest.actualReward.toWei(),
|
|
791
|
-
this.address.address
|
|
792
|
-
);
|
|
793
|
-
swapInfo = await avnu.getSwapInfo(
|
|
794
|
-
quote,
|
|
795
|
-
this.address.address,
|
|
796
|
-
0,
|
|
797
|
-
this.address.address
|
|
798
|
-
);
|
|
944
|
+
let _pools = [...pools];
|
|
945
|
+
_pools = _pools.sort(
|
|
946
|
+
(a, b) => Number(b.amount.toString()) - Number(a.amount.toString())
|
|
947
|
+
);
|
|
948
|
+
_pools.forEach((p) => {
|
|
949
|
+
const flow: IInvestmentFlow = {
|
|
950
|
+
title: `Pool name: ${p.pool_name}`,
|
|
951
|
+
subItems: [
|
|
952
|
+
{
|
|
953
|
+
key: `APY`,
|
|
954
|
+
value: `${(p.APY.netApy * 100).toFixed(2)}%`
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
key: "Weight",
|
|
958
|
+
value: `${(p.current_weight * 100).toFixed(2)} / ${(
|
|
959
|
+
p.max_weight * 100
|
|
960
|
+
).toFixed(2)}%`
|
|
961
|
+
}
|
|
962
|
+
],
|
|
963
|
+
linkedFlows: [],
|
|
964
|
+
style: p.amount.greaterThan(0)
|
|
965
|
+
? { backgroundColor: FlowChartColors.Blue.valueOf() }
|
|
966
|
+
: { color: "gray" }
|
|
967
|
+
};
|
|
968
|
+
baseFlow.linkedFlows.push(flow);
|
|
969
|
+
});
|
|
970
|
+
return [baseFlow];
|
|
799
971
|
}
|
|
800
972
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
{
|
|
805
|
-
id: harvest.claim.id,
|
|
806
|
-
amount: harvest.claim.amount.toWei(),
|
|
807
|
-
claimee: harvest.claim.claimee.address
|
|
808
|
-
},
|
|
809
|
-
harvest.proof,
|
|
810
|
-
swapInfo
|
|
811
|
-
])
|
|
812
|
-
];
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
/**
|
|
816
|
-
* Calculates the fees deducted in different vTokens based on the current and previous state.
|
|
817
|
-
* @param previousTotalSupply - The total supply of the strategy token before the transaction
|
|
818
|
-
* @returns {Promise<Array<{ vToken: ContractAddr, fee: Web3Number }>>} Array of fees deducted in different vTokens
|
|
819
|
-
*/
|
|
820
|
-
async getFee(
|
|
821
|
-
allowedPools: Array<PoolInfoFull>
|
|
822
|
-
): Promise<Array<{ vToken: ContractAddr; fee: Web3Number }>> {
|
|
823
|
-
const assets = Web3Number.fromWei(
|
|
824
|
-
(await this.contract.total_assets()).toString(),
|
|
825
|
-
this.asset().decimals
|
|
826
|
-
);
|
|
827
|
-
const totalSupply = Web3Number.fromWei(
|
|
828
|
-
(await this.contract.total_supply()).toString(),
|
|
829
|
-
this.asset().decimals
|
|
830
|
-
);
|
|
831
|
-
const prevIndex = Web3Number.fromWei(
|
|
832
|
-
(await this.contract.get_previous_index()).toString(),
|
|
833
|
-
18
|
|
834
|
-
);
|
|
835
|
-
const currIndex = new Web3Number(1, 18)
|
|
836
|
-
.multipliedBy(assets)
|
|
837
|
-
.dividedBy(totalSupply);
|
|
838
|
-
|
|
839
|
-
logger.verbose(`Previous index: ${prevIndex.toString()}`);
|
|
840
|
-
logger.verbose(`Assets: ${assets.toString()}`);
|
|
841
|
-
logger.verbose(`Total supply: ${totalSupply.toString()}`);
|
|
842
|
-
logger.verbose(`Current index: ${currIndex.toNumber()}`);
|
|
843
|
-
|
|
844
|
-
if (currIndex.lt(prevIndex)) {
|
|
845
|
-
logger.verbose(
|
|
846
|
-
`getFee::Current index is less than previous index, no fees to be deducted`
|
|
847
|
-
);
|
|
848
|
-
return [];
|
|
973
|
+
async getPendingRewards(): Promise<HarvestInfo[]> {
|
|
974
|
+
const vesuHarvests = new VesuHarvests(this.config);
|
|
975
|
+
return await vesuHarvests.getUnHarvestedRewards(this.address);
|
|
849
976
|
}
|
|
850
977
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
978
|
+
async harvest(acc: Account) {
|
|
979
|
+
const pendingRewards = await this.getPendingRewards();
|
|
980
|
+
if (pendingRewards.length == 0) {
|
|
981
|
+
throw new Error(`No pending rewards found`);
|
|
982
|
+
}
|
|
983
|
+
const harvest = pendingRewards[0];
|
|
984
|
+
const avnu = new AvnuWrapper();
|
|
985
|
+
let swapInfo: SwapInfo = {
|
|
986
|
+
token_from_address: harvest.token.address,
|
|
987
|
+
token_from_amount: uint256.bnToUint256(
|
|
988
|
+
harvest.actualReward.toWei()
|
|
989
|
+
),
|
|
990
|
+
token_to_address: this.asset().address.address,
|
|
991
|
+
token_to_amount: uint256.bnToUint256(0),
|
|
992
|
+
token_to_min_amount: uint256.bnToUint256(0),
|
|
993
|
+
beneficiary: this.address.address,
|
|
994
|
+
integrator_fee_amount_bps: 0,
|
|
995
|
+
integrator_fee_recipient: this.address.address,
|
|
996
|
+
routes: []
|
|
997
|
+
};
|
|
998
|
+
if (!this.asset().address.eqString(harvest.token.address)) {
|
|
999
|
+
const quote = await avnu.getQuotes(
|
|
1000
|
+
harvest.token.address,
|
|
1001
|
+
this.asset().address.address,
|
|
1002
|
+
harvest.actualReward.toWei(),
|
|
1003
|
+
this.address.address
|
|
1004
|
+
);
|
|
1005
|
+
swapInfo = await avnu.getSwapInfo(
|
|
1006
|
+
quote,
|
|
1007
|
+
this.address.address,
|
|
1008
|
+
0,
|
|
1009
|
+
this.address.address
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
return [
|
|
1014
|
+
this.contract.populate("harvest", [
|
|
1015
|
+
harvest.rewardsContract.address,
|
|
1016
|
+
{
|
|
1017
|
+
id: harvest.claim.id,
|
|
1018
|
+
amount: harvest.claim.amount.toWei(),
|
|
1019
|
+
claimee: harvest.claim.claimee.address
|
|
1020
|
+
},
|
|
1021
|
+
harvest.proof,
|
|
1022
|
+
swapInfo
|
|
1023
|
+
])
|
|
1024
|
+
];
|
|
862
1025
|
}
|
|
863
1026
|
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1027
|
+
/**
|
|
1028
|
+
* Calculates the fees deducted in different vTokens based on the current and previous state.
|
|
1029
|
+
* @param previousTotalSupply - The total supply of the strategy token before the transaction
|
|
1030
|
+
* @returns {Promise<Array<{ vToken: ContractAddr, fee: Web3Number }>>} Array of fees deducted in different vTokens
|
|
1031
|
+
*/
|
|
1032
|
+
async getFee(
|
|
1033
|
+
allowedPools: Array<PoolInfoFull>
|
|
1034
|
+
): Promise<Array<{ vToken: ContractAddr; fee: Web3Number }>> {
|
|
1035
|
+
const assets = Web3Number.fromWei(
|
|
1036
|
+
(await this.contract.total_assets()).toString(),
|
|
1037
|
+
this.asset().decimals
|
|
1038
|
+
);
|
|
1039
|
+
const totalSupply = Web3Number.fromWei(
|
|
1040
|
+
(await this.contract.total_supply()).toString(),
|
|
1041
|
+
this.asset().decimals
|
|
1042
|
+
);
|
|
1043
|
+
const prevIndex = Web3Number.fromWei(
|
|
1044
|
+
(await this.contract.get_previous_index()).toString(),
|
|
1045
|
+
18
|
|
1046
|
+
);
|
|
1047
|
+
const currIndex = new Web3Number(1, 18)
|
|
1048
|
+
.multipliedBy(assets)
|
|
1049
|
+
.dividedBy(totalSupply);
|
|
1050
|
+
|
|
1051
|
+
logger.verbose(`Previous index: ${prevIndex.toString()}`);
|
|
1052
|
+
logger.verbose(`Assets: ${assets.toString()}`);
|
|
1053
|
+
logger.verbose(`Total supply: ${totalSupply.toString()}`);
|
|
1054
|
+
logger.verbose(`Current index: ${currIndex.toNumber()}`);
|
|
1055
|
+
|
|
1056
|
+
if (currIndex.lt(prevIndex)) {
|
|
1057
|
+
logger.verbose(
|
|
1058
|
+
`getFee::Current index is less than previous index, no fees to be deducted`
|
|
1059
|
+
);
|
|
1060
|
+
return [];
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
const indexDiff = currIndex.minus(prevIndex);
|
|
1064
|
+
logger.verbose(`Index diff: ${indexDiff.toString()}`);
|
|
1065
|
+
const numerator = totalSupply
|
|
1066
|
+
.multipliedBy(indexDiff)
|
|
1067
|
+
.multipliedBy(this.metadata.additionalInfo.feeBps);
|
|
1068
|
+
const denominator = 10000;
|
|
1069
|
+
let fee = numerator.dividedBy(denominator);
|
|
1070
|
+
logger.verbose(`Fee: ${fee.toString()}`);
|
|
1071
|
+
|
|
1072
|
+
if (fee.lte(0)) {
|
|
1073
|
+
return [];
|
|
1074
|
+
}
|
|
868
1075
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
if (remainingFee.lte(balance)) {
|
|
874
|
-
fees.push({ vToken, fee: remainingFee });
|
|
875
|
-
break;
|
|
876
|
-
} else {
|
|
877
|
-
fees.push({ vToken, fee: Web3Number.fromWei(balance.toString(), 18) });
|
|
878
|
-
remainingFee = remainingFee.minus(
|
|
879
|
-
Web3Number.fromWei(balance.toString(), 18)
|
|
1076
|
+
const fees: Array<{ vToken: ContractAddr; fee: Web3Number }> = [];
|
|
1077
|
+
let remainingFee = fee.plus(
|
|
1078
|
+
Web3Number.fromWei("100", this.asset().decimals)
|
|
880
1079
|
);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
1080
|
|
|
884
|
-
|
|
1081
|
+
for (const pool of allowedPools) {
|
|
1082
|
+
const vToken = pool.v_token;
|
|
1083
|
+
const balance = pool.amount;
|
|
1084
|
+
|
|
1085
|
+
if (remainingFee.lte(balance)) {
|
|
1086
|
+
fees.push({ vToken, fee: remainingFee });
|
|
1087
|
+
break;
|
|
1088
|
+
} else {
|
|
1089
|
+
fees.push({
|
|
1090
|
+
vToken,
|
|
1091
|
+
fee: Web3Number.fromWei(balance.toString(), 18)
|
|
1092
|
+
});
|
|
1093
|
+
remainingFee = remainingFee.minus(
|
|
1094
|
+
Web3Number.fromWei(balance.toString(), 18)
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
885
1098
|
|
|
886
|
-
|
|
887
|
-
|
|
1099
|
+
logger.verbose(`Fees: ${JSON.stringify(fees)}`);
|
|
1100
|
+
|
|
1101
|
+
return fees;
|
|
1102
|
+
}
|
|
888
1103
|
}
|
|
889
1104
|
|
|
890
|
-
const _description =
|
|
1105
|
+
const _description =
|
|
1106
|
+
"Automatically diversify {{TOKEN}} holdings into different Vesu pools while reducing risk and maximizing yield. Defi spring STRK Rewards are auto-compounded as well.";
|
|
891
1107
|
|
|
892
1108
|
const _protocol: IProtocol = {
|
|
893
|
-
|
|
894
|
-
|
|
1109
|
+
name: "Vesu",
|
|
1110
|
+
logo: "https://static-assets-8zct.onrender.com/integrations/vesu/logo.png"
|
|
895
1111
|
};
|
|
896
1112
|
// need to fine tune better
|
|
897
1113
|
const _riskFactor: RiskFactor[] = [
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1114
|
+
{
|
|
1115
|
+
type: RiskType.SMART_CONTRACT_RISK,
|
|
1116
|
+
value: 0.5,
|
|
1117
|
+
weight: 25,
|
|
1118
|
+
reason: "Audited by CSC"
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
type: RiskType.COUNTERPARTY_RISK,
|
|
1122
|
+
value: 1,
|
|
1123
|
+
weight: 50,
|
|
1124
|
+
reason: "Reasonable max LTV ratios and Curated by well-known risk managers like Re7"
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
type: RiskType.ORACLE_RISK,
|
|
1128
|
+
value: 0.5,
|
|
1129
|
+
weight: 25,
|
|
1130
|
+
reason: "Uses Pragma price feeds, Most reputable price feed on Starknet"
|
|
1131
|
+
}
|
|
901
1132
|
];
|
|
902
1133
|
const AUDIT_URL =
|
|
903
|
-
|
|
1134
|
+
"https://assets.troves.fi/strkfarm/audit_report_vesu_and_ekubo_strats.pdf";
|
|
904
1135
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
answer: "Yes, of course! You will earn Vesu points for your deposits."
|
|
914
|
-
},
|
|
915
|
-
{
|
|
916
|
-
question: "How does the strategy optimize yield?",
|
|
917
|
-
answer:
|
|
918
|
-
"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."
|
|
919
|
-
},
|
|
920
|
-
{
|
|
921
|
-
question: "What are the risks associated with this strategy?",
|
|
922
|
-
answer:
|
|
923
|
-
"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."
|
|
924
|
-
},
|
|
925
|
-
{
|
|
926
|
-
question: "How are fees calculated and deducted?",
|
|
927
|
-
answer:
|
|
928
|
-
"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."
|
|
929
|
-
},
|
|
930
|
-
{
|
|
931
|
-
question: "What happens if a pool exceeds its maximum weight?",
|
|
932
|
-
answer:
|
|
933
|
-
"If a pool exceeds its maximum weight, the strategy rebalances by withdrawing excess funds and reallocating them to other pools with available capacity."
|
|
934
|
-
},
|
|
935
|
-
{
|
|
936
|
-
question: "Can I withdraw my assets at any time?",
|
|
937
|
-
answer:
|
|
938
|
-
"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."
|
|
939
|
-
},
|
|
940
|
-
{
|
|
941
|
-
question: "What happens to my Defi Spring STRK rewards?",
|
|
942
|
-
answer:
|
|
943
|
-
"STRK rewards are automatically harvested and reinvested into the strategy every week to maximize compounding returns."
|
|
944
|
-
},
|
|
945
|
-
{
|
|
946
|
-
question: "Is the strategy audited?",
|
|
947
|
-
answer:
|
|
948
|
-
<div>Yes, the strategy has been audited. You can review the audit report in our docs <a href="https://docs.troves.fi/p/strategies/vesu-fusion-rebalancing-vaults#technical-details" style={{textDecoration: 'underline', marginLeft: '5px'}}>Here</a>.</div>
|
|
949
|
-
}
|
|
950
|
-
];
|
|
1136
|
+
// Helper to create common risk object
|
|
1137
|
+
const getVesuRebalanceRisk = () => ({
|
|
1138
|
+
riskFactor: _riskFactor,
|
|
1139
|
+
netRisk:
|
|
1140
|
+
_riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
|
|
1141
|
+
_riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
|
|
1142
|
+
notARisks: getNoRiskTags(_riskFactor)
|
|
1143
|
+
});
|
|
951
1144
|
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
{
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1145
|
+
// Helper to create Vesu Rebalance strategy settings
|
|
1146
|
+
const createVesuRebalanceSettings = (tokenSymbol: string): StrategySettings => {
|
|
1147
|
+
const depositToken = Global.getDefaultTokens().find(
|
|
1148
|
+
(t) => t.symbol === tokenSymbol
|
|
1149
|
+
)!;
|
|
1150
|
+
return {
|
|
1151
|
+
maxTVL: Web3Number.fromWei("0", depositToken.decimals),
|
|
1152
|
+
isPaused: false,
|
|
1153
|
+
liveStatus: StrategyLiveStatus.ACTIVE,
|
|
1154
|
+
isAudited: true,
|
|
1155
|
+
isInstantWithdrawal: true,
|
|
1156
|
+
quoteToken: depositToken,
|
|
1157
|
+
alerts: []
|
|
1158
|
+
};
|
|
1159
|
+
};
|
|
1160
|
+
|
|
1161
|
+
// Helper to create a Vesu Rebalance strategy
|
|
1162
|
+
const createVesuRebalanceStrategy = (
|
|
1163
|
+
name: string,
|
|
1164
|
+
tokenSymbol: string,
|
|
1165
|
+
address: string
|
|
1166
|
+
): IStrategyMetadata<VesuRebalanceSettings> => ({
|
|
1167
|
+
id: `vesu_fusion_${tokenSymbol.toLowerCase()}`,
|
|
1168
|
+
name,
|
|
1169
|
+
description: _description.replace("{{TOKEN}}", tokenSymbol),
|
|
1170
|
+
address: ContractAddr.from(address),
|
|
1171
|
+
launchBlock: 0,
|
|
1172
|
+
type: "ERC4626" as const,
|
|
1173
|
+
depositTokens: [
|
|
1174
|
+
Global.getDefaultTokens().find((t) => t.symbol === tokenSymbol)!
|
|
1175
|
+
],
|
|
1176
|
+
protocols: [_protocol],
|
|
1177
|
+
auditUrl: AUDIT_URL,
|
|
1178
|
+
settings: createVesuRebalanceSettings(tokenSymbol),
|
|
1179
|
+
risk: getVesuRebalanceRisk(),
|
|
1180
|
+
additionalInfo: {
|
|
979
1181
|
feeBps: 1000
|
|
980
|
-
},
|
|
981
|
-
faqs,
|
|
982
|
-
contractDetails: [],
|
|
983
|
-
investmentSteps: []
|
|
984
1182
|
},
|
|
1183
|
+
faqs,
|
|
1184
|
+
contractDetails: [],
|
|
1185
|
+
investmentSteps: []
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
// Shared security & redemption metadata for all Vesu strategies
|
|
1189
|
+
const VESU_SECURITY = {
|
|
1190
|
+
auditStatus: AuditStatus.AUDITED,
|
|
1191
|
+
sourceCode: {
|
|
1192
|
+
type: SourceCodeType.OPEN_SOURCE,
|
|
1193
|
+
contractLink: "https://github.com/trovesfi/troves-contracts"
|
|
1194
|
+
},
|
|
1195
|
+
accessControl: {
|
|
1196
|
+
type: AccessControlType.STANDARD_ACCOUNT,
|
|
1197
|
+
addresses: [ContractAddr.from("0x0")],
|
|
1198
|
+
timeLock: "2 Days"
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
|
|
1202
|
+
const VESU_REDEMPTION_INFO = {
|
|
1203
|
+
instantWithdrawalVault: InstantWithdrawalVault.YES
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
const faqs: FAQ[] = [
|
|
985
1207
|
{
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
address: ContractAddr.from(
|
|
989
|
-
"0x5eaf5ee75231cecf79921ff8ded4b5ffe96be718bcb3daf206690ad1a9ad0ca"
|
|
990
|
-
),
|
|
991
|
-
launchBlock: 0,
|
|
992
|
-
type: "ERC4626",
|
|
993
|
-
auditUrl: AUDIT_URL,
|
|
994
|
-
depositTokens: [
|
|
995
|
-
Global.getDefaultTokens().find((t) => t.symbol === "ETH")!
|
|
996
|
-
],
|
|
997
|
-
protocols: [_protocol],
|
|
998
|
-
maxTVL: Web3Number.fromWei("0", 18),
|
|
999
|
-
risk: {
|
|
1000
|
-
riskFactor: _riskFactor,
|
|
1001
|
-
netRisk:
|
|
1002
|
-
_riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
|
|
1003
|
-
_riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
|
|
1004
|
-
notARisks: getNoRiskTags(_riskFactor)
|
|
1005
|
-
},
|
|
1006
|
-
additionalInfo: {
|
|
1007
|
-
feeBps: 1000
|
|
1008
|
-
},
|
|
1009
|
-
faqs,
|
|
1010
|
-
contractDetails: [],
|
|
1011
|
-
investmentSteps: []
|
|
1208
|
+
question: "What is the Vesu Rebalancing Strategy?",
|
|
1209
|
+
answer: "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."
|
|
1012
1210
|
},
|
|
1013
1211
|
{
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
address: ContractAddr.from(
|
|
1017
|
-
"0xa858c97e9454f407d1bd7c57472fc8d8d8449a777c822b41d18e387816f29c"
|
|
1018
|
-
),
|
|
1019
|
-
launchBlock: 0,
|
|
1020
|
-
type: "ERC4626",
|
|
1021
|
-
auditUrl: AUDIT_URL,
|
|
1022
|
-
depositTokens: [
|
|
1023
|
-
Global.getDefaultTokens().find((t) => t.symbol === "USDC")!
|
|
1024
|
-
],
|
|
1025
|
-
protocols: [_protocol],
|
|
1026
|
-
maxTVL: Web3Number.fromWei("0", 6),
|
|
1027
|
-
risk: {
|
|
1028
|
-
riskFactor: _riskFactor,
|
|
1029
|
-
netRisk:
|
|
1030
|
-
_riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
|
|
1031
|
-
_riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
|
|
1032
|
-
notARisks: getNoRiskTags(_riskFactor)
|
|
1033
|
-
},
|
|
1034
|
-
additionalInfo: {
|
|
1035
|
-
feeBps: 1000
|
|
1036
|
-
},
|
|
1037
|
-
faqs,
|
|
1038
|
-
contractDetails: [],
|
|
1039
|
-
investmentSteps: []
|
|
1212
|
+
question: "Will I earn Vesu points?",
|
|
1213
|
+
answer: "Yes, of course! You will earn Vesu points for your deposits."
|
|
1040
1214
|
},
|
|
1041
1215
|
{
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
// },
|
|
1081
|
-
// additionalInfo: {
|
|
1082
|
-
// feeBps: 1000,
|
|
1083
|
-
// },
|
|
1084
|
-
|
|
1216
|
+
question: "How does the strategy optimize yield?",
|
|
1217
|
+
answer: "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."
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
question: "What are the risks associated with this strategy?",
|
|
1221
|
+
answer: "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."
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
question: "How are fees calculated and deducted?",
|
|
1225
|
+
answer: "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."
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
question: "What happens if a pool exceeds its maximum weight?",
|
|
1229
|
+
answer: "If a pool exceeds its maximum weight, the strategy rebalances by withdrawing excess funds and reallocating them to other pools with available capacity."
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
question: "Can I withdraw my assets at any time?",
|
|
1233
|
+
answer: "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."
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
question: "What happens to my Defi Spring STRK rewards?",
|
|
1237
|
+
answer: "STRK rewards are automatically harvested and reinvested into the strategy every week to maximize compounding returns."
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
question: "Is the strategy audited?",
|
|
1241
|
+
answer: (
|
|
1242
|
+
<div>
|
|
1243
|
+
Yes, the strategy has been audited. You can review the audit
|
|
1244
|
+
report in our docs{" "}
|
|
1245
|
+
<a
|
|
1246
|
+
href="https://docs.troves.fi/p/strategies/vesu-fusion-rebalancing-vaults#technical-details"
|
|
1247
|
+
style={{ textDecoration: "underline", marginLeft: "5px" }}
|
|
1248
|
+
>
|
|
1249
|
+
Here
|
|
1250
|
+
</a>
|
|
1251
|
+
.
|
|
1252
|
+
</div>
|
|
1253
|
+
)
|
|
1085
1254
|
}
|
|
1086
1255
|
];
|
|
1087
1256
|
|
|
1257
|
+
/**
|
|
1258
|
+
* Represents the Vesu Rebalance Strategies.
|
|
1259
|
+
*/
|
|
1260
|
+
export const VesuRebalanceStrategies: IStrategyMetadata<VesuRebalanceSettings>[] =
|
|
1261
|
+
[
|
|
1262
|
+
createVesuRebalanceStrategy(
|
|
1263
|
+
"Vesu Fusion STRK",
|
|
1264
|
+
"STRK",
|
|
1265
|
+
"0x7fb5bcb8525954a60fde4e8fb8220477696ce7117ef264775a1770e23571929"
|
|
1266
|
+
),
|
|
1267
|
+
createVesuRebalanceStrategy(
|
|
1268
|
+
"Vesu Fusion ETH",
|
|
1269
|
+
"ETH",
|
|
1270
|
+
"0x5eaf5ee75231cecf79921ff8ded4b5ffe96be718bcb3daf206690ad1a9ad0ca"
|
|
1271
|
+
),
|
|
1272
|
+
createVesuRebalanceStrategy(
|
|
1273
|
+
"Vesu Fusion USDC",
|
|
1274
|
+
"USDC",
|
|
1275
|
+
"0xa858c97e9454f407d1bd7c57472fc8d8d8449a777c822b41d18e387816f29c"
|
|
1276
|
+
),
|
|
1277
|
+
createVesuRebalanceStrategy(
|
|
1278
|
+
"Vesu Fusion USDT",
|
|
1279
|
+
"USDT",
|
|
1280
|
+
"0x115e94e722cfc4c77a2f15c4aefb0928c1c0029e5a57570df24c650cb7cec2c"
|
|
1281
|
+
)
|
|
1282
|
+
];
|
|
1283
|
+
|
|
1088
1284
|
// auto assign contract details to each strategy
|
|
1089
1285
|
VesuRebalanceStrategies.forEach((s) => {
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1286
|
+
// set contract details
|
|
1287
|
+
s.contractDetails = [
|
|
1288
|
+
{
|
|
1289
|
+
address: s.address,
|
|
1290
|
+
name: "Vault",
|
|
1291
|
+
sourceCodeUrl:
|
|
1292
|
+
"https://github.com/strkfarm/strkfarm-contracts/tree/main/src/strategies/vesu_rebalance"
|
|
1293
|
+
},
|
|
1294
|
+
...COMMON_CONTRACTS
|
|
1295
|
+
];
|
|
1296
|
+
// set docs link
|
|
1297
|
+
s.docs =
|
|
1298
|
+
"https://docs.troves.fi/p/strategies/vesu-fusion-rebalancing-vaults";
|
|
1299
|
+
|
|
1300
|
+
// set description
|
|
1301
|
+
s.description = highlightTextWithLinks(
|
|
1302
|
+
_description.replace("{{TOKEN}}", s.depositTokens[0].symbol),
|
|
1303
|
+
[
|
|
1304
|
+
{
|
|
1305
|
+
highlight: "Vesu pools",
|
|
1306
|
+
link: "https://vesu.xyz/pools"
|
|
1307
|
+
},
|
|
1308
|
+
{
|
|
1309
|
+
highlight: "Defi spring STRK Rewards",
|
|
1310
|
+
link: "https://defispring.starknet.io/"
|
|
1311
|
+
}
|
|
1312
|
+
]
|
|
1313
|
+
);
|
|
1314
|
+
|
|
1315
|
+
// add investment steps
|
|
1316
|
+
s.investmentSteps = [
|
|
1317
|
+
"Split the amount and Supply to configured Vesu pools",
|
|
1318
|
+
"Monitor and Rebalance funds across multiple Vesu pools to maximize yield",
|
|
1319
|
+
"Harvest and supply Defi Spring STRK rewards every week (Auto-compound)"
|
|
1320
|
+
];
|
|
1321
|
+
});
|