@strkfarm/sdk 2.0.0-dev.26 → 2.0.0-dev.28

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 (70) hide show
  1. package/dist/cli.js +190 -36
  2. package/dist/cli.mjs +188 -34
  3. package/dist/index.browser.global.js +79130 -49354
  4. package/dist/index.browser.mjs +18039 -11431
  5. package/dist/index.d.ts +2869 -898
  6. package/dist/index.js +19036 -12207
  7. package/dist/index.mjs +18942 -12158
  8. package/package.json +1 -1
  9. package/src/data/avnu.abi.json +840 -0
  10. package/src/data/ekubo-price-fethcer.abi.json +265 -0
  11. package/src/dataTypes/_bignumber.ts +13 -4
  12. package/src/dataTypes/index.ts +3 -2
  13. package/src/dataTypes/mynumber.ts +141 -0
  14. package/src/global.ts +76 -41
  15. package/src/index.browser.ts +2 -1
  16. package/src/interfaces/common.tsx +167 -2
  17. package/src/modules/ExtendedWrapperSDk/types.ts +26 -4
  18. package/src/modules/ExtendedWrapperSDk/wrapper.ts +110 -67
  19. package/src/modules/apollo-client-config.ts +28 -0
  20. package/src/modules/avnu.ts +4 -4
  21. package/src/modules/ekubo-pricer.ts +79 -0
  22. package/src/modules/ekubo-quoter.ts +46 -30
  23. package/src/modules/erc20.ts +17 -0
  24. package/src/modules/harvests.ts +43 -29
  25. package/src/modules/pragma.ts +23 -8
  26. package/src/modules/pricer-from-api.ts +156 -15
  27. package/src/modules/pricer-lst.ts +1 -1
  28. package/src/modules/pricer.ts +40 -4
  29. package/src/modules/pricerBase.ts +2 -1
  30. package/src/node/deployer.ts +36 -1
  31. package/src/node/pricer-redis.ts +2 -1
  32. package/src/strategies/base-strategy.ts +78 -10
  33. package/src/strategies/ekubo-cl-vault.tsx +906 -347
  34. package/src/strategies/factory.ts +159 -0
  35. package/src/strategies/index.ts +6 -1
  36. package/src/strategies/registry.ts +239 -0
  37. package/src/strategies/sensei.ts +335 -7
  38. package/src/strategies/svk-strategy.ts +97 -27
  39. package/src/strategies/types.ts +4 -0
  40. package/src/strategies/universal-adapters/adapter-utils.ts +2 -1
  41. package/src/strategies/universal-adapters/avnu-adapter.ts +177 -268
  42. package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
  43. package/src/strategies/universal-adapters/common-adapter.ts +206 -203
  44. package/src/strategies/universal-adapters/extended-adapter.ts +155 -336
  45. package/src/strategies/universal-adapters/index.ts +9 -8
  46. package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
  47. package/src/strategies/universal-adapters/usdc<>usdce-adapter.ts +200 -0
  48. package/src/strategies/universal-adapters/vesu-adapter.ts +110 -75
  49. package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +476 -0
  50. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +762 -844
  51. package/src/strategies/universal-adapters/vesu-position-common.ts +251 -0
  52. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
  53. package/src/strategies/universal-lst-muliplier-strategy.tsx +396 -204
  54. package/src/strategies/universal-strategy.tsx +1426 -1178
  55. package/src/strategies/vesu-extended-strategy/services/executionService.ts +2251 -0
  56. package/src/strategies/vesu-extended-strategy/services/extended-vesu-state-manager.ts +2941 -0
  57. package/src/strategies/vesu-extended-strategy/services/operationService.ts +12 -1
  58. package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +52 -0
  59. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +1 -0
  60. package/src/strategies/vesu-extended-strategy/utils/constants.ts +3 -1
  61. package/src/strategies/vesu-extended-strategy/utils/helper.ts +158 -124
  62. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +377 -1781
  63. package/src/strategies/vesu-rebalance.tsx +255 -152
  64. package/src/utils/health-factor-math.ts +4 -1
  65. package/src/utils/index.ts +2 -1
  66. package/src/utils/logger.browser.ts +22 -4
  67. package/src/utils/logger.node.ts +259 -24
  68. package/src/utils/starknet-call-parser.ts +1036 -0
  69. package/src/utils/strategy-utils.ts +61 -0
  70. package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
@@ -11,8 +11,15 @@ import {
11
11
  Protocols,
12
12
  RiskFactor,
13
13
  RiskType,
14
+ StrategyTag,
14
15
  TokenInfo,
15
16
  VaultPosition,
17
+ AuditStatus,
18
+ SourceCodeType,
19
+ AccessControlType,
20
+ InstantWithdrawalVault,
21
+ VaultType,
22
+ StrategyLiveStatus,
16
23
  } from "@/interfaces";
17
24
  import { PricerBase } from "@/modules/pricerBase";
18
25
  import { assert } from "@/utils";
@@ -38,10 +45,11 @@ import { EkuboHarvests, HarvestInfo } from "@/modules/harvests";
38
45
  import { logger } from "@/utils/logger";
39
46
  import { COMMON_CONTRACTS } from "./constants";
40
47
  import { DepegRiskLevel, ImpermanentLossLevel, MarketRiskLevel, SmartContractRiskLevel } from "@/interfaces/risks";
41
- import { gql } from "@apollo/client";
48
+ import { from, gql } from "@apollo/client";
42
49
  import apolloClient from "@/modules/apollo-client";
43
50
  import { binarySearch } from "@/utils/math-utils";
44
51
  import { PositionInfo } from "./universal-adapters/baseAdapter";
52
+ import { Quote } from "@avnu/avnu-sdk";
45
53
 
46
54
  export interface EkuboPoolKey {
47
55
  token0: ContractAddr;
@@ -296,7 +304,10 @@ export class EkuboCLVault extends BaseStrategy<
296
304
  return [this.contract.populate("handle_fees", [])];
297
305
  }
298
306
 
299
- async getFeeHistory(timePeriod: '24h' | '7d' | '30d' | '3m' = '24h'): Promise<{
307
+ async getFeeHistory(
308
+ timePeriod: '24h' | '7d' | '30d' | '3m' | '6m' = '24h',
309
+ range?: { startTimestamp?: number; endTimestamp?: number }
310
+ ): Promise<{
300
311
  summary: DualTokenInfo,
301
312
  history: FeeHistory[]
302
313
  }> {
@@ -305,8 +316,15 @@ export class EkuboCLVault extends BaseStrategy<
305
316
  query ContractFeeEarnings(
306
317
  $timeframe: String!
307
318
  $contract: String!
319
+ $startTimestamp: Float
320
+ $endTimestamp: Float
308
321
  ) {
309
- contractFeeEarnings(timeframe: $timeframe, contract: $contract) {
322
+ contractFeeEarnings(
323
+ timeframe: $timeframe
324
+ contract: $contract
325
+ startTimestamp: $startTimestamp
326
+ endTimestamp: $endTimestamp
327
+ ) {
310
328
  contract
311
329
  dailyEarnings {
312
330
  date
@@ -319,7 +337,9 @@ export class EkuboCLVault extends BaseStrategy<
319
337
  `,
320
338
  variables: {
321
339
  timeframe: timePeriod,
322
- contract: this.address.address
340
+ contract: this.address.address,
341
+ startTimestamp: range?.startTimestamp,
342
+ endTimestamp: range?.endTimestamp
323
343
  },
324
344
  fetchPolicy: 'no-cache',
325
345
  });
@@ -418,22 +438,41 @@ export class EkuboCLVault extends BaseStrategy<
418
438
  const tvlBefore = await this._getTVL(blockBefore);
419
439
  const supplyBefore = await this.totalSupply(blockBefore);
420
440
  const priceBefore = await this.getCurrentPrice(blockBefore);
421
-
422
- const tvlInToken0Now = (tvlNow.amount0
423
- .multipliedBy(priceNow.price))
424
- .plus(tvlNow.amount1);
425
- const tvlPerShareNow = tvlInToken0Now
441
+ const poolKey = await this.getPoolKey(blockBefore);
442
+ logger.verbose(`priceBefore: ${priceBefore.price.toString()}`);
443
+ logger.verbose(`priceNow: ${priceNow.price.toString()}`);
444
+ logger.verbose(`tvlBefore: ${JSON.stringify(tvlBefore)}`);
445
+ logger.verbose(`tvlNow: ${JSON.stringify(tvlNow)}`);
446
+
447
+ const isQuoteTokenToken0 = this.metadata.additionalInfo.quoteAsset.address.eq(poolKey.token0);
448
+ logger.verbose(`isQuoteTokenToken0: ${isQuoteTokenToken0}`);
449
+ let tvlBeforeInBaseAsset = Web3Number.fromWei(0, this.metadata.additionalInfo.quoteAsset.decimals);
450
+ let tvlNowInBaseAsset = Web3Number.fromWei(0, this.metadata.additionalInfo.quoteAsset.decimals);
451
+ if (!isQuoteTokenToken0) {
452
+ tvlNowInBaseAsset = (tvlNow.amount0
453
+ .multipliedBy(priceNow.price))
454
+ .plus(tvlNow.amount1);
455
+ tvlBeforeInBaseAsset = (tvlBefore.amount0
456
+ .multipliedBy(priceBefore.price))
457
+ .plus(tvlBefore.amount1);
458
+ } else {
459
+ tvlNowInBaseAsset = (tvlNow.amount1
460
+ .multipliedBy(1 / priceNow.price))
461
+ .plus(tvlNow.amount0);
462
+ tvlBeforeInBaseAsset = (tvlBefore.amount1
463
+ .multipliedBy(1 / priceBefore.price))
464
+ .plus(tvlBefore.amount0);
465
+ }
466
+ const tvlPerShareNow = tvlNowInBaseAsset
426
467
  .multipliedBy(1e18)
427
468
  .dividedBy(adjustedSupplyNow.toString());
428
- const tvlInToken0Bf = (tvlBefore.amount0
429
- .multipliedBy(priceBefore.price))
430
- .plus(tvlBefore.amount1);
431
- const tvlPerShareBf = tvlInToken0Bf
469
+
470
+ const tvlPerShareBf = tvlBeforeInBaseAsset
432
471
  .multipliedBy(1e18)
433
472
  .dividedBy(supplyBefore.toString());
434
473
  const timeDiffSeconds = blockNowTime - blockBeforeInfo.timestamp;
435
- logger.verbose(`tvlInToken0Now: ${tvlInToken0Now.toString()}`);
436
- logger.verbose(`tvlInToken0Bf: ${tvlInToken0Bf.toString()}`);
474
+ logger.verbose(`tvlNowInBaseAsset: ${tvlNowInBaseAsset.toString()}`);
475
+ logger.verbose(`tvlBeforeInBaseAsset: ${tvlBeforeInBaseAsset.toString()}`);
437
476
  logger.verbose(`tvlPerShareNow: ${tvlPerShareNow.toString()}`);
438
477
  logger.verbose(`tvlPerShareBf: ${tvlPerShareBf.toString()}`);
439
478
  logger.verbose(`Price before: ${priceBefore.price.toString()}`);
@@ -451,6 +490,155 @@ export class EkuboCLVault extends BaseStrategy<
451
490
  return (apyForGivenBlocks * (365 * 24 * 3600)) / timeDiffSeconds;
452
491
  }
453
492
 
493
+ /**
494
+ * Calculate lifetime earnings for a user
495
+ * Not yet implemented for Ekubo CL Vault strategy
496
+ */
497
+ getLifetimeEarnings(
498
+ userTVL: SingleTokenInfo,
499
+ investmentFlows: Array<{ amount: string; type: string; timestamp: number; tx_hash: string }>
500
+ ): any {
501
+ throw new Error("getLifetimeEarnings is not implemented yet for this strategy");
502
+ }
503
+
504
+ /**
505
+ * Calculates realized APY based on TVL per share growth, always valued in USDC.
506
+ * This is a vault-level metric (same for all users) and works for all strategies,
507
+ * regardless of quote asset configuration.
508
+ */
509
+ async getUserRealizedAPY(
510
+ blockIdentifier: BlockIdentifier = "latest",
511
+ sinceBlocks = 600000
512
+ ): Promise<number> {
513
+ throw new Error("getUserRealizedAPY not implemented yet for Ekubo CL Vault strategy");
514
+
515
+ /*
516
+ logger.verbose(
517
+ `${EkuboCLVault.name}: getUserRealizedAPY => starting with blockIdentifier=${blockIdentifier}, sinceBlocks=${sinceBlocks}`
518
+ );
519
+
520
+ // TVL amounts (in token units) at current reference block
521
+ const tvlNow = await this._getTVL(blockIdentifier);
522
+ const supplyNow = await this.totalSupply(blockIdentifier);
523
+
524
+ // Determine current block number and timestamp
525
+ let blockNow =
526
+ typeof blockIdentifier == "number"
527
+ ? blockIdentifier
528
+ : (await this.config.provider.getBlockLatestAccepted()).block_number;
529
+ const blockNowTime =
530
+ typeof blockIdentifier == "number"
531
+ ? (await this.config.provider.getBlockWithTxs(blockIdentifier))
532
+ .timestamp
533
+ : new Date().getTime() / 1000;
534
+
535
+ // Look back window, but never before launch block
536
+ const blockBefore = Math.max(
537
+ Number(blockNow) - sinceBlocks,
538
+ this.metadata.launchBlock
539
+ );
540
+
541
+ // Adjust current supply by subtracting harvest reward shares (same idea as netSharesBasedTrueAPY)
542
+ const adjustedSupplyNow = supplyNow.minus(
543
+ await this.getHarvestRewardShares(blockBefore, Number(blockNow))
544
+ );
545
+
546
+ // Historical block info and TVL
547
+ const blockBeforeInfo = await this.config.provider.getBlockWithTxs(
548
+ blockBefore
549
+ );
550
+ const tvlBefore = await this._getTVL(blockBefore);
551
+ const supplyBefore = await this.totalSupply(blockBefore);
552
+
553
+ // Always value TVL in USDC using the pricer for both tokens at both blocks
554
+ const token0Info = this.metadata.depositTokens[0];
555
+ const token1Info = this.metadata.depositTokens[1];
556
+
557
+ const P0Now = await this.pricer.getPrice(
558
+ token0Info.symbol,
559
+ Number(blockNow)
560
+ );
561
+ const P1Now = await this.pricer.getPrice(
562
+ token1Info.symbol,
563
+ Number(blockNow)
564
+ );
565
+ const P0Before = await this.pricer.getPrice(
566
+ token0Info.symbol,
567
+ blockBefore
568
+ );
569
+ const P1Before = await this.pricer.getPrice(
570
+ token1Info.symbol,
571
+ blockBefore
572
+ );
573
+
574
+ // Convert token balances to USDC TVL using current and historical prices
575
+ const tvlNowUsdNumber =
576
+ Number(tvlNow.amount0.toFixed(13)) * P0Now.price +
577
+ Number(tvlNow.amount1.toFixed(13)) * P1Now.price;
578
+ const tvlBeforeUsdNumber =
579
+ Number(tvlBefore.amount0.toFixed(13)) * P0Before.price +
580
+ Number(tvlBefore.amount1.toFixed(13)) * P1Before.price;
581
+
582
+ // Represent USDC TVL as Web3Number with 6 decimals (USDC standard)
583
+ const tvlNowInUSDC = new Web3Number(
584
+ tvlNowUsdNumber.toFixed(13),
585
+ 6
586
+ );
587
+ const tvlBeforeInUSDC = new Web3Number(
588
+ tvlBeforeUsdNumber.toFixed(13),
589
+ 6
590
+ );
591
+
592
+ const tvlPerShareNow = tvlNowInUSDC
593
+ .multipliedBy(1e18)
594
+ .dividedBy(adjustedSupplyNow.toString());
595
+
596
+ const tvlPerShareBf = tvlBeforeInUSDC
597
+ .multipliedBy(1e18)
598
+ .dividedBy(supplyBefore.toString());
599
+
600
+ const timeDiffSeconds = blockNowTime - blockBeforeInfo.timestamp;
601
+
602
+ logger.verbose(
603
+ `${EkuboCLVault.name}: getUserRealizedAPY => token0=${token0Info.symbol}, token1=${token1Info.symbol}`
604
+ );
605
+ logger.verbose(
606
+ `${EkuboCLVault.name}: getUserRealizedAPY => P0Now=${P0Now.price}, P1Now=${P1Now.price}, P0Before=${P0Before.price}, P1Before=${P1Before.price}`
607
+ );
608
+ logger.verbose(
609
+ `${EkuboCLVault.name}: getUserRealizedAPY => raw tvlNow amounts: token0=${tvlNow.amount0.toString()}, token1=${tvlNow.amount1.toString()}`
610
+ );
611
+ logger.verbose(
612
+ `${EkuboCLVault.name}: getUserRealizedAPY => raw tvlBefore amounts: token0=${tvlBefore.amount0.toString()}, token1=${tvlBefore.amount1.toString()}`
613
+ );
614
+ logger.verbose(
615
+ `${EkuboCLVault.name}: getUserRealizedAPY => tvlNowUsdNumber=${tvlNowUsdNumber}, tvlBeforeUsdNumber=${tvlBeforeUsdNumber}`
616
+ );
617
+ logger.verbose(
618
+ `${EkuboCLVault.name}: getUserRealizedAPY => tvlNowInUSDC: ${tvlNowInUSDC.toString()}, tvlBeforeInUSDC: ${tvlBeforeInUSDC.toString()}`
619
+ );
620
+ logger.verbose(
621
+ `${EkuboCLVault.name}: getUserRealizedAPY => tvlPerShareNow: ${tvlPerShareNow.toString()}, tvlPerShareBf: ${tvlPerShareBf.toString()}`
622
+ );
623
+ logger.verbose(
624
+ `${EkuboCLVault.name}: getUserRealizedAPY => Supply before: ${supplyBefore.toString()}, Supply now (adjusted): ${adjustedSupplyNow.toString()}`
625
+ );
626
+ logger.verbose(
627
+ `${EkuboCLVault.name}: getUserRealizedAPY => Time diff in seconds: ${timeDiffSeconds}`
628
+ );
629
+
630
+ const apyForGivenBlocks =
631
+ Number(
632
+ tvlPerShareNow
633
+ .minus(tvlPerShareBf)
634
+ .multipliedBy(10000)
635
+ .dividedBy(tvlPerShareBf)
636
+ ) / 10000;
637
+
638
+ return (apyForGivenBlocks * (365 * 24 * 3600)) / timeDiffSeconds;
639
+ */
640
+ }
641
+
454
642
  async feeBasedAPY(
455
643
  timeperiod: '24h' | '7d' | '30d' | '3m' = '24h'
456
644
  ): Promise<number> {
@@ -485,20 +673,15 @@ export class EkuboCLVault extends BaseStrategy<
485
673
  blockIdentifier: BlockIdentifier = "latest",
486
674
  sinceBlocks = 600000,
487
675
  timeperiod: '24h' | '7d' | '30d' | '3m' = '24h' // temp thing for fee based APY
488
- ): Promise<APYInfo> {
489
- const isUSDCQouteToken = this.metadata.additionalInfo.quoteAsset.symbol === "USDC";
676
+ ): Promise<number> {
677
+ // ! switch to USDC later
678
+ const isUSDCQouteToken = this.metadata.additionalInfo.quoteAsset.symbol === "USDC.e" || this.metadata.additionalInfo.quoteAsset.symbol === "USDC";
490
679
  if (!isUSDCQouteToken) {
491
680
  // good for LSTs and stables
492
- return {
493
- net: await this.netSharesBasedTrueAPY(blockIdentifier, sinceBlocks),
494
- splits: []
495
- };
681
+ return await this.netSharesBasedTrueAPY(blockIdentifier, sinceBlocks);
496
682
  } else {
497
683
  // good for non-stables
498
- return {
499
- net: await this.feeBasedAPY(timeperiod),
500
- splits: []
501
- };
684
+ return await this.feeBasedAPY(timeperiod);
502
685
  }
503
686
  }
504
687
 
@@ -559,8 +742,14 @@ export class EkuboCLVault extends BaseStrategy<
559
742
  assets.amount1.toString(),
560
743
  token1Info.decimals
561
744
  );
562
- const P0 = await this.pricer.getPrice(token0Info.symbol);
563
- const P1 = await this.pricer.getPrice(token1Info.symbol);
745
+
746
+ // Convert blockIdentifier to block number for pricer if it's a number
747
+ const blockNumber = typeof blockIdentifier === 'number' || typeof blockIdentifier === 'bigint'
748
+ ? Number(blockIdentifier)
749
+ : undefined;
750
+
751
+ const P0 = await this.pricer.getPrice(token0Info.symbol, blockNumber);
752
+ const P1 = await this.pricer.getPrice(token1Info.symbol, blockNumber);
564
753
  const token0Usd = Number(amount0.toFixed(13)) * P0.price;
565
754
  const token1Usd = Number(amount1.toFixed(13)) * P1.price;
566
755
 
@@ -1026,6 +1215,12 @@ export class EkuboCLVault extends BaseStrategy<
1026
1215
  amount1: availableAmount1.minus(y),
1027
1216
  ratio: 0,
1028
1217
  };
1218
+ } else if (ratio.eq(Infinity)) {
1219
+ return {
1220
+ amount0: availableAmount0,
1221
+ amount1: Web3Number.fromWei("0", availableAmount1.decimals),
1222
+ ratio: Infinity,
1223
+ };
1029
1224
  }
1030
1225
  return {
1031
1226
  amount0: availableAmount0.plus(x),
@@ -1075,7 +1270,12 @@ export class EkuboCLVault extends BaseStrategy<
1075
1270
  };
1076
1271
  }
1077
1272
 
1078
- async getSwapInfoToHandleUnused(considerRebalance: boolean = true, newBounds: EkuboBounds | null = null, maxIterations = 20, priceRatioPrecision = 4): Promise<SwapInfo> {
1273
+ async getSwapInfoToHandleUnused(
1274
+ considerRebalance: boolean = true,
1275
+ newBounds: EkuboBounds | null = null,
1276
+ maxIterations = 20, priceRatioPrecision = 4,
1277
+ getQuoteCallback: (tokenToSell: string, tokenToBuy: string, amountWei: string, beneficiary: string) => Promise<Quote> = this.avnu.getQuotes
1278
+ ): Promise<SwapInfo> {
1079
1279
  const poolKey = await this.getPoolKey();
1080
1280
 
1081
1281
  // fetch current unused balances of vault
@@ -1140,7 +1340,8 @@ export class EkuboCLVault extends BaseStrategy<
1140
1340
  token1Bal,
1141
1341
  ekuboBounds,
1142
1342
  maxIterations,
1143
- priceRatioPrecision
1343
+ priceRatioPrecision,
1344
+ getQuoteCallback
1144
1345
  );
1145
1346
  }
1146
1347
 
@@ -1186,6 +1387,7 @@ export class EkuboCLVault extends BaseStrategy<
1186
1387
  const tokenToSell = expectedAmounts.amount0.lessThan(token0Bal)
1187
1388
  ? poolKey.token0
1188
1389
  : poolKey.token1;
1390
+ logger.verbose(`getSwapParams => tokenToSell: ${tokenToSell.address}, expectedAmounts: ${expectedAmounts.amount0.toString()}, bal0: ${token0Bal.toString()}`);
1189
1391
  // The other token is the one to buy
1190
1392
  const tokenToBuy =
1191
1393
  tokenToSell == poolKey.token0 ? poolKey.token1 : poolKey.token0;
@@ -1210,13 +1412,13 @@ export class EkuboCLVault extends BaseStrategy<
1210
1412
  /**
1211
1413
  * @description Calculates swap info based on given amounts of token0 and token1
1212
1414
  * Use token0 and token1 balances to determine the expected amounts for new bounds
1213
- * @param poolKey
1214
- * @param token0Bal
1215
- * @param token1Bal
1415
+ * @param poolKey
1416
+ * @param token0Bal
1417
+ * @param token1Bal
1216
1418
  * @param bounds // new bounds
1217
- * @param maxIterations
1419
+ * @param maxIterations
1218
1420
  * @returns {Promise<SwapInfo>}
1219
- *
1421
+ *
1220
1422
  */
1221
1423
  async getSwapInfoGivenAmounts(
1222
1424
  poolKey: EkuboPoolKey,
@@ -1224,7 +1426,8 @@ export class EkuboCLVault extends BaseStrategy<
1224
1426
  token1Bal: Web3Number,
1225
1427
  bounds: EkuboBounds,
1226
1428
  maxIterations: number = 20,
1227
- priceRatioPrecision: number = 4
1429
+ priceRatioPrecision: number = 4,
1430
+ getQuoteCallback: (tokenToSell: string, tokenToBuy: string, amountWei: string, beneficiary: string) => Promise<Quote> = this.avnu.getQuotes
1228
1431
  ): Promise<SwapInfo> {
1229
1432
  logger.verbose(
1230
1433
  `${
@@ -1279,12 +1482,7 @@ export class EkuboCLVault extends BaseStrategy<
1279
1482
  }
1280
1483
 
1281
1484
  // Get a quote for swapping the calculated amount
1282
- const quote = await this.avnu.getQuotes(
1283
- tokenToSell.address,
1284
- tokenToBuy.address,
1285
- amountToSell.toWei(),
1286
- this.address.address
1287
- );
1485
+ const quote = await getQuoteCallback(tokenToSell.address, tokenToBuy.address, amountToSell.toWei(), this.address.address);
1288
1486
 
1289
1487
  // If all of the token is to be swapped, return the swap info directly
1290
1488
  if (remainingSellAmount.eq(0)) {
@@ -1394,8 +1592,11 @@ export class EkuboCLVault extends BaseStrategy<
1394
1592
  * @param retry - Current retry attempt number (default 0)
1395
1593
  * @param adjustmentFactor - Percentage to adjust swap amount by (default 1)
1396
1594
  * @param isToken0Deficit - Whether token0 balance needs increasing (default true)
1595
+ * @param MAX_RETRIES - Maximum number of retries (default 40)
1596
+ * @param sameErrorCount - For certain errors, we just retry with same amount again. This is the count of such retries (default { count: 0, error: null })
1597
+ * @param MAX_SAME_ERROR_COUNT - For certain errors, we just retry with same amount again. This limits such retries (default 10)
1397
1598
  * @returns Array of contract calls needed for rebalancing
1398
- * @throws Error if max retries reached without successful rebalance
1599
+ * @throws Error if max retries reached without successful rebalance or max same errors reached
1399
1600
  */
1400
1601
  async rebalanceIter(
1401
1602
  swapInfo: SwapInfo,
@@ -1405,15 +1606,23 @@ export class EkuboCLVault extends BaseStrategy<
1405
1606
  retry = 0,
1406
1607
  lowerLimit = 0n,
1407
1608
  upperLimit = 0n,
1408
- MAX_RETRIES = 40
1609
+ MAX_RETRIES = 40,
1610
+ sameErrorCount: { count: number, error: null | string } = { count: 0, error: null },
1611
+ MAX_SAME_ERROR_COUNT = 10
1409
1612
  ): Promise<Call[]> {
1410
1613
 
1411
1614
  logger.verbose(
1412
1615
  `Rebalancing ${this.metadata.name}: ` +
1413
- `retry=${retry}, lowerLimit=${lowerLimit}, upperLimit=${upperLimit}, isSellTokenToken0=${isSellTokenToken0}`
1616
+ `retry=${retry}, lowerLimit=${lowerLimit}, upperLimit=${upperLimit}, isSellTokenToken0=${isSellTokenToken0}, MAX_RETRIES=${MAX_RETRIES}, sameErrorCount=${sameErrorCount.error} (${sameErrorCount.count})`
1414
1617
  );
1415
1618
 
1619
+ if (sameErrorCount.count >= MAX_SAME_ERROR_COUNT) {
1620
+ logger.error(`Rebalance failed after ${MAX_SAME_ERROR_COUNT} same errors`);
1621
+ throw new Error(`Rebalance failed after ${MAX_SAME_ERROR_COUNT} same errors`);
1622
+ }
1623
+
1416
1624
  const fromAmount = uint256.uint256ToBN(swapInfo.token_from_amount);
1625
+ const fromTokenInfo = await Global.getTokenInfoFromAddr(ContractAddr.from(swapInfo.token_from_address));
1417
1626
  logger.verbose(
1418
1627
  `Selling ${fromAmount.toString()} of token ${swapInfo.token_from_address}`
1419
1628
  );
@@ -1433,8 +1642,8 @@ export class EkuboCLVault extends BaseStrategy<
1433
1642
  );
1434
1643
 
1435
1644
  const newSwapInfo = { ...swapInfo };
1436
- const currentAmount = Web3Number.fromWei(fromAmount.toString(), 18); // 18 is ok, as its toWei eventually anyways
1437
- logger.verbose(`Current amount: ${currentAmount.toString()}`);
1645
+ const currentAmount = Web3Number.fromWei(fromAmount.toString(), fromTokenInfo.decimals);
1646
+ logger.verbose(`Current amount: ${currentAmount.toString()}, lowerLimit: ${lowerLimit.toString()}, upperLimit: ${upperLimit.toString()}`);
1438
1647
  if (
1439
1648
  err.message.includes("invalid token0 balance") ||
1440
1649
  err.message.includes("invalid token0 amount")
@@ -1487,11 +1696,43 @@ export class EkuboCLVault extends BaseStrategy<
1487
1696
  }
1488
1697
  newSwapInfo.token_from_amount = uint256.bnToUint256(nextAmount);
1489
1698
  }
1699
+ } else if (err.message.includes("Residual tokens")) {
1700
+ logger.error("Residual tokens");
1701
+ if (sameErrorCount.error == "Residual tokens") {
1702
+ sameErrorCount.count++;
1703
+ } else {
1704
+ sameErrorCount.error = "Residual tokens";
1705
+ sameErrorCount.count = 1;
1706
+ }
1707
+ // dont do anything, just try again.
1708
+ } else if (err.message.includes("Insufficient tokens received")) {
1709
+ logger.error("Insufficient tokens received");
1710
+ if (sameErrorCount.error == "Insufficient tokens received") {
1711
+ sameErrorCount.count++;
1712
+ } else {
1713
+ sameErrorCount.error = "Insufficient tokens received";
1714
+ sameErrorCount.count = 1;
1715
+ }
1716
+ // dont do anything, just try again.
1717
+ } else if (err.message.includes("Could not reach the end of the program")) {
1718
+ logger.error("Could not reach the end of the program, may be the block is full (could be a temp/permanent gas issue)");
1719
+ if (sameErrorCount.error == "Could not reach the end of the program") {
1720
+ sameErrorCount.count++;
1721
+ } else {
1722
+ sameErrorCount.error = "Could not reach the end of the program";
1723
+ sameErrorCount.count = 1;
1724
+ }
1725
+ // just try again.
1490
1726
  } else {
1491
1727
  logger.error("Unexpected error:", err);
1492
1728
  throw err;
1493
1729
  }
1494
1730
  newSwapInfo.token_to_min_amount = uint256.bnToUint256("0");
1731
+
1732
+ // if (uint256.uint256ToBN(newSwapInfo.token_from_amount) == fromAmount && sameErrorCount.error == 'loop-stuck') {
1733
+ // logger.error("Swap amount did not change, cannot proceed");
1734
+ // sameErrorCount = { count: MAX_SAME_ERROR_COUNT, error: null };
1735
+ // }
1495
1736
  return this.rebalanceIter(
1496
1737
  newSwapInfo,
1497
1738
  acc,
@@ -1499,7 +1740,10 @@ export class EkuboCLVault extends BaseStrategy<
1499
1740
  isSellTokenToken0,
1500
1741
  retry + 1,
1501
1742
  lowerLimit,
1502
- upperLimit
1743
+ upperLimit,
1744
+ MAX_RETRIES,
1745
+ sameErrorCount,
1746
+ MAX_SAME_ERROR_COUNT
1503
1747
  );
1504
1748
  }
1505
1749
  }
@@ -1592,20 +1836,34 @@ export class EkuboCLVault extends BaseStrategy<
1592
1836
  };
1593
1837
  }
1594
1838
 
1595
- async harvest(acc: Account, maxIterations = 20, priceRatioPrecision = 4): Promise<Call[]> {
1839
+ async getPendingRewards(): Promise<HarvestInfo[]> {
1596
1840
  const ekuboHarvests = new EkuboHarvests(this.config);
1597
- const unClaimedRewards = await ekuboHarvests.getUnHarvestedRewards(
1598
- this.address
1599
- );
1841
+ return await ekuboHarvests.getUnHarvestedRewards(this.address);
1842
+ }
1843
+
1844
+ async harvest(acc: Account, maxIterations = 20, priceRatioPrecision = 4, minRewardAmount: Web3Number = new Web3Number(0, 18)): Promise<Call[]> {
1845
+ const _pendingRewards = await this.getPendingRewards();
1846
+ const pendingRewards = _pendingRewards.filter(claim => claim.actualReward.greaterThanOrEqualTo(minRewardAmount));
1847
+ if (pendingRewards.length == 0) {
1848
+ logger.verbose(`${EkuboCLVault.name}: harvest => no pending rewards found`);
1849
+ return [];
1850
+ }
1851
+ // get necessary info for the harvest
1600
1852
  const poolKey = await this.getPoolKey();
1601
1853
  const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
1602
1854
  const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
1603
1855
  const bounds = await this.getCurrentBounds();
1604
1856
  logger.verbose(
1605
- `${EkuboCLVault.name}: harvest => unClaimedRewards: ${unClaimedRewards.length}`
1857
+ `${EkuboCLVault.name}: harvest => unClaimedRewards: ${pendingRewards.length}`
1606
1858
  );
1859
+
1860
+ // execute the harvest
1607
1861
  const calls: Call[] = [];
1608
- for (let claim of unClaimedRewards) {
1862
+ // do one at a time.
1863
+ const chosenClaim = pendingRewards[0];
1864
+ logger.info(`${EkuboCLVault.name}: harvest => doing one at a time`);
1865
+ logger.info(`${EkuboCLVault.name}: harvest => chosenClaim -> Claim ID: ${chosenClaim.claim.id}, Amount: ${chosenClaim.claim.amount.toString()}, actualAmount: ${chosenClaim.actualReward.toString()}, addr: ${chosenClaim.claim.claimee.toString()}`);
1866
+ for (let claim of [chosenClaim]) {
1609
1867
  const fee = claim.claim.amount
1610
1868
  .multipliedBy(this.metadata.additionalInfo.feeBps)
1611
1869
  .dividedBy(10000);
@@ -1653,8 +1911,8 @@ export class EkuboCLVault extends BaseStrategy<
1653
1911
 
1654
1912
  /**
1655
1913
  * @description This funciton requires atleast one of the pool tokens to be reward token
1656
- * i.e. STRK.
1657
- * @param params
1914
+ * i.e. STRK.
1915
+ * @param params
1658
1916
  */
1659
1917
  async _handleRewardAndVaultTokenMatchHarvest(params: {
1660
1918
  claim: HarvestInfo;
@@ -1678,60 +1936,50 @@ export class EkuboCLVault extends BaseStrategy<
1678
1936
  logger.verbose(
1679
1937
  `${
1680
1938
  EkuboCLVault.name
1681
- }: harvest => token0Amt: ${token0Amt.toString()}, token1Amt: ${token1Amt.toString()}`
1682
- );
1683
-
1684
- // THis function cannot handle swapping of non-STRK pool,
1685
- // bcz atleast one of token0Amt or token1Amt are in STRK terms.
1686
- const swapInfo = await this.getSwapInfoGivenAmounts(
1687
- poolKey,
1688
- token0Amt,
1689
- token1Amt,
1690
- bounds,
1691
- maxIterations,
1692
- priceRatioPrecision
1693
- );
1694
- swapInfo.token_to_address = token0Info.address.address;
1695
- logger.verbose(
1696
- `${EkuboCLVault.name}: harvest => swapInfo: ${JSON.stringify(swapInfo)}`
1939
+ }: harvest => token0Amt: ${token0Amt.toFixed(18)}, token1Amt: ${token1Amt.toFixed(18)}`
1697
1940
  );
1698
1941
 
1699
1942
  logger.verbose(
1700
1943
  `${EkuboCLVault.name}: harvest => claim: ${JSON.stringify(claim)}`
1701
1944
  );
1702
- const harvestEstimateCall = async (swapInfo1: SwapInfo) => {
1703
- const swap1Amount = Web3Number.fromWei(
1704
- uint256.uint256ToBN(swapInfo1.token_from_amount).toString(),
1705
- 18 // cause its always STRK?
1945
+ const claimTokenInfo = await Global.getTokenInfoFromAddr(claim.token);
1946
+ const harvestEstimateCall = async (baseSwapInfo: SwapInfo) => {
1947
+ // - the base swap if actual swap from claim token to non-claim token
1948
+ // - the other swap is just claim token to claim token (e.g. STRK to STRK)
1949
+ // which is just dummy
1950
+ let baseSwapAmount = Web3Number.fromWei(
1951
+ uint256.uint256ToBN(baseSwapInfo.token_from_amount).toString(),
1952
+ claimTokenInfo.decimals
1706
1953
  ).minimum(
1707
- postFeeAmount.toFixed(18) // cause always strk
1954
+ postFeeAmount.toFixed(claimTokenInfo.decimals)
1708
1955
  ); // ensure we don't swap more than we have
1709
- swapInfo.token_from_amount = uint256.bnToUint256(swap1Amount.toWei());
1710
- swapInfo.token_to_min_amount = uint256.bnToUint256(
1711
- swap1Amount.multipliedBy(0).toWei() // placeholder
1712
- ); // 0.01% slippage
1956
+ if (baseSwapAmount.lt(0.0001)) {
1957
+ baseSwapAmount = new Web3Number(0, claimTokenInfo.decimals);
1958
+ }
1959
+ baseSwapInfo.token_from_amount = uint256.bnToUint256(baseSwapAmount.toWei());
1713
1960
 
1961
+ const isToken0ClaimToken = claim.token.eq(poolKey.token0);
1714
1962
  logger.verbose(
1715
- `${EkuboCLVault.name}: harvest => swap1Amount: ${swap1Amount}`
1963
+ `${EkuboCLVault.name}: harvest => isToken0ClaimToken: ${isToken0ClaimToken}, baseSwapAmount: ${baseSwapAmount}`
1716
1964
  );
1717
1965
 
1718
- const remainingAmount = postFeeAmount.minus(swap1Amount).maximum(0);
1966
+ const remainingAmount = postFeeAmount.minus(baseSwapAmount).maximum(0);
1719
1967
  logger.verbose(
1720
1968
  `${EkuboCLVault.name}: harvest => remainingAmount: ${remainingAmount}`
1721
1969
  );
1722
- const swapInfo2 = {
1723
- ...swapInfo,
1724
- token_from_amount: uint256.bnToUint256(remainingAmount.toWei()),
1725
- };
1726
- swapInfo2.token_to_address = token1Info.address.address;
1970
+
1971
+ // obv, same to same
1972
+ let dummySwapInfo = AvnuWrapper.buildZeroSwap(claim.token, this.address.address, claim.token);
1973
+ dummySwapInfo.token_from_amount = uint256.bnToUint256(remainingAmount.toWei());
1974
+
1727
1975
  logger.verbose(
1728
- `${EkuboCLVault.name}: harvest => swapInfo: ${JSON.stringify(
1729
- swapInfo
1976
+ `${EkuboCLVault.name}: harvest => dummySwapInfo: ${JSON.stringify(
1977
+ dummySwapInfo
1730
1978
  )}`
1731
1979
  );
1732
1980
  logger.verbose(
1733
- `${EkuboCLVault.name}: harvest => swapInfo2: ${JSON.stringify(
1734
- swapInfo2
1981
+ `${EkuboCLVault.name}: harvest => baseSwapInfo: ${JSON.stringify(
1982
+ baseSwapInfo
1735
1983
  )}`
1736
1984
  );
1737
1985
  const calldata = [
@@ -1742,18 +1990,41 @@ export class EkuboCLVault extends BaseStrategy<
1742
1990
  claimee: claim.claim.claimee.address,
1743
1991
  },
1744
1992
  claim.proof.map((p) => num.getDecimalString(p)),
1745
- swapInfo,
1746
- swapInfo2,
1993
+ isToken0ClaimToken ? dummySwapInfo : baseSwapInfo, // is token0 claim token, its just dummy swap
1994
+ isToken0ClaimToken ? baseSwapInfo : dummySwapInfo,
1747
1995
  ];
1748
- logger.verbose(
1749
- `${EkuboCLVault.name}: harvest => calldata: ${JSON.stringify(
1750
- calldata
1751
- )}`
1752
- );
1753
1996
  return [this.contract.populate("harvest", calldata)];
1754
1997
  };
1998
+
1999
+ // if token0 == claim token, then the base swapInfo is from claim token to token1
2000
+ // if token1 == claim token, then the base swapInfo is from claim token to token0
2001
+ const isToken0ClaimToken = claim.token.eq(poolKey.token0);
2002
+ let baseSwapInfo = AvnuWrapper.buildZeroSwap(claim.token, this.address.address, isToken0ClaimToken ? token1Info.address : token0Info.address);
2003
+ baseSwapInfo.token_from_amount = uint256.bnToUint256(postFeeAmount.toWei()); // we try to swap all to start with
2004
+
2005
+ // if token0 != claim token, then we swap from claim token to token0
2006
+ if (postFeeAmount.greaterThan(0) && !isToken0ClaimToken) {
2007
+ const avnuWrapper = new AvnuWrapper();
2008
+ const quote = await avnuWrapper.getQuotes(
2009
+ claim.token.address,
2010
+ token0Info.address.address,
2011
+ postFeeAmount.toWei(),
2012
+ this.address.address
2013
+ );
2014
+ baseSwapInfo = await avnuWrapper.getSwapInfo(quote, this.address.address, 0, this.address.address);
2015
+ } else if (postFeeAmount.greaterThan(0) && isToken0ClaimToken) {
2016
+ // if token0 == claim token, then we swap from claim token to token1
2017
+ const avnuWrapper = new AvnuWrapper();
2018
+ const quote = await avnuWrapper.getQuotes(
2019
+ claim.token.address,
2020
+ token1Info.address.address,
2021
+ postFeeAmount.toWei(),
2022
+ this.address.address
2023
+ );
2024
+ baseSwapInfo = await avnuWrapper.getSwapInfo(quote, this.address.address, 0, this.address.address);
2025
+ }
1755
2026
  const _callsFinal = await this.rebalanceIter(
1756
- swapInfo,
2027
+ baseSwapInfo,
1757
2028
  acc,
1758
2029
  harvestEstimateCall,
1759
2030
  claim.token.eq(poolKey.token0),
@@ -1772,9 +2043,9 @@ export class EkuboCLVault extends BaseStrategy<
1772
2043
 
1773
2044
  /**
1774
2045
  * @description This function handles harvesting of reward token that is not the same as any of the vault token
1775
- * i.e. STRK is not part of vault tokens like BTC/ETH
1776
- * @param params
1777
- * @returns
2046
+ * i.e. STRK is not part of vault tokens like BTC/ETH
2047
+ * @param params
2048
+ * @returns
1778
2049
  */
1779
2050
  async _handleRewardAndVaultTokenMismatchHarvest(params: {
1780
2051
  claim: HarvestInfo;
@@ -1812,7 +2083,7 @@ export class EkuboCLVault extends BaseStrategy<
1812
2083
  return [harvestCall];
1813
2084
  }
1814
2085
 
1815
- // given an amount (i.e. portion of reward to use to swap to token0), returns info on increasing or decreasing
2086
+ // given an amount (i.e. portion of reward to use to swap to token0), returns info on increasing or decreasing
1816
2087
  // amount for binary search
1817
2088
  async harvestMismatchEstimateCallFn(params: {
1818
2089
  postFeeAmount: Web3Number;
@@ -1823,39 +2094,47 @@ export class EkuboCLVault extends BaseStrategy<
1823
2094
  }) {
1824
2095
  const { postFeeAmount, claim, token0Info, token1Info, acc } = params;
1825
2096
  let harvestCall: Call | null = null;
2097
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => postFeeAmount: ${postFeeAmount.toString()}`);
1826
2098
 
2099
+ let attempt = 0;
2100
+ let MAX_ATTEMPTS = 50;
1827
2101
  const binarySearchCallbackFn = async (mid: bigint) => {
2102
+ attempt++;
2103
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => mid: ${mid}, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1828
2104
  const rewardPart2 = BigInt(postFeeAmount.toWei()) - mid;
1829
2105
  const avnuWrapper = new AvnuWrapper();
1830
2106
  const beneficiary = this.address.address;
1831
2107
 
1832
2108
  // get quote for 1st part
1833
- const quote1 = await avnuWrapper.getQuotes(
2109
+ const quote1Prom = avnuWrapper.getQuotes(
1834
2110
  claim.token.address,
1835
2111
  token0Info.address.address,
1836
2112
  mid.toString(),
1837
2113
  beneficiary
1838
2114
  );
2115
+ const quote2Prom = avnuWrapper.getQuotes(
2116
+ claim.token.address,
2117
+ token1Info.address.address,
2118
+ rewardPart2.toString(),
2119
+ beneficiary
2120
+ );
2121
+ const [quote1, quote2] = await Promise.all([quote1Prom, quote2Prom]);
2122
+
1839
2123
  // default min amount is ok
1840
2124
  const swapInfo1 = await avnuWrapper.getSwapInfo(
1841
2125
  quote1,
1842
2126
  beneficiary,
1843
- 0,
2127
+ 0, // fee bps
1844
2128
  beneficiary
1845
2129
  );
1846
2130
 
1847
2131
  // get quote for 2nd part
1848
- const quote2 = await avnuWrapper.getQuotes(
1849
- claim.token.address,
1850
- token1Info.address.address,
1851
- rewardPart2.toString(),
1852
- beneficiary
1853
- );
2132
+
1854
2133
  // default min amount is ok
1855
2134
  const swapInfo2 = await avnuWrapper.getSwapInfo(
1856
2135
  quote2,
1857
2136
  beneficiary,
1858
- 0,
2137
+ 0, // fee bps
1859
2138
  beneficiary
1860
2139
  );
1861
2140
 
@@ -1874,27 +2153,36 @@ export class EkuboCLVault extends BaseStrategy<
1874
2153
  ];
1875
2154
  harvestCall = this.contract.populate("harvest", calldata)
1876
2155
  const gas = await acc.estimateInvokeFee(harvestCall);
2156
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => gas: ${gas.overall_fee.toString()}, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1877
2157
  return 'found';
1878
2158
  } catch (err: any) {
1879
2159
  if (err.message.includes('invalid token0 amount')) {
2160
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => invalid token0 amount, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1880
2161
  // too much token0 amount left, may be swap less to token0
1881
2162
  return 'go_low';
1882
2163
  } else if (err.message.includes('invalid token1 amount')) {
2164
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => invalid token1 amount, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1883
2165
  // too much token1 balance left, may be swap more to token0
1884
2166
  return 'go_high';
1885
2167
  }
2168
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => error: ${err.message}, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1886
2169
  return 'retry';
1887
2170
  }
1888
2171
  }
1889
2172
 
1890
2173
  // run the binary search
1891
2174
  await binarySearch(0n, BigInt(postFeeAmount.toWei()), binarySearchCallbackFn);
1892
-
2175
+
1893
2176
  return harvestCall;
1894
2177
  }
1895
2178
 
1896
2179
  async getInvestmentFlows() {
1897
- const netYield = await this.netAPY();
2180
+ // for LSTs, we use 30d, else 7d for the yield calculation
2181
+ // TODO Make the block compute more dynamic
2182
+ const blocksDiff = this.metadata.additionalInfo.lstContract
2183
+ ? 600000
2184
+ : 600000 / 4;
2185
+ const netYield = await this.netAPY("latest", blocksDiff, "7d" as any);
1898
2186
  const poolKey = await this.getPoolKey();
1899
2187
 
1900
2188
  const linkedFlow: IInvestmentFlow = {
@@ -1915,7 +2203,7 @@ export class EkuboCLVault extends BaseStrategy<
1915
2203
  id: "base",
1916
2204
  title: "Your Deposit",
1917
2205
  subItems: [
1918
- { key: `Net yield`, value: `${(netYield.net * 100).toFixed(2)}%` },
2206
+ { key: `Net yield`, value: `${(netYield * 100).toFixed(2)}%` },
1919
2207
  {
1920
2208
  key: `Performance Fee`,
1921
2209
  value: `${(this.metadata.additionalInfo.feeBps / 100).toFixed(2)}%`,
@@ -1931,8 +2219,8 @@ export class EkuboCLVault extends BaseStrategy<
1931
2219
  subItems: [
1932
2220
  {
1933
2221
  key: "Range selection",
1934
- value: (typeof this.metadata.additionalInfo.newBounds == 'string') ?
1935
- this.metadata.additionalInfo.newBounds :
2222
+ value: (typeof this.metadata.additionalInfo.newBounds == 'string') ?
2223
+ this.metadata.additionalInfo.newBounds :
1936
2224
  `${
1937
2225
  this.metadata.additionalInfo.newBounds.lower *
1938
2226
  Number(poolKey.tick_spacing)
@@ -2053,7 +2341,11 @@ function getLSTFAQs(lstSymbol: string): FAQ[] {
2053
2341
  ]
2054
2342
  }
2055
2343
 
2344
+ const vaultTypeDescription = 'Automatically collects fees and rebalances positions on Ekubo to optimize yield';
2345
+ const vaultType = VaultType.AUTOMATED_LP;
2346
+
2056
2347
  const xSTRKSTRK: IStrategyMetadata<CLVaultStrategySettings> = {
2348
+ id: "ekubo_cl_xstrkstrk",
2057
2349
  name: "Ekubo xSTRK/STRK",
2058
2350
  description: <></>,
2059
2351
  address: ContractAddr.from(
@@ -2061,6 +2353,10 @@ const xSTRKSTRK: IStrategyMetadata<CLVaultStrategySettings> = {
2061
2353
  ),
2062
2354
  launchBlock: 1209881,
2063
2355
  type: "Other",
2356
+ vaultType: {
2357
+ type: vaultType,
2358
+ description: vaultTypeDescription
2359
+ },
2064
2360
  // must be same order as poolKey token0 and token1
2065
2361
  depositTokens: [
2066
2362
  Global.getDefaultTokens().find((t) => t.symbol === "xSTRK")!,
@@ -2068,7 +2364,10 @@ const xSTRKSTRK: IStrategyMetadata<CLVaultStrategySettings> = {
2068
2364
  ],
2069
2365
  protocols: [_protocol],
2070
2366
  auditUrl: AUDIT_URL,
2071
- maxTVL: Web3Number.fromWei("0", 18),
2367
+ curator: {
2368
+ name: "Unwrap Labs",
2369
+ logo: "https://assets.troves.fi/integrations/unwraplabs/white.png"
2370
+ },
2072
2371
  risk: {
2073
2372
  riskFactor: _lstPoolRiskFactors,
2074
2373
  netRisk:
@@ -2078,6 +2377,7 @@ const xSTRKSTRK: IStrategyMetadata<CLVaultStrategySettings> = {
2078
2377
  },
2079
2378
  apyMethodology:
2080
2379
  "APY based on 30-day historical performance, including fees and rewards.",
2380
+ realizedAPYMethodology: "The realizedAPY is based on past 14 days performance by the vault",
2081
2381
  additionalInfo: {
2082
2382
  newBounds: {
2083
2383
  lower: -1,
@@ -2094,270 +2394,529 @@ const xSTRKSTRK: IStrategyMetadata<CLVaultStrategySettings> = {
2094
2394
  },
2095
2395
  quoteAsset: Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
2096
2396
  },
2397
+ settings: {
2398
+ maxTVL: Web3Number.fromWei("0", 18),
2399
+ isAudited: true,
2400
+ isPaused: false,
2401
+ liveStatus: StrategyLiveStatus.ACTIVE,
2402
+ isInstantWithdrawal: true,
2403
+ hideNetEarnings: true,
2404
+ isTransactionHistDisabled: true,
2405
+ quoteToken: Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
2406
+ alerts: [
2407
+ {
2408
+ type: "info",
2409
+ text: (
2410
+ <p>
2411
+ Depending on the current position range and price, your
2412
+ input amounts are automatically adjusted to nearest
2413
+ required amounts. If you have insufficient tokens, you
2414
+ can acquire the required tokens on{" "}
2415
+ <a
2416
+ href="https://avnu.fi"
2417
+ target="_blank"
2418
+ rel="noopener noreferrer"
2419
+ >
2420
+ Avnu
2421
+ </a>
2422
+ </p>
2423
+ ),
2424
+ tab: "deposit"
2425
+ },
2426
+ {
2427
+ type: "info",
2428
+ text: (
2429
+ <>
2430
+ Depending on the current position range and price, you
2431
+ may receive both of the tokens or one of the tokens
2432
+ depending on the price
2433
+ </>
2434
+ ),
2435
+ tab: "withdraw"
2436
+ }
2437
+ ],
2438
+ tags: [StrategyTag.AUTOMATED_LP]
2439
+ },
2097
2440
  faqs: getLSTFAQs("xSTRK"),
2098
2441
  points: [{
2099
- multiplier: 1,
2442
+ multiplier: 15,
2100
2443
  logo: 'https://endur.fi/favicon.ico',
2101
2444
  toolTip: "This strategy holds xSTRK and STRK tokens. Earn 1x Endur points on your xSTRK portion of Liquidity. STRK portion will earn Endur's DEX Bonus points. Points can be found on endur.fi.",
2102
2445
  }],
2103
2446
  contractDetails: [],
2104
- investmentSteps: []
2447
+ investmentSteps: [],
2448
+ tags: [StrategyTag.AUTOMATED_LP],
2449
+ security: {
2450
+ auditStatus: AuditStatus.AUDITED,
2451
+ sourceCode: {
2452
+ type: SourceCodeType.OPEN_SOURCE,
2453
+ contractLink: "https://github.com/trovesfi/troves-contracts",
2454
+ },
2455
+ accessControl: {
2456
+ type: AccessControlType.STANDARD_ACCOUNT,
2457
+ addresses: [ContractAddr.from("0x0")],
2458
+ timeLock: "2 Days",
2459
+ },
2460
+ },
2461
+ redemptionInfo: {
2462
+ instantWithdrawalVault: InstantWithdrawalVault.YES,
2463
+ redemptionsInfo: [],
2464
+ alerts: [],
2465
+ },
2466
+ usualTimeToEarnings: null,
2467
+ usualTimeToEarningsDescription: null,
2105
2468
  };
2106
2469
 
2107
- const lstStrategies: IStrategyMetadata<CLVaultStrategySettings>[] = [
2108
- xSTRKSTRK,
2109
- {
2110
- ...xSTRKSTRK,
2111
- name: "Ekubo xWBTC/WBTC",
2112
- description: <></>,
2113
- address: ContractAddr.from(
2114
- "0x2ea99b4971d3c277fa4a9b4beb7d4d7d169e683393a29eef263d5d57b4380a"
2115
- ),
2116
- launchBlock: 2338309,
2117
- // must be same order as poolKey token0 and token1
2118
- depositTokens: [
2119
- Global.getDefaultTokens().find((t) => t.symbol === "WBTC")!,
2120
- Global.getDefaultTokens().find((t) => t.symbol === "xWBTC")!,
2121
- ],
2122
- additionalInfo: {
2123
- ...xSTRKSTRK.additionalInfo,
2124
- quoteAsset: Global.getDefaultTokens().find((t) => t.symbol === "WBTC")!,
2125
- lstContract: Global.getDefaultTokens().find((t) => t.symbol === "xWBTC")!.address,
2470
+ // Helper to create common LST alerts
2471
+ const getLSTAlerts = () => [
2472
+ {
2473
+ tab: "deposit" as const,
2474
+ text: (
2475
+ <>
2476
+ To acquire the LST, please visit{" "}
2477
+ <a
2478
+ href="https://app.endur.fi"
2479
+ target="_blank"
2480
+ rel="noopener noreferrer"
2481
+ >
2482
+ endur.fi
2483
+ </a>
2484
+ </>
2485
+ ),
2486
+ type: "info" as const
2126
2487
  },
2127
- faqs: getLSTFAQs("xWBTC"),
2128
- points: [],
2129
- contractDetails: [],
2130
- investmentSteps: []
2131
- },
2132
- {
2133
- ...xSTRKSTRK,
2134
- name: "Ekubo xtBTC/tBTC",
2135
- description: <></>,
2136
- address: ContractAddr.from(
2137
- "0x785dc3dfc4e80ef2690a99512481e3ed3a5266180adda5a47e856245d68a4af"
2138
- ),
2139
- launchBlock: 2415667,
2140
- // must be same order as poolKey token0 and token1
2141
- depositTokens: [
2142
- Global.getDefaultTokens().find((t) => t.symbol === "xtBTC")!,
2143
- Global.getDefaultTokens().find((t) => t.symbol === "tBTC")!,
2144
- ],
2145
- additionalInfo: {
2146
- ...xSTRKSTRK.additionalInfo,
2147
- quoteAsset: Global.getDefaultTokens().find((t) => t.symbol === "tBTC")!,
2148
- lstContract: Global.getDefaultTokens().find((t) => t.symbol === "xtBTC")!.address,
2488
+ {
2489
+ type: "info" as const,
2490
+ text: (
2491
+ <p>
2492
+ Depending on the current position range and price, your input
2493
+ amounts are automatically adjusted to nearest required amounts.
2494
+ If you have insufficient tokens, you can acquire the required
2495
+ tokens on{" "}
2496
+ <a
2497
+ href="https://avnu.fi"
2498
+ target="_blank"
2499
+ rel="noopener noreferrer"
2500
+ >
2501
+ Avnu
2502
+ </a>
2503
+ </p>
2504
+ ),
2505
+ tab: "deposit" as const
2149
2506
  },
2150
- faqs: getLSTFAQs("xtBTC"),
2151
- points: [],
2152
- contractDetails: [],
2153
- investmentSteps: []
2154
- },
2155
- {
2507
+ {
2508
+ type: "info" as const,
2509
+ text: (
2510
+ <>
2511
+ Depending on the current position range and price, you may
2512
+ receive both of the tokens or one of the tokens depending on the
2513
+ price
2514
+ </>
2515
+ ),
2516
+ tab: "withdraw" as const
2517
+ }
2518
+ ];
2519
+
2520
+ // Helper to create LST strategy settings
2521
+ const createLSTSettings = (quoteTokenSymbol: string) => ({
2522
+ ...xSTRKSTRK.settings,
2523
+ isAudited: true,
2524
+ liveStatus: StrategyLiveStatus.ACTIVE,
2525
+ isInstantWithdrawal: true,
2526
+ hideNetEarnings: true,
2527
+ isTransactionHistDisabled: true,
2528
+ quoteToken: Global.getDefaultTokens().find(
2529
+ (t) => t.symbol === quoteTokenSymbol
2530
+ )!,
2531
+ alerts: getLSTAlerts(),
2532
+ tags: [StrategyTag.AUTOMATED_LP] as StrategyTag[],
2533
+ });
2534
+
2535
+ // Helper to create an LST strategy
2536
+ const createLSTStrategy = (params: {
2537
+ id: string;
2538
+ name: string;
2539
+ address: string;
2540
+ launchBlock: number;
2541
+ depositToken0Symbol: string;
2542
+ depositToken1Symbol: string;
2543
+ quoteTokenSymbol: string;
2544
+ lstSymbol: string;
2545
+ lstContractAddress?: string;
2546
+ }): IStrategyMetadata<CLVaultStrategySettings> => ({
2156
2547
  ...xSTRKSTRK,
2157
- name: "Ekubo xsBTC/solvBTC",
2548
+ id: params.id,
2549
+ name: params.name,
2158
2550
  description: <></>,
2159
- address: ContractAddr.from(
2160
- "0x3af1c7faa7c464cf2c494e988972ad1939f1103dbfb6e47e9bf0c47e49b14ef"
2161
- ),
2162
- launchBlock: 2344809,
2163
2551
  // must be same order as poolKey token0 and token1
2164
- depositTokens: [
2165
- Global.getDefaultTokens().find((t) => t.symbol === "xsBTC")!,
2166
- Global.getDefaultTokens().find((t) => t.symbol === "solvBTC")!,
2167
- ],
2168
- additionalInfo: {
2169
- ...xSTRKSTRK.additionalInfo,
2170
- quoteAsset: Global.getDefaultTokens().find((t) => t.symbol === "solvBTC")!,
2171
- lstContract: Global.getDefaultTokens().find((t) => t.symbol === "xsBTC")!.address,
2552
+ address: ContractAddr.from(params.address),
2553
+ launchBlock: params.launchBlock,
2554
+ vaultType: {
2555
+ type: vaultType,
2556
+ description: vaultTypeDescription
2172
2557
  },
2173
- faqs: getLSTFAQs("xsBTC"),
2174
- points: [],
2175
- contractDetails: [],
2176
- investmentSteps: []
2177
- },
2178
- {
2179
- ...xSTRKSTRK,
2180
- name: "Ekubo xLBTC/LBTC",
2181
- description: <></>,
2182
- address: ContractAddr.from(
2183
- "0x314c4653ab1aa01f5465773cb879f525d7e369a137bc3ae084761aee99a1712"
2184
- ),
2185
- launchBlock: 2412442,
2186
- // must be same order as poolKey token0 and token1
2187
2558
  depositTokens: [
2188
- Global.getDefaultTokens().find((t) => t.symbol === "LBTC")!,
2189
- Global.getDefaultTokens().find((t) => t.symbol === "xLBTC")!,
2559
+ Global.getDefaultTokens().find(
2560
+ (t) => t.symbol === params.depositToken0Symbol
2561
+ )!,
2562
+ Global.getDefaultTokens().find((t) => t.symbol === params.depositToken1Symbol)!
2190
2563
  ],
2564
+ realizedAPYMethodology: "The realizedAPY is based on past 14 days performance by the vault",
2191
2565
  additionalInfo: {
2192
- ...xSTRKSTRK.additionalInfo,
2193
- quoteAsset: Global.getDefaultTokens().find((t) => t.symbol === "LBTC")!,
2194
- lstContract: Global.getDefaultTokens().find((t) => t.symbol === "xLBTC")!.address,
2566
+ ...xSTRKSTRK.additionalInfo,
2567
+ quoteAsset: Global.getDefaultTokens().find(
2568
+ (t) => t.symbol === params.quoteTokenSymbol
2569
+ )!,
2570
+ lstContract: params.lstContractAddress
2571
+ ? ContractAddr.from(params.lstContractAddress)
2572
+ : Global.getDefaultTokens().find((t) => t.symbol === params.lstSymbol)!
2573
+ .address
2195
2574
  },
2196
- faqs: getLSTFAQs("xLBTC"),
2575
+ settings: createLSTSettings(params.quoteTokenSymbol),
2576
+ faqs: getLSTFAQs(params.lstSymbol),
2197
2577
  points: [],
2198
2578
  contractDetails: [],
2199
- investmentSteps: []
2200
- }
2579
+ investmentSteps: [],
2580
+ tags: params.id.toLowerCase().includes('btc') ? [StrategyTag.BTC, StrategyTag.AUTOMATED_LP] : [StrategyTag.AUTOMATED_LP]
2581
+ });
2582
+
2583
+ const lstStrategies: IStrategyMetadata<CLVaultStrategySettings>[] = [
2584
+ xSTRKSTRK,
2585
+ createLSTStrategy({
2586
+ id: "ekubo_cl_xwbtcwbtc",
2587
+ name: "Ekubo xWBTC/WBTC",
2588
+ address: "0x2ea99b4971d3c277fa4a9b4beb7d4d7d169e683393a29eef263d5d57b4380a",
2589
+ launchBlock: 2338309,
2590
+ depositToken0Symbol: "WBTC",
2591
+ depositToken1Symbol: "xWBTC",
2592
+ quoteTokenSymbol: "WBTC",
2593
+ lstSymbol: "xWBTC",
2594
+ }),
2595
+ createLSTStrategy({
2596
+ id: "ekubo_cl_xtbtctbtc",
2597
+ name: "Ekubo xtBTC/tBTC",
2598
+ address: "0x785dc3dfc4e80ef2690a99512481e3ed3a5266180adda5a47e856245d68a4af",
2599
+ launchBlock: 2415667,
2600
+ depositToken0Symbol: "xtBTC",
2601
+ depositToken1Symbol: "tBTC",
2602
+ quoteTokenSymbol: "tBTC",
2603
+ lstSymbol: "xtBTC",
2604
+ }),
2605
+ createLSTStrategy({
2606
+ id: "ekubo_cl_xsbtcsolvbtc",
2607
+ name: "Ekubo xsBTC/solvBTC",
2608
+ address: "0x3af1c7faa7c464cf2c494e988972ad1939f1103dbfb6e47e9bf0c47e49b14ef",
2609
+ launchBlock: 2344809,
2610
+ depositToken0Symbol: "xsBTC",
2611
+ depositToken1Symbol: "solvBTC",
2612
+ quoteTokenSymbol: "solvBTC",
2613
+ lstSymbol: "xsBTC",
2614
+ }),
2615
+ createLSTStrategy({
2616
+ id: "ekubo_cl_xlbtclbtc",
2617
+ name: "Ekubo xLBTC/LBTC",
2618
+ address: "0x314c4653ab1aa01f5465773cb879f525d7e369a137bc3ae084761aee99a1712",
2619
+ launchBlock: 2412442,
2620
+ depositToken0Symbol: "LBTC",
2621
+ depositToken1Symbol: "xLBTC",
2622
+ quoteTokenSymbol: "LBTC",
2623
+ lstSymbol: "xLBTC",
2624
+ })
2201
2625
  ];
2202
2626
 
2203
- const ETHUSDCRe7Strategy: IStrategyMetadata<CLVaultStrategySettings> = {
2204
- ...xSTRKSTRK,
2205
- name: "Ekubo ETH/USDC",
2206
- description: <></>,
2207
- address: ContractAddr.from(
2208
- "0x160d8fa4569ef6a12e6bf47cb943d7b5ebba8a41a69a14c1d943050ba5ff947"
2209
- ),
2210
- launchBlock: 1504232,
2211
- // must be same order as poolKey token0 and token1
2212
- depositTokens: [
2213
- Global.getDefaultTokens().find((t) => t.symbol === "ETH")!,
2214
- Global.getDefaultTokens().find((t) => t.symbol === "USDC")!
2215
- ],
2216
- apyMethodology:
2217
- "Annualized fee APY, calculated as fees earned in the last 7d divided by TVL",
2218
- additionalInfo: {
2219
- newBounds: "Managed by Re7",
2220
- truePrice: 1,
2221
- feeBps: 1000,
2222
- rebalanceConditions: {
2223
- customShouldRebalance: async (currentPrice: number) =>
2224
- currentPrice > 0.99 && currentPrice < 1.01,
2225
- minWaitHours: 6,
2226
- direction: "any"
2627
+ const getRe7Alerts = () => [
2628
+ {
2629
+ type: "info" as const,
2630
+ text: (
2631
+ <p>
2632
+ Depending on the current position range and price, your input
2633
+ amounts are automatically adjusted to nearest required amounts.
2634
+ If you have insufficient tokens, you can acquire the required
2635
+ tokens on{" "}
2636
+ <a
2637
+ href="https://avnu.fi"
2638
+ target="_blank"
2639
+ rel="noopener noreferrer"
2640
+ >
2641
+ Avnu
2642
+ </a>
2643
+ </p>
2644
+ ),
2645
+ tab: "deposit" as const
2227
2646
  },
2228
- quoteAsset: Global.getDefaultTokens().find((t) => t.symbol === "USDC")!,
2229
- },
2230
- faqs: [
2647
+ {
2648
+ type: "info" as const,
2649
+ text: (
2650
+ <>
2651
+ Depending on the current position range and price, you may
2652
+ receive both of the tokens or one of the tokens depending on the
2653
+ price
2654
+ </>
2655
+ ),
2656
+ tab: "withdraw" as const
2657
+ }
2658
+ ];
2659
+
2660
+ // Helper to create Re7 strategy settings
2661
+ const createRe7Settings = (quoteTokenSymbol: string, isBTC: boolean, isDeprecated: boolean) => ({
2662
+ ...xSTRKSTRK.settings,
2663
+ isAudited: true,
2664
+ liveStatus: isDeprecated ? StrategyLiveStatus.DEPRECATED : StrategyLiveStatus.ACTIVE,
2665
+ isInstantWithdrawal: true,
2666
+ hideNetEarnings: true,
2667
+ isTransactionHistDisabled: false,
2668
+ quoteToken: Global.getDefaultTokens().find(
2669
+ (t) => t.symbol === quoteTokenSymbol
2670
+ )!,
2671
+ alerts: getRe7Alerts(),
2672
+ tags: isBTC ? [StrategyTag.BTC, StrategyTag.AUTOMATED_LP] : [StrategyTag.AUTOMATED_LP] as StrategyTag[],
2673
+ });
2674
+
2675
+ // Helper to create Re7 FAQs
2676
+ const getRe7FAQs = () => [
2231
2677
  ...faqs,
2232
2678
  {
2233
- question: "Who is the curator of this strategy?",
2234
- answer:
2235
- <div>Re7 Labs is the curator of this strategy. Re7 Labs is a well-known Web3 asset management firm. This strategy is completely managed by them, including ownership of the vault. Troves is developer of the smart contracts and maintains infrastructure to help users access these strategies. You can find more information about them on their website <a href='https://www.re7labs.xyz' style={{textDecoration: "underline", marginLeft: "2px"}} target="_blank">here</a>.</div>
2679
+ question: "Who is the curator of this strategy?",
2680
+ answer: (
2681
+ <div>
2682
+ Re7 Labs is the curator of this strategy. Re7 Labs is a
2683
+ well-known Web3 asset management firm. This strategy is
2684
+ completely managed by them, including ownership of the vault.
2685
+ Troves is developer of the smart contracts and maintains
2686
+ infrastructure to help users access these strategies. You can
2687
+ find more information about them on their website{" "}
2688
+ <a
2689
+ href="https://www.re7labs.xyz"
2690
+ style={{
2691
+ textDecoration: "underline",
2692
+ marginLeft: "2px"
2693
+ }}
2694
+ target="_blank"
2695
+ >
2696
+ here
2697
+ </a>
2698
+ .
2699
+ </div>
2700
+ )
2236
2701
  },
2237
2702
  {
2238
- question: "How is the APY calculated?",
2239
- answer:
2240
- <div>It's an annualized fee APY, calculated as fees earned in the last 24h divided by TVL. Factors like impermanent loss are not considered.</div>
2241
- },
2242
- ],
2243
- risk: highRisk,
2244
- points: [],
2245
- curator: { name: "Re7 Labs", logo: "https://www.re7labs.xyz/favicon.ico" }
2703
+ question: "How is the APY calculated?",
2704
+ answer: (
2705
+ <div>
2706
+ It's an annualized fee APY, calculated as fees earned in the
2707
+ last 24h divided by TVL. Factors like impermanent loss are not
2708
+ considered.
2709
+ </div>
2710
+ )
2711
+ }
2712
+ ];
2713
+
2714
+ // Helper to create a Re7 strategy
2715
+ const createRe7Strategy = (
2716
+ id: string,
2717
+ name: string,
2718
+ address: string,
2719
+ launchBlock: number,
2720
+ depositToken0Symbol: string,
2721
+ depositToken1Symbol: string,
2722
+ quoteTokenSymbol: string,
2723
+ risk:
2724
+ | typeof highRisk
2725
+ | typeof mediumRisk
2726
+ | { riskFactor: RiskFactor[]; netRisk: number; notARisks: RiskType[] },
2727
+ isBTC: boolean
2728
+ ): IStrategyMetadata<CLVaultStrategySettings> => {
2729
+ const isDeprecated = name.toLowerCase().includes('usdc.e');
2730
+ return {
2731
+ ...xSTRKSTRK,
2732
+ id,
2733
+ name,
2734
+ description: <></>,
2735
+ address: ContractAddr.from(address),
2736
+ launchBlock,
2737
+ vaultType: {
2738
+ type: vaultType,
2739
+ description: vaultTypeDescription
2740
+ },
2741
+ depositTokens: [
2742
+ Global.getDefaultTokens().find(
2743
+ (t) => t.symbol === depositToken0Symbol
2744
+ )!,
2745
+ Global.getDefaultTokens().find((t) => t.symbol === depositToken1Symbol)!
2746
+ ],
2747
+ apyMethodology:
2748
+ "Annualized fee APY, calculated as fees earned in the last 7d divided by TVL",
2749
+ additionalInfo: {
2750
+ newBounds: "Managed by Re7",
2751
+ truePrice: 1,
2752
+ feeBps: 1000,
2753
+ rebalanceConditions: {
2754
+ customShouldRebalance: async (currentPrice: number) =>
2755
+ currentPrice > 0.99 && currentPrice < 1.01,
2756
+ minWaitHours: 6,
2757
+ direction: "any" as const
2758
+ },
2759
+ quoteAsset: Global.getDefaultTokens().find(
2760
+ (t) => t.symbol === quoteTokenSymbol
2761
+ )!
2762
+ },
2763
+ settings: createRe7Settings(quoteTokenSymbol, isBTC, isDeprecated),
2764
+ faqs: getRe7FAQs(),
2765
+ risk,
2766
+ points: [],
2767
+ curator: { name: "Re7 Labs", logo: "https://www.re7labs.xyz/favicon.ico" },
2768
+ tags: isBTC ? [StrategyTag.BTC, StrategyTag.AUTOMATED_LP] : [StrategyTag.AUTOMATED_LP] as StrategyTag[],
2769
+ discontinuationInfo: isDeprecated ? {
2770
+ info: "This strategy has been deprecated and is no longer accepting new deposits."
2771
+ } : undefined,
2772
+ };
2773
+ };
2774
+
2775
+ const ETHUSDCRe7Strategy = createRe7Strategy(
2776
+ "ekubo_cl_ethusdc",
2777
+ "Ekubo ETH/USDC.e",
2778
+ "0x160d8fa4569ef6a12e6bf47cb943d7b5ebba8a41a69a14c1d943050ba5ff947",
2779
+ 1504232,
2780
+ "ETH",
2781
+ "USDC.e",
2782
+ "USDC.e",
2783
+ highRisk,
2784
+ false // isBTC
2785
+ );
2786
+
2787
+ const stableCoinRisk = {
2788
+ riskFactor: _stableCoinPoolRiskFactors,
2789
+ netRisk:
2790
+ _stableCoinPoolRiskFactors.reduce(
2791
+ (acc, curr) => acc + curr.value * curr.weight,
2792
+ 0
2793
+ ) /
2794
+ _stableCoinPoolRiskFactors.reduce((acc, curr) => acc + curr.weight, 0),
2795
+ notARisks: getNoRiskTags(_stableCoinPoolRiskFactors)
2246
2796
  };
2247
2797
 
2248
2798
  const RE7Strategies: IStrategyMetadata<CLVaultStrategySettings>[] = [
2249
- ETHUSDCRe7Strategy,
2250
- {
2251
- ...ETHUSDCRe7Strategy,
2252
- name: "Ekubo USDC/USDT",
2253
- description: <></>,
2254
- address: ContractAddr.from(
2255
- "0x3a4f8debaf12af97bb911099bc011d63d6c208d4c5ba8e15d7f437785b0aaa2"
2799
+ ETHUSDCRe7Strategy,
2800
+ createRe7Strategy(
2801
+ "ekubo_cl_usdcusdt",
2802
+ "Ekubo USDC.e/USDT",
2803
+ "0x3a4f8debaf12af97bb911099bc011d63d6c208d4c5ba8e15d7f437785b0aaa2",
2804
+ 1506139,
2805
+ "USDC.e",
2806
+ "USDT",
2807
+ "USDC.e",
2808
+ stableCoinRisk,
2809
+ false // isBTC
2256
2810
  ),
2257
- launchBlock: 1506139,
2258
- // must be same order as poolKey token0 and token1
2259
- depositTokens: [
2260
- Global.getDefaultTokens().find((t) => t.symbol === "USDC")!,
2261
- Global.getDefaultTokens().find((t) => t.symbol === "USDT")!
2262
- ],
2263
- risk: {
2264
- riskFactor: _stableCoinPoolRiskFactors,
2265
- netRisk:
2266
- _stableCoinPoolRiskFactors.reduce((acc, curr) => acc + curr.value * curr.weight, 0) /
2267
- _stableCoinPoolRiskFactors.reduce((acc, curr) => acc + curr.weight, 0),
2268
- notARisks: getNoRiskTags(_stableCoinPoolRiskFactors),
2269
- }
2270
- },
2271
- {
2272
- ...ETHUSDCRe7Strategy,
2273
- name: "Ekubo STRK/USDC",
2274
- description: <></>,
2275
- address: ContractAddr.from(
2276
- "0x351b36d0d9d8b40010658825adeeddb1397436cd41acd0ff6c6e23aaa8b5b30"
2811
+ createRe7Strategy(
2812
+ "ekubo_cl_strkusdc",
2813
+ "Ekubo STRK/USDC.e",
2814
+ "0x351b36d0d9d8b40010658825adeeddb1397436cd41acd0ff6c6e23aaa8b5b30",
2815
+ 1504079,
2816
+ "STRK",
2817
+ "USDC.e",
2818
+ "USDC.e",
2819
+ highRisk,
2820
+ false // isBTC
2277
2821
  ),
2278
- launchBlock: 1504079,
2279
- // must be same order as poolKey token0 and token1
2280
- depositTokens: [
2281
- Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
2282
- Global.getDefaultTokens().find((t) => t.symbol === "USDC")!
2283
- ],
2284
- risk: highRisk,
2285
- },
2286
- {
2287
- ...ETHUSDCRe7Strategy,
2288
- name: "Ekubo STRK/ETH",
2289
- description: <></>,
2290
- address: ContractAddr.from(
2291
- "0x4ce3024b0ee879009112d7b0e073f8a87153dd35b029347d4247ffe48d28f51"
2822
+ createRe7Strategy(
2823
+ "ekubo_cl_strketh",
2824
+ "Ekubo STRK/ETH",
2825
+ "0x4ce3024b0ee879009112d7b0e073f8a87153dd35b029347d4247ffe48d28f51",
2826
+ 1504149,
2827
+ "STRK",
2828
+ "ETH",
2829
+ "USDC",
2830
+ highRisk,
2831
+ false // isBTC
2292
2832
  ),
2293
- launchBlock: 1504149,
2294
- // must be same order as poolKey token0 and token1
2295
- depositTokens: [
2296
- Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
2297
- Global.getDefaultTokens().find((t) => t.symbol === "ETH")!
2298
- ],
2299
- risk: highRisk,
2300
- },
2301
- {
2302
- ...ETHUSDCRe7Strategy,
2303
- name: "Ekubo WBTC/USDC",
2304
- description: <></>,
2305
- address: ContractAddr.from(
2306
- "0x2bcaef2eb7706875a5fdc6853dd961a0590f850bc3a031c59887189b5e84ba1"
2833
+ createRe7Strategy(
2834
+ "ekubo_cl_wbtcusdc",
2835
+ "Ekubo WBTC/USDC.e",
2836
+ "0x2bcaef2eb7706875a5fdc6853dd961a0590f850bc3a031c59887189b5e84ba1",
2837
+ 1506144,
2838
+ "WBTC",
2839
+ "USDC.e",
2840
+ "USDC.e",
2841
+ mediumRisk,
2842
+ true // isBTC
2307
2843
  ),
2308
- launchBlock: 1506144,
2309
- // must be same order as poolKey token0 and token1
2310
- depositTokens: [
2311
- Global.getDefaultTokens().find((t) => t.symbol === "WBTC")!,
2312
- Global.getDefaultTokens().find((t) => t.symbol === "USDC")!
2313
- ],
2314
- risk: mediumRisk,
2315
- },
2316
- {
2317
- ...ETHUSDCRe7Strategy,
2318
- name: "Ekubo tBTC/USDC",
2319
- description: <></>,
2320
- address: ContractAddr.from(
2321
- "0x4aad891a2d4432fba06b6558631bb13f6bbd7f6f33ab8c3111e344889ea4456"
2844
+ // createRe7Strategy(
2845
+ // "ekubo_cl_tbtcusdce",
2846
+ // "Ekubo tBTC/USDC.e",
2847
+ // "0x4aad891a2d4432fba06b6558631bb13f6bbd7f6f33ab8c3111e344889ea4456",
2848
+ // 1501764,
2849
+ // "tBTC",
2850
+ // "USDC.e",
2851
+ // "USDC.e",
2852
+ // mediumRisk,
2853
+ // ),
2854
+ createRe7Strategy(
2855
+ "ekubo_cl_wbtceth",
2856
+ "Ekubo WBTC/ETH",
2857
+ "0x1c9232b8186d9317652f05055615f18a120c2ad9e5ee96c39e031c257fb945b",
2858
+ 1506145,
2859
+ "WBTC",
2860
+ "ETH",
2861
+ "USDC",
2862
+ mediumRisk,
2863
+ true // isBTC
2322
2864
  ),
2323
- launchBlock: 1501764,
2324
- // must be same order as poolKey token0 and token1
2325
- depositTokens: [
2326
- Global.getDefaultTokens().find((t) => t.symbol === "tBTC")!,
2327
- Global.getDefaultTokens().find((t) => t.symbol === "USDC")!
2328
- ],
2329
- risk: mediumRisk,
2330
- },
2331
- {
2332
- ...ETHUSDCRe7Strategy,
2333
- name: "Ekubo WBTC/ETH",
2334
- description: <></>,
2335
- address: ContractAddr.from(
2336
- "0x1c9232b8186d9317652f05055615f18a120c2ad9e5ee96c39e031c257fb945b"
2865
+ createRe7Strategy(
2866
+ "ekubo_cl_wbtcstrk",
2867
+ "Ekubo WBTC/STRK",
2868
+ "0x1248e385c23a929a015ec298a26560fa7745bbd6e41a886550e337b02714b1b",
2869
+ 1506147,
2870
+ "WBTC",
2871
+ "STRK",
2872
+ "USDC",
2873
+ highRisk,
2874
+ true // isBTC
2337
2875
  ),
2338
- launchBlock: 1506145,
2339
- // must be same order as poolKey token0 and token1
2340
- depositTokens: [
2341
- Global.getDefaultTokens().find((t) => t.symbol === "WBTC")!,
2342
- Global.getDefaultTokens().find((t) => t.symbol === "ETH")!
2343
- ],
2344
- risk: mediumRisk,
2345
- },
2346
- {
2347
- ...ETHUSDCRe7Strategy,
2348
- name: "Ekubo WBTC/STRK",
2349
- description: <></>,
2350
- address: ContractAddr.from(
2351
- "0x1248e385c23a929a015ec298a26560fa7745bbd6e41a886550e337b02714b1b"
2876
+ createRe7Strategy(
2877
+ "ekubo_cl_usdc_v2usdt",
2878
+ "Ekubo USDC/USDT",
2879
+ "0x5203a08b471e46bf33990ac83aff577bbe5a5d789e61de2c6531e3c4773d1c9",
2880
+ 3998018,
2881
+ "USDC",
2882
+ "USDT",
2883
+ "USDC",
2884
+ stableCoinRisk,
2885
+ false // isBTC
2352
2886
  ),
2353
- launchBlock: 1506147,
2354
- // must be same order as poolKey token0 and token1
2355
- depositTokens: [
2356
- Global.getDefaultTokens().find((t) => t.symbol === "WBTC")!,
2357
- Global.getDefaultTokens().find((t) => t.symbol === "STRK")!
2358
- ],
2359
- risk: highRisk,
2360
- }
2887
+ createRe7Strategy(
2888
+ "ekubo_cl_ethusdc_v2",
2889
+ "Ekubo ETH/USDC",
2890
+ "0x4d00c7423b3c0fae3640f6099ac97acbfd8708f099e09bfe3a7a6a680399228",
2891
+ 3998025,
2892
+ "USDC",
2893
+ "ETH",
2894
+ "USDC",
2895
+ highRisk,
2896
+ false // isBTC
2897
+ ),
2898
+ createRe7Strategy(
2899
+ "ekubo_cl_strkusdc_v2",
2900
+ "Ekubo STRK/USDC",
2901
+ "0x4de22bd0a8eb4d0a18736e66dd36d20ba50bc106346bbfac3dbeaac1ab37ce1",
2902
+ 3998030,
2903
+ "USDC",
2904
+ "STRK",
2905
+ "USDC",
2906
+ highRisk,
2907
+ false // isBTC
2908
+ ),
2909
+ createRe7Strategy(
2910
+ "ekubo_cl_wbtcusdc_v2",
2911
+ "Ekubo WBTC/USDC",
2912
+ "0x76101c3b80af1103c9c6d541ca627f61b5ae7ae79d7fce96ccdf7bdb648450d",
2913
+ 3998034,
2914
+ "USDC",
2915
+ "WBTC",
2916
+ "USDC",
2917
+ mediumRisk,
2918
+ true // isBTC
2919
+ )
2361
2920
  ];
2362
2921
 
2363
2922
  /**
@@ -2375,7 +2934,7 @@ EkuboCLVaultStrategies.forEach((s) => {
2375
2934
  address: s.address,
2376
2935
  name: "Vault",
2377
2936
  sourceCodeUrl: "https://github.com/strkfarm/strkfarm-contracts/tree/main/src/strategies/cl_vault"
2378
- },
2937
+ },
2379
2938
  // ...COMMON_CONTRACTS
2380
2939
  ];
2381
2940
  // set docs link
@@ -2419,4 +2978,4 @@ EkuboCLVaultStrategies.forEach((s) => {
2419
2978
  "Monitor and Rebalance position to optimize yield",
2420
2979
  "Harvest and re-invest any rewards every week (Auto-compound)",
2421
2980
  ]
2422
- });
2981
+ });