@strkfarm/sdk 1.1.70 → 2.0.0-dev.1

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.
Files changed (53) hide show
  1. package/dist/cli.js +2 -2
  2. package/dist/cli.mjs +2 -2
  3. package/dist/index.browser.global.js +66861 -59746
  4. package/dist/index.browser.mjs +24970 -18579
  5. package/dist/index.d.ts +1969 -776
  6. package/dist/index.js +25264 -18850
  7. package/dist/index.mjs +25463 -19089
  8. package/package.json +80 -76
  9. package/src/data/extended-deposit.abi.json +3613 -0
  10. package/src/data/universal-vault.abi.json +135 -20
  11. package/src/dataTypes/address.ts +8 -1
  12. package/src/global.ts +240 -193
  13. package/src/interfaces/common.tsx +26 -2
  14. package/src/modules/ExtendedWrapperSDk/index.ts +62 -0
  15. package/src/modules/ExtendedWrapperSDk/types.ts +311 -0
  16. package/src/modules/ExtendedWrapperSDk/wrapper.ts +395 -0
  17. package/src/modules/avnu.ts +17 -4
  18. package/src/modules/ekubo-quoter.ts +98 -10
  19. package/src/modules/erc20.ts +67 -21
  20. package/src/modules/harvests.ts +16 -29
  21. package/src/modules/index.ts +5 -1
  22. package/src/modules/lst-apr.ts +36 -0
  23. package/src/modules/midas.ts +159 -0
  24. package/src/modules/pricer-from-api.ts +2 -2
  25. package/src/modules/pricer-lst.ts +1 -1
  26. package/src/modules/pricer.ts +3 -38
  27. package/src/modules/token-market-data.ts +202 -0
  28. package/src/node/deployer.ts +1 -36
  29. package/src/strategies/autoCompounderStrk.ts +1 -1
  30. package/src/strategies/base-strategy.ts +20 -3
  31. package/src/strategies/ekubo-cl-vault.tsx +123 -306
  32. package/src/strategies/index.ts +4 -1
  33. package/src/strategies/svk-strategy.ts +247 -0
  34. package/src/strategies/universal-adapters/adapter-optimizer.ts +65 -0
  35. package/src/strategies/universal-adapters/adapter-utils.ts +5 -1
  36. package/src/strategies/universal-adapters/avnu-adapter.ts +418 -0
  37. package/src/strategies/universal-adapters/baseAdapter.ts +181 -153
  38. package/src/strategies/universal-adapters/common-adapter.ts +98 -77
  39. package/src/strategies/universal-adapters/extended-adapter.ts +544 -0
  40. package/src/strategies/universal-adapters/index.ts +5 -1
  41. package/src/strategies/universal-adapters/unused-balance-adapter.ts +109 -0
  42. package/src/strategies/universal-adapters/vesu-adapter.ts +220 -218
  43. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +924 -0
  44. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +58 -51
  45. package/src/strategies/universal-lst-muliplier-strategy.tsx +707 -774
  46. package/src/strategies/universal-strategy.tsx +1098 -1180
  47. package/src/strategies/vesu-extended-strategy/services/operationService.ts +28 -0
  48. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +77 -0
  49. package/src/strategies/vesu-extended-strategy/utils/constants.ts +48 -0
  50. package/src/strategies/vesu-extended-strategy/utils/helper.ts +374 -0
  51. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +992 -0
  52. package/src/strategies/vesu-rebalance.tsx +16 -19
  53. package/src/utils/health-factor-math.ts +11 -5
@@ -0,0 +1,992 @@
1
+ import {
2
+ getMainnetConfig,
3
+ IConfig,
4
+ IStrategyMetadata,
5
+ TokenInfo,
6
+ } from "@/interfaces";
7
+ import {
8
+ UNIVERSAL_MANAGE_IDS,
9
+ UniversalStrategySettings,
10
+ } from "../universal-strategy";
11
+ import { calculateExtendedLevergae } from "./utils/helper";
12
+ import { logger } from "@/utils";
13
+ import { AUDIT_URL } from "../universal-lst-muliplier-strategy";
14
+ import { getNoRiskTags } from "@/interfaces";
15
+ import { _riskFactor } from "../universal-lst-muliplier-strategy";
16
+ import { EXTENDED_QTY_PRECISION, LIMIT_BALANCE, MAX_PRICE_DIFFERENCE_BETWEEN_AVNU_AND_EXTENDED, MIN_PRICE_DIFFERENCE_BETWEEN_AVNU_AND_EXTENDED, MINIMUM_EXTENDED_POSITION_SIZE, USDC_TOKEN_DECIMALS, WBTC_TOKEN_DECIMALS } from "./utils/constants";
17
+ import { PricerBase } from "@/modules/pricerBase";
18
+ import { ContractAddr, Web3Number } from "@/dataTypes";
19
+ import { Global } from "@/global";
20
+ import { ERC20 } from "@/modules";
21
+ import { Balance, OrderSide } from "@/modules/ExtendedWrapperSDk";
22
+ import { Protocols } from "@/interfaces";
23
+ import { MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP } from "./utils/constants";
24
+ import { getInvestmentSteps, getFAQs } from "../universal-lst-muliplier-strategy";
25
+ import { getContractDetails } from "../universal-strategy";
26
+ import { highlightTextWithLinks } from "@/interfaces";
27
+ import { PositionInfo } from "../universal-adapters";
28
+ import { APYType } from "../universal-adapters";
29
+ import { AUMTypes } from "../universal-strategy";
30
+ import { VesuPools } from "../universal-adapters";
31
+ import {
32
+ BaseAdapterConfig,
33
+ CommonAdapter,
34
+ UnusedBalanceAdapter,
35
+ VesuMultiplyAdapter,
36
+ } from "../universal-adapters";
37
+ import { Operations } from "./services/operationService";
38
+ import {
39
+ AVNU_EXCHANGE,
40
+ AVNU_QUOTE_URL,
41
+ AVNU_MIDDLEWARE,
42
+ EXTENDED_CONTRACT,
43
+ } from "../universal-adapters/adapter-utils";
44
+ import { PricerFromApi } from "@/modules";
45
+ import { ExtendedAdapter } from "../universal-adapters/extended-adapter";
46
+ import { SVKStrategy } from "../svk-strategy";
47
+ import { AvnuAdapter } from "../universal-adapters/avnu-adapter";
48
+ import {
49
+ calculateAmountDistribution,
50
+ calculateAmountDistributionForWithdrawal,
51
+ calculateVesuLeverage,
52
+ calculateVesUPositionSizeGivenExtended
53
+ } from "./utils/helper";
54
+ import { SingleTokenInfo } from "../base-strategy";
55
+ import { Call } from "starknet";
56
+
57
+
58
+ export interface VesuExtendedStrategySettings
59
+ extends UniversalStrategySettings {
60
+ underlyingToken: TokenInfo;
61
+ borrowable_assets: TokenInfo[];
62
+ targetHealthFactor: number;
63
+ quoteAmountToFetchPrice: Web3Number;
64
+ minHealthFactor: number;
65
+ aumOracle: ContractAddr;
66
+ minimumWBTCDifferenceForAvnuSwap: number;
67
+ }
68
+
69
+ export class VesuExtendedMultiplierStrategy<
70
+ S extends VesuExtendedStrategySettings
71
+ >
72
+ extends SVKStrategy<S>
73
+ implements Operations {
74
+
75
+ constructor(
76
+ config: IConfig,
77
+ pricer: PricerBase,
78
+ metadata: IStrategyMetadata<S>
79
+ ) {
80
+ super(config, pricer, metadata);
81
+ this.metadata.additionalInfo.adapters.forEach((adapter) => {
82
+ adapter.adapter.config.networkConfig = this.config;
83
+ adapter.adapter.config.pricer = this.pricer;
84
+ if ((adapter.adapter as VesuMultiplyAdapter).vesuAdapter) {
85
+ (adapter.adapter as VesuMultiplyAdapter).vesuAdapter.networkConfig =
86
+ this.config;
87
+ (adapter.adapter as VesuMultiplyAdapter).vesuAdapter.pricer =
88
+ this.pricer;
89
+ }
90
+ });
91
+ }
92
+
93
+ getTag() {
94
+ return `${VesuExtendedMultiplierStrategy.name}:${this.metadata.name}`;
95
+ }
96
+
97
+ async getAssetPrices() {
98
+ const wbtcToken = Global.getDefaultTokens().find(
99
+ (token) => token.symbol === "WBTC"
100
+ )!;
101
+ const usdcToken = Global.getDefaultTokens().find(
102
+ (token) => token.symbol === "USDC"
103
+ )!;
104
+ const collateralPrice = await this.pricer.getPrice(wbtcToken.symbol);
105
+ const debtPrice = await this.pricer.getPrice(usdcToken.symbol);
106
+ return {
107
+ collateralPrice,
108
+ debtPrice,
109
+ };
110
+ }
111
+
112
+ async getUnusedBalanceWBTC(): Promise<SingleTokenInfo> {
113
+ const collateralToken = this.metadata.additionalInfo.borrowable_assets[0]!;
114
+ const balance = await new ERC20(this.config).balanceOf(
115
+ collateralToken.address,
116
+ this.metadata.additionalInfo.vaultAllocator,
117
+ collateralToken.decimals
118
+ );
119
+ const price = await this.pricer.getPrice(collateralToken.symbol);
120
+ const usdValue =
121
+ Number(balance.toFixed(collateralToken.decimals)) * price.price;
122
+ return {
123
+ tokenInfo: collateralToken,
124
+ amount: balance,
125
+ usdValue,
126
+ };
127
+ }
128
+
129
+ async getVesuAdapter(): Promise<VesuMultiplyAdapter | null> {
130
+ const vesuAdapter = this.metadata.additionalInfo.adapters.find(
131
+ (adapter) => adapter.adapter.name === VesuMultiplyAdapter.name
132
+ );
133
+ if (!vesuAdapter) {
134
+ logger.error("vesu adapter not found");
135
+ return null;
136
+ }
137
+ return vesuAdapter.adapter as VesuMultiplyAdapter;
138
+ }
139
+
140
+ async getAvnuAdapter(): Promise<AvnuAdapter | null> {
141
+ const avnuAdapter = this.metadata.additionalInfo.adapters.find(
142
+ (adapter) => adapter.adapter.name === AvnuAdapter.name
143
+ );
144
+ if (!avnuAdapter) {
145
+ logger.error("avnu adapter not found");
146
+ return null;
147
+ }
148
+ return avnuAdapter.adapter as AvnuAdapter;
149
+ }
150
+
151
+ async getExtendedAdapter(): Promise<ExtendedAdapter | null> {
152
+ const extendedAdapter = this.metadata.additionalInfo.adapters.find(
153
+ (adapter) => adapter.adapter.name === ExtendedAdapter.name
154
+ );
155
+ if (!extendedAdapter) {
156
+ logger.error("extended adapter not found");
157
+ return null;
158
+ }
159
+ return extendedAdapter.adapter as ExtendedAdapter;
160
+ }
161
+
162
+ async moveAssetsToVaultAllocator(amount: Web3Number): Promise<Call[]> {
163
+ try{
164
+ const usdcToken = Global.getDefaultTokens().find(
165
+ (token) => token.symbol === "USDC"
166
+ )!;
167
+ const approveCall = new ERC20(this.config).approve(
168
+ usdcToken.address,
169
+ this.metadata.additionalInfo.vaultAllocator,
170
+ amount
171
+ );
172
+ const transferCall = new ERC20(this.config).transfer(
173
+ usdcToken.address,
174
+ this.metadata.additionalInfo.vaultAllocator,
175
+ amount
176
+ );
177
+ return [approveCall, transferCall];
178
+ }catch(err){
179
+ logger.error(`error moving assets to vault allocator: ${err}`);
180
+ return [];
181
+ }
182
+ }
183
+
184
+ async shouldInvest(): Promise<{
185
+ shouldInvest: boolean;
186
+ vesuAmount: Web3Number;
187
+ extendedAmount: Web3Number;
188
+ extendedLeverage: number;
189
+ collateralPrice: number;
190
+ debtPrice: number;
191
+ vesuLeverage: number;
192
+ }> {
193
+ try {
194
+ const vesuAdapter = await this.getVesuAdapter();
195
+ const extendedAdapter = await this.getExtendedAdapter();
196
+ if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
197
+ logger.error(
198
+ `vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
199
+ );
200
+ return {
201
+ shouldInvest: false,
202
+ vesuAmount: new Web3Number(0, 0),
203
+ extendedAmount: new Web3Number(0, 0),
204
+ extendedLeverage: 0,
205
+ collateralPrice: 0,
206
+ debtPrice: 0,
207
+ vesuLeverage: 0,
208
+ };
209
+ }
210
+ const balance = await this.getUnusedBalance();
211
+ const usdcBalanceOnExtended = await extendedAdapter.getExtendedDepositAmount();
212
+ /** The LIMIT_BALANCE is the bffer amount to keep in the investing Cycle */
213
+ const amountToInvest = balance.amount.plus(usdcBalanceOnExtended?.availableForWithdrawal ?? 0).minus(LIMIT_BALANCE);
214
+
215
+ if (amountToInvest.lessThan(0)) {
216
+ return {
217
+ shouldInvest: false,
218
+ vesuAmount: new Web3Number(0, 0),
219
+ extendedAmount: new Web3Number(0, 0),
220
+ extendedLeverage: 0,
221
+ collateralPrice: 0,
222
+ debtPrice: 0,
223
+ vesuLeverage: 0,
224
+ };
225
+ }
226
+
227
+ const extendedPositon = await extendedAdapter.getAllOpenPositions();
228
+ if (!extendedPositon) {
229
+ logger.error("error getting extended position to decide move assets");
230
+ return {
231
+ shouldInvest: false,
232
+ vesuAmount: new Web3Number(0, 0),
233
+ extendedAmount: new Web3Number(0, 0),
234
+ extendedLeverage: 0,
235
+ collateralPrice: 0,
236
+ debtPrice: 0,
237
+ vesuLeverage: 0,
238
+ };
239
+ }
240
+ const { collateralTokenAmount } =
241
+ await vesuAdapter.vesuAdapter.getAssetPrices();
242
+
243
+ const {
244
+ collateralPrice,
245
+ debtPrice
246
+ } = await this.getAssetPrices();
247
+ const { vesu_amount, extended_amount, extended_leverage, vesu_leverage } =
248
+ await calculateAmountDistribution(
249
+ amountToInvest.toNumber(),
250
+ extendedAdapter.client,
251
+ extendedAdapter.config.extendedMarketName,
252
+ collateralPrice.price,
253
+ debtPrice.price,
254
+ collateralTokenAmount,
255
+ extendedPositon
256
+ );
257
+ if (
258
+ !vesu_amount ||
259
+ !extended_amount ||
260
+ !extended_leverage ||
261
+ !vesu_leverage
262
+ ) {
263
+ logger.error(
264
+ `Not enough amount to invest: vesu_amount=${vesu_amount}, extended_amount=${extended_amount}`
265
+ );
266
+ return {
267
+ shouldInvest: false,
268
+ vesuAmount: new Web3Number(0, 0),
269
+ extendedAmount: new Web3Number(0, 0),
270
+ extendedLeverage: 0,
271
+ collateralPrice: 0,
272
+ debtPrice: 0,
273
+ vesuLeverage: 0,
274
+ };
275
+ }
276
+ return {
277
+ shouldInvest: true,
278
+ vesuAmount: vesu_amount,
279
+ extendedAmount: extended_amount,
280
+ extendedLeverage: extended_leverage,
281
+ vesuLeverage: vesu_leverage,
282
+ collateralPrice: collateralPrice.price,
283
+ debtPrice: debtPrice.price,
284
+ };
285
+ } catch (err) {
286
+ logger.error(`error deciding invest: ${err}`);
287
+ return {
288
+ shouldInvest: false,
289
+ vesuAmount: new Web3Number(0, 0),
290
+ extendedAmount: new Web3Number(0, 0),
291
+ extendedLeverage: 0,
292
+ collateralPrice: 0,
293
+ debtPrice: 0,
294
+ vesuLeverage: 0,
295
+ };
296
+ }
297
+ }
298
+
299
+ async shouldMoveAssets(extendedAmount: Web3Number, vesuAmount: Web3Number): Promise<Call[]> {
300
+ try {
301
+ const vesuAdapter = await this.getVesuAdapter();
302
+ const extendedAdapter = await this.getExtendedAdapter();
303
+ let calls: Call[] = [];
304
+ if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
305
+ logger.error(
306
+ `vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
307
+ );
308
+ return calls;
309
+ }
310
+ console.log("extendedAmount", extendedAmount);
311
+ console.log("vesuAmount", vesuAmount);
312
+ if (extendedAmount.lessThan(0)) {
313
+ try {
314
+ const extendedCalls = await this.moveAssets(
315
+ {
316
+ to: Protocols.VAULT.name,
317
+ from: Protocols.EXTENDED.name,
318
+ amount: extendedAmount.abs(),
319
+ },
320
+ extendedAdapter,
321
+ vesuAdapter
322
+ );
323
+ calls.push(...extendedCalls);
324
+ } catch (err) {
325
+ logger.error(`Failed moving assets to vault: ${err}`);
326
+ }
327
+ }
328
+
329
+ if (vesuAmount.lessThan(0)) {
330
+ try {
331
+ const vesuCalls = await this.moveAssets(
332
+ {
333
+ to: Protocols.EXTENDED.name,
334
+ from: Protocols.VESU.name,
335
+ amount: vesuAmount.abs(),
336
+ },
337
+ extendedAdapter,
338
+ vesuAdapter
339
+ );
340
+ calls.push(...vesuCalls);
341
+ } catch (err) {
342
+ logger.error(`Failed moving assets to vault: ${err}`);
343
+ }
344
+ }
345
+
346
+ const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
347
+ if (!extendedHoldings) {
348
+ logger.error(`error getting extended holdings: ${extendedHoldings}`);
349
+ return calls;
350
+ }
351
+ const usdcAmountInWallet = (await this.getUnusedBalance()).amount;
352
+ const usdcAmountOnExtended = parseFloat(
353
+ extendedHoldings.availableForWithdrawal
354
+ );
355
+ if (extendedAmount.minus(usdcAmountOnExtended).greaterThan(0)) {
356
+ //move assets to extended
357
+ try {
358
+ const extendedCalls = await this.moveAssets(
359
+ {
360
+ to: Protocols.EXTENDED.name,
361
+ from: Protocols.VAULT.name,
362
+ amount: extendedAmount.minus(usdcAmountOnExtended),
363
+ },
364
+ extendedAdapter,
365
+ vesuAdapter
366
+ );
367
+ calls.push(...extendedCalls);
368
+ } catch (err) {
369
+ logger.error(`Failed moving assets to extended: ${err}`);
370
+ }
371
+ }
372
+ if (vesuAmount.minus(usdcAmountInWallet).greaterThan(0)) {
373
+ //move assets to vesu
374
+ try {
375
+ const vesuCalls = await this.moveAssets(
376
+ {
377
+ to: Protocols.VESU.name,
378
+ from: Protocols.EXTENDED.name,
379
+ amount: vesuAmount.minus(usdcAmountInWallet),
380
+ },
381
+ extendedAdapter,
382
+ vesuAdapter
383
+ );
384
+ calls.push(...vesuCalls);
385
+ } catch (err) {
386
+ logger.error(`Failed moving assets to vault: ${err}`);
387
+ }
388
+ }
389
+ return calls;
390
+ } catch (err) {
391
+ logger.error(`Failed moving assets to vesu: ${err}`);
392
+ return [];
393
+ }
394
+ }
395
+
396
+ async moveAssets(
397
+ params: {
398
+ amount: Web3Number;
399
+ from: string;
400
+ to: string;
401
+ },
402
+ extendedAdapter: ExtendedAdapter,
403
+ vesuAdapter: VesuMultiplyAdapter
404
+ ): Promise<Call[]> {
405
+ try {
406
+ const avnuAdapter = await this.getAvnuAdapter();
407
+ if (!avnuAdapter) {
408
+ logger.error(`avnu adapter not found: ${avnuAdapter}`);
409
+ return [];
410
+ }
411
+ logger.info("moveAssets params", params);
412
+ const collateralToken = vesuAdapter.config.supportedPositions[0].asset;
413
+ const {
414
+ collateralPrice,
415
+ } = await this.getAssetPrices();
416
+ if (params.to === Protocols.EXTENDED.name && params.from === Protocols.VAULT.name) {
417
+ const proofsInfo = extendedAdapter.getProofs(
418
+ true,
419
+ this.getMerkleTree()
420
+ );
421
+ const calls = [];
422
+ const proofGroups = proofsInfo.proofs;
423
+ const call = this.getManageCall(
424
+ proofGroups,
425
+ await proofsInfo.callConstructor({ amount: params.amount })
426
+ );
427
+ calls.push(call);
428
+ return calls;
429
+ } else if (params.to === Protocols.VAULT.name && params.from === Protocols.EXTENDED.name) {
430
+ const withdrawalFromExtended =
431
+ await extendedAdapter.withdrawFromExtended(params.amount);
432
+ if (withdrawalFromExtended) {
433
+ /**
434
+ * We need to move assets from my wallet back to vault contract
435
+ */
436
+ const calls = await this.moveAssetsToVaultAllocator(params.amount);
437
+ if (calls.length > 0) {
438
+ return calls;
439
+ }
440
+ } else {
441
+ logger.error("withdrawal from extended failed");
442
+ }
443
+ return [];
444
+ } else if (params.to === Protocols.VAULT.name && params.from === Protocols.VESU.name) {
445
+ //withdraw from vesu
446
+ const vesuAmountInBTC = new Web3Number(
447
+ params.amount.dividedBy(collateralPrice.price).toNumber(),
448
+ collateralToken.decimals
449
+ );
450
+ const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
451
+ const calls = [];
452
+ const proofGroups = proofsInfo.proofs;
453
+ const call = this.getManageCall(
454
+ proofGroups,
455
+ await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
456
+ );
457
+ calls.push(call);
458
+ const swapProofsInfo = avnuAdapter.getProofs(false, this.getMerkleTree());
459
+ const swapProofGroups = swapProofsInfo.proofs;
460
+ const swapCall = this.getManageCall(
461
+ swapProofGroups,
462
+ await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
463
+ );
464
+ calls.push(swapCall);
465
+ return calls;
466
+ } else if (params.to === Protocols.EXTENDED.name && params.from === Protocols.VESU.name) {
467
+ const vesuAmountInBTC = new Web3Number(
468
+ params.amount.dividedBy(collateralPrice.price).toNumber(),
469
+ collateralToken.decimals
470
+ );
471
+ const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
472
+ const calls = [];
473
+ const proofGroups = proofsInfo.proofs;
474
+ const call = this.getManageCall(
475
+ proofGroups,
476
+ await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
477
+ );
478
+ calls.push(call);
479
+ const swapProofsInfo = avnuAdapter.getProofs(false, this.getMerkleTree());
480
+ const swapProofGroups = swapProofsInfo.proofs;
481
+ const swapCall = this.getManageCall(
482
+ swapProofGroups,
483
+ await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
484
+ );
485
+ calls.push(swapCall);
486
+ const proofsInfoDeposit = extendedAdapter.getProofs(
487
+ true,
488
+ this.getMerkleTree()
489
+ );
490
+ //Deposit Amount would still be in usdc
491
+ const proofGroupsDeposit = proofsInfoDeposit.proofs;
492
+ const callDeposit = this.getManageCall(
493
+ proofGroupsDeposit,
494
+ await proofsInfoDeposit.callConstructor({ amount: params.amount })
495
+ );
496
+ calls.push(callDeposit);
497
+ return calls;
498
+ }
499
+ console.warn("moveAssets received unsupported route:", params);
500
+ return [];
501
+ } catch (err) {
502
+ logger.error(`error moving assets: ${err}`);
503
+ return [];
504
+ }
505
+ }
506
+
507
+ async handleDeposit(): Promise<{
508
+ extendedAmountInBTC : Web3Number,
509
+ calls:Call[]}> {
510
+ try {
511
+ const vesuAdapter = await this.getVesuAdapter();
512
+ const extendedAdapter = await this.getExtendedAdapter();
513
+ const avnuAdapter = await this.getAvnuAdapter();
514
+ if (
515
+ !vesuAdapter ||
516
+ !extendedAdapter ||
517
+ !extendedAdapter.client ||
518
+ !avnuAdapter
519
+ ) {
520
+ logger.error(
521
+ "vesu or extended adapter not found",
522
+ vesuAdapter,
523
+ extendedAdapter
524
+ );
525
+ return {
526
+ extendedAmountInBTC: new Web3Number(0, 0),
527
+ calls: [],
528
+ };
529
+ }
530
+ const extendedLeverage = calculateExtendedLevergae();
531
+ const isPriceDifferenceBetweenAvnuAndExtended = await this.checkPriceDifferenceBetweenAvnuAndExtended(extendedAdapter, vesuAdapter, avnuAdapter);
532
+ if (!isPriceDifferenceBetweenAvnuAndExtended) {
533
+ logger.error("price difference between avnu and extended doesn't fit the range");
534
+ return {
535
+ extendedAmountInBTC: new Web3Number(0, 0),
536
+ calls: [],
537
+ };
538
+ }
539
+ const position = await extendedAdapter.getAllOpenPositions();
540
+ if (!position) {
541
+ logger.error("error getting extended position", position);
542
+ return {
543
+ extendedAmountInBTC: new Web3Number(0, 0),
544
+ calls: [],
545
+ };
546
+ }
547
+ const extendedPositionValue = position.length > 0 ? parseFloat(position[0].value) : 0;
548
+ const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
549
+ if (!extendedHoldings) {
550
+ logger.error(`error getting extended holdings: ${extendedHoldings}`);
551
+ return {
552
+ extendedAmountInBTC: new Web3Number(0, 0),
553
+ calls: [],
554
+ };
555
+ }
556
+ const extendedHoldingAmount = new Web3Number(
557
+ extendedHoldings.availableForWithdrawal,
558
+ USDC_TOKEN_DECIMALS
559
+ );
560
+ const {
561
+ collateralTokenAmount,
562
+ } = await vesuAdapter.vesuAdapter.getAssetPrices();
563
+ const { collateralPrice } = await this.getAssetPrices();
564
+ const { vesuAmountInBTC, extendedAmountInBTC } = calculateVesUPositionSizeGivenExtended(
565
+ extendedPositionValue,
566
+ extendedHoldingAmount,
567
+ collateralTokenAmount,
568
+ collateralPrice.price
569
+ );
570
+ console.log("vesuAmountInBTC", vesuAmountInBTC);
571
+ console.log("extendedAmountInBTC", extendedAmountInBTC);
572
+ let calls: Call[] = [];
573
+ if(vesuAmountInBTC.greaterThan(MINIMUM_EXTENDED_POSITION_SIZE)){
574
+ const proofsInfo = vesuAdapter.getProofs(true, this.getMerkleTree());
575
+ const proofGroups = proofsInfo.proofs;
576
+ const call = this.getManageCall(
577
+ proofGroups,
578
+ await proofsInfo.callConstructor({
579
+ amount: vesuAmountInBTC,
580
+ })
581
+ );
582
+ const { amount: wbtcAmountInVaultAllocator } = await this.getUnusedBalanceWBTC();
583
+ if (wbtcAmountInVaultAllocator.lessThan(vesuAmountInBTC)) {
584
+ console.log("error wbtc amount in vault allocator is less than vesu amount in btc", wbtcAmountInVaultAllocator, vesuAmountInBTC);
585
+ const swapProofsInfo = avnuAdapter.getProofs(true, this.getMerkleTree());
586
+ const swapProofGroups = swapProofsInfo.proofs;
587
+ const swapCall = this.getManageCall(
588
+ swapProofGroups,
589
+ await swapProofsInfo.callConstructor({
590
+ amount: vesuAmountInBTC,
591
+ })
592
+ );
593
+ calls.push(swapCall);
594
+ }
595
+ calls.push(call);
596
+ }
597
+
598
+ const shortPosition = extendedAmountInBTC.multipliedBy(3).abs().greaterThan(MINIMUM_EXTENDED_POSITION_SIZE) ? await extendedAdapter.createOrder(
599
+ extendedLeverage.toString(),
600
+ extendedAmountInBTC.toNumber(),
601
+ OrderSide.SELL
602
+ ) : null;
603
+ if (!shortPosition && extendedAmountInBTC.multipliedBy(3).abs().greaterThan(MINIMUM_EXTENDED_POSITION_SIZE)) {
604
+ logger.error(`error creating short position thus no position to be opened on vesu: ${shortPosition}`);
605
+ return {
606
+ extendedAmountInBTC: new Web3Number(0, 0),
607
+ calls: [],
608
+ };
609
+ }
610
+ return {
611
+ extendedAmountInBTC: extendedAmountInBTC,
612
+ calls: calls,
613
+ };
614
+ } catch (err) {
615
+ logger.error(`error handling deposit: ${err}`);
616
+ return {
617
+ extendedAmountInBTC: new Web3Number(0, 0),
618
+ calls: [],
619
+ };;
620
+ }
621
+ }
622
+
623
+ async checkPriceDifferenceBetweenAvnuAndExtended(extendedAdapter:ExtendedAdapter, vesuAdapter:VesuMultiplyAdapter, avnuAdapter:AvnuAdapter): Promise<boolean> {
624
+ const {
625
+ ask, bid
626
+ }= await extendedAdapter.fetchOrderBookBTCUSDC();
627
+ const price = ask.plus(bid).dividedBy(2);
628
+ const btcToken = vesuAdapter.config.supportedPositions[1].asset;
629
+ const btcPriceAvnu = await avnuAdapter.getPriceOfToken(btcToken.address.toString());
630
+ if (!btcPriceAvnu) {
631
+ logger.error(`error getting btc price avnu: ${btcPriceAvnu}`);
632
+ return false;
633
+ }
634
+ const priceDifference = price.minus(btcPriceAvnu).toNumber();
635
+ if(priceDifference < MAX_PRICE_DIFFERENCE_BETWEEN_AVNU_AND_EXTENDED && priceDifference > MIN_PRICE_DIFFERENCE_BETWEEN_AVNU_AND_EXTENDED){
636
+ return true;
637
+ }
638
+ logger.error(`price difference between avnu and extended doesn't fit the range, priceDifference: ${priceDifference}`);
639
+ return false;
640
+ }
641
+
642
+ async handleWithdraw(amount: Web3Number): Promise<Call[]> {
643
+ try {
644
+ const usdcBalanceVaultAllocator = await this.getUnusedBalance()
645
+ const usdcBalanceDifference = amount.minus(usdcBalanceVaultAllocator.usdValue);
646
+ logger.info(`usdcBalanceDifference, ${usdcBalanceDifference.toNumber()}`);
647
+ if (usdcBalanceDifference.lessThan(0)) {
648
+ const withdrawCall = await this.getBringLiquidityCall({
649
+ amount: amount
650
+ })
651
+ logger.info("withdraw call", withdrawCall);
652
+ return [withdrawCall];
653
+ }
654
+ const vesuAdapter = await this.getVesuAdapter();
655
+ const extendedAdapter = await this.getExtendedAdapter();
656
+ if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
657
+ logger.error(
658
+ `vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
659
+ );
660
+ return [];
661
+ }
662
+ const { collateralTokenAmount } =
663
+ await vesuAdapter.vesuAdapter.getAssetPrices();
664
+ const {
665
+ collateralPrice
666
+ } = await this.getAssetPrices();
667
+ const extendedPositon = await extendedAdapter.getAllOpenPositions();
668
+ const amountDistributionForWithdrawal =
669
+ await calculateAmountDistributionForWithdrawal(
670
+ amount,
671
+ collateralPrice.price,
672
+ collateralTokenAmount,
673
+ extendedPositon
674
+ );
675
+ if (!amountDistributionForWithdrawal) {
676
+ logger.error(
677
+ `error calculating amount distribution for withdrawal: ${amountDistributionForWithdrawal}`
678
+ );
679
+ return [];
680
+ }
681
+ const { vesu_amount, extended_amount } = amountDistributionForWithdrawal;
682
+ if (vesu_amount.greaterThan(0)) {
683
+ const result = await this.moveAssets(
684
+ {
685
+ amount: vesu_amount,
686
+ from: Protocols.VESU.name,
687
+ to: Protocols.VAULT.name,
688
+ },
689
+ extendedAdapter,
690
+ vesuAdapter
691
+ );
692
+ return result;
693
+ }
694
+ if (extended_amount.greaterThan(0)) {
695
+ const result = await this.moveAssets(
696
+ {
697
+ amount: extended_amount,
698
+ from: Protocols.EXTENDED.name,
699
+ to: Protocols.VAULT.name,
700
+ },
701
+ extendedAdapter,
702
+ vesuAdapter
703
+ );
704
+ return result;
705
+ }
706
+ return [];
707
+ } catch (err) {
708
+ logger.error(`error handling withdrawal: ${err}`);
709
+ return [];
710
+ }
711
+ }
712
+
713
+ async getAUM(): Promise<{ net: SingleTokenInfo, prevAum: Web3Number, splits: PositionInfo[] }> {
714
+ const allPositions: PositionInfo[] = [];
715
+ for (let adapter of this.metadata.additionalInfo.adapters) {
716
+ const positions = await adapter.adapter.getPositions();
717
+ allPositions.push(...positions);
718
+ }
719
+
720
+ const assetPrice = await this.pricer.getPrice(this.asset().symbol);
721
+ let netAUM = new Web3Number(0, this.asset().decimals);
722
+ for (let position of allPositions) {
723
+ if (position.tokenInfo.address.eq(this.asset().address)) {
724
+ netAUM = netAUM.plus(position.amount);
725
+ } else {
726
+ netAUM = netAUM.plus(position.usdValue / assetPrice.price);
727
+ }
728
+ }
729
+
730
+ const prevAum = await this.getPrevAUM();
731
+ const realAUM: PositionInfo = {
732
+ tokenInfo: this.asset(),
733
+ amount: netAUM,
734
+ usdValue: netAUM.toNumber() * assetPrice.price,
735
+ apy: { apy: netAUM.toNumber() * assetPrice.price, type: APYType.BASE },
736
+ remarks: AUMTypes.FINALISED,
737
+ protocol: Protocols.NONE // just placeholder
738
+ };
739
+
740
+ const estimatedAUMDelta: PositionInfo = {
741
+ tokenInfo: this.asset(),
742
+ amount: Web3Number.fromWei('0', this.asset().decimals),
743
+ usdValue: 0,
744
+ apy: { apy: 0, type: APYType.BASE },
745
+ remarks: AUMTypes.DEFISPRING,
746
+ protocol: Protocols.NONE // just placeholder
747
+ };
748
+
749
+ return {
750
+ net: {
751
+ tokenInfo: this.asset(),
752
+ amount: netAUM,
753
+ usdValue: netAUM.toNumber() * assetPrice.price
754
+ }, prevAum: prevAum, splits: [realAUM, estimatedAUMDelta]
755
+ };
756
+ }
757
+
758
+ }
759
+
760
+ function getLooperSettings(
761
+ lstSymbol: string,
762
+ underlyingSymbol: string,
763
+ vaultSettings: VesuExtendedStrategySettings,
764
+ pool1: ContractAddr,
765
+ extendedBackendUrl: string,
766
+ extendedApiKey: string,
767
+ vaultIdExtended: number,
768
+ ) {
769
+ vaultSettings.leafAdapters = [];
770
+
771
+ const wbtcToken = Global.getDefaultTokens().find(
772
+ (token) => token.symbol === lstSymbol
773
+ )!;
774
+ const usdcToken = Global.getDefaultTokens().find(
775
+ (token) => token.symbol === underlyingSymbol
776
+ )!;
777
+
778
+ const baseAdapterConfig: BaseAdapterConfig = {
779
+ baseToken: wbtcToken,
780
+ supportedPositions: [
781
+ { asset: usdcToken, isDebt: true },
782
+ { asset: wbtcToken, isDebt: false },
783
+ ],
784
+ //Since we open 2 positions, we need to add both positions, one is debt another is collateral
785
+ networkConfig: getMainnetConfig(),
786
+ pricer: new PricerFromApi(getMainnetConfig(), Global.getDefaultTokens()),
787
+ vaultAllocator: vaultSettings.vaultAllocator,
788
+ vaultAddress: vaultSettings.vaultAddress,
789
+ };
790
+
791
+ const avnuAdapter = new AvnuAdapter({
792
+ ...baseAdapterConfig,
793
+ avnuContract: AVNU_MIDDLEWARE,
794
+ slippage: 0.01,
795
+ baseUrl: AVNU_QUOTE_URL,
796
+ });
797
+
798
+ const extendedAdapter = new ExtendedAdapter({
799
+ ...baseAdapterConfig,
800
+ supportedPositions: [
801
+ { asset: usdcToken, isDebt: true },
802
+ ],
803
+ vaultIdExtended: vaultIdExtended,
804
+ extendedContract: EXTENDED_CONTRACT,
805
+ extendedBackendUrl: extendedBackendUrl,
806
+ extendedApiKey: extendedApiKey,
807
+ extendedTimeout: 30000,
808
+ extendedRetries: 3,
809
+ extendedBaseUrl: "https://api.starknet.extended.exchange",
810
+ extendedMarketName: "BTC-USD",
811
+ extendedPrecision: 5,
812
+ avnuAdapter: avnuAdapter,
813
+ });
814
+
815
+ const vesuMultiplyAdapter = new VesuMultiplyAdapter({
816
+ poolId: pool1,
817
+ collateral: wbtcToken,
818
+ debt: usdcToken,
819
+ targetHealthFactor: vaultSettings.targetHealthFactor,
820
+ minHealthFactor: vaultSettings.minHealthFactor,
821
+ quoteAmountToFetchPrice: vaultSettings.quoteAmountToFetchPrice,
822
+ ...baseAdapterConfig,
823
+ supportedPositions: [
824
+ { asset: wbtcToken, isDebt: false },
825
+ { asset: usdcToken, isDebt: true },
826
+ ],
827
+ });
828
+
829
+ const unusedBalanceAdapter = new UnusedBalanceAdapter({
830
+ ...baseAdapterConfig,
831
+ });
832
+
833
+ vaultSettings.adapters.push({
834
+ id: `${vesuMultiplyAdapter.name}_${wbtcToken.symbol}_${usdcToken.symbol}`,
835
+ adapter: vesuMultiplyAdapter,
836
+ });
837
+
838
+ vaultSettings.adapters.push({
839
+ id: `${unusedBalanceAdapter.name}_${wbtcToken.symbol}`,
840
+ adapter: unusedBalanceAdapter,
841
+ });
842
+
843
+ vaultSettings.adapters.push({
844
+ id: `${extendedAdapter.name}_${wbtcToken.symbol}`,
845
+ adapter: extendedAdapter,
846
+ });
847
+
848
+ vaultSettings.adapters.push({
849
+ id: `${avnuAdapter.name}_${wbtcToken.symbol}`,
850
+ adapter: avnuAdapter,
851
+ });
852
+
853
+ const commonAdapter = new CommonAdapter({
854
+ id: UNIVERSAL_MANAGE_IDS.FLASH_LOAN,
855
+ vaultAddress: vaultSettings.vaultAddress,
856
+ vaultAllocator: vaultSettings.vaultAllocator,
857
+ manager: vaultSettings.manager,
858
+ asset: wbtcToken.address,
859
+ });
860
+
861
+ vaultSettings.leafAdapters.push(() => vesuMultiplyAdapter.getDepositLeaf());
862
+ vaultSettings.leafAdapters.push(() =>
863
+ vesuMultiplyAdapter.getWithdrawLeaf()
864
+ );
865
+ vaultSettings.leafAdapters.push(() => extendedAdapter.getDepositLeaf());
866
+ vaultSettings.leafAdapters.push(() => avnuAdapter.getDepositLeaf());
867
+ vaultSettings.leafAdapters.push(() => avnuAdapter.getWithdrawLeaf());
868
+ // Doubt here, should this be usdcToken.address, or wbtcToken.address?
869
+ vaultSettings.leafAdapters.push(
870
+ commonAdapter
871
+ .getApproveAdapter(
872
+ usdcToken.address,
873
+ vaultSettings.vaultAddress,
874
+ UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY
875
+ )
876
+ .bind(commonAdapter)
877
+ );
878
+
879
+ vaultSettings.leafAdapters.push(
880
+ commonAdapter
881
+ .getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
882
+ .bind(commonAdapter)
883
+ );
884
+ return vaultSettings;
885
+ }
886
+
887
+
888
+ function getDescription(tokenSymbol: string, underlyingSymbol: string) {
889
+ return VaultDescription(tokenSymbol, underlyingSymbol);
890
+ }
891
+
892
+ export default function VaultDescription(
893
+ lstSymbol: string,
894
+ underlyingSymbol: string
895
+ ) {
896
+ const containerStyle = {
897
+ maxWidth: "800px",
898
+ margin: "0 auto",
899
+ backgroundColor: "#111",
900
+ color: "#eee",
901
+ fontFamily: "Arial, sans-serif",
902
+ borderRadius: "12px",
903
+ };
904
+
905
+ return (
906
+ <div style={containerStyle}>
907
+ <h1 style={{ fontSize: "18px", marginBottom: "10px" }}>Liquidation risk managed leverged {lstSymbol} Vault</h1>
908
+ <p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
909
+ This Levered Endur {lstSymbol} vault is a tokenized leveraged Vault, auto-compounding strategy that takes upto 5x leverage on {lstSymbol} by borrow {underlyingSymbol}. Borrowed amount
910
+ is swapped to {lstSymbol} to create leverage. Depositors receive vault shares that
911
+ represent a proportional claim on the underlying assets and accrued yield.
912
+ </p>
913
+
914
+ <p style={{ fontSize: "14px", lineHeight: "1.5", marginBottom: "16px" }}>
915
+ This vault uses Vesu for lending and borrowing. The oracle used by this pool is a {highlightTextWithLinks("conversion rate oracle", [{ highlight: "conversion rate oracle", link: "https://docs.pragma.build/starknet/development#conversion-rate" }])}
916
+ {" "}which is resilient to liquidity issues and price volatility, hence reducing the risk of liquidation. However, overtime, if left un-monitored, debt can increase enough to trigger a liquidation. But no worries, our continuous monitoring systems look for situations with reduced health factor and balance collateral/debt to bring it back to safe levels. With Troves, you can have a peaceful sleep.
917
+ </p>
918
+
919
+ <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
920
+ <p style={{ fontSize: "13px", color: "#ccc" }}>
921
+ <strong>Withdrawals:</strong> Requests can take up to <strong>1-2 hours</strong> to process as the vault unwinds and settles routing.
922
+ </p>
923
+ </div>
924
+ <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
925
+ <p style={{ fontSize: "13px", color: "#ccc" }}>
926
+ <strong>Debt limits:</strong> Pools on Vesu have debt caps that are gradually increased over time. Until caps are raised, deposited Tokens remain in the vault, generating a shared net return for all depositors. There is no additional fee taken by Troves on Yield token's APY, its only on added gain.
927
+ </p>
928
+ </div>
929
+ {/* <div style={{ backgroundColor: "#222", padding: "10px", borderRadius: "8px", marginBottom: "20px", border: "1px solid #444" }}>
930
+ <p style={{ fontSize: "13px", color: "#ccc" }}>
931
+ <strong>APY assumptions:</strong> APY shown is the max possible value given current LST and borrowing rates. True APY will be subject to the actual leverage, based on above point. More insights on exact APY will be added soon.
932
+ </p>
933
+ </div> */}
934
+ </div>
935
+ );
936
+ }
937
+
938
+ const re7UsdcPrimeDevansh: VesuExtendedStrategySettings = {
939
+ vaultAddress: ContractAddr.from("0x520a2e945dd0762e5284fc1b012f62ca04e238e105eb362d5e0ce208928729d"),
940
+ manager: ContractAddr.from("0x6abe24d31cbc16d7c4acb2421a826f96273e225b4a2d168d91e65123da0bfb9"),
941
+ vaultAllocator: ContractAddr.from("0x6e4f716e22efb164ee4a831233e513f45396ad3be4cff3c07386be0226febed"),
942
+ redeemRequestNFT: ContractAddr.from("0x66060e1874e05506b18e6029188ec49bf231a411ad57642311bbdf3cb22e5f"),
943
+ aumOracle: ContractAddr.from("0x301d883b9b45c76132638e39326b3f464c492599623263d405ec0df991e27ab"),
944
+ leafAdapters: [],
945
+ adapters: [],
946
+ targetHealthFactor: 1.4,
947
+ minHealthFactor: 1.05,
948
+ underlyingToken: Global.getDefaultTokens().find(
949
+ (token) => token.symbol === "USDC"
950
+ )!,
951
+ quoteAmountToFetchPrice: new Web3Number(
952
+ "0.001",
953
+ Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!.decimals
954
+ ),
955
+ borrowable_assets: [Global.getDefaultTokens().find(token => token.symbol === "WBTC")!],
956
+ minimumWBTCDifferenceForAvnuSwap: MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP,
957
+ }
958
+
959
+ export const VesuExtendedTestStrategies = (extendedBackendUrl: string, extendedApiKey: string, vaultIdExtended: number): IStrategyMetadata<VesuExtendedStrategySettings>[] => {
960
+ return [
961
+ getStrategySettingsVesuExtended('WBTC', 'USDC', re7UsdcPrimeDevansh, false, false, extendedBackendUrl, extendedApiKey, vaultIdExtended),
962
+ ]
963
+ }
964
+
965
+
966
+
967
+ function getStrategySettingsVesuExtended(lstSymbol: string, underlyingSymbol: string, addresses: VesuExtendedStrategySettings, isPreview: boolean = false, isLST: boolean, extendedBackendUrl: string, extendedApiKey: string, vaultIdExtended: number): IStrategyMetadata<VesuExtendedStrategySettings> {
968
+ return {
969
+ name: `Extended Test ${underlyingSymbol}`,
970
+ description: getDescription(lstSymbol, underlyingSymbol),
971
+ address: addresses.vaultAddress,
972
+ launchBlock: 0,
973
+ type: 'Other',
974
+ depositTokens: [Global.getDefaultTokens().find(token => token.symbol === underlyingSymbol)!],
975
+ additionalInfo: getLooperSettings(lstSymbol, underlyingSymbol, addresses, VesuPools.Re7USDCPrime, extendedBackendUrl, extendedApiKey, vaultIdExtended),
976
+ risk: {
977
+ riskFactor: _riskFactor,
978
+ netRisk:
979
+ _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
980
+ _riskFactor.reduce((acc, curr) => acc + curr.weight, 0),
981
+ notARisks: getNoRiskTags(_riskFactor)
982
+ },
983
+ auditUrl: AUDIT_URL,
984
+ protocols: [Protocols.ENDUR, Protocols.VESU],
985
+ maxTVL: Web3Number.fromWei(0, 18),
986
+ contractDetails: getContractDetails(addresses),
987
+ faqs: getFAQs(lstSymbol, underlyingSymbol, isLST),
988
+ investmentSteps: getInvestmentSteps(lstSymbol, underlyingSymbol),
989
+ isPreview: isPreview,
990
+ apyMethodology: isLST ? 'Current annualized APY in terms of base asset of the LST. There is no additional fee taken by Troves on LST APY. We charge a 10% performance fee on the additional gain which is already accounted in the APY shown.' : 'Current annualized APY in terms of base asset of the Yield Token. There is no additional fee taken by Troves on yield token APY. We charge a 10% performance fee on the additional gain which is already accounted in the APY shown.'
991
+ }
992
+ }