@strkfarm/sdk 2.0.0-dev.4 → 2.0.0-dev.41
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 +190 -36
- package/dist/cli.mjs +188 -34
- package/dist/index.browser.global.js +116250 -90801
- package/dist/index.browser.mjs +13050 -10957
- package/dist/index.d.ts +2232 -1933
- package/dist/index.js +13380 -11084
- package/dist/index.mjs +13280 -11007
- package/package.json +6 -7
- package/src/data/avnu.abi.json +840 -0
- package/src/data/ekubo-price-fethcer.abi.json +265 -0
- package/src/data/redeem-request-nft.abi.json +752 -0
- package/src/data/universal-vault.abi.json +8 -7
- package/src/dataTypes/_bignumber.ts +13 -4
- package/src/dataTypes/bignumber.browser.ts +10 -1
- package/src/dataTypes/bignumber.node.ts +10 -1
- package/src/dataTypes/index.ts +3 -2
- package/src/dataTypes/mynumber.ts +141 -0
- package/src/global.ts +93 -36
- package/src/index.browser.ts +2 -1
- package/src/interfaces/common.tsx +218 -5
- package/src/modules/apollo-client-config.ts +28 -0
- package/src/modules/avnu.ts +21 -12
- package/src/modules/ekubo-pricer.ts +79 -0
- package/src/modules/ekubo-quoter.ts +48 -30
- package/src/modules/erc20.ts +17 -0
- package/src/modules/harvests.ts +43 -29
- package/src/modules/index.ts +2 -1
- package/src/modules/pragma.ts +23 -8
- package/src/modules/pricer-avnu-api.ts +114 -0
- package/src/modules/pricer-from-api.ts +156 -15
- package/src/modules/pricer-lst.ts +1 -1
- package/src/modules/pricer.ts +94 -40
- package/src/modules/pricerBase.ts +2 -1
- package/src/node/deployer.ts +36 -1
- package/src/node/pricer-redis.ts +3 -1
- package/src/strategies/base-strategy.ts +168 -16
- package/src/strategies/constants.ts +8 -3
- package/src/strategies/ekubo-cl-vault.tsx +1047 -351
- package/src/strategies/factory.ts +199 -0
- package/src/strategies/index.ts +5 -3
- package/src/strategies/registry.ts +262 -0
- package/src/strategies/sensei.ts +353 -9
- package/src/strategies/svk-strategy.ts +283 -31
- package/src/strategies/token-boosted-xstrk-carry-strategy.tsx +1262 -0
- package/src/strategies/types.ts +4 -0
- package/src/strategies/universal-adapters/adapter-utils.ts +4 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +196 -272
- package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
- package/src/strategies/universal-adapters/common-adapter.ts +206 -203
- package/src/strategies/universal-adapters/index.ts +10 -8
- package/src/strategies/universal-adapters/svk-troves-adapter.ts +511 -0
- package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +120 -82
- package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +525 -0
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1098 -712
- package/src/strategies/universal-adapters/vesu-position-common.ts +258 -0
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
- package/src/strategies/universal-lst-muliplier-strategy.tsx +631 -414
- package/src/strategies/universal-strategy.tsx +1331 -1173
- package/src/strategies/vesu-rebalance.tsx +252 -152
- package/src/strategies/yoloVault.ts +1087 -0
- package/src/utils/cacheClass.ts +11 -2
- package/src/utils/health-factor-math.ts +33 -1
- package/src/utils/index.ts +3 -1
- package/src/utils/logger.browser.ts +22 -4
- package/src/utils/logger.node.ts +259 -24
- package/src/utils/starknet-call-parser.ts +1036 -0
- package/src/utils/strategy-utils.ts +61 -0
- 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/strategies/universal-adapters/extended-adapter.ts +0 -661
- package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
- 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 -372
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +0 -1140
|
@@ -0,0 +1,1262 @@
|
|
|
1
|
+
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
2
|
+
import { Global } from "@/global";
|
|
3
|
+
import {
|
|
4
|
+
AccessControlType,
|
|
5
|
+
AuditStatus,
|
|
6
|
+
FAQ,
|
|
7
|
+
getMainnetConfig,
|
|
8
|
+
getNoRiskTags,
|
|
9
|
+
IConfig,
|
|
10
|
+
InstantWithdrawalVault,
|
|
11
|
+
IStrategyMetadata,
|
|
12
|
+
Protocols,
|
|
13
|
+
RiskFactor,
|
|
14
|
+
RiskType,
|
|
15
|
+
SourceCodeType,
|
|
16
|
+
StrategyLiveStatus,
|
|
17
|
+
StrategyTag,
|
|
18
|
+
TokenInfo,
|
|
19
|
+
VaultPosition,
|
|
20
|
+
VaultType,
|
|
21
|
+
} from "@/interfaces";
|
|
22
|
+
import {
|
|
23
|
+
CounterpartyRiskLevel,
|
|
24
|
+
DepegRiskLevel,
|
|
25
|
+
LiquidationRiskLevel,
|
|
26
|
+
LowLiquidityRiskLevel,
|
|
27
|
+
MarketRiskLevel,
|
|
28
|
+
OracleRiskLevel,
|
|
29
|
+
SmartContractRiskLevel,
|
|
30
|
+
TechnicalRiskLevel,
|
|
31
|
+
} from "@/interfaces/risks";
|
|
32
|
+
import { ERC20, PricerFromApi, TokenMarketData } from "@/modules";
|
|
33
|
+
import { PricerBase } from "@/modules/pricerBase";
|
|
34
|
+
import { logger, assert } from "@/utils";
|
|
35
|
+
import { BlockIdentifier, Call, uint256, num } from "starknet";
|
|
36
|
+
import {
|
|
37
|
+
SingleTokenInfo,
|
|
38
|
+
UserPositionCard,
|
|
39
|
+
UserPositionCardsInput,
|
|
40
|
+
} from "./base-strategy";
|
|
41
|
+
import { SVKStrategy } from "./svk-strategy";
|
|
42
|
+
import {
|
|
43
|
+
APYType,
|
|
44
|
+
AvnuAdapter,
|
|
45
|
+
BaseAdapterConfig,
|
|
46
|
+
CommonAdapter,
|
|
47
|
+
PositionInfo,
|
|
48
|
+
TokenTransferAdapter,
|
|
49
|
+
VesuModifyPositionAdapter,
|
|
50
|
+
} from "./universal-adapters";
|
|
51
|
+
import { AdapterOptimizer } from "./universal-adapters/adapter-optimizer";
|
|
52
|
+
import {
|
|
53
|
+
AVNU_EXCHANGE,
|
|
54
|
+
AVNU_QUOTE_URL,
|
|
55
|
+
} from "./universal-adapters/adapter-utils";
|
|
56
|
+
import { SvkTrovesAdapter } from "./universal-adapters/svk-troves-adapter";
|
|
57
|
+
import { VesuPools, VesuAdapter } from "./universal-adapters/vesu-adapter";
|
|
58
|
+
import {
|
|
59
|
+
AUMTypes,
|
|
60
|
+
getContractDetails,
|
|
61
|
+
UNIVERSAL_MANAGE_IDS,
|
|
62
|
+
UniversalStrategySettings,
|
|
63
|
+
} from "./universal-strategy";
|
|
64
|
+
|
|
65
|
+
export interface BoostedxSTRKCarryStrategySettings extends UniversalStrategySettings {
|
|
66
|
+
depositToken: TokenInfo; // USDC, WBTC, etc. - the collateral token
|
|
67
|
+
debtToken: TokenInfo; // STRK, etc. - the debt token
|
|
68
|
+
lstHyperToken: TokenInfo; // xSTRK, etc. - the LST token deposited into hyper vault
|
|
69
|
+
vesuPoolId: ContractAddr;
|
|
70
|
+
maxLTV: number; // e.g. 0.66
|
|
71
|
+
targetLTV: number;
|
|
72
|
+
hyperLstVaultAddress: ContractAddr; // Address of the hyper vault for LST
|
|
73
|
+
hyperLstRedeemNFT: ContractAddr; // NFT contract for pending hyper redeems
|
|
74
|
+
trovesStrategyId: string; // e.g. "hyper_xstrk" - used for APY fetching
|
|
75
|
+
// TODO: will refac later on to a better approach where we dont need flags
|
|
76
|
+
hasBtcFiRewards: boolean; // Whether this strategy has BTC.Fi rewards (affects reporting flow)
|
|
77
|
+
// Adapter IDs computed from token symbols
|
|
78
|
+
adapterIds?: {
|
|
79
|
+
vesu: string;
|
|
80
|
+
avnu: string;
|
|
81
|
+
hyper: string;
|
|
82
|
+
transfer: string;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class BoostedxSTRKCarryStrategy<
|
|
87
|
+
S extends BoostedxSTRKCarryStrategySettings,
|
|
88
|
+
> extends SVKStrategy<S> {
|
|
89
|
+
constructor(
|
|
90
|
+
config: IConfig,
|
|
91
|
+
pricer: PricerBase,
|
|
92
|
+
metadata: IStrategyMetadata<S>,
|
|
93
|
+
) {
|
|
94
|
+
super(config, pricer, metadata);
|
|
95
|
+
|
|
96
|
+
this.metadata.additionalInfo.adapters.forEach((adapter) => {
|
|
97
|
+
adapter.adapter.config.networkConfig = this.config;
|
|
98
|
+
adapter.adapter.config.pricer = this.pricer;
|
|
99
|
+
if ((adapter.adapter as any)._vesuAdapter) {
|
|
100
|
+
(adapter.adapter as any)._vesuAdapter.networkConfig = this.config;
|
|
101
|
+
(adapter.adapter as any)._vesuAdapter.pricer = this.pricer;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getTag(): string {
|
|
107
|
+
return `${BoostedxSTRKCarryStrategy.name}:${this.metadata.name}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private getAdapterById<T>(id: string): T {
|
|
111
|
+
const entry = this.metadata.additionalInfo.adapters.find(
|
|
112
|
+
(a) => a.id === id,
|
|
113
|
+
);
|
|
114
|
+
if (!entry) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`${this.getTag()}::getAdapterById: adapter not found for id "${id}"`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return entry.adapter as T;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private async getTruePriceForToken(token: TokenInfo): Promise<number> {
|
|
123
|
+
return new TokenMarketData(this.pricer, this.config).getTruePrice(token);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async getMinOutputAmountLSTBuy(amountInUnderlying: Web3Number) {
|
|
127
|
+
const lst = this.metadata.additionalInfo.lstHyperToken;
|
|
128
|
+
const lstTruePrice = await this.getTruePriceForToken(lst);
|
|
129
|
+
const minOutputAmount = amountInUnderlying
|
|
130
|
+
.dividedBy(lstTruePrice)
|
|
131
|
+
.multipliedBy(0.99979); // 0.21 % avnu fees
|
|
132
|
+
return new Web3Number(minOutputAmount.toString(), lst.decimals);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async getMinOutputAmountLSTSell(amountInLST: Web3Number) {
|
|
136
|
+
const lst = this.metadata.additionalInfo.lstHyperToken;
|
|
137
|
+
const lstTruePrice = await this.getTruePriceForToken(lst);
|
|
138
|
+
return amountInLST.multipliedBy(lstTruePrice).multipliedBy(0.995);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async getVesuModifyPositionCall(params: {
|
|
142
|
+
isDeposit: boolean;
|
|
143
|
+
leg1DepositAmount: Web3Number;
|
|
144
|
+
debtAmount?: Web3Number;
|
|
145
|
+
}): Promise<Call> {
|
|
146
|
+
logger.verbose(
|
|
147
|
+
`${this.getTag()}::getVesuModifyPositionCall isDeposit=${params.isDeposit}, amount=${params.leg1DepositAmount}, debtAmount=${params.debtAmount?.toNumber()}`,
|
|
148
|
+
);
|
|
149
|
+
return this.buildManageCallFromAdapter(
|
|
150
|
+
this.getAdapterById<VesuModifyPositionAdapter>(
|
|
151
|
+
this.metadata.additionalInfo.adapterIds!.vesu,
|
|
152
|
+
),
|
|
153
|
+
params.isDeposit,
|
|
154
|
+
params.leg1DepositAmount,
|
|
155
|
+
params.debtAmount ? { debtAmount: params.debtAmount } : undefined,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async getVesuPositions(): Promise<VaultPosition[]> {
|
|
160
|
+
const positions = await this.getAdapterById<VesuModifyPositionAdapter>(
|
|
161
|
+
this.metadata.additionalInfo.adapterIds!.vesu,
|
|
162
|
+
).getPositions();
|
|
163
|
+
return positions.map((p) => ({
|
|
164
|
+
amount: p.amount,
|
|
165
|
+
usdValue: p.usdValue,
|
|
166
|
+
remarks: p.remarks ?? "",
|
|
167
|
+
token: p.tokenInfo,
|
|
168
|
+
protocol: p.protocol,
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async getVesuHealthFactor(
|
|
173
|
+
blockNumber: BlockIdentifier = "latest",
|
|
174
|
+
): Promise<number> {
|
|
175
|
+
const vesuAdapter = this.getAdapterById<VesuModifyPositionAdapter>(
|
|
176
|
+
this.metadata.additionalInfo.adapterIds!.vesu,
|
|
177
|
+
);
|
|
178
|
+
return await vesuAdapter._vesuAdapter.getHealthFactor(blockNumber);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// TODO: will have to still modify for instant withdrawals as of now
|
|
182
|
+
async getModifyHyperPositionCall(params: {
|
|
183
|
+
isDeposit: boolean;
|
|
184
|
+
amount: Web3Number;
|
|
185
|
+
}): Promise<Call> {
|
|
186
|
+
logger.verbose(
|
|
187
|
+
`${this.getTag()}::getModifyHyperPositionCall isDeposit=${params.isDeposit}, amount=${params.amount}`,
|
|
188
|
+
);
|
|
189
|
+
return this.buildManageCallFromAdapter(
|
|
190
|
+
this.getAdapterById<SvkTrovesAdapter>(
|
|
191
|
+
this.metadata.additionalInfo.adapterIds!.hyper,
|
|
192
|
+
),
|
|
193
|
+
params.isDeposit,
|
|
194
|
+
params.amount,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async getAvnuSwapCall(params: {
|
|
199
|
+
isDeposit: boolean;
|
|
200
|
+
amount: Web3Number;
|
|
201
|
+
minAmount?: Web3Number;
|
|
202
|
+
}): Promise<Call> {
|
|
203
|
+
logger.verbose(
|
|
204
|
+
`${this.getTag()}::getAvnuSwapCall isDeposit=${params.isDeposit}, amount=${params.amount}, minAmount=${params.minAmount?.toNumber()}`,
|
|
205
|
+
);
|
|
206
|
+
return this.buildManageCallFromAdapter(
|
|
207
|
+
this.getAdapterById<AvnuAdapter>(
|
|
208
|
+
this.metadata.additionalInfo.adapterIds!.avnu,
|
|
209
|
+
),
|
|
210
|
+
params.isDeposit,
|
|
211
|
+
params.amount,
|
|
212
|
+
params.minAmount ? { minAmount: params.minAmount } : undefined,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Returns the deposit token balance sitting idle inside the vault contract itself.
|
|
218
|
+
* This balance can offset the required liquidity during withdrawal processing.
|
|
219
|
+
*/
|
|
220
|
+
async getUnusedBalanceVault(): Promise<Web3Number> {
|
|
221
|
+
const depositToken = this.metadata.additionalInfo.depositToken;
|
|
222
|
+
return new ERC20(this.config).balanceOf(
|
|
223
|
+
depositToken.address,
|
|
224
|
+
this.metadata.additionalInfo.vaultAddress,
|
|
225
|
+
depositToken.decimals,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Returns the current borrow token balance sitting unused in the vault allocator.
|
|
231
|
+
* This covers borrow token from prior borrow cycles that hasn't been swapped yet.
|
|
232
|
+
*/
|
|
233
|
+
// Technically we can use this or we can even use the avnu-adapter to get the unused debt
|
|
234
|
+
async getUnusedDebt(): Promise<Web3Number> {
|
|
235
|
+
const debtToken = this.metadata.additionalInfo.debtToken;
|
|
236
|
+
return new ERC20(this.config).balanceOf(
|
|
237
|
+
debtToken.address,
|
|
238
|
+
this.metadata.additionalInfo.vaultAllocator,
|
|
239
|
+
debtToken.decimals,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Returns the LST balance sitting in the vault allocator.
|
|
245
|
+
* This is non-zero when the previous cycle's request_redeem on the HyperPosition
|
|
246
|
+
* has been settled — the redeemed LST lands here and is ready to be swapped.
|
|
247
|
+
*/
|
|
248
|
+
async getLstInVaultAllocator(): Promise<Web3Number> {
|
|
249
|
+
const lstToken = this.metadata.additionalInfo.lstHyperToken;
|
|
250
|
+
return new ERC20(this.config).balanceOf(
|
|
251
|
+
lstToken.address.address,
|
|
252
|
+
this.metadata.additionalInfo.vaultAllocator,
|
|
253
|
+
lstToken.decimals,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Simulates depositing `depositAmount` on Vesu and returns the
|
|
259
|
+
* incremental borrow token that would be borrowed. Needed to size the AVNU swap
|
|
260
|
+
* call that is batched together with the Vesu call in the same transaction.
|
|
261
|
+
*/
|
|
262
|
+
async computeVesuDepositBorrowAmount(
|
|
263
|
+
depositAmount: Web3Number,
|
|
264
|
+
): Promise<Web3Number> {
|
|
265
|
+
return this.getAdapterById<VesuModifyPositionAdapter>(
|
|
266
|
+
this.metadata.additionalInfo.adapterIds!.vesu,
|
|
267
|
+
).getExpectedDepositDebtDelta(depositAmount);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async computeVesuWithdrawDebtDelta(
|
|
271
|
+
withdrawAmount: Web3Number,
|
|
272
|
+
): Promise<Web3Number> {
|
|
273
|
+
return this.getAdapterById<VesuModifyPositionAdapter>(
|
|
274
|
+
this.metadata.additionalInfo.adapterIds!.vesu,
|
|
275
|
+
).getExpectedWithdrawDebtDelta(withdrawAmount);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async getPendingHyperAssets(): Promise<Web3Number> {
|
|
279
|
+
const lstToken = this.metadata.additionalInfo.lstHyperToken;
|
|
280
|
+
const hyperLstRedeemNFT = this.metadata.additionalInfo.hyperLstRedeemNFT;
|
|
281
|
+
return this.getAdapterById<SvkTrovesAdapter>(
|
|
282
|
+
this.metadata.additionalInfo.adapterIds!.hyper,
|
|
283
|
+
).getPendingAssetsFromOwnerNFTMethod(
|
|
284
|
+
hyperLstRedeemNFT,
|
|
285
|
+
this.metadata.additionalInfo.vaultAllocator,
|
|
286
|
+
lstToken.decimals,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// TODO: rn we are just making these functions in the strategy itself but
|
|
291
|
+
// later on we will move them to the SVKStrategy for generalization
|
|
292
|
+
|
|
293
|
+
async getUserTVL(
|
|
294
|
+
user: ContractAddr,
|
|
295
|
+
blockIdentifier: BlockIdentifier = "latest",
|
|
296
|
+
) {
|
|
297
|
+
const shares: any = await this.contract.call("balanceOf", [user.address], {
|
|
298
|
+
blockIdentifier,
|
|
299
|
+
});
|
|
300
|
+
const assets: any = await this.contract.call(
|
|
301
|
+
"convert_to_assets",
|
|
302
|
+
[uint256.bnToUint256(shares)],
|
|
303
|
+
{ blockIdentifier },
|
|
304
|
+
);
|
|
305
|
+
const amount = Web3Number.fromWei(
|
|
306
|
+
assets.toString(),
|
|
307
|
+
this.metadata.depositTokens[0].decimals,
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const blockNumber =
|
|
311
|
+
typeof blockIdentifier === "number" || typeof blockIdentifier === "bigint"
|
|
312
|
+
? Number(blockIdentifier)
|
|
313
|
+
: undefined;
|
|
314
|
+
|
|
315
|
+
const price = await this.pricer.getPrice(
|
|
316
|
+
this.metadata.depositTokens[0].symbol,
|
|
317
|
+
blockNumber,
|
|
318
|
+
);
|
|
319
|
+
const usdValue = Number(amount.toFixed(6)) * price.price;
|
|
320
|
+
return {
|
|
321
|
+
tokenInfo: this.asset(),
|
|
322
|
+
amount,
|
|
323
|
+
usdValue,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async getUserPositionCards(
|
|
328
|
+
input: UserPositionCardsInput,
|
|
329
|
+
): Promise<UserPositionCard[]> {
|
|
330
|
+
const cards = await super.getUserPositionCards(input);
|
|
331
|
+
const realizedApyRaw = await this.getUserRealizedAPY().catch((error) => {
|
|
332
|
+
logger.warn(
|
|
333
|
+
`${this.getTag()}::getUserPositionCards realized APY fallback`,
|
|
334
|
+
error,
|
|
335
|
+
);
|
|
336
|
+
return null;
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const realizedApy =
|
|
340
|
+
typeof realizedApyRaw === "number"
|
|
341
|
+
? this.formatPercentForCard(realizedApyRaw)
|
|
342
|
+
: "N/A";
|
|
343
|
+
|
|
344
|
+
cards.push({
|
|
345
|
+
title: "Realized APY",
|
|
346
|
+
tooltip:
|
|
347
|
+
this.metadata.realizedApyMethodology ||
|
|
348
|
+
"Realized APY is based on past 14 days performance",
|
|
349
|
+
value: realizedApy,
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
return cards;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async getAUM(): Promise<{
|
|
356
|
+
net: SingleTokenInfo;
|
|
357
|
+
prevAum: Web3Number;
|
|
358
|
+
splits: PositionInfo[];
|
|
359
|
+
}> {
|
|
360
|
+
const underlying = this.asset(); // Deposit token (USDC, WBTC, etc.)
|
|
361
|
+
const depositTokenPrice = await this.pricer.getPrice(underlying.symbol);
|
|
362
|
+
|
|
363
|
+
const allPositions = await this.getVaultPositions();
|
|
364
|
+
|
|
365
|
+
let netAUM = new Web3Number(0, underlying.decimals);
|
|
366
|
+
for (const position of allPositions) {
|
|
367
|
+
if (position.token.address.eq(underlying.address)) {
|
|
368
|
+
// Same token as underlying - add amount directly
|
|
369
|
+
netAUM = netAUM.plus(position.amount);
|
|
370
|
+
} else {
|
|
371
|
+
// Different token - convert USD value to underlying token amount
|
|
372
|
+
const amountInUnderlying = new Web3Number(
|
|
373
|
+
position.usdValue.toString(),
|
|
374
|
+
underlying.decimals,
|
|
375
|
+
).dividedBy(depositTokenPrice.price);
|
|
376
|
+
netAUM = netAUM.plus(amountInUnderlying);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const prevAum = await this.getPrevAUM();
|
|
381
|
+
logger.verbose(`${this.getTag()} Actual AUM: ${netAUM}`);
|
|
382
|
+
|
|
383
|
+
// Calculate BTCFI rewards contribution
|
|
384
|
+
const rewardAssets = await this.getRewardsAUM(prevAum);
|
|
385
|
+
logger.verbose(`${this.getTag()} Rewards AUM: ${rewardAssets}`);
|
|
386
|
+
|
|
387
|
+
// Sum up total AUM
|
|
388
|
+
const totalAUM = netAUM.plus(rewardAssets);
|
|
389
|
+
logger.verbose(`${this.getTag()} Total AUM: ${totalAUM}`);
|
|
390
|
+
|
|
391
|
+
const realAUM: PositionInfo = {
|
|
392
|
+
tokenInfo: underlying,
|
|
393
|
+
amount: netAUM,
|
|
394
|
+
usdValue: netAUM.toNumber() * depositTokenPrice.price,
|
|
395
|
+
apy: { apy: 0, type: APYType.BASE },
|
|
396
|
+
remarks: AUMTypes.FINALISED,
|
|
397
|
+
protocol: Protocols.NONE,
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const estimatedAUMDelta: PositionInfo = {
|
|
401
|
+
tokenInfo: underlying,
|
|
402
|
+
amount: rewardAssets,
|
|
403
|
+
usdValue: rewardAssets.toNumber() * depositTokenPrice.price,
|
|
404
|
+
apy: { apy: 0, type: APYType.BASE },
|
|
405
|
+
remarks: AUMTypes.BTCFI,
|
|
406
|
+
protocol: Protocols.NONE,
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
net: {
|
|
411
|
+
tokenInfo: underlying,
|
|
412
|
+
amount: totalAUM,
|
|
413
|
+
usdValue: totalAUM.toNumber() * depositTokenPrice.price,
|
|
414
|
+
},
|
|
415
|
+
prevAum,
|
|
416
|
+
splits: [realAUM, estimatedAUMDelta],
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Get Vesu APYs for collateral and debt positions
|
|
422
|
+
*/
|
|
423
|
+
async getVesuAPYs(): Promise<{
|
|
424
|
+
baseAPYs: number[];
|
|
425
|
+
rewardAPYs: number[];
|
|
426
|
+
positions: VaultPosition[];
|
|
427
|
+
}> {
|
|
428
|
+
const vesuAdapter = this.getAdapterById<VesuModifyPositionAdapter>(
|
|
429
|
+
this.metadata.additionalInfo.adapterIds!.vesu,
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
// Get Vesu pool data
|
|
433
|
+
const allVesuPools = await VesuAdapter.getVesuPools();
|
|
434
|
+
const pool = allVesuPools.pools.find((p: any) =>
|
|
435
|
+
vesuAdapter.config.poolId.eqString(num.getHexString(p.id)),
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
if (!pool) {
|
|
439
|
+
throw new Error(
|
|
440
|
+
`${this.getTag()}::getVesuAPYs: Pool not found for ${vesuAdapter.config.poolId.address}`,
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// logger.verbose(
|
|
445
|
+
// `${this.getTag()}::getVesuAPYs: vesu-pool: ${JSON.stringify(pool)}`,
|
|
446
|
+
// );
|
|
447
|
+
|
|
448
|
+
// Get positions
|
|
449
|
+
const positions = await this.getVesuPositions();
|
|
450
|
+
logger.verbose(
|
|
451
|
+
`${this.getTag()}::getVesuAPYs: positions: ${JSON.stringify(positions)}`,
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
const baseAPYs: number[] = [];
|
|
455
|
+
const rewardAPYs: number[] = [];
|
|
456
|
+
|
|
457
|
+
// Find collateral and debt asset stats
|
|
458
|
+
const collateralAsset = pool.assets.find(
|
|
459
|
+
(a: any) =>
|
|
460
|
+
a.symbol.toLowerCase() ===
|
|
461
|
+
vesuAdapter.config.collateral.symbol.toLowerCase(),
|
|
462
|
+
)?.stats!;
|
|
463
|
+
const debtAsset = pool.assets.find(
|
|
464
|
+
(a: any) =>
|
|
465
|
+
a.symbol.toLowerCase() === vesuAdapter.config.debt.symbol.toLowerCase(),
|
|
466
|
+
)?.stats!;
|
|
467
|
+
|
|
468
|
+
if (!collateralAsset || !debtAsset) {
|
|
469
|
+
throw new Error(
|
|
470
|
+
`${this.getTag()}::getVesuAPYs: Collateral or debt asset stats not found`,
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const supplyApy = Number(collateralAsset.supplyApy.value || 0) / 1e18;
|
|
475
|
+
const borrowApr = Number(debtAsset.borrowApr.value) / 1e18;
|
|
476
|
+
const collateralRewardsApr = this.metadata.additionalInfo.hasBtcFiRewards
|
|
477
|
+
? Number(collateralAsset.btcFiSupplyApr?.value || "0") / 1e18
|
|
478
|
+
: 0;
|
|
479
|
+
|
|
480
|
+
logger.verbose(
|
|
481
|
+
`${this.getTag()}::getVesuAPYs: collateralRewardsApr: ${collateralRewardsApr}`,
|
|
482
|
+
);
|
|
483
|
+
logger.verbose(
|
|
484
|
+
`${this.getTag()}::getVesuAPYs: collateralAsset: ${JSON.stringify(collateralAsset)}`,
|
|
485
|
+
);
|
|
486
|
+
logger.verbose(
|
|
487
|
+
`${this.getTag()}::getVesuAPYs: supplyApy=${supplyApy}, borrowApr=${borrowApr}, collateralRewards=${collateralRewardsApr}`,
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
// Collateral: supply APY, Debt: borrow APR (cost)
|
|
491
|
+
baseAPYs.push(supplyApy, borrowApr);
|
|
492
|
+
rewardAPYs.push(collateralRewardsApr, 0);
|
|
493
|
+
|
|
494
|
+
logger.verbose(
|
|
495
|
+
`${this.getTag()}::getVesuAPYs: baseAPYs: ${JSON.stringify(baseAPYs)}, rewardAPYs: ${JSON.stringify(rewardAPYs)}`,
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
assert(
|
|
499
|
+
baseAPYs.length === positions.length,
|
|
500
|
+
"APYs and positions length mismatch",
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
return { baseAPYs, rewardAPYs, positions };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Get APY from the Hyper LST vault position
|
|
508
|
+
*/
|
|
509
|
+
async getHyperVaultAPY(): Promise<{ apy: number; weight: number }> {
|
|
510
|
+
try {
|
|
511
|
+
const hyperAdapter = this.getAdapterById<SvkTrovesAdapter>(
|
|
512
|
+
this.metadata.additionalInfo.adapterIds!.hyper,
|
|
513
|
+
);
|
|
514
|
+
const positions = await hyperAdapter.getPositions();
|
|
515
|
+
|
|
516
|
+
if (positions.length === 0 || positions[0].amount.isZero()) {
|
|
517
|
+
return { apy: 0, weight: 0 };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const position = positions[0];
|
|
521
|
+
const apy = position.apy.apy;
|
|
522
|
+
const weight = position.usdValue;
|
|
523
|
+
|
|
524
|
+
logger.verbose(
|
|
525
|
+
`${this.getTag()}::getHyperVaultAPY: apy=${apy}, weight=${weight}`,
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
return { apy, weight };
|
|
529
|
+
} catch (error) {
|
|
530
|
+
logger.warn(
|
|
531
|
+
`${this.getTag()}::getHyperVaultAPY: Error getting Hyper vault APY: ${error}`,
|
|
532
|
+
);
|
|
533
|
+
return { apy: 0, weight: 0 };
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Get APY for unused balance (idle funds in vault allocator)
|
|
539
|
+
*/
|
|
540
|
+
protected async getUnusedBalanceAPY() {
|
|
541
|
+
return {
|
|
542
|
+
apy: 0,
|
|
543
|
+
weight: 0,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Compute weighted APY from individual APYs, weights, and total AUM
|
|
549
|
+
*/
|
|
550
|
+
private computeAPY(
|
|
551
|
+
apys: number[],
|
|
552
|
+
weights: number[],
|
|
553
|
+
totalWeight: number,
|
|
554
|
+
): number {
|
|
555
|
+
assert(apys.length === weights.length, "APYs and weights length mismatch");
|
|
556
|
+
const weightedSum = apys.reduce((acc, apy, i) => acc + apy * weights[i], 0);
|
|
557
|
+
logger.verbose(
|
|
558
|
+
`${this.getTag()} computeAPY: apys: ${JSON.stringify(apys)}, weights: ${JSON.stringify(weights)}, weightedSum: ${weightedSum}, totalWeight: ${totalWeight}`,
|
|
559
|
+
);
|
|
560
|
+
return weightedSum / totalWeight;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Calculate net APY from base and reward APYs
|
|
565
|
+
*/
|
|
566
|
+
protected async returnNetAPY(
|
|
567
|
+
baseAPYs: number[],
|
|
568
|
+
rewardAPYs: number[],
|
|
569
|
+
weights: number[],
|
|
570
|
+
totalWeightUSD: number,
|
|
571
|
+
) {
|
|
572
|
+
// If no positions, return 0
|
|
573
|
+
if (weights.every((p) => p == 0)) {
|
|
574
|
+
return {
|
|
575
|
+
net: 0,
|
|
576
|
+
splits: [
|
|
577
|
+
{
|
|
578
|
+
apy: 0,
|
|
579
|
+
id: "base",
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
apy: 0,
|
|
583
|
+
id: "btcfi",
|
|
584
|
+
},
|
|
585
|
+
],
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const baseAPY = this.computeAPY(baseAPYs, weights, totalWeightUSD);
|
|
590
|
+
const rewardAPY = this.computeAPY(rewardAPYs, weights, totalWeightUSD);
|
|
591
|
+
const netAPY = baseAPY + rewardAPY;
|
|
592
|
+
logger.verbose(
|
|
593
|
+
`${this.getTag()}::netAPY: net: ${netAPY}, baseAPY: ${baseAPY}, rewardAPY: ${rewardAPY}`,
|
|
594
|
+
);
|
|
595
|
+
return {
|
|
596
|
+
net: netAPY,
|
|
597
|
+
splits: [
|
|
598
|
+
{
|
|
599
|
+
apy: baseAPY,
|
|
600
|
+
id: "base",
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
apy: rewardAPY,
|
|
604
|
+
id: "btcfi",
|
|
605
|
+
},
|
|
606
|
+
],
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Calculate net APY across all positions (Vesu, Hyper vault, unused balance)
|
|
612
|
+
* weighted by USD value
|
|
613
|
+
*/
|
|
614
|
+
// TODO: will be reviewing this later on, we only need BTCFI APY for now for AUM
|
|
615
|
+
async netAPY(): Promise<{
|
|
616
|
+
net: number;
|
|
617
|
+
splits: { apy: number; id: string }[];
|
|
618
|
+
}> {
|
|
619
|
+
try {
|
|
620
|
+
// Get Vesu APYs and positions
|
|
621
|
+
const { positions, baseAPYs, rewardAPYs } = await this.getVesuAPYs();
|
|
622
|
+
|
|
623
|
+
// Get Hyper vault APY
|
|
624
|
+
const hyperAPY = await this.getHyperVaultAPY();
|
|
625
|
+
baseAPYs.push(hyperAPY.apy);
|
|
626
|
+
rewardAPYs.push(0); // Hyper vault rewards already included in its APY
|
|
627
|
+
|
|
628
|
+
// Get unused balance APY
|
|
629
|
+
const unusedBalanceAPY = await this.getUnusedBalanceAPY();
|
|
630
|
+
baseAPYs.push(unusedBalanceAPY.apy);
|
|
631
|
+
rewardAPYs.push(0);
|
|
632
|
+
|
|
633
|
+
// Compute weights - debt usdValue is already negative from adapter
|
|
634
|
+
const weights = positions.map((p) => p.usdValue);
|
|
635
|
+
weights.push(hyperAPY.weight);
|
|
636
|
+
weights.push(unusedBalanceAPY.weight);
|
|
637
|
+
|
|
638
|
+
logger.verbose(
|
|
639
|
+
`${this.getTag()}::netAPY: weights: ${JSON.stringify(weights)}`,
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
// Calculate total weight (current total AUM in USD)
|
|
643
|
+
const totalWeightUSD = weights.reduce((sum, weight) => sum + weight, 0);
|
|
644
|
+
|
|
645
|
+
logger.verbose(
|
|
646
|
+
`${this.getTag()}::netAPY: totalWeightUSD: ${totalWeightUSD}`,
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
return this.returnNetAPY(baseAPYs, rewardAPYs, weights, totalWeightUSD);
|
|
650
|
+
} catch (error: any) {
|
|
651
|
+
logger.error(
|
|
652
|
+
`${this.getTag()}::netAPY: Error calculating net APY: ${error?.message || error}`,
|
|
653
|
+
);
|
|
654
|
+
return {
|
|
655
|
+
net: 0,
|
|
656
|
+
splits: [
|
|
657
|
+
{
|
|
658
|
+
apy: 0,
|
|
659
|
+
id: "base",
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
apy: 0,
|
|
663
|
+
id: "btcfi",
|
|
664
|
+
},
|
|
665
|
+
],
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Calculate future rewards (e.g. BTCFI rewards) contribution to AUM
|
|
672
|
+
* Similar to universal-strategy.tsx approach
|
|
673
|
+
*/
|
|
674
|
+
protected async getRewardsAUM(prevAum: Web3Number): Promise<Web3Number> {
|
|
675
|
+
if (!this.metadata.additionalInfo.hasBtcFiRewards) {
|
|
676
|
+
return Web3Number.fromWei("0", this.asset().decimals);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
try {
|
|
680
|
+
const lastReportTime = await this.contract.call(
|
|
681
|
+
"last_report_timestamp",
|
|
682
|
+
[],
|
|
683
|
+
);
|
|
684
|
+
|
|
685
|
+
// Calculate estimated growth from rewards
|
|
686
|
+
const netAPY = await this.netAPY();
|
|
687
|
+
// Account only 80% of value for conservative estimate
|
|
688
|
+
const btcfiAPY =
|
|
689
|
+
(netAPY.splits.find((s) => s.id === "btcfi")?.apy || 0) * 0.8;
|
|
690
|
+
|
|
691
|
+
if (!btcfiAPY) {
|
|
692
|
+
logger.verbose(
|
|
693
|
+
`${this.getTag()} No BTCFI APY found, returning 0 rewards`,
|
|
694
|
+
);
|
|
695
|
+
return Web3Number.fromWei("0", this.asset().decimals);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Compute rewards contribution to AUM
|
|
699
|
+
const timeDiff = Math.round(Date.now() / 1000) - Number(lastReportTime);
|
|
700
|
+
const growthRate = (timeDiff * btcfiAPY) / (365 * 24 * 60 * 60);
|
|
701
|
+
const rewardAssets = prevAum.multipliedBy(growthRate);
|
|
702
|
+
|
|
703
|
+
logger.verbose(
|
|
704
|
+
`${this.getTag()} BtcFi AUM time difference: ${timeDiff}s`,
|
|
705
|
+
);
|
|
706
|
+
logger.verbose(`${this.getTag()} Previous AUM: ${prevAum.toString()}`);
|
|
707
|
+
logger.verbose(`${this.getTag()} BtcFi APY: ${btcfiAPY}`);
|
|
708
|
+
logger.verbose(`${this.getTag()} Growth rate: ${growthRate}`);
|
|
709
|
+
logger.verbose(
|
|
710
|
+
`${this.getTag()} Rewards AUM: ${rewardAssets.toString()}`,
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
return rewardAssets;
|
|
714
|
+
} catch (error: any) {
|
|
715
|
+
logger.warn(
|
|
716
|
+
`${this.getTag()} Error calculating rewards AUM: ${error?.message || error}`,
|
|
717
|
+
);
|
|
718
|
+
return Web3Number.fromWei("0", this.asset().decimals);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// TODO: can refactor later but seems redundant since not being used ANYWHERE
|
|
723
|
+
// Most of the fund management done through adapters only
|
|
724
|
+
|
|
725
|
+
async getFundManagementCall(params: {
|
|
726
|
+
isDeposit: boolean;
|
|
727
|
+
leg1DepositAmount: Web3Number;
|
|
728
|
+
}) {
|
|
729
|
+
logger.verbose(
|
|
730
|
+
`${this.getTag()}::getFundManagementCall params: ${JSON.stringify(params)}`,
|
|
731
|
+
);
|
|
732
|
+
const allAdapters = this.metadata.additionalInfo.adapters.map(
|
|
733
|
+
(a) => a.adapter,
|
|
734
|
+
);
|
|
735
|
+
|
|
736
|
+
if (!params.isDeposit) {
|
|
737
|
+
const unusedBalance = await this.getUnusedBalance();
|
|
738
|
+
logger.verbose(
|
|
739
|
+
`${this.getTag()}::getFundManagementCall unusedBalance: ${unusedBalance.amount}, required: ${params.leg1DepositAmount}`,
|
|
740
|
+
);
|
|
741
|
+
if (unusedBalance.amount.gte(params.leg1DepositAmount)) {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const adapters = await AdapterOptimizer.getAdapterToUse(
|
|
746
|
+
allAdapters,
|
|
747
|
+
false,
|
|
748
|
+
params.leg1DepositAmount,
|
|
749
|
+
);
|
|
750
|
+
if (adapters.length > 0) {
|
|
751
|
+
const proofsInfo = adapters.map((adapter) =>
|
|
752
|
+
adapter.getProofs(false, this.getMerkleTree()),
|
|
753
|
+
);
|
|
754
|
+
const calls: Call[] = [];
|
|
755
|
+
for (const info of proofsInfo) {
|
|
756
|
+
const manageCalls = await info.callConstructor({
|
|
757
|
+
amount: params.leg1DepositAmount,
|
|
758
|
+
});
|
|
759
|
+
const call = this.getManageCall(
|
|
760
|
+
this.getProofGroupsForManageCalls(manageCalls),
|
|
761
|
+
manageCalls,
|
|
762
|
+
);
|
|
763
|
+
calls.push(call);
|
|
764
|
+
}
|
|
765
|
+
return calls;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
throw new Error(
|
|
769
|
+
`${this.getTag()}::getFundManagementCall: no adapters for withdraw: ${unusedBalance.amount}`,
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const adapters = await AdapterOptimizer.getAdapterToUse(
|
|
774
|
+
allAdapters,
|
|
775
|
+
true,
|
|
776
|
+
params.leg1DepositAmount,
|
|
777
|
+
);
|
|
778
|
+
if (adapters.length > 0) {
|
|
779
|
+
const proofsInfo = adapters.map((adapter) =>
|
|
780
|
+
adapter.getProofs(true, this.getMerkleTree()),
|
|
781
|
+
);
|
|
782
|
+
const calls: Call[] = [];
|
|
783
|
+
for (const info of proofsInfo) {
|
|
784
|
+
const manageCalls = await info.callConstructor({
|
|
785
|
+
amount: params.leg1DepositAmount,
|
|
786
|
+
});
|
|
787
|
+
const call = this.getManageCall(
|
|
788
|
+
this.getProofGroupsForManageCalls(manageCalls),
|
|
789
|
+
manageCalls,
|
|
790
|
+
);
|
|
791
|
+
calls.push(call);
|
|
792
|
+
}
|
|
793
|
+
return calls;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
throw new Error(
|
|
797
|
+
`${this.getTag()}::getFundManagementCall: no adapters for deposit: ${params.leg1DepositAmount}`,
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function getBoostedxSTRKCarrySettings(
|
|
803
|
+
vaultSettings: BoostedxSTRKCarryStrategySettings,
|
|
804
|
+
) {
|
|
805
|
+
vaultSettings.leafAdapters = [];
|
|
806
|
+
|
|
807
|
+
// Use metadata-driven token configuration
|
|
808
|
+
const depositToken = vaultSettings.depositToken;
|
|
809
|
+
const debtToken = vaultSettings.debtToken;
|
|
810
|
+
const lstToken = vaultSettings.lstHyperToken;
|
|
811
|
+
|
|
812
|
+
// Build dynamic adapter IDs based on token symbols
|
|
813
|
+
const adapterIds = {
|
|
814
|
+
vesu: `vesu_${depositToken.symbol.toLowerCase()}_${debtToken.symbol.toLowerCase()}`,
|
|
815
|
+
avnu: `avnu_${debtToken.symbol.toLowerCase()}_${lstToken.symbol.toLowerCase()}`,
|
|
816
|
+
hyper: `hyper_${lstToken.symbol.toLowerCase()}`,
|
|
817
|
+
transfer: `${depositToken.symbol.toLowerCase()}_transfer`,
|
|
818
|
+
};
|
|
819
|
+
vaultSettings.adapterIds = adapterIds;
|
|
820
|
+
|
|
821
|
+
const baseAdapterConfig: BaseAdapterConfig = {
|
|
822
|
+
baseToken: depositToken,
|
|
823
|
+
supportedPositions: [{ asset: depositToken, isDebt: false }],
|
|
824
|
+
networkConfig: getMainnetConfig(),
|
|
825
|
+
pricer: new PricerFromApi(getMainnetConfig(), Global.getDefaultTokens()),
|
|
826
|
+
vaultAllocator: vaultSettings.vaultAllocator,
|
|
827
|
+
vaultAddress: vaultSettings.vaultAddress,
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
// ── 1. VesuModifyPositionAdapter (deposit token collateral / borrow token debt) ──
|
|
831
|
+
|
|
832
|
+
const vesuModifyPositionAdapter = new VesuModifyPositionAdapter({
|
|
833
|
+
poolId: vaultSettings.vesuPoolId,
|
|
834
|
+
collateral: depositToken,
|
|
835
|
+
debt: debtToken,
|
|
836
|
+
targetLtv: vaultSettings.targetLTV,
|
|
837
|
+
maxLtv: vaultSettings.maxLTV,
|
|
838
|
+
...baseAdapterConfig,
|
|
839
|
+
supportedPositions: [
|
|
840
|
+
{ asset: depositToken, isDebt: false },
|
|
841
|
+
{ asset: debtToken, isDebt: true },
|
|
842
|
+
],
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
// ── 2. AvnuAdapter (borrow token ↔ LST swaps) ──
|
|
846
|
+
const avnuAdapter = new AvnuAdapter({
|
|
847
|
+
baseUrl: AVNU_QUOTE_URL,
|
|
848
|
+
avnuContract: AVNU_EXCHANGE,
|
|
849
|
+
slippage: 0.01,
|
|
850
|
+
minimumExtendedPriceDifferenceForSwapOpen: 0,
|
|
851
|
+
maximumExtendedPriceDifferenceForSwapClosing: 0,
|
|
852
|
+
...baseAdapterConfig,
|
|
853
|
+
baseToken: debtToken,
|
|
854
|
+
supportedPositions: [
|
|
855
|
+
{ asset: debtToken, isDebt: false },
|
|
856
|
+
{ asset: lstToken, isDebt: false },
|
|
857
|
+
],
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
// ── 3. SvkTrovesAdapter (deposit LST into / withdraw from Hyper vault) ──
|
|
861
|
+
const svkTrovesAdapter = new SvkTrovesAdapter({
|
|
862
|
+
...baseAdapterConfig,
|
|
863
|
+
baseToken: lstToken,
|
|
864
|
+
supportedPositions: [{ asset: lstToken, isDebt: false }],
|
|
865
|
+
strategyVault: vaultSettings.hyperLstVaultAddress,
|
|
866
|
+
trovesStrategyId: vaultSettings.trovesStrategyId,
|
|
867
|
+
redeemRequestNFT: vaultSettings.hyperLstRedeemNFT,
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
// ── 5. CommonAdapter (approve + bring liquidity) ──
|
|
871
|
+
const commonAdapter = new CommonAdapter({
|
|
872
|
+
id: UNIVERSAL_MANAGE_IDS.FLASH_LOAN,
|
|
873
|
+
vaultAddress: vaultSettings.vaultAddress,
|
|
874
|
+
vaultAllocator: vaultSettings.vaultAllocator,
|
|
875
|
+
manager: vaultSettings.manager,
|
|
876
|
+
asset: depositToken.address,
|
|
877
|
+
});
|
|
878
|
+
|
|
879
|
+
// ── Register adapters for position tracking ──
|
|
880
|
+
vaultSettings.adapters.push(
|
|
881
|
+
{ id: adapterIds.vesu, adapter: vesuModifyPositionAdapter },
|
|
882
|
+
// Used to track swapped funds in vaultAllocator
|
|
883
|
+
{ id: adapterIds.avnu, adapter: avnuAdapter },
|
|
884
|
+
{ id: adapterIds.hyper, adapter: svkTrovesAdapter },
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
// ── Register leaf adapters for merkle tree ──
|
|
888
|
+
// Vesu modify position
|
|
889
|
+
vaultSettings.leafAdapters.push(() =>
|
|
890
|
+
vesuModifyPositionAdapter.getDepositLeaf(),
|
|
891
|
+
);
|
|
892
|
+
vaultSettings.leafAdapters.push(() =>
|
|
893
|
+
vesuModifyPositionAdapter.getWithdrawLeaf(),
|
|
894
|
+
);
|
|
895
|
+
|
|
896
|
+
// Avnu swaps (borrow token ↔ LST)
|
|
897
|
+
vaultSettings.leafAdapters.push(() => avnuAdapter.getDepositLeaf());
|
|
898
|
+
vaultSettings.leafAdapters.push(() => avnuAdapter.getWithdrawLeaf());
|
|
899
|
+
|
|
900
|
+
// Hyper LST vault deposit / withdraw
|
|
901
|
+
vaultSettings.leafAdapters.push(() => svkTrovesAdapter.getDepositLeaf());
|
|
902
|
+
vaultSettings.leafAdapters.push(() => svkTrovesAdapter.getWithdrawLeaf());
|
|
903
|
+
|
|
904
|
+
// Bring liquidity back to vault
|
|
905
|
+
vaultSettings.leafAdapters.push(
|
|
906
|
+
commonAdapter
|
|
907
|
+
.getApproveAdapter(
|
|
908
|
+
depositToken.address,
|
|
909
|
+
vaultSettings.vaultAddress,
|
|
910
|
+
UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY,
|
|
911
|
+
)
|
|
912
|
+
.bind(commonAdapter),
|
|
913
|
+
);
|
|
914
|
+
vaultSettings.leafAdapters.push(
|
|
915
|
+
commonAdapter
|
|
916
|
+
.getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
|
|
917
|
+
.bind(commonAdapter),
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
return vaultSettings;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const boostedxSTRKCarrySettings: BoostedxSTRKCarryStrategySettings = {
|
|
924
|
+
vaultAddress: ContractAddr.from(
|
|
925
|
+
"0x02490bd7fa730790e220b40a3cb7bec14ddf030e15c9813610aabbe391f781c4",
|
|
926
|
+
),
|
|
927
|
+
manager: ContractAddr.from(
|
|
928
|
+
"0x011cd83d2a8c4da86da7b2e2351e9d073568b2fe0fef00dbbdab03ebfc7923b9",
|
|
929
|
+
),
|
|
930
|
+
vaultAllocator: ContractAddr.from(
|
|
931
|
+
"0x0671f407da7daeb57a9ecfea17bd61b8fcb987d8d0c43855e5f07c2f08a424be",
|
|
932
|
+
),
|
|
933
|
+
redeemRequestNFT: ContractAddr.from(
|
|
934
|
+
"0x0669d0af171fb4b5d93a2e51f897ae3a2526b0011120fd30e20922de6aa204a1",
|
|
935
|
+
),
|
|
936
|
+
// Not there for USDC but will be there for WBTC
|
|
937
|
+
aumOracle: ContractAddr.from("0x0"),
|
|
938
|
+
leafAdapters: [],
|
|
939
|
+
adapters: [],
|
|
940
|
+
// Calc using the maxLTV / targetLTV (0.5)
|
|
941
|
+
targetHealthFactor: 1.36,
|
|
942
|
+
// Calc using the maxLTV / maxAcceptableLTV (0.55)
|
|
943
|
+
minHealthFactor: 1.3,
|
|
944
|
+
vesuPoolId: VesuPools.Prime,
|
|
945
|
+
// New metadata-driven token configuration
|
|
946
|
+
depositToken: Global.getDefaultTokens().find((t) => t.symbol === "USDC")!,
|
|
947
|
+
debtToken: Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
|
|
948
|
+
lstHyperToken: Global.getDefaultTokens().find((t) => t.symbol === "xSTRK")!,
|
|
949
|
+
maxLTV: 0.68,
|
|
950
|
+
targetLTV: 0.5,
|
|
951
|
+
// BTC.Fi rewards flag - false for USDC (uses old report flow)
|
|
952
|
+
hasBtcFiRewards: false,
|
|
953
|
+
// New fields
|
|
954
|
+
hyperLstVaultAddress: ContractAddr.from(
|
|
955
|
+
"0x46c7a54c82b1fe374353859f554a40b8bd31d3e30f742901579e7b57b1b5960",
|
|
956
|
+
),
|
|
957
|
+
hyperLstRedeemNFT: ContractAddr.from(
|
|
958
|
+
"0x51e40b839dc0c2feca923f863072673b94abfa2483345be3b30b457a90d095",
|
|
959
|
+
),
|
|
960
|
+
trovesStrategyId: "hyper_xstrk",
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
const wbtcBoostedSettings: BoostedxSTRKCarryStrategySettings = {
|
|
964
|
+
vaultAddress: ContractAddr.from(
|
|
965
|
+
"0x00a4c7c06313a3e06e408dd19a57e633d26cec5b9c4483223bca5d5b467f4ff6",
|
|
966
|
+
),
|
|
967
|
+
manager: ContractAddr.from(
|
|
968
|
+
"0x002aef32849cd9c011fe5fa09940d818b5895370d0a5076d9e0b95fee91bbad8",
|
|
969
|
+
),
|
|
970
|
+
vaultAllocator: ContractAddr.from(
|
|
971
|
+
"0x006b7896f559084862c7a2e6606a9aecadc1367b61b0264225e5890401031337",
|
|
972
|
+
),
|
|
973
|
+
redeemRequestNFT: ContractAddr.from(
|
|
974
|
+
"0x05502bd98af66c3d1227aa4ac7107eb6867b1686c02a62e2edbda91a686be1e3",
|
|
975
|
+
),
|
|
976
|
+
aumOracle: ContractAddr.from(
|
|
977
|
+
"0x03bd63a5e03de8af1fc28d80c016511d739303eacec67767d50608b1fee6a7df",
|
|
978
|
+
),
|
|
979
|
+
leafAdapters: [],
|
|
980
|
+
adapters: [],
|
|
981
|
+
targetHealthFactor: 1.4,
|
|
982
|
+
minHealthFactor: 1.35,
|
|
983
|
+
vesuPoolId: VesuPools.Prime,
|
|
984
|
+
depositToken: Global.getDefaultTokens().find((t) => t.symbol === "WBTC")!,
|
|
985
|
+
debtToken: Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
|
|
986
|
+
lstHyperToken: Global.getDefaultTokens().find((t) => t.symbol === "xSTRK")!,
|
|
987
|
+
maxLTV: 0.7,
|
|
988
|
+
targetLTV: 0.5,
|
|
989
|
+
hasBtcFiRewards: true,
|
|
990
|
+
hyperLstVaultAddress: ContractAddr.from(
|
|
991
|
+
"0x46c7a54c82b1fe374353859f554a40b8bd31d3e30f742901579e7b57b1b5960",
|
|
992
|
+
),
|
|
993
|
+
hyperLstRedeemNFT: ContractAddr.from(
|
|
994
|
+
"0x51e40b839dc0c2feca923f863072673b94abfa2483345be3b30b457a90d095",
|
|
995
|
+
),
|
|
996
|
+
trovesStrategyId: "hyper_xstrk",
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
// Risk weights drive the displayed net risk score (weighted average of factor levels).
|
|
1000
|
+
// Highest weights go to risks that can directly impair principal or block exits; lower weights
|
|
1001
|
+
// for mitigated or secondary drivers. Weights sum to 100 for easy reasoning about share of total.
|
|
1002
|
+
const boostedCarryRiskFactors: RiskFactor[] = [
|
|
1003
|
+
{
|
|
1004
|
+
type: RiskType.SMART_CONTRACT_RISK,
|
|
1005
|
+
value: SmartContractRiskLevel.WELL_AUDITED,
|
|
1006
|
+
// 20% — core trust assumption; SVK is audited but still multi-contract (vault, manager, adapters).
|
|
1007
|
+
weight: 20,
|
|
1008
|
+
reason:
|
|
1009
|
+
"Built on the Starknet Vault Kit (SVK), with audits under the Sherlock-audited branch.",
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
type: RiskType.LIQUIDATION_RISK,
|
|
1013
|
+
value: LiquidationRiskLevel.MODERATE_PROBABILITY,
|
|
1014
|
+
// 20% — tied with smart contract; uncorrelated collateral/debt is the main principal risk here.
|
|
1015
|
+
weight: 20,
|
|
1016
|
+
reason:
|
|
1017
|
+
"Collateral (USDC or WBTC) and STRK debt are not price-correlated. We target ~50% LTV and rebalance above ~55%, with a health-factor buffer, but extreme moves or prolonged monitoring gaps can still trigger liquidation.",
|
|
1018
|
+
},
|
|
1019
|
+
{
|
|
1020
|
+
type: RiskType.LOW_LIQUIDITY_RISK,
|
|
1021
|
+
value: LowLiquidityRiskLevel.MODERATE_CONCERNS,
|
|
1022
|
+
// 15% — below liquidation but above ops risks; every unwind/withdraw depends on xSTRK→STRK depth.
|
|
1023
|
+
weight: 15,
|
|
1024
|
+
reason:
|
|
1025
|
+
"Closing the loop requires selling xSTRK to STRK on DEXes to repay Vesu debt. xSTRK liquidity can be thin at fair prices; larger withdrawals or rebalances may take longer or complete in smaller chunks.",
|
|
1026
|
+
},
|
|
1027
|
+
{
|
|
1028
|
+
type: RiskType.TECHNICAL_RISK,
|
|
1029
|
+
value: TechnicalRiskLevel.SOME_COMPLEXITY,
|
|
1030
|
+
// 13% — monitoring/rebalancing stack matters, but usually recoverable without instant loss.
|
|
1031
|
+
weight: 13,
|
|
1032
|
+
reason:
|
|
1033
|
+
"Yield routes across Vesu, Avnu, and Hyper xSTRK with automated monitoring. Technical failures can delay rebalancing.",
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
type: RiskType.DEPEG_RISK,
|
|
1037
|
+
value: DepegRiskLevel.OCCASIONAL_DEPEG,
|
|
1038
|
+
// 12% — affects yield and swap notionals; USDC/WBTC depeg is rarer than xSTRK/STRK drift.
|
|
1039
|
+
weight: 12,
|
|
1040
|
+
reason:
|
|
1041
|
+
"xSTRK yield depends on its exchange rate vs STRK; USDC and WBTC carry standard stablecoin and BTC market risks.",
|
|
1042
|
+
},
|
|
1043
|
+
{
|
|
1044
|
+
type: RiskType.ORACLE_RISK,
|
|
1045
|
+
value: OracleRiskLevel.SINGLE_RELIABLE,
|
|
1046
|
+
// 10% — Vesu HF depends on feeds; Starknet oracles are established but not redundant here.
|
|
1047
|
+
weight: 10,
|
|
1048
|
+
reason: "Vesu collateral and debt valuations rely on Starknet price feeds.",
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
type: RiskType.COUNTERPARTY_RISK,
|
|
1052
|
+
value: CounterpartyRiskLevel.REPUTABLE_COUNTERPARTY,
|
|
1053
|
+
// 10% — same tier as oracle; Vesu/Endur/Troves are mature but still protocol counterparty risk.
|
|
1054
|
+
weight: 10,
|
|
1055
|
+
reason:
|
|
1056
|
+
"Exposure to Vesu lending, Endur (xSTRK), and Troves Hyper vault infrastructure.",
|
|
1057
|
+
},
|
|
1058
|
+
{
|
|
1059
|
+
type: RiskType.MARKET_RISK,
|
|
1060
|
+
value: MarketRiskLevel.MODERATE_VOLATILITY,
|
|
1061
|
+
// 5% — largely captured by liquidation + depeg factors; kept small to avoid double-counting vol.
|
|
1062
|
+
weight: 5,
|
|
1063
|
+
reason:
|
|
1064
|
+
"STRK, BTC, and stablecoin volatility can move loan-to-value and swap costs.",
|
|
1065
|
+
},
|
|
1066
|
+
];
|
|
1067
|
+
|
|
1068
|
+
function getBoostedCarryRisk() {
|
|
1069
|
+
const netRisk =
|
|
1070
|
+
boostedCarryRiskFactors.reduce(
|
|
1071
|
+
(acc, curr) => acc + curr.value * curr.weight,
|
|
1072
|
+
0,
|
|
1073
|
+
) / boostedCarryRiskFactors.reduce((acc, curr) => acc + curr.weight, 0);
|
|
1074
|
+
return {
|
|
1075
|
+
riskFactor: boostedCarryRiskFactors,
|
|
1076
|
+
netRisk,
|
|
1077
|
+
notARisks: getNoRiskTags(boostedCarryRiskFactors),
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
function getBoostedCarryFAQs(
|
|
1082
|
+
depositSymbol: string,
|
|
1083
|
+
debtSymbol: string,
|
|
1084
|
+
lstSymbol: string,
|
|
1085
|
+
): FAQ[] {
|
|
1086
|
+
return [
|
|
1087
|
+
{
|
|
1088
|
+
question: `What is ${depositSymbol} Boosted?`,
|
|
1089
|
+
answer: `${depositSymbol} Boosted is a carry-style vault: your ${depositSymbol} is supplied as collateral on Vesu, ${debtSymbol} is borrowed, swapped to ${lstSymbol}, and deposited into Hyper-${lstSymbol} for additional yield on top of the lending loop.`,
|
|
1090
|
+
},
|
|
1091
|
+
{
|
|
1092
|
+
question: "How does this strategy generate yield?",
|
|
1093
|
+
answer: `Yield comes from several layers: ${lstSymbol} staking yield inside Hyper-${lstSymbol}, the spread between borrowing ${debtSymbol} and holding ${lstSymbol}, and efficient routing via Avnu. The vault targets about 50% loan-to-value and rebalances when LTV rises above ~55% so risk and yield stay in balance.`,
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
question: "Which protocols are used?",
|
|
1097
|
+
answer: (
|
|
1098
|
+
<span>
|
|
1099
|
+
<strong>Vesu</strong> for collateral and borrowing,{" "}
|
|
1100
|
+
<strong>Avnu</strong> for swaps, <strong>Endur</strong> for{" "}
|
|
1101
|
+
{lstSymbol}, and the Troves <strong>Hyper-{lstSymbol}</strong> vault
|
|
1102
|
+
for the boosted leg.
|
|
1103
|
+
</span>
|
|
1104
|
+
),
|
|
1105
|
+
},
|
|
1106
|
+
{
|
|
1107
|
+
question: "Is there liquidation risk?",
|
|
1108
|
+
answer: `Yes. Your ${depositSymbol} collateral and ${debtSymbol} debt can move in different directions, which affects your health factor on Vesu. We actively monitor positions and rebalance automatically through our systems to keep loan-to-value near target. However, unexpected technical failures or very sharp market moves can still lead to liquidation. Under normal conditions we aim for a buffer of roughly 30% adverse price movement before liquidation becomes likely — but if monitoring is unavailable for many hours during a trending market, that buffer can be consumed. In extreme scenarios, liquidation remains possible.`,
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
question: "What do I receive when I deposit?",
|
|
1112
|
+
answer:
|
|
1113
|
+
"You receive vault tokens representing your share of the vault. They reflect both principal and accrued yield.",
|
|
1114
|
+
},
|
|
1115
|
+
{
|
|
1116
|
+
question: "How long do withdrawals take?",
|
|
1117
|
+
answer:
|
|
1118
|
+
"Withdrawals typically take 1–2 hours. You receive an NFT for your request; funds are sent to the NFT owner once the vault unwinds its positions. The vault must sell xSTRK back to STRK to repay debt — if DEX liquidity is thin or your withdrawal is large, unwinding can take longer or happen in smaller steps to limit slippage.",
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
question: "Is this vault non-custodial?",
|
|
1122
|
+
answer:
|
|
1123
|
+
"Yes. The vault runs on-chain. You hold your vault tokens, and positions are transparent via on-chain contracts.",
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
question: "Are there any fees?",
|
|
1127
|
+
answer:
|
|
1128
|
+
"Troves charges a 15% performance fee on yield only — not on your deposited principal. The APY shown is already net of this fee. When you withdraw, a 0.1% redemption fee applies. That small fee helps keep the vault efficient for long-term depositors, very short stays create extra swap and rebalancing costs for everyone. If you stay for a reasonable period, your yield will typically far outweigh this one-time charge.",
|
|
1129
|
+
},
|
|
1130
|
+
{
|
|
1131
|
+
question: "Is the vault audited?",
|
|
1132
|
+
answer: (
|
|
1133
|
+
<span>
|
|
1134
|
+
Yes. The strategy is built on the audited Starknet Vault Kit. See the
|
|
1135
|
+
security details beside the strategy name and the{" "}
|
|
1136
|
+
<a
|
|
1137
|
+
href="https://github.com/trovesfi/starknet_vault_kit/tree/sherlock-audited"
|
|
1138
|
+
target="_blank"
|
|
1139
|
+
rel="noopener noreferrer"
|
|
1140
|
+
style={{ textDecoration: "underline" }}
|
|
1141
|
+
>
|
|
1142
|
+
open-source repo
|
|
1143
|
+
</a>
|
|
1144
|
+
.
|
|
1145
|
+
</span>
|
|
1146
|
+
),
|
|
1147
|
+
},
|
|
1148
|
+
];
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
function getStrategySettings(
|
|
1152
|
+
settings: BoostedxSTRKCarryStrategySettings,
|
|
1153
|
+
meta: { id: string; name: string; launchBlock: number },
|
|
1154
|
+
): IStrategyMetadata<BoostedxSTRKCarryStrategySettings> {
|
|
1155
|
+
const depositToken = settings.depositToken;
|
|
1156
|
+
const debtToken = settings.debtToken;
|
|
1157
|
+
const lstToken = settings.lstHyperToken;
|
|
1158
|
+
|
|
1159
|
+
return {
|
|
1160
|
+
id: meta.id,
|
|
1161
|
+
name: meta.name,
|
|
1162
|
+
description: `Deposits ${depositToken.symbol} as collateral on Vesu, borrows ${debtToken.symbol}, swaps to ${lstToken.symbol}, and deposits into Hyper-${lstToken.symbol} for boosted yield. Target LTV is about 50%, with rebalances happening when LTV is above 55%.`,
|
|
1163
|
+
address: settings.vaultAddress,
|
|
1164
|
+
launchBlock: meta.launchBlock,
|
|
1165
|
+
type: "ERC4626" as const,
|
|
1166
|
+
vaultType: {
|
|
1167
|
+
type: VaultType.META_VAULT,
|
|
1168
|
+
description: `Deposits ${depositToken.symbol} as collateral on Vesu, borrows ${debtToken.symbol}, swaps to ${lstToken.symbol}, and deposits into Hyper-${lstToken.symbol} for boosted yield`,
|
|
1169
|
+
},
|
|
1170
|
+
depositTokens: [depositToken],
|
|
1171
|
+
additionalInfo: getBoostedxSTRKCarrySettings(settings),
|
|
1172
|
+
risk: getBoostedCarryRisk(),
|
|
1173
|
+
protocols: [Protocols.VESU, Protocols.ENDUR, Protocols.TROVES],
|
|
1174
|
+
curator: {
|
|
1175
|
+
name: "Unwrap Labs",
|
|
1176
|
+
logo: "https://assets.troves.fi/integrations/unwraplabs/white.png",
|
|
1177
|
+
},
|
|
1178
|
+
settings: {
|
|
1179
|
+
maxTVL: Web3Number.fromWei(0, depositToken.decimals),
|
|
1180
|
+
liveStatus: StrategyLiveStatus.HOT,
|
|
1181
|
+
isPaused: false,
|
|
1182
|
+
isAudited: true,
|
|
1183
|
+
isInstantWithdrawal: false,
|
|
1184
|
+
hideHarvestInfo: true,
|
|
1185
|
+
quoteToken: depositToken,
|
|
1186
|
+
alerts: [
|
|
1187
|
+
{
|
|
1188
|
+
tab: "withdraw" as const,
|
|
1189
|
+
text: "On withdrawal, you will receive an NFT representing your withdrawal request. The funds will be automatically sent to your wallet (NFT owner) in 1-2 hours. You can monitor the status in transactions tab.",
|
|
1190
|
+
type: "info" as const,
|
|
1191
|
+
},
|
|
1192
|
+
],
|
|
1193
|
+
},
|
|
1194
|
+
contractDetails: getContractDetails(settings),
|
|
1195
|
+
feeBps: {
|
|
1196
|
+
performanceFeeBps: 1500,
|
|
1197
|
+
},
|
|
1198
|
+
faqs: getBoostedCarryFAQs(
|
|
1199
|
+
depositToken.symbol,
|
|
1200
|
+
debtToken.symbol,
|
|
1201
|
+
lstToken.symbol,
|
|
1202
|
+
),
|
|
1203
|
+
apyMethodology:
|
|
1204
|
+
"APY reflects net returns performance fee on yield (not on principal). Underlying xSTRK appreciation and Vesu borrow costs can move over time; displayed APY is an estimate.",
|
|
1205
|
+
investmentSteps: [
|
|
1206
|
+
`Deposit ${depositToken.symbol} into the vault`,
|
|
1207
|
+
`${depositToken.symbol} is supplied as collateral on Vesu, ${debtToken.symbol} is borrowed`,
|
|
1208
|
+
`Borrowed ${debtToken.symbol} is swapped to ${lstToken.symbol} via Avnu`,
|
|
1209
|
+
`${lstToken.symbol} is deposited into the Hyper-${lstToken.symbol} vault for additional yield`,
|
|
1210
|
+
`Actively monitored and rebalanced to maintain optimal LTV and yield.`,
|
|
1211
|
+
],
|
|
1212
|
+
// TODO: config later
|
|
1213
|
+
tags: [StrategyTag.META_VAULT],
|
|
1214
|
+
security: {
|
|
1215
|
+
auditStatus: AuditStatus.AUDITED,
|
|
1216
|
+
sourceCode: {
|
|
1217
|
+
type: SourceCodeType.OPEN_SOURCE,
|
|
1218
|
+
contractLink:
|
|
1219
|
+
"https://github.com/trovesfi/starknet_vault_kit/tree/sherlock-audited",
|
|
1220
|
+
},
|
|
1221
|
+
// TODO
|
|
1222
|
+
accessControl: {
|
|
1223
|
+
type: AccessControlType.STANDARD_ACCOUNT,
|
|
1224
|
+
addresses: [ContractAddr.from("0x0")],
|
|
1225
|
+
timeLock: "0 Days",
|
|
1226
|
+
},
|
|
1227
|
+
},
|
|
1228
|
+
redemptionInfo: {
|
|
1229
|
+
instantWithdrawalVault: InstantWithdrawalVault.NO,
|
|
1230
|
+
redemptionsInfo: [
|
|
1231
|
+
{
|
|
1232
|
+
title: "Typical Duration",
|
|
1233
|
+
description: "1-2 hours",
|
|
1234
|
+
},
|
|
1235
|
+
],
|
|
1236
|
+
alerts: [
|
|
1237
|
+
{
|
|
1238
|
+
type: "info",
|
|
1239
|
+
text: "Redemption times are estimates and may vary based on network conditions and liquidity requirements.",
|
|
1240
|
+
tab: "withdraw",
|
|
1241
|
+
},
|
|
1242
|
+
],
|
|
1243
|
+
},
|
|
1244
|
+
usualTimeToEarnings: "2 weeks",
|
|
1245
|
+
usualTimeToEarningsDescription:
|
|
1246
|
+
"This strategy depends on Hyper xSTRK's yield, which depends on the price of xSTRK on DEX appreciating. It may be possible the increase is not continuous and generally rebases atleast once every 2 weeks.",
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
export const BoostedxSTRKCarryStrategies: IStrategyMetadata<BoostedxSTRKCarryStrategySettings>[] =
|
|
1251
|
+
[
|
|
1252
|
+
getStrategySettings(boostedxSTRKCarrySettings, {
|
|
1253
|
+
id: "usdc_boosted",
|
|
1254
|
+
name: "USDC Boosted",
|
|
1255
|
+
launchBlock: 10502825,
|
|
1256
|
+
}),
|
|
1257
|
+
getStrategySettings(wbtcBoostedSettings, {
|
|
1258
|
+
id: "wbtc_boosted",
|
|
1259
|
+
name: "WBTC Boosted",
|
|
1260
|
+
launchBlock: 10502758,
|
|
1261
|
+
}),
|
|
1262
|
+
];
|