@strkfarm/sdk 2.0.0-dev.34 → 2.0.0-dev.36
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 +2 -2
- package/dist/cli.mjs +2 -2
- package/dist/index.browser.global.js +16173 -24081
- package/dist/index.browser.mjs +8572 -16661
- package/dist/index.d.ts +600 -2666
- package/dist/index.js +8660 -16784
- package/dist/index.mjs +8585 -16674
- package/package.json +3 -3
- package/src/data/redeem-request-nft.abi.json +752 -0
- package/src/data/universal-vault.abi.json +8 -7
- package/src/dataTypes/bignumber.browser.ts +5 -1
- package/src/dataTypes/bignumber.node.ts +5 -0
- package/src/global.ts +21 -1
- package/src/interfaces/common.tsx +39 -4
- package/src/modules/avnu.ts +19 -10
- package/src/modules/index.ts +1 -1
- package/src/strategies/base-strategy.ts +92 -8
- package/src/strategies/constants.ts +8 -3
- package/src/strategies/ekubo-cl-vault.tsx +150 -16
- package/src/strategies/factory.ts +21 -1
- package/src/strategies/index.ts +2 -6
- package/src/strategies/registry.ts +28 -5
- package/src/strategies/sensei.ts +29 -13
- package/src/strategies/svk-strategy.ts +29 -4
- package/src/strategies/token-boosted-xstrk-carry-strategy.tsx +1057 -0
- package/src/strategies/universal-adapters/adapter-utils.ts +2 -0
- package/src/strategies/universal-adapters/avnu-adapter.ts +19 -10
- package/src/strategies/universal-adapters/index.ts +1 -2
- package/src/strategies/universal-adapters/svk-troves-adapter.ts +160 -13
- package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +91 -42
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +75 -52
- package/src/strategies/universal-adapters/vesu-position-common.ts +38 -31
- package/src/strategies/universal-lst-muliplier-strategy.tsx +222 -269
- package/src/strategies/universal-strategy.tsx +166 -105
- package/src/strategies/vesu-rebalance.tsx +3 -6
- package/src/strategies/yoloVault.ts +1084 -0
- package/src/utils/health-factor-math.ts +29 -0
- package/src/modules/ExtendedWrapperSDk/index.ts +0 -62
- package/src/modules/ExtendedWrapperSDk/types.ts +0 -334
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +0 -611
- package/src/strategies/universal-adapters/extended-adapter.ts +0 -835
- package/src/strategies/universal-adapters/usdc<>usdce-adapter.ts +0 -200
- package/src/strategies/vesu-extended-strategy/services/executionService.ts +0 -2233
- package/src/strategies/vesu-extended-strategy/services/extended-vesu-state-manager.ts +0 -4254
- package/src/strategies/vesu-extended-strategy/services/ltv-imbalance-rebalance-math.ts +0 -783
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +0 -56
- package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +0 -88
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +0 -78
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +0 -48
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +0 -528
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +0 -1014
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
2
|
+
import { Global } from "@/global";
|
|
3
|
+
import {
|
|
4
|
+
AccessControlType,
|
|
5
|
+
AuditStatus,
|
|
6
|
+
getMainnetConfig,
|
|
7
|
+
IConfig,
|
|
8
|
+
InstantWithdrawalVault,
|
|
9
|
+
IStrategyMetadata,
|
|
10
|
+
Protocols,
|
|
11
|
+
SourceCodeType,
|
|
12
|
+
StrategyTag,
|
|
13
|
+
TokenInfo,
|
|
14
|
+
VaultPosition,
|
|
15
|
+
VaultType,
|
|
16
|
+
} from "@/interfaces";
|
|
17
|
+
import { ERC20, PricerFromApi, TokenMarketData } from "@/modules";
|
|
18
|
+
import { PricerBase } from "@/modules/pricerBase";
|
|
19
|
+
import { logger, assert } from "@/utils";
|
|
20
|
+
import { BlockIdentifier, Call, uint256, num } from "starknet";
|
|
21
|
+
import { SingleTokenInfo } from "./base-strategy";
|
|
22
|
+
import { SVKStrategy } from "./svk-strategy";
|
|
23
|
+
import {
|
|
24
|
+
APYType,
|
|
25
|
+
AvnuAdapter,
|
|
26
|
+
BaseAdapterConfig,
|
|
27
|
+
CommonAdapter,
|
|
28
|
+
PositionInfo,
|
|
29
|
+
TokenTransferAdapter,
|
|
30
|
+
VesuModifyPositionAdapter,
|
|
31
|
+
} from "./universal-adapters";
|
|
32
|
+
import { AdapterOptimizer } from "./universal-adapters/adapter-optimizer";
|
|
33
|
+
import {
|
|
34
|
+
AVNU_EXCHANGE,
|
|
35
|
+
AVNU_QUOTE_URL,
|
|
36
|
+
} from "./universal-adapters/adapter-utils";
|
|
37
|
+
import { SvkTrovesAdapter } from "./universal-adapters/svk-troves-adapter";
|
|
38
|
+
import { VesuPools, VesuAdapter } from "./universal-adapters/vesu-adapter";
|
|
39
|
+
import {
|
|
40
|
+
AUMTypes,
|
|
41
|
+
getContractDetails,
|
|
42
|
+
UNIVERSAL_MANAGE_IDS,
|
|
43
|
+
UniversalStrategySettings,
|
|
44
|
+
} from "./universal-strategy";
|
|
45
|
+
|
|
46
|
+
export interface BoostedxSTRKCarryStrategySettings extends UniversalStrategySettings {
|
|
47
|
+
depositToken: TokenInfo; // USDC, WBTC, etc. - the collateral token
|
|
48
|
+
debtToken: TokenInfo; // STRK, etc. - the debt token
|
|
49
|
+
lstHyperToken: TokenInfo; // xSTRK, etc. - the LST token deposited into hyper vault
|
|
50
|
+
vesuPoolId: ContractAddr;
|
|
51
|
+
maxLTV: number; // e.g. 0.66
|
|
52
|
+
targetLTV: number;
|
|
53
|
+
hyperLstVaultAddress: ContractAddr; // Address of the hyper vault for LST
|
|
54
|
+
hyperLstRedeemNFT: ContractAddr; // NFT contract for pending hyper redeems
|
|
55
|
+
trovesStrategyId: string; // e.g. "hyper_xstrk" - used for APY fetching
|
|
56
|
+
// TODO: will refac later on to a better approach where we dont need flags
|
|
57
|
+
hasBtcFiRewards: boolean; // Whether this strategy has BTC.Fi rewards (affects reporting flow)
|
|
58
|
+
// Adapter IDs computed from token symbols
|
|
59
|
+
adapterIds?: {
|
|
60
|
+
vesu: string;
|
|
61
|
+
avnu: string;
|
|
62
|
+
hyper: string;
|
|
63
|
+
transfer: string;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export class BoostedxSTRKCarryStrategy<
|
|
68
|
+
S extends BoostedxSTRKCarryStrategySettings,
|
|
69
|
+
> extends SVKStrategy<S> {
|
|
70
|
+
constructor(
|
|
71
|
+
config: IConfig,
|
|
72
|
+
pricer: PricerBase,
|
|
73
|
+
metadata: IStrategyMetadata<S>,
|
|
74
|
+
) {
|
|
75
|
+
super(config, pricer, metadata);
|
|
76
|
+
|
|
77
|
+
this.metadata.additionalInfo.adapters.forEach((adapter) => {
|
|
78
|
+
adapter.adapter.config.networkConfig = this.config;
|
|
79
|
+
adapter.adapter.config.pricer = this.pricer;
|
|
80
|
+
if ((adapter.adapter as any)._vesuAdapter) {
|
|
81
|
+
(adapter.adapter as any)._vesuAdapter.networkConfig = this.config;
|
|
82
|
+
(adapter.adapter as any)._vesuAdapter.pricer = this.pricer;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getTag(): string {
|
|
88
|
+
return `${BoostedxSTRKCarryStrategy.name}:${this.metadata.name}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private getAdapterById<T>(id: string): T {
|
|
92
|
+
const entry = this.metadata.additionalInfo.adapters.find(
|
|
93
|
+
(a) => a.id === id,
|
|
94
|
+
);
|
|
95
|
+
if (!entry) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`${this.getTag()}::getAdapterById: adapter not found for id "${id}"`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return entry.adapter as T;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private async getTruePriceForToken(token: TokenInfo): Promise<number> {
|
|
104
|
+
return new TokenMarketData(this.pricer, this.config).getTruePrice(token);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async getMinOutputAmountLSTBuy(amountInUnderlying: Web3Number) {
|
|
108
|
+
const lst = this.metadata.additionalInfo.lstHyperToken;
|
|
109
|
+
const lstTruePrice = await this.getTruePriceForToken(lst);
|
|
110
|
+
const minOutputAmount = amountInUnderlying
|
|
111
|
+
.dividedBy(lstTruePrice)
|
|
112
|
+
.multipliedBy(0.99979); // 0.21 % avnu fees
|
|
113
|
+
return new Web3Number(minOutputAmount.toString(), lst.decimals);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async getMinOutputAmountLSTSell(amountInLST: Web3Number) {
|
|
117
|
+
const lst = this.metadata.additionalInfo.lstHyperToken;
|
|
118
|
+
const lstTruePrice = await this.getTruePriceForToken(lst);
|
|
119
|
+
return amountInLST.multipliedBy(lstTruePrice).multipliedBy(0.995);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async getVesuModifyPositionCall(params: {
|
|
123
|
+
isDeposit: boolean;
|
|
124
|
+
leg1DepositAmount: Web3Number;
|
|
125
|
+
debtAmount?: Web3Number;
|
|
126
|
+
}): Promise<Call> {
|
|
127
|
+
logger.verbose(
|
|
128
|
+
`${this.getTag()}::getVesuModifyPositionCall isDeposit=${params.isDeposit}, amount=${params.leg1DepositAmount}, debtAmount=${params.debtAmount?.toNumber()}`,
|
|
129
|
+
);
|
|
130
|
+
return this.buildManageCallFromAdapter(
|
|
131
|
+
this.getAdapterById<VesuModifyPositionAdapter>(
|
|
132
|
+
this.metadata.additionalInfo.adapterIds!.vesu,
|
|
133
|
+
),
|
|
134
|
+
params.isDeposit,
|
|
135
|
+
params.leg1DepositAmount,
|
|
136
|
+
params.debtAmount ? { debtAmount: params.debtAmount } : undefined,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async getVesuPositions(): Promise<VaultPosition[]> {
|
|
141
|
+
const positions = await this.getAdapterById<VesuModifyPositionAdapter>(
|
|
142
|
+
this.metadata.additionalInfo.adapterIds!.vesu,
|
|
143
|
+
).getPositions();
|
|
144
|
+
return positions.map((p) => ({
|
|
145
|
+
amount: p.amount,
|
|
146
|
+
usdValue: p.usdValue,
|
|
147
|
+
remarks: p.remarks ?? "",
|
|
148
|
+
token: p.tokenInfo,
|
|
149
|
+
protocol: p.protocol,
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async getVesuHealthFactor(
|
|
154
|
+
blockNumber: BlockIdentifier = "latest",
|
|
155
|
+
): Promise<number> {
|
|
156
|
+
const vesuAdapter = this.getAdapterById<VesuModifyPositionAdapter>(
|
|
157
|
+
this.metadata.additionalInfo.adapterIds!.vesu,
|
|
158
|
+
);
|
|
159
|
+
return await vesuAdapter._vesuAdapter.getHealthFactor(blockNumber);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// TODO: will have to still modify for instant withdrawals as of now
|
|
163
|
+
async getModifyHyperPositionCall(params: {
|
|
164
|
+
isDeposit: boolean;
|
|
165
|
+
amount: Web3Number;
|
|
166
|
+
}): Promise<Call> {
|
|
167
|
+
logger.verbose(
|
|
168
|
+
`${this.getTag()}::getModifyHyperPositionCall isDeposit=${params.isDeposit}, amount=${params.amount}`,
|
|
169
|
+
);
|
|
170
|
+
return this.buildManageCallFromAdapter(
|
|
171
|
+
this.getAdapterById<SvkTrovesAdapter>(
|
|
172
|
+
this.metadata.additionalInfo.adapterIds!.hyper,
|
|
173
|
+
),
|
|
174
|
+
params.isDeposit,
|
|
175
|
+
params.amount,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async getAvnuSwapCall(params: {
|
|
180
|
+
isDeposit: boolean;
|
|
181
|
+
amount: Web3Number;
|
|
182
|
+
minAmount?: Web3Number;
|
|
183
|
+
}): Promise<Call> {
|
|
184
|
+
logger.verbose(
|
|
185
|
+
`${this.getTag()}::getAvnuSwapCall isDeposit=${params.isDeposit}, amount=${params.amount}, minAmount=${params.minAmount?.toNumber()}`,
|
|
186
|
+
);
|
|
187
|
+
return this.buildManageCallFromAdapter(
|
|
188
|
+
this.getAdapterById<AvnuAdapter>(
|
|
189
|
+
this.metadata.additionalInfo.adapterIds!.avnu,
|
|
190
|
+
),
|
|
191
|
+
params.isDeposit,
|
|
192
|
+
params.amount,
|
|
193
|
+
params.minAmount ? { minAmount: params.minAmount } : undefined,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Returns the deposit token balance sitting idle inside the vault contract itself.
|
|
199
|
+
* This balance can offset the required liquidity during withdrawal processing.
|
|
200
|
+
*/
|
|
201
|
+
async getUnusedBalanceVault(): Promise<Web3Number> {
|
|
202
|
+
const depositToken = this.metadata.additionalInfo.depositToken;
|
|
203
|
+
return new ERC20(this.config).balanceOf(
|
|
204
|
+
depositToken.address,
|
|
205
|
+
this.metadata.additionalInfo.vaultAddress,
|
|
206
|
+
depositToken.decimals,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Returns the current borrow token balance sitting unused in the vault allocator.
|
|
212
|
+
* This covers borrow token from prior borrow cycles that hasn't been swapped yet.
|
|
213
|
+
*/
|
|
214
|
+
// Technically we can use this or we can even use the avnu-adapter to get the unused debt
|
|
215
|
+
async getUnusedDebt(): Promise<Web3Number> {
|
|
216
|
+
const debtToken = this.metadata.additionalInfo.debtToken;
|
|
217
|
+
return new ERC20(this.config).balanceOf(
|
|
218
|
+
debtToken.address,
|
|
219
|
+
this.metadata.additionalInfo.vaultAllocator,
|
|
220
|
+
debtToken.decimals,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Returns the LST balance sitting in the vault allocator.
|
|
226
|
+
* This is non-zero when the previous cycle's request_redeem on the HyperPosition
|
|
227
|
+
* has been settled — the redeemed LST lands here and is ready to be swapped.
|
|
228
|
+
*/
|
|
229
|
+
async getLstInVaultAllocator(): Promise<Web3Number> {
|
|
230
|
+
const lstToken = this.metadata.additionalInfo.lstHyperToken;
|
|
231
|
+
return new ERC20(this.config).balanceOf(
|
|
232
|
+
lstToken.address.address,
|
|
233
|
+
this.metadata.additionalInfo.vaultAllocator,
|
|
234
|
+
lstToken.decimals,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Simulates depositing `depositAmount` on Vesu and returns the
|
|
240
|
+
* incremental borrow token that would be borrowed. Needed to size the AVNU swap
|
|
241
|
+
* call that is batched together with the Vesu call in the same transaction.
|
|
242
|
+
*/
|
|
243
|
+
async computeVesuDepositBorrowAmount(
|
|
244
|
+
depositAmount: Web3Number,
|
|
245
|
+
): Promise<Web3Number> {
|
|
246
|
+
return this.getAdapterById<VesuModifyPositionAdapter>(
|
|
247
|
+
this.metadata.additionalInfo.adapterIds!.vesu,
|
|
248
|
+
).getExpectedDepositDebtDelta(depositAmount);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async computeVesuWithdrawDebtDelta(
|
|
252
|
+
withdrawAmount: Web3Number,
|
|
253
|
+
): Promise<Web3Number> {
|
|
254
|
+
return this.getAdapterById<VesuModifyPositionAdapter>(
|
|
255
|
+
this.metadata.additionalInfo.adapterIds!.vesu,
|
|
256
|
+
).getExpectedWithdrawDebtDelta(withdrawAmount);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async getPendingHyperAssets(): Promise<Web3Number> {
|
|
260
|
+
const lstToken = this.metadata.additionalInfo.lstHyperToken;
|
|
261
|
+
const hyperLstRedeemNFT = this.metadata.additionalInfo.hyperLstRedeemNFT;
|
|
262
|
+
return this.getAdapterById<SvkTrovesAdapter>(
|
|
263
|
+
this.metadata.additionalInfo.adapterIds!.hyper,
|
|
264
|
+
).getPendingAssetsFromOwnerNFTMethod(
|
|
265
|
+
hyperLstRedeemNFT,
|
|
266
|
+
this.metadata.additionalInfo.vaultAllocator,
|
|
267
|
+
lstToken.decimals,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// TODO: rn we are just making these functions in the strategy itself but
|
|
272
|
+
// later on we will move them to the SVKStrategy for generalization
|
|
273
|
+
|
|
274
|
+
async getUserTVL(
|
|
275
|
+
user: ContractAddr,
|
|
276
|
+
blockIdentifier: BlockIdentifier = "latest",
|
|
277
|
+
) {
|
|
278
|
+
const shares: any = await this.contract.call("balanceOf", [user.address], {
|
|
279
|
+
blockIdentifier,
|
|
280
|
+
});
|
|
281
|
+
const assets: any = await this.contract.call(
|
|
282
|
+
"convert_to_assets",
|
|
283
|
+
[uint256.bnToUint256(shares)],
|
|
284
|
+
{ blockIdentifier },
|
|
285
|
+
);
|
|
286
|
+
const amount = Web3Number.fromWei(
|
|
287
|
+
assets.toString(),
|
|
288
|
+
this.metadata.depositTokens[0].decimals,
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const blockNumber =
|
|
292
|
+
typeof blockIdentifier === "number" || typeof blockIdentifier === "bigint"
|
|
293
|
+
? Number(blockIdentifier)
|
|
294
|
+
: undefined;
|
|
295
|
+
|
|
296
|
+
const price = await this.pricer.getPrice(
|
|
297
|
+
this.metadata.depositTokens[0].symbol,
|
|
298
|
+
blockNumber,
|
|
299
|
+
);
|
|
300
|
+
const usdValue = Number(amount.toFixed(6)) * price.price;
|
|
301
|
+
return {
|
|
302
|
+
tokenInfo: this.asset(),
|
|
303
|
+
amount,
|
|
304
|
+
usdValue,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async getAUM(): Promise<{
|
|
309
|
+
net: SingleTokenInfo;
|
|
310
|
+
prevAum: Web3Number;
|
|
311
|
+
splits: PositionInfo[];
|
|
312
|
+
}> {
|
|
313
|
+
const underlying = this.asset(); // Deposit token (USDC, WBTC, etc.)
|
|
314
|
+
const depositTokenPrice = await this.pricer.getPrice(underlying.symbol);
|
|
315
|
+
|
|
316
|
+
const allPositions = await this.getVaultPositions();
|
|
317
|
+
|
|
318
|
+
let netAUM = new Web3Number(0, underlying.decimals);
|
|
319
|
+
for (const position of allPositions) {
|
|
320
|
+
if (position.token.address.eq(underlying.address)) {
|
|
321
|
+
// Same token as underlying - add amount directly
|
|
322
|
+
netAUM = netAUM.plus(position.amount);
|
|
323
|
+
} else {
|
|
324
|
+
// Different token - convert USD value to underlying token amount
|
|
325
|
+
const amountInUnderlying = new Web3Number(
|
|
326
|
+
position.usdValue.toString(),
|
|
327
|
+
underlying.decimals,
|
|
328
|
+
).dividedBy(depositTokenPrice.price);
|
|
329
|
+
netAUM = netAUM.plus(amountInUnderlying);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const prevAum = await this.getPrevAUM();
|
|
334
|
+
logger.verbose(`${this.getTag()} Actual AUM: ${netAUM}`);
|
|
335
|
+
|
|
336
|
+
// Calculate BTCFI rewards contribution
|
|
337
|
+
const rewardAssets = await this.getRewardsAUM(prevAum);
|
|
338
|
+
logger.verbose(`${this.getTag()} Rewards AUM: ${rewardAssets}`);
|
|
339
|
+
|
|
340
|
+
// Sum up total AUM
|
|
341
|
+
const totalAUM = netAUM.plus(rewardAssets);
|
|
342
|
+
logger.verbose(`${this.getTag()} Total AUM: ${totalAUM}`);
|
|
343
|
+
|
|
344
|
+
const realAUM: PositionInfo = {
|
|
345
|
+
tokenInfo: underlying,
|
|
346
|
+
amount: netAUM,
|
|
347
|
+
usdValue: netAUM.toNumber() * depositTokenPrice.price,
|
|
348
|
+
apy: { apy: 0, type: APYType.BASE },
|
|
349
|
+
remarks: AUMTypes.FINALISED,
|
|
350
|
+
protocol: Protocols.NONE,
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
const estimatedAUMDelta: PositionInfo = {
|
|
354
|
+
tokenInfo: underlying,
|
|
355
|
+
amount: rewardAssets,
|
|
356
|
+
usdValue: rewardAssets.toNumber() * depositTokenPrice.price,
|
|
357
|
+
apy: { apy: 0, type: APYType.BASE },
|
|
358
|
+
remarks: AUMTypes.BTCFI,
|
|
359
|
+
protocol: Protocols.NONE,
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
net: {
|
|
364
|
+
tokenInfo: underlying,
|
|
365
|
+
amount: totalAUM,
|
|
366
|
+
usdValue: totalAUM.toNumber() * depositTokenPrice.price,
|
|
367
|
+
},
|
|
368
|
+
prevAum,
|
|
369
|
+
splits: [realAUM, estimatedAUMDelta],
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Get Vesu APYs for collateral and debt positions
|
|
375
|
+
*/
|
|
376
|
+
async getVesuAPYs(): Promise<{
|
|
377
|
+
baseAPYs: number[];
|
|
378
|
+
rewardAPYs: number[];
|
|
379
|
+
positions: VaultPosition[];
|
|
380
|
+
}> {
|
|
381
|
+
const vesuAdapter = this.getAdapterById<VesuModifyPositionAdapter>(
|
|
382
|
+
this.metadata.additionalInfo.adapterIds!.vesu,
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
// Get Vesu pool data
|
|
386
|
+
const allVesuPools = await VesuAdapter.getVesuPools();
|
|
387
|
+
const pool = allVesuPools.pools.find((p: any) =>
|
|
388
|
+
vesuAdapter.config.poolId.eqString(num.getHexString(p.id)),
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
if (!pool) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`${this.getTag()}::getVesuAPYs: Pool not found for ${vesuAdapter.config.poolId.address}`,
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// logger.verbose(
|
|
398
|
+
// `${this.getTag()}::getVesuAPYs: vesu-pool: ${JSON.stringify(pool)}`,
|
|
399
|
+
// );
|
|
400
|
+
|
|
401
|
+
// Get positions
|
|
402
|
+
const positions = await this.getVesuPositions();
|
|
403
|
+
logger.verbose(
|
|
404
|
+
`${this.getTag()}::getVesuAPYs: positions: ${JSON.stringify(positions)}`,
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
const baseAPYs: number[] = [];
|
|
408
|
+
const rewardAPYs: number[] = [];
|
|
409
|
+
|
|
410
|
+
// Find collateral and debt asset stats
|
|
411
|
+
const collateralAsset = pool.assets.find(
|
|
412
|
+
(a: any) =>
|
|
413
|
+
a.symbol.toLowerCase() ===
|
|
414
|
+
vesuAdapter.config.collateral.symbol.toLowerCase(),
|
|
415
|
+
)?.stats!;
|
|
416
|
+
const debtAsset = pool.assets.find(
|
|
417
|
+
(a: any) =>
|
|
418
|
+
a.symbol.toLowerCase() === vesuAdapter.config.debt.symbol.toLowerCase(),
|
|
419
|
+
)?.stats!;
|
|
420
|
+
|
|
421
|
+
if (!collateralAsset || !debtAsset) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`${this.getTag()}::getVesuAPYs: Collateral or debt asset stats not found`,
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const supplyApy = Number(collateralAsset.supplyApy.value || 0) / 1e18;
|
|
428
|
+
const borrowApr = Number(debtAsset.borrowApr.value) / 1e18;
|
|
429
|
+
const collateralRewardsApr = this.metadata.additionalInfo.hasBtcFiRewards
|
|
430
|
+
? Number(collateralAsset.btcFiSupplyApr?.value || "0") / 1e18
|
|
431
|
+
: 0;
|
|
432
|
+
|
|
433
|
+
logger.verbose(
|
|
434
|
+
`${this.getTag()}::getVesuAPYs: collateralRewardsApr: ${collateralRewardsApr}`,
|
|
435
|
+
);
|
|
436
|
+
logger.verbose(
|
|
437
|
+
`${this.getTag()}::getVesuAPYs: collateralAsset: ${JSON.stringify(collateralAsset)}`,
|
|
438
|
+
);
|
|
439
|
+
logger.verbose(
|
|
440
|
+
`${this.getTag()}::getVesuAPYs: supplyApy=${supplyApy}, borrowApr=${borrowApr}, collateralRewards=${collateralRewardsApr}`,
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
// Collateral: supply APY, Debt: borrow APR (cost)
|
|
444
|
+
baseAPYs.push(supplyApy, borrowApr);
|
|
445
|
+
rewardAPYs.push(collateralRewardsApr, 0);
|
|
446
|
+
|
|
447
|
+
logger.verbose(
|
|
448
|
+
`${this.getTag()}::getVesuAPYs: baseAPYs: ${JSON.stringify(baseAPYs)}, rewardAPYs: ${JSON.stringify(rewardAPYs)}`,
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
assert(
|
|
452
|
+
baseAPYs.length === positions.length,
|
|
453
|
+
"APYs and positions length mismatch",
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
return { baseAPYs, rewardAPYs, positions };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get APY from the Hyper LST vault position
|
|
461
|
+
*/
|
|
462
|
+
async getHyperVaultAPY(): Promise<{ apy: number; weight: number }> {
|
|
463
|
+
try {
|
|
464
|
+
const hyperAdapter = this.getAdapterById<SvkTrovesAdapter>(
|
|
465
|
+
this.metadata.additionalInfo.adapterIds!.hyper,
|
|
466
|
+
);
|
|
467
|
+
const positions = await hyperAdapter.getPositions();
|
|
468
|
+
|
|
469
|
+
if (positions.length === 0 || positions[0].amount.isZero()) {
|
|
470
|
+
return { apy: 0, weight: 0 };
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const position = positions[0];
|
|
474
|
+
const apy = position.apy.apy;
|
|
475
|
+
const weight = position.usdValue;
|
|
476
|
+
|
|
477
|
+
logger.verbose(
|
|
478
|
+
`${this.getTag()}::getHyperVaultAPY: apy=${apy}, weight=${weight}`,
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
return { apy, weight };
|
|
482
|
+
} catch (error) {
|
|
483
|
+
logger.warn(
|
|
484
|
+
`${this.getTag()}::getHyperVaultAPY: Error getting Hyper vault APY: ${error}`,
|
|
485
|
+
);
|
|
486
|
+
return { apy: 0, weight: 0 };
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Get APY for unused balance (idle funds in vault allocator)
|
|
492
|
+
*/
|
|
493
|
+
protected async getUnusedBalanceAPY() {
|
|
494
|
+
return {
|
|
495
|
+
apy: 0,
|
|
496
|
+
weight: 0,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Compute weighted APY from individual APYs, weights, and total AUM
|
|
502
|
+
*/
|
|
503
|
+
private computeAPY(
|
|
504
|
+
apys: number[],
|
|
505
|
+
weights: number[],
|
|
506
|
+
totalWeight: number,
|
|
507
|
+
): number {
|
|
508
|
+
assert(apys.length === weights.length, "APYs and weights length mismatch");
|
|
509
|
+
const weightedSum = apys.reduce((acc, apy, i) => acc + apy * weights[i], 0);
|
|
510
|
+
logger.verbose(
|
|
511
|
+
`${this.getTag()} computeAPY: apys: ${JSON.stringify(apys)}, weights: ${JSON.stringify(weights)}, weightedSum: ${weightedSum}, totalWeight: ${totalWeight}`,
|
|
512
|
+
);
|
|
513
|
+
return weightedSum / totalWeight;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Calculate net APY from base and reward APYs
|
|
518
|
+
*/
|
|
519
|
+
protected async returnNetAPY(
|
|
520
|
+
baseAPYs: number[],
|
|
521
|
+
rewardAPYs: number[],
|
|
522
|
+
weights: number[],
|
|
523
|
+
totalWeightUSD: number,
|
|
524
|
+
) {
|
|
525
|
+
// If no positions, return 0
|
|
526
|
+
if (weights.every((p) => p == 0)) {
|
|
527
|
+
return {
|
|
528
|
+
net: 0,
|
|
529
|
+
splits: [
|
|
530
|
+
{
|
|
531
|
+
apy: 0,
|
|
532
|
+
id: "base",
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
apy: 0,
|
|
536
|
+
id: "btcfi",
|
|
537
|
+
},
|
|
538
|
+
],
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const baseAPY = this.computeAPY(baseAPYs, weights, totalWeightUSD);
|
|
543
|
+
const rewardAPY = this.computeAPY(rewardAPYs, weights, totalWeightUSD);
|
|
544
|
+
const netAPY = baseAPY + rewardAPY;
|
|
545
|
+
logger.verbose(
|
|
546
|
+
`${this.getTag()}::netAPY: net: ${netAPY}, baseAPY: ${baseAPY}, rewardAPY: ${rewardAPY}`,
|
|
547
|
+
);
|
|
548
|
+
return {
|
|
549
|
+
net: netAPY,
|
|
550
|
+
splits: [
|
|
551
|
+
{
|
|
552
|
+
apy: baseAPY,
|
|
553
|
+
id: "base",
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
apy: rewardAPY,
|
|
557
|
+
id: "btcfi",
|
|
558
|
+
},
|
|
559
|
+
],
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Calculate net APY across all positions (Vesu, Hyper vault, unused balance)
|
|
565
|
+
* weighted by USD value
|
|
566
|
+
*/
|
|
567
|
+
// TODO: will be reviewing this later on, we only need BTCFI APY for now for AUM
|
|
568
|
+
async netAPY(): Promise<{
|
|
569
|
+
net: number;
|
|
570
|
+
splits: { apy: number; id: string }[];
|
|
571
|
+
}> {
|
|
572
|
+
try {
|
|
573
|
+
// Get Vesu APYs and positions
|
|
574
|
+
const { positions, baseAPYs, rewardAPYs } = await this.getVesuAPYs();
|
|
575
|
+
|
|
576
|
+
// Get Hyper vault APY
|
|
577
|
+
const hyperAPY = await this.getHyperVaultAPY();
|
|
578
|
+
baseAPYs.push(hyperAPY.apy);
|
|
579
|
+
rewardAPYs.push(0); // Hyper vault rewards already included in its APY
|
|
580
|
+
|
|
581
|
+
// Get unused balance APY
|
|
582
|
+
const unusedBalanceAPY = await this.getUnusedBalanceAPY();
|
|
583
|
+
baseAPYs.push(unusedBalanceAPY.apy);
|
|
584
|
+
rewardAPYs.push(0);
|
|
585
|
+
|
|
586
|
+
// Compute weights - debt usdValue is already negative from adapter
|
|
587
|
+
const weights = positions.map((p) => p.usdValue);
|
|
588
|
+
weights.push(hyperAPY.weight);
|
|
589
|
+
weights.push(unusedBalanceAPY.weight);
|
|
590
|
+
|
|
591
|
+
logger.verbose(
|
|
592
|
+
`${this.getTag()}::netAPY: weights: ${JSON.stringify(weights)}`,
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
// Calculate total weight (current total AUM in USD)
|
|
596
|
+
const totalWeightUSD = weights.reduce((sum, weight) => sum + weight, 0);
|
|
597
|
+
|
|
598
|
+
logger.verbose(
|
|
599
|
+
`${this.getTag()}::netAPY: totalWeightUSD: ${totalWeightUSD}`,
|
|
600
|
+
);
|
|
601
|
+
|
|
602
|
+
return this.returnNetAPY(baseAPYs, rewardAPYs, weights, totalWeightUSD);
|
|
603
|
+
} catch (error: any) {
|
|
604
|
+
logger.error(
|
|
605
|
+
`${this.getTag()}::netAPY: Error calculating net APY: ${error?.message || error}`,
|
|
606
|
+
);
|
|
607
|
+
return {
|
|
608
|
+
net: 0,
|
|
609
|
+
splits: [
|
|
610
|
+
{
|
|
611
|
+
apy: 0,
|
|
612
|
+
id: "base",
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
apy: 0,
|
|
616
|
+
id: "btcfi",
|
|
617
|
+
},
|
|
618
|
+
],
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Calculate future rewards (e.g. BTCFI rewards) contribution to AUM
|
|
625
|
+
* Similar to universal-strategy.tsx approach
|
|
626
|
+
*/
|
|
627
|
+
protected async getRewardsAUM(prevAum: Web3Number): Promise<Web3Number> {
|
|
628
|
+
if (!this.metadata.additionalInfo.hasBtcFiRewards) {
|
|
629
|
+
return Web3Number.fromWei("0", this.asset().decimals);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
try {
|
|
633
|
+
const lastReportTime = await this.contract.call(
|
|
634
|
+
"last_report_timestamp",
|
|
635
|
+
[],
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
// Calculate estimated growth from rewards
|
|
639
|
+
const netAPY = await this.netAPY();
|
|
640
|
+
// Account only 80% of value for conservative estimate
|
|
641
|
+
const btcfiAPY =
|
|
642
|
+
(netAPY.splits.find((s) => s.id === "btcfi")?.apy || 0) * 0.8;
|
|
643
|
+
|
|
644
|
+
if (!btcfiAPY) {
|
|
645
|
+
logger.verbose(
|
|
646
|
+
`${this.getTag()} No BTCFI APY found, returning 0 rewards`,
|
|
647
|
+
);
|
|
648
|
+
return Web3Number.fromWei("0", this.asset().decimals);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Compute rewards contribution to AUM
|
|
652
|
+
const timeDiff = Math.round(Date.now() / 1000) - Number(lastReportTime);
|
|
653
|
+
const growthRate = (timeDiff * btcfiAPY) / (365 * 24 * 60 * 60);
|
|
654
|
+
const rewardAssets = prevAum.multipliedBy(growthRate);
|
|
655
|
+
|
|
656
|
+
logger.verbose(
|
|
657
|
+
`${this.getTag()} BtcFi AUM time difference: ${timeDiff}s`,
|
|
658
|
+
);
|
|
659
|
+
logger.verbose(`${this.getTag()} Previous AUM: ${prevAum.toString()}`);
|
|
660
|
+
logger.verbose(`${this.getTag()} BtcFi APY: ${btcfiAPY}`);
|
|
661
|
+
logger.verbose(`${this.getTag()} Growth rate: ${growthRate}`);
|
|
662
|
+
logger.verbose(
|
|
663
|
+
`${this.getTag()} Rewards AUM: ${rewardAssets.toString()}`,
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
return rewardAssets;
|
|
667
|
+
} catch (error: any) {
|
|
668
|
+
logger.warn(
|
|
669
|
+
`${this.getTag()} Error calculating rewards AUM: ${error?.message || error}`,
|
|
670
|
+
);
|
|
671
|
+
return Web3Number.fromWei("0", this.asset().decimals);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// TODO: can refactor later but seems redundant since not being used ANYWHERE
|
|
676
|
+
// Most of the fund management done through adapters only
|
|
677
|
+
|
|
678
|
+
async getFundManagementCall(params: {
|
|
679
|
+
isDeposit: boolean;
|
|
680
|
+
leg1DepositAmount: Web3Number;
|
|
681
|
+
}) {
|
|
682
|
+
logger.verbose(
|
|
683
|
+
`${this.getTag()}::getFundManagementCall params: ${JSON.stringify(params)}`,
|
|
684
|
+
);
|
|
685
|
+
const allAdapters = this.metadata.additionalInfo.adapters.map(
|
|
686
|
+
(a) => a.adapter,
|
|
687
|
+
);
|
|
688
|
+
|
|
689
|
+
if (!params.isDeposit) {
|
|
690
|
+
const unusedBalance = await this.getUnusedBalance();
|
|
691
|
+
logger.verbose(
|
|
692
|
+
`${this.getTag()}::getFundManagementCall unusedBalance: ${unusedBalance.amount}, required: ${params.leg1DepositAmount}`,
|
|
693
|
+
);
|
|
694
|
+
if (unusedBalance.amount.gte(params.leg1DepositAmount)) {
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const adapters = await AdapterOptimizer.getAdapterToUse(
|
|
699
|
+
allAdapters,
|
|
700
|
+
false,
|
|
701
|
+
params.leg1DepositAmount,
|
|
702
|
+
);
|
|
703
|
+
if (adapters.length > 0) {
|
|
704
|
+
const proofsInfo = adapters.map((adapter) =>
|
|
705
|
+
adapter.getProofs(false, this.getMerkleTree()),
|
|
706
|
+
);
|
|
707
|
+
const calls: Call[] = [];
|
|
708
|
+
for (const info of proofsInfo) {
|
|
709
|
+
const manageCalls = await info.callConstructor({
|
|
710
|
+
amount: params.leg1DepositAmount,
|
|
711
|
+
});
|
|
712
|
+
const call = this.getManageCall(
|
|
713
|
+
this.getProofGroupsForManageCalls(manageCalls),
|
|
714
|
+
manageCalls,
|
|
715
|
+
);
|
|
716
|
+
calls.push(call);
|
|
717
|
+
}
|
|
718
|
+
return calls;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
throw new Error(
|
|
722
|
+
`${this.getTag()}::getFundManagementCall: no adapters for withdraw: ${unusedBalance.amount}`,
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const adapters = await AdapterOptimizer.getAdapterToUse(
|
|
727
|
+
allAdapters,
|
|
728
|
+
true,
|
|
729
|
+
params.leg1DepositAmount,
|
|
730
|
+
);
|
|
731
|
+
if (adapters.length > 0) {
|
|
732
|
+
const proofsInfo = adapters.map((adapter) =>
|
|
733
|
+
adapter.getProofs(true, this.getMerkleTree()),
|
|
734
|
+
);
|
|
735
|
+
const calls: Call[] = [];
|
|
736
|
+
for (const info of proofsInfo) {
|
|
737
|
+
const manageCalls = await info.callConstructor({
|
|
738
|
+
amount: params.leg1DepositAmount,
|
|
739
|
+
});
|
|
740
|
+
const call = this.getManageCall(
|
|
741
|
+
this.getProofGroupsForManageCalls(manageCalls),
|
|
742
|
+
manageCalls,
|
|
743
|
+
);
|
|
744
|
+
calls.push(call);
|
|
745
|
+
}
|
|
746
|
+
return calls;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
throw new Error(
|
|
750
|
+
`${this.getTag()}::getFundManagementCall: no adapters for deposit: ${params.leg1DepositAmount}`,
|
|
751
|
+
);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
function getBoostedxSTRKCarrySettings(
|
|
756
|
+
vaultSettings: BoostedxSTRKCarryStrategySettings,
|
|
757
|
+
) {
|
|
758
|
+
vaultSettings.leafAdapters = [];
|
|
759
|
+
|
|
760
|
+
// Use metadata-driven token configuration
|
|
761
|
+
const depositToken = vaultSettings.depositToken;
|
|
762
|
+
const debtToken = vaultSettings.debtToken;
|
|
763
|
+
const lstToken = vaultSettings.lstHyperToken;
|
|
764
|
+
|
|
765
|
+
// Build dynamic adapter IDs based on token symbols
|
|
766
|
+
const adapterIds = {
|
|
767
|
+
vesu: `vesu_${depositToken.symbol.toLowerCase()}_${debtToken.symbol.toLowerCase()}`,
|
|
768
|
+
avnu: `avnu_${debtToken.symbol.toLowerCase()}_${lstToken.symbol.toLowerCase()}`,
|
|
769
|
+
hyper: `hyper_${lstToken.symbol.toLowerCase()}`,
|
|
770
|
+
transfer: `${depositToken.symbol.toLowerCase()}_transfer`,
|
|
771
|
+
};
|
|
772
|
+
vaultSettings.adapterIds = adapterIds;
|
|
773
|
+
|
|
774
|
+
const baseAdapterConfig: BaseAdapterConfig = {
|
|
775
|
+
baseToken: depositToken,
|
|
776
|
+
supportedPositions: [{ asset: depositToken, isDebt: false }],
|
|
777
|
+
networkConfig: getMainnetConfig(),
|
|
778
|
+
pricer: new PricerFromApi(getMainnetConfig(), Global.getDefaultTokens()),
|
|
779
|
+
vaultAllocator: vaultSettings.vaultAllocator,
|
|
780
|
+
vaultAddress: vaultSettings.vaultAddress,
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
// ── 1. VesuModifyPositionAdapter (deposit token collateral / borrow token debt) ──
|
|
784
|
+
|
|
785
|
+
const vesuModifyPositionAdapter = new VesuModifyPositionAdapter({
|
|
786
|
+
poolId: vaultSettings.vesuPoolId,
|
|
787
|
+
collateral: depositToken,
|
|
788
|
+
debt: debtToken,
|
|
789
|
+
targetLtv: vaultSettings.targetLTV,
|
|
790
|
+
maxLtv: vaultSettings.maxLTV,
|
|
791
|
+
...baseAdapterConfig,
|
|
792
|
+
supportedPositions: [
|
|
793
|
+
{ asset: depositToken, isDebt: false },
|
|
794
|
+
{ asset: debtToken, isDebt: true },
|
|
795
|
+
],
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
// ── 2. AvnuAdapter (borrow token ↔ LST swaps) ──
|
|
799
|
+
const avnuAdapter = new AvnuAdapter({
|
|
800
|
+
baseUrl: AVNU_QUOTE_URL,
|
|
801
|
+
avnuContract: AVNU_EXCHANGE,
|
|
802
|
+
slippage: 0.01,
|
|
803
|
+
minimumExtendedPriceDifferenceForSwapOpen: 0,
|
|
804
|
+
maximumExtendedPriceDifferenceForSwapClosing: 0,
|
|
805
|
+
...baseAdapterConfig,
|
|
806
|
+
baseToken: debtToken,
|
|
807
|
+
supportedPositions: [
|
|
808
|
+
{ asset: debtToken, isDebt: false },
|
|
809
|
+
{ asset: lstToken, isDebt: false },
|
|
810
|
+
],
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
// ── 3. SvkTrovesAdapter (deposit LST into / withdraw from Hyper vault) ──
|
|
814
|
+
const svkTrovesAdapter = new SvkTrovesAdapter({
|
|
815
|
+
...baseAdapterConfig,
|
|
816
|
+
baseToken: lstToken,
|
|
817
|
+
supportedPositions: [{ asset: lstToken, isDebt: false }],
|
|
818
|
+
strategyVault: vaultSettings.hyperLstVaultAddress,
|
|
819
|
+
trovesStrategyId: vaultSettings.trovesStrategyId,
|
|
820
|
+
redeemRequestNFT: vaultSettings.hyperLstRedeemNFT,
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
// ── 5. CommonAdapter (approve + bring liquidity) ──
|
|
824
|
+
const commonAdapter = new CommonAdapter({
|
|
825
|
+
id: UNIVERSAL_MANAGE_IDS.FLASH_LOAN,
|
|
826
|
+
vaultAddress: vaultSettings.vaultAddress,
|
|
827
|
+
vaultAllocator: vaultSettings.vaultAllocator,
|
|
828
|
+
manager: vaultSettings.manager,
|
|
829
|
+
asset: depositToken.address,
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
// ── Register adapters for position tracking ──
|
|
833
|
+
vaultSettings.adapters.push(
|
|
834
|
+
{ id: adapterIds.vesu, adapter: vesuModifyPositionAdapter },
|
|
835
|
+
// Used to track swapped funds in vaultAllocator
|
|
836
|
+
{ id: adapterIds.avnu, adapter: avnuAdapter },
|
|
837
|
+
{ id: adapterIds.hyper, adapter: svkTrovesAdapter },
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
// ── Register leaf adapters for merkle tree ──
|
|
841
|
+
// Vesu modify position
|
|
842
|
+
vaultSettings.leafAdapters.push(() =>
|
|
843
|
+
vesuModifyPositionAdapter.getDepositLeaf(),
|
|
844
|
+
);
|
|
845
|
+
vaultSettings.leafAdapters.push(() =>
|
|
846
|
+
vesuModifyPositionAdapter.getWithdrawLeaf(),
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
// Avnu swaps (borrow token ↔ LST)
|
|
850
|
+
vaultSettings.leafAdapters.push(() => avnuAdapter.getDepositLeaf());
|
|
851
|
+
vaultSettings.leafAdapters.push(() => avnuAdapter.getWithdrawLeaf());
|
|
852
|
+
|
|
853
|
+
// Hyper LST vault deposit / withdraw
|
|
854
|
+
vaultSettings.leafAdapters.push(() => svkTrovesAdapter.getDepositLeaf());
|
|
855
|
+
vaultSettings.leafAdapters.push(() => svkTrovesAdapter.getWithdrawLeaf());
|
|
856
|
+
|
|
857
|
+
// Bring liquidity back to vault
|
|
858
|
+
vaultSettings.leafAdapters.push(
|
|
859
|
+
commonAdapter
|
|
860
|
+
.getApproveAdapter(
|
|
861
|
+
depositToken.address,
|
|
862
|
+
vaultSettings.vaultAddress,
|
|
863
|
+
UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY,
|
|
864
|
+
)
|
|
865
|
+
.bind(commonAdapter),
|
|
866
|
+
);
|
|
867
|
+
vaultSettings.leafAdapters.push(
|
|
868
|
+
commonAdapter
|
|
869
|
+
.getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
|
|
870
|
+
.bind(commonAdapter),
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
return vaultSettings;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
const boostedxSTRKCarrySettings: BoostedxSTRKCarryStrategySettings = {
|
|
877
|
+
vaultAddress: ContractAddr.from(
|
|
878
|
+
"0xcdb0e3b2e076a2cdc4ee958b726b47c066239ef91c5ac80c94cf814147b84",
|
|
879
|
+
),
|
|
880
|
+
manager: ContractAddr.from(
|
|
881
|
+
"0x72eea9bac9fa8cfffda637d3b990851446860c6fd8987d6cb50e659b01ee50f",
|
|
882
|
+
),
|
|
883
|
+
vaultAllocator: ContractAddr.from(
|
|
884
|
+
"0x6d3101cff7f821412a99ebe23bb31a1950f93276285102eb4313e3601f5f927",
|
|
885
|
+
),
|
|
886
|
+
redeemRequestNFT: ContractAddr.from(
|
|
887
|
+
"0x47dcc6889ca8db4e9eea8f55421e10f8ce7e356ccb45260a1c49a76f733c309",
|
|
888
|
+
),
|
|
889
|
+
// Not there for USDC but will be there for WBTC
|
|
890
|
+
aumOracle: ContractAddr.from("0x0"),
|
|
891
|
+
leafAdapters: [],
|
|
892
|
+
adapters: [],
|
|
893
|
+
// Calc using the maxLTV / targetLTV (0.5)
|
|
894
|
+
targetHealthFactor: 1.32,
|
|
895
|
+
// Calc using the maxLTV / maxAcceptableLTV (0.55)
|
|
896
|
+
minHealthFactor: 1.2,
|
|
897
|
+
vesuPoolId: VesuPools.Prime,
|
|
898
|
+
// New metadata-driven token configuration
|
|
899
|
+
depositToken: Global.getDefaultTokens().find((t) => t.symbol === "USDC")!,
|
|
900
|
+
debtToken: Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
|
|
901
|
+
lstHyperToken: Global.getDefaultTokens().find((t) => t.symbol === "xSTRK")!,
|
|
902
|
+
maxLTV: 0.66,
|
|
903
|
+
targetLTV: 0.5,
|
|
904
|
+
// BTC.Fi rewards flag - false for USDC (uses old report flow)
|
|
905
|
+
hasBtcFiRewards: false,
|
|
906
|
+
// New fields
|
|
907
|
+
hyperLstVaultAddress: ContractAddr.from(
|
|
908
|
+
"0x46c7a54c82b1fe374353859f554a40b8bd31d3e30f742901579e7b57b1b5960",
|
|
909
|
+
),
|
|
910
|
+
hyperLstRedeemNFT: ContractAddr.from(
|
|
911
|
+
"0x51e40b839dc0c2feca923f863072673b94abfa2483345be3b30b457a90d095",
|
|
912
|
+
),
|
|
913
|
+
trovesStrategyId: "hyper_xstrk",
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
const wbtcBoostedSettings: BoostedxSTRKCarryStrategySettings = {
|
|
917
|
+
vaultAddress: ContractAddr.from(
|
|
918
|
+
"0x69e081304917cbf2d39772483aa8b4b389c1a55d658493b28139e921cf83b6",
|
|
919
|
+
),
|
|
920
|
+
manager: ContractAddr.from(
|
|
921
|
+
"0x1936da7771d657fa6c41a2c6a7b64f7fc9c88782098f537ea4180263930f9a8",
|
|
922
|
+
),
|
|
923
|
+
vaultAllocator: ContractAddr.from(
|
|
924
|
+
"0x68008fce59ad4dfb63cc293f7bf020d12902957ea85f95c4e1c1e98f415b668",
|
|
925
|
+
),
|
|
926
|
+
redeemRequestNFT: ContractAddr.from(
|
|
927
|
+
"0x1ed34e1a94ca6dd766ded6f207c67a3857ea489d16b42cd03403c5071f131c7",
|
|
928
|
+
),
|
|
929
|
+
aumOracle: ContractAddr.from(
|
|
930
|
+
"0xed774c6484dba664354ebaa1c993a37e9ad4c14870cda0762ae0194caed056",
|
|
931
|
+
),
|
|
932
|
+
leafAdapters: [],
|
|
933
|
+
adapters: [],
|
|
934
|
+
targetHealthFactor: 1.32,
|
|
935
|
+
minHealthFactor: 1.2,
|
|
936
|
+
vesuPoolId: VesuPools.Prime,
|
|
937
|
+
depositToken: Global.getDefaultTokens().find((t) => t.symbol === "WBTC")!,
|
|
938
|
+
debtToken: Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
|
|
939
|
+
lstHyperToken: Global.getDefaultTokens().find((t) => t.symbol === "xSTRK")!,
|
|
940
|
+
maxLTV: 0.66,
|
|
941
|
+
targetLTV: 0.5,
|
|
942
|
+
hasBtcFiRewards: true,
|
|
943
|
+
hyperLstVaultAddress: ContractAddr.from(
|
|
944
|
+
"0x46c7a54c82b1fe374353859f554a40b8bd31d3e30f742901579e7b57b1b5960",
|
|
945
|
+
),
|
|
946
|
+
hyperLstRedeemNFT: ContractAddr.from(
|
|
947
|
+
"0x51e40b839dc0c2feca923f863072673b94abfa2483345be3b30b457a90d095",
|
|
948
|
+
),
|
|
949
|
+
trovesStrategyId: "hyper_xstrk",
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
function getStrategySettings(
|
|
953
|
+
settings: BoostedxSTRKCarryStrategySettings,
|
|
954
|
+
meta: { id: string; name: string; launchBlock: number },
|
|
955
|
+
): IStrategyMetadata<BoostedxSTRKCarryStrategySettings> {
|
|
956
|
+
const depositToken = settings.depositToken;
|
|
957
|
+
const debtToken = settings.debtToken;
|
|
958
|
+
const lstToken = settings.lstHyperToken;
|
|
959
|
+
|
|
960
|
+
return {
|
|
961
|
+
id: meta.id,
|
|
962
|
+
name: meta.name,
|
|
963
|
+
description: `Deposits ${depositToken.symbol} as collateral on Vesu, borrows ${debtToken.symbol}, swaps to ${lstToken.symbol}, and deposits into Hyper-${lstToken.symbol} for boosted yield`,
|
|
964
|
+
address: settings.vaultAddress,
|
|
965
|
+
launchBlock: meta.launchBlock,
|
|
966
|
+
type: "ERC4626" as const,
|
|
967
|
+
vaultType: {
|
|
968
|
+
type: VaultType.META_VAULT,
|
|
969
|
+
description: `Deposits ${depositToken.symbol} as collateral on Vesu, borrows ${debtToken.symbol}, swaps to ${lstToken.symbol}, and deposits into Hyper-${lstToken.symbol} for boosted yield`,
|
|
970
|
+
},
|
|
971
|
+
depositTokens: [depositToken],
|
|
972
|
+
additionalInfo: getBoostedxSTRKCarrySettings(settings),
|
|
973
|
+
// TODO: config lateron
|
|
974
|
+
risk: {
|
|
975
|
+
riskFactor: [],
|
|
976
|
+
netRisk: 0,
|
|
977
|
+
notARisks: [],
|
|
978
|
+
},
|
|
979
|
+
protocols: [Protocols.VESU, Protocols.TROVES],
|
|
980
|
+
curator: {
|
|
981
|
+
name: "Unwrap Labs",
|
|
982
|
+
logo: "https://assets.troves.fi/integrations/unwraplabs/white.png",
|
|
983
|
+
},
|
|
984
|
+
settings: {
|
|
985
|
+
maxTVL: Web3Number.fromWei(0, depositToken.decimals),
|
|
986
|
+
isPaused: false,
|
|
987
|
+
isAudited: false,
|
|
988
|
+
isInstantWithdrawal: false,
|
|
989
|
+
hideHarvestInfo: true,
|
|
990
|
+
quoteToken: depositToken,
|
|
991
|
+
alerts: [
|
|
992
|
+
{
|
|
993
|
+
tab: "withdraw" as const,
|
|
994
|
+
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.",
|
|
995
|
+
type: "info" as const,
|
|
996
|
+
},
|
|
997
|
+
],
|
|
998
|
+
},
|
|
999
|
+
contractDetails: getContractDetails(settings),
|
|
1000
|
+
// TODO: config later
|
|
1001
|
+
faqs: [],
|
|
1002
|
+
investmentSteps: [
|
|
1003
|
+
`Deposit ${depositToken.symbol} into the vault`,
|
|
1004
|
+
`${depositToken.symbol} is supplied as collateral on Vesu, ${debtToken.symbol} is borrowed`,
|
|
1005
|
+
`Borrowed ${debtToken.symbol} is swapped to ${lstToken.symbol} via Avnu`,
|
|
1006
|
+
`${lstToken.symbol} is deposited into the Hyper-${lstToken.symbol} vault for additional yield`,
|
|
1007
|
+
`On withdrawal, the pipeline reverses to return ${depositToken.symbol}`,
|
|
1008
|
+
],
|
|
1009
|
+
// TODO: config later
|
|
1010
|
+
tags: [StrategyTag.META_VAULT],
|
|
1011
|
+
security: {
|
|
1012
|
+
auditStatus: AuditStatus.AUDITED,
|
|
1013
|
+
sourceCode: {
|
|
1014
|
+
type: SourceCodeType.CLOSED_SOURCE,
|
|
1015
|
+
contractLink: "https://github.com/trovesfi/troves-contracts",
|
|
1016
|
+
},
|
|
1017
|
+
accessControl: {
|
|
1018
|
+
type: AccessControlType.STANDARD_ACCOUNT,
|
|
1019
|
+
addresses: [ContractAddr.from("0x0")],
|
|
1020
|
+
timeLock: "2 Days",
|
|
1021
|
+
},
|
|
1022
|
+
},
|
|
1023
|
+
redemptionInfo: {
|
|
1024
|
+
instantWithdrawalVault: InstantWithdrawalVault.NO,
|
|
1025
|
+
redemptionsInfo: [
|
|
1026
|
+
{
|
|
1027
|
+
title: "Typical Duration",
|
|
1028
|
+
description: "1-2 hours",
|
|
1029
|
+
},
|
|
1030
|
+
],
|
|
1031
|
+
alerts: [
|
|
1032
|
+
{
|
|
1033
|
+
type: "info",
|
|
1034
|
+
text: "Redemption times are estimates and may vary based on network conditions and liquidity requirements.",
|
|
1035
|
+
tab: "withdraw",
|
|
1036
|
+
},
|
|
1037
|
+
],
|
|
1038
|
+
},
|
|
1039
|
+
usualTimeToEarnings: null,
|
|
1040
|
+
usualTimeToEarningsDescription: null,
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// TODO: rename to BoostedCollateralStrategies later on or smthin
|
|
1045
|
+
export const BoostedxSTRKCarryStrategies: IStrategyMetadata<BoostedxSTRKCarryStrategySettings>[] =
|
|
1046
|
+
[
|
|
1047
|
+
getStrategySettings(boostedxSTRKCarrySettings, {
|
|
1048
|
+
id: "usdc_boosted",
|
|
1049
|
+
name: "USDC Boosted",
|
|
1050
|
+
launchBlock: 8742931,
|
|
1051
|
+
}),
|
|
1052
|
+
getStrategySettings(wbtcBoostedSettings, {
|
|
1053
|
+
id: "wbtc_boosted",
|
|
1054
|
+
name: "WBTC Boosted",
|
|
1055
|
+
launchBlock: 9803209,
|
|
1056
|
+
}),
|
|
1057
|
+
];
|