@strkfarm/sdk 2.0.0-dev.4 → 2.0.0-dev.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/cli.js +190 -36
  2. package/dist/cli.mjs +188 -34
  3. package/dist/index.browser.global.js +116250 -90801
  4. package/dist/index.browser.mjs +13050 -10957
  5. package/dist/index.d.ts +2232 -1933
  6. package/dist/index.js +13380 -11084
  7. package/dist/index.mjs +13280 -11007
  8. package/package.json +6 -7
  9. package/src/data/avnu.abi.json +840 -0
  10. package/src/data/ekubo-price-fethcer.abi.json +265 -0
  11. package/src/data/redeem-request-nft.abi.json +752 -0
  12. package/src/data/universal-vault.abi.json +8 -7
  13. package/src/dataTypes/_bignumber.ts +13 -4
  14. package/src/dataTypes/bignumber.browser.ts +10 -1
  15. package/src/dataTypes/bignumber.node.ts +10 -1
  16. package/src/dataTypes/index.ts +3 -2
  17. package/src/dataTypes/mynumber.ts +141 -0
  18. package/src/global.ts +93 -36
  19. package/src/index.browser.ts +2 -1
  20. package/src/interfaces/common.tsx +218 -5
  21. package/src/modules/apollo-client-config.ts +28 -0
  22. package/src/modules/avnu.ts +21 -12
  23. package/src/modules/ekubo-pricer.ts +79 -0
  24. package/src/modules/ekubo-quoter.ts +48 -30
  25. package/src/modules/erc20.ts +17 -0
  26. package/src/modules/harvests.ts +43 -29
  27. package/src/modules/index.ts +2 -1
  28. package/src/modules/pragma.ts +23 -8
  29. package/src/modules/pricer-avnu-api.ts +114 -0
  30. package/src/modules/pricer-from-api.ts +156 -15
  31. package/src/modules/pricer-lst.ts +1 -1
  32. package/src/modules/pricer.ts +94 -40
  33. package/src/modules/pricerBase.ts +2 -1
  34. package/src/node/deployer.ts +36 -1
  35. package/src/node/pricer-redis.ts +3 -1
  36. package/src/strategies/base-strategy.ts +168 -16
  37. package/src/strategies/constants.ts +8 -3
  38. package/src/strategies/ekubo-cl-vault.tsx +1047 -351
  39. package/src/strategies/factory.ts +199 -0
  40. package/src/strategies/index.ts +5 -3
  41. package/src/strategies/registry.ts +262 -0
  42. package/src/strategies/sensei.ts +353 -9
  43. package/src/strategies/svk-strategy.ts +283 -31
  44. package/src/strategies/token-boosted-xstrk-carry-strategy.tsx +1262 -0
  45. package/src/strategies/types.ts +4 -0
  46. package/src/strategies/universal-adapters/adapter-utils.ts +4 -1
  47. package/src/strategies/universal-adapters/avnu-adapter.ts +196 -272
  48. package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
  49. package/src/strategies/universal-adapters/common-adapter.ts +206 -203
  50. package/src/strategies/universal-adapters/index.ts +10 -8
  51. package/src/strategies/universal-adapters/svk-troves-adapter.ts +511 -0
  52. package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
  53. package/src/strategies/universal-adapters/vesu-adapter.ts +120 -82
  54. package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +525 -0
  55. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1098 -712
  56. package/src/strategies/universal-adapters/vesu-position-common.ts +258 -0
  57. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
  58. package/src/strategies/universal-lst-muliplier-strategy.tsx +631 -414
  59. package/src/strategies/universal-strategy.tsx +1331 -1173
  60. package/src/strategies/vesu-rebalance.tsx +252 -152
  61. package/src/strategies/yoloVault.ts +1087 -0
  62. package/src/utils/cacheClass.ts +11 -2
  63. package/src/utils/health-factor-math.ts +33 -1
  64. package/src/utils/index.ts +3 -1
  65. package/src/utils/logger.browser.ts +22 -4
  66. package/src/utils/logger.node.ts +259 -24
  67. package/src/utils/starknet-call-parser.ts +1036 -0
  68. package/src/utils/strategy-utils.ts +61 -0
  69. package/src/modules/ExtendedWrapperSDk/index.ts +0 -62
  70. package/src/modules/ExtendedWrapperSDk/types.ts +0 -311
  71. package/src/modules/ExtendedWrapperSDk/wrapper.ts +0 -395
  72. package/src/strategies/universal-adapters/extended-adapter.ts +0 -661
  73. package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
  74. package/src/strategies/vesu-extended-strategy/services/operationService.ts +0 -34
  75. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +0 -77
  76. package/src/strategies/vesu-extended-strategy/utils/constants.ts +0 -49
  77. package/src/strategies/vesu-extended-strategy/utils/helper.ts +0 -372
  78. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +0 -1140
@@ -11,8 +11,16 @@ 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,
23
+ UnwrapLabsCurator,
16
24
  } from "@/interfaces";
17
25
  import { PricerBase } from "@/modules/pricerBase";
18
26
  import { assert } from "@/utils";
@@ -30,18 +38,25 @@ import EkuboMathAbi from "@/data/ekubo-math.abi.json";
30
38
  import ERC4626Abi from "@/data/erc4626.abi.json";
31
39
  import { Global } from "@/global";
32
40
  import { AvnuWrapper, ERC20, SwapInfo } from "@/modules";
33
- import { APYInfo, BaseStrategy, SingleTokenInfo } from "./base-strategy";
41
+ import {
42
+ APYInfo,
43
+ BaseStrategy,
44
+ SingleTokenInfo,
45
+ UserPositionCard,
46
+ UserPositionCardsInput,
47
+ } from "./base-strategy";
34
48
  import { DualActionAmount } from "./base-strategy";
35
49
  import { DualTokenInfo } from "./base-strategy";
36
50
  import { log } from "winston";
37
51
  import { EkuboHarvests, HarvestInfo } from "@/modules/harvests";
38
52
  import { logger } from "@/utils/logger";
39
- import { COMMON_CONTRACTS } from "./constants";
40
53
  import { DepegRiskLevel, ImpermanentLossLevel, MarketRiskLevel, SmartContractRiskLevel } from "@/interfaces/risks";
41
- import { gql } from "@apollo/client";
54
+ import { from, gql } from "@apollo/client";
42
55
  import apolloClient from "@/modules/apollo-client";
43
56
  import { binarySearch } from "@/utils/math-utils";
44
57
  import { PositionInfo } from "./universal-adapters/baseAdapter";
58
+ import { Quote } from "@avnu/avnu-sdk";
59
+ import { MY_ACCESS_CONTROL } from "./constants";
45
60
 
46
61
  export interface EkuboPoolKey {
47
62
  token0: ContractAddr;
@@ -118,7 +133,10 @@ export class EkuboCLVault extends BaseStrategy<
118
133
  pricer: PricerBase,
119
134
  metadata: IStrategyMetadata<CLVaultStrategySettings>
120
135
  ) {
121
- super(config);
136
+ super(config, {
137
+ depositInputMode: "dual",
138
+ withdrawInputMode: "dual"
139
+ });
122
140
  this.pricer = pricer;
123
141
 
124
142
  assert(
@@ -296,7 +314,10 @@ export class EkuboCLVault extends BaseStrategy<
296
314
  return [this.contract.populate("handle_fees", [])];
297
315
  }
298
316
 
299
- async getFeeHistory(timePeriod: '24h' | '7d' | '30d' | '3m' = '24h'): Promise<{
317
+ async getFeeHistory(
318
+ timePeriod: '24h' | '7d' | '30d' | '3m' | '6m' = '24h',
319
+ range?: { startTimestamp?: number; endTimestamp?: number }
320
+ ): Promise<{
300
321
  summary: DualTokenInfo,
301
322
  history: FeeHistory[]
302
323
  }> {
@@ -305,8 +326,15 @@ export class EkuboCLVault extends BaseStrategy<
305
326
  query ContractFeeEarnings(
306
327
  $timeframe: String!
307
328
  $contract: String!
329
+ $startTimestamp: Float
330
+ $endTimestamp: Float
308
331
  ) {
309
- contractFeeEarnings(timeframe: $timeframe, contract: $contract) {
332
+ contractFeeEarnings(
333
+ timeframe: $timeframe
334
+ contract: $contract
335
+ startTimestamp: $startTimestamp
336
+ endTimestamp: $endTimestamp
337
+ ) {
310
338
  contract
311
339
  dailyEarnings {
312
340
  date
@@ -319,7 +347,9 @@ export class EkuboCLVault extends BaseStrategy<
319
347
  `,
320
348
  variables: {
321
349
  timeframe: timePeriod,
322
- contract: this.address.address
350
+ contract: this.address.address,
351
+ startTimestamp: range?.startTimestamp,
352
+ endTimestamp: range?.endTimestamp
323
353
  },
324
354
  fetchPolicy: 'no-cache',
325
355
  });
@@ -418,22 +448,41 @@ export class EkuboCLVault extends BaseStrategy<
418
448
  const tvlBefore = await this._getTVL(blockBefore);
419
449
  const supplyBefore = await this.totalSupply(blockBefore);
420
450
  const priceBefore = await this.getCurrentPrice(blockBefore);
421
-
422
- const tvlInToken0Now = (tvlNow.amount0
423
- .multipliedBy(priceNow.price))
424
- .plus(tvlNow.amount1);
425
- const tvlPerShareNow = tvlInToken0Now
451
+ const poolKey = await this.getPoolKey(blockBefore);
452
+ logger.verbose(`priceBefore: ${priceBefore.price.toString()}`);
453
+ logger.verbose(`priceNow: ${priceNow.price.toString()}`);
454
+ logger.verbose(`tvlBefore: ${JSON.stringify(tvlBefore)}`);
455
+ logger.verbose(`tvlNow: ${JSON.stringify(tvlNow)}`);
456
+
457
+ const isQuoteTokenToken0 = this.metadata.additionalInfo.quoteAsset.address.eq(poolKey.token0);
458
+ logger.verbose(`isQuoteTokenToken0: ${isQuoteTokenToken0}`);
459
+ let tvlBeforeInBaseAsset = Web3Number.fromWei(0, this.metadata.additionalInfo.quoteAsset.decimals);
460
+ let tvlNowInBaseAsset = Web3Number.fromWei(0, this.metadata.additionalInfo.quoteAsset.decimals);
461
+ if (!isQuoteTokenToken0) {
462
+ tvlNowInBaseAsset = (tvlNow.amount0
463
+ .multipliedBy(priceNow.price))
464
+ .plus(tvlNow.amount1);
465
+ tvlBeforeInBaseAsset = (tvlBefore.amount0
466
+ .multipliedBy(priceBefore.price))
467
+ .plus(tvlBefore.amount1);
468
+ } else {
469
+ tvlNowInBaseAsset = (tvlNow.amount1
470
+ .multipliedBy(1 / priceNow.price))
471
+ .plus(tvlNow.amount0);
472
+ tvlBeforeInBaseAsset = (tvlBefore.amount1
473
+ .multipliedBy(1 / priceBefore.price))
474
+ .plus(tvlBefore.amount0);
475
+ }
476
+ const tvlPerShareNow = tvlNowInBaseAsset
426
477
  .multipliedBy(1e18)
427
478
  .dividedBy(adjustedSupplyNow.toString());
428
- const tvlInToken0Bf = (tvlBefore.amount0
429
- .multipliedBy(priceBefore.price))
430
- .plus(tvlBefore.amount1);
431
- const tvlPerShareBf = tvlInToken0Bf
479
+
480
+ const tvlPerShareBf = tvlBeforeInBaseAsset
432
481
  .multipliedBy(1e18)
433
482
  .dividedBy(supplyBefore.toString());
434
483
  const timeDiffSeconds = blockNowTime - blockBeforeInfo.timestamp;
435
- logger.verbose(`tvlInToken0Now: ${tvlInToken0Now.toString()}`);
436
- logger.verbose(`tvlInToken0Bf: ${tvlInToken0Bf.toString()}`);
484
+ logger.verbose(`tvlNowInBaseAsset: ${tvlNowInBaseAsset.toString()}`);
485
+ logger.verbose(`tvlBeforeInBaseAsset: ${tvlBeforeInBaseAsset.toString()}`);
437
486
  logger.verbose(`tvlPerShareNow: ${tvlPerShareNow.toString()}`);
438
487
  logger.verbose(`tvlPerShareBf: ${tvlPerShareBf.toString()}`);
439
488
  logger.verbose(`Price before: ${priceBefore.price.toString()}`);
@@ -451,6 +500,204 @@ export class EkuboCLVault extends BaseStrategy<
451
500
  return (apyForGivenBlocks * (365 * 24 * 3600)) / timeDiffSeconds;
452
501
  }
453
502
 
503
+ /**
504
+ * Calculate lifetime earnings for a user
505
+ * Not yet implemented for Ekubo CL Vault strategy
506
+ */
507
+ getLifetimeEarnings(
508
+ userTVL: SingleTokenInfo,
509
+ investmentFlows: Array<{ amount: string; type: string; timestamp: number; tx_hash: string }>
510
+ ): any {
511
+ throw new Error("getLifetimeEarnings is not implemented yet for this strategy");
512
+ }
513
+
514
+ /**
515
+ * Calculates realized APY based on TVL per share growth, always valued in USDC.
516
+ * This is a vault-level metric (same for all users) and works for all strategies,
517
+ * regardless of quote asset configuration.
518
+ */
519
+ async getUserRealizedAPY(
520
+ blockIdentifier: BlockIdentifier = "latest",
521
+ sinceBlocks = 600000
522
+ ): Promise<number> {
523
+ throw new Error("getUserRealizedAPY not implemented yet for Ekubo CL Vault strategy");
524
+
525
+ /*
526
+ logger.verbose(
527
+ `${EkuboCLVault.name}: getUserRealizedAPY => starting with blockIdentifier=${blockIdentifier}, sinceBlocks=${sinceBlocks}`
528
+ );
529
+
530
+ // TVL amounts (in token units) at current reference block
531
+ const tvlNow = await this._getTVL(blockIdentifier);
532
+ const supplyNow = await this.totalSupply(blockIdentifier);
533
+
534
+ // Determine current block number and timestamp
535
+ let blockNow =
536
+ typeof blockIdentifier == "number"
537
+ ? blockIdentifier
538
+ : (await this.config.provider.getBlockLatestAccepted()).block_number;
539
+ const blockNowTime =
540
+ typeof blockIdentifier == "number"
541
+ ? (await this.config.provider.getBlockWithTxs(blockIdentifier))
542
+ .timestamp
543
+ : new Date().getTime() / 1000;
544
+
545
+ // Look back window, but never before launch block
546
+ const blockBefore = Math.max(
547
+ Number(blockNow) - sinceBlocks,
548
+ this.metadata.launchBlock
549
+ );
550
+
551
+ // Adjust current supply by subtracting harvest reward shares (same idea as netSharesBasedTrueAPY)
552
+ const adjustedSupplyNow = supplyNow.minus(
553
+ await this.getHarvestRewardShares(blockBefore, Number(blockNow))
554
+ );
555
+
556
+ // Historical block info and TVL
557
+ const blockBeforeInfo = await this.config.provider.getBlockWithTxs(
558
+ blockBefore
559
+ );
560
+ const tvlBefore = await this._getTVL(blockBefore);
561
+ const supplyBefore = await this.totalSupply(blockBefore);
562
+
563
+ // Always value TVL in USDC using the pricer for both tokens at both blocks
564
+ const token0Info = this.metadata.depositTokens[0];
565
+ const token1Info = this.metadata.depositTokens[1];
566
+
567
+ const P0Now = await this.pricer.getPrice(
568
+ token0Info.symbol,
569
+ Number(blockNow)
570
+ );
571
+ const P1Now = await this.pricer.getPrice(
572
+ token1Info.symbol,
573
+ Number(blockNow)
574
+ );
575
+ const P0Before = await this.pricer.getPrice(
576
+ token0Info.symbol,
577
+ blockBefore
578
+ );
579
+ const P1Before = await this.pricer.getPrice(
580
+ token1Info.symbol,
581
+ blockBefore
582
+ );
583
+
584
+ // Convert token balances to USDC TVL using current and historical prices
585
+ const tvlNowUsdNumber =
586
+ Number(tvlNow.amount0.toFixed(13)) * P0Now.price +
587
+ Number(tvlNow.amount1.toFixed(13)) * P1Now.price;
588
+ const tvlBeforeUsdNumber =
589
+ Number(tvlBefore.amount0.toFixed(13)) * P0Before.price +
590
+ Number(tvlBefore.amount1.toFixed(13)) * P1Before.price;
591
+
592
+ // Represent USDC TVL as Web3Number with 6 decimals (USDC standard)
593
+ const tvlNowInUSDC = new Web3Number(
594
+ tvlNowUsdNumber.toFixed(13),
595
+ 6
596
+ );
597
+ const tvlBeforeInUSDC = new Web3Number(
598
+ tvlBeforeUsdNumber.toFixed(13),
599
+ 6
600
+ );
601
+
602
+ const tvlPerShareNow = tvlNowInUSDC
603
+ .multipliedBy(1e18)
604
+ .dividedBy(adjustedSupplyNow.toString());
605
+
606
+ const tvlPerShareBf = tvlBeforeInUSDC
607
+ .multipliedBy(1e18)
608
+ .dividedBy(supplyBefore.toString());
609
+
610
+ const timeDiffSeconds = blockNowTime - blockBeforeInfo.timestamp;
611
+
612
+ logger.verbose(
613
+ `${EkuboCLVault.name}: getUserRealizedAPY => token0=${token0Info.symbol}, token1=${token1Info.symbol}`
614
+ );
615
+ logger.verbose(
616
+ `${EkuboCLVault.name}: getUserRealizedAPY => P0Now=${P0Now.price}, P1Now=${P1Now.price}, P0Before=${P0Before.price}, P1Before=${P1Before.price}`
617
+ );
618
+ logger.verbose(
619
+ `${EkuboCLVault.name}: getUserRealizedAPY => raw tvlNow amounts: token0=${tvlNow.amount0.toString()}, token1=${tvlNow.amount1.toString()}`
620
+ );
621
+ logger.verbose(
622
+ `${EkuboCLVault.name}: getUserRealizedAPY => raw tvlBefore amounts: token0=${tvlBefore.amount0.toString()}, token1=${tvlBefore.amount1.toString()}`
623
+ );
624
+ logger.verbose(
625
+ `${EkuboCLVault.name}: getUserRealizedAPY => tvlNowUsdNumber=${tvlNowUsdNumber}, tvlBeforeUsdNumber=${tvlBeforeUsdNumber}`
626
+ );
627
+ logger.verbose(
628
+ `${EkuboCLVault.name}: getUserRealizedAPY => tvlNowInUSDC: ${tvlNowInUSDC.toString()}, tvlBeforeInUSDC: ${tvlBeforeInUSDC.toString()}`
629
+ );
630
+ logger.verbose(
631
+ `${EkuboCLVault.name}: getUserRealizedAPY => tvlPerShareNow: ${tvlPerShareNow.toString()}, tvlPerShareBf: ${tvlPerShareBf.toString()}`
632
+ );
633
+ logger.verbose(
634
+ `${EkuboCLVault.name}: getUserRealizedAPY => Supply before: ${supplyBefore.toString()}, Supply now (adjusted): ${adjustedSupplyNow.toString()}`
635
+ );
636
+ logger.verbose(
637
+ `${EkuboCLVault.name}: getUserRealizedAPY => Time diff in seconds: ${timeDiffSeconds}`
638
+ );
639
+
640
+ const apyForGivenBlocks =
641
+ Number(
642
+ tvlPerShareNow
643
+ .minus(tvlPerShareBf)
644
+ .multipliedBy(10000)
645
+ .dividedBy(tvlPerShareBf)
646
+ ) / 10000;
647
+
648
+ return (apyForGivenBlocks * (365 * 24 * 3600)) / timeDiffSeconds;
649
+ */
650
+ }
651
+
652
+ async getMaxTVL(): Promise<Web3Number> {
653
+ // This strategy doesn't have a maxTVL so returning 0 simply
654
+ return new Web3Number('0', 18);
655
+ }
656
+
657
+ async getUserPositionCards(input: UserPositionCardsInput): Promise<UserPositionCard[]> {
658
+ const quoteToken = this.metadata.additionalInfo.quoteAsset;
659
+ const [userTVL, quotePrice] = await Promise.all([
660
+ this.getUserTVL(input.user),
661
+ this.pricer.getPrice(quoteToken.symbol),
662
+ ]);
663
+
664
+ const token0IsQuote = userTVL.token0.tokenInfo.address.eq(quoteToken.address);
665
+ const token1IsQuote = userTVL.token1.tokenInfo.address.eq(quoteToken.address);
666
+ const token0QuoteAmount = token0IsQuote
667
+ ? userTVL.token0.amount.toNumber()
668
+ : userTVL.token0.usdValue / (quotePrice.price || 1);
669
+ const token1QuoteAmount = token1IsQuote
670
+ ? userTVL.token1.amount.toNumber()
671
+ : userTVL.token1.usdValue / (quotePrice.price || 1);
672
+ const totalQuoteAmount = token0QuoteAmount + token1QuoteAmount;
673
+ const quoteAmountDisplay = Number.isFinite(totalQuoteAmount)
674
+ ? totalQuoteAmount.toLocaleString("en-US", {
675
+ maximumFractionDigits: quoteToken.displayDecimals ?? 2,
676
+ minimumFractionDigits: 0,
677
+ })
678
+ : "0";
679
+ const allocationValue = `${this.formatTokenAmountForCard(userTVL.token0.amount, userTVL.token0.tokenInfo)} / ${this.formatTokenAmountForCard(userTVL.token1.amount, userTVL.token1.tokenInfo)}`;
680
+ const allocationSubValue = `${this.formatUSDForCard(userTVL.token0.usdValue)} / ${this.formatUSDForCard(userTVL.token1.usdValue)}`;
681
+
682
+ const cards: UserPositionCard[] = [
683
+ {
684
+ title: "Your Holdings",
685
+ tooltip: `${quoteToken.symbol} equivalent value of your Ekubo position`,
686
+ value: `${quoteAmountDisplay} ${quoteToken.symbol}`,
687
+ subValue: `≈ ${this.formatUSDForCard(userTVL.usdValue)}`,
688
+ subValueColor: "positive",
689
+ },
690
+ {
691
+ title: "Holding Allocation",
692
+ tooltip: "Split of your position between token0 and token1",
693
+ value: allocationValue,
694
+ subValue: `≈ ${allocationSubValue}`,
695
+ subValueColor: "default",
696
+ },
697
+ ];
698
+ return cards;
699
+ }
700
+
454
701
  async feeBasedAPY(
455
702
  timeperiod: '24h' | '7d' | '30d' | '3m' = '24h'
456
703
  ): Promise<number> {
@@ -485,20 +732,15 @@ export class EkuboCLVault extends BaseStrategy<
485
732
  blockIdentifier: BlockIdentifier = "latest",
486
733
  sinceBlocks = 600000,
487
734
  timeperiod: '24h' | '7d' | '30d' | '3m' = '24h' // temp thing for fee based APY
488
- ): Promise<APYInfo> {
489
- const isUSDCQouteToken = this.metadata.additionalInfo.quoteAsset.symbol === "USDC";
735
+ ): Promise<number> {
736
+ // ! switch to USDC later
737
+ const isUSDCQouteToken = this.metadata.additionalInfo.quoteAsset.symbol === "USDC.e" || this.metadata.additionalInfo.quoteAsset.symbol === "USDC";
490
738
  if (!isUSDCQouteToken) {
491
739
  // good for LSTs and stables
492
- return {
493
- net: await this.netSharesBasedTrueAPY(blockIdentifier, sinceBlocks),
494
- splits: []
495
- };
740
+ return await this.netSharesBasedTrueAPY(blockIdentifier, sinceBlocks);
496
741
  } else {
497
742
  // good for non-stables
498
- return {
499
- net: await this.feeBasedAPY(timeperiod),
500
- splits: []
501
- };
743
+ return await this.feeBasedAPY(timeperiod);
502
744
  }
503
745
  }
504
746
 
@@ -559,8 +801,14 @@ export class EkuboCLVault extends BaseStrategy<
559
801
  assets.amount1.toString(),
560
802
  token1Info.decimals
561
803
  );
562
- const P0 = await this.pricer.getPrice(token0Info.symbol);
563
- const P1 = await this.pricer.getPrice(token1Info.symbol);
804
+
805
+ // Convert blockIdentifier to block number for pricer if it's a number
806
+ const blockNumber = typeof blockIdentifier === 'number' || typeof blockIdentifier === 'bigint'
807
+ ? Number(blockIdentifier)
808
+ : undefined;
809
+
810
+ const P0 = await this.pricer.getPrice(token0Info.symbol, blockNumber);
811
+ const P1 = await this.pricer.getPrice(token1Info.symbol, blockNumber);
564
812
  const token0Usd = Number(amount0.toFixed(13)) * P0.price;
565
813
  const token1Usd = Number(amount1.toFixed(13)) * P1.price;
566
814
 
@@ -627,8 +875,26 @@ export class EkuboCLVault extends BaseStrategy<
627
875
  const P1 = await this.pricer.getPrice(token1Info.symbol);
628
876
  const token0Usd = Number(amount0.toFixed(13)) * P0.price;
629
877
  const token1Usd = Number(amount1.toFixed(13)) * P1.price;
878
+ const totalUsdValue = token0Usd + token1Usd;
879
+
880
+ if (
881
+ (totalUsdValue === 0 || token0Usd === 0 || token1Usd === 0 || amount0.eq(0) || amount1.eq(0)) &&
882
+ this.metadata.settings?.liveStatus === StrategyLiveStatus.ACTIVE
883
+ ) {
884
+ logger.warn(
885
+ `${this.metadata.name}:getTVL - Zero value detected: ` +
886
+ `usdValue=${totalUsdValue}, ` +
887
+ `amount0=${amount0.toString()}, ` +
888
+ `amount1=${amount1.toString()}, ` +
889
+ `token0Price=${P0.price}, ` +
890
+ `token1Price=${P1.price}, ` +
891
+ `token0Usd=${token0Usd}, ` +
892
+ `token1Usd=${token1Usd}`
893
+ );
894
+ }
895
+
630
896
  return {
631
- usdValue: token0Usd + token1Usd,
897
+ usdValue: totalUsdValue,
632
898
  token0: {
633
899
  tokenInfo: token0Info,
634
900
  amount: amount0,
@@ -1026,6 +1292,12 @@ export class EkuboCLVault extends BaseStrategy<
1026
1292
  amount1: availableAmount1.minus(y),
1027
1293
  ratio: 0,
1028
1294
  };
1295
+ } else if (ratio.eq(Infinity)) {
1296
+ return {
1297
+ amount0: availableAmount0,
1298
+ amount1: Web3Number.fromWei("0", availableAmount1.decimals),
1299
+ ratio: Infinity,
1300
+ };
1029
1301
  }
1030
1302
  return {
1031
1303
  amount0: availableAmount0.plus(x),
@@ -1075,7 +1347,12 @@ export class EkuboCLVault extends BaseStrategy<
1075
1347
  };
1076
1348
  }
1077
1349
 
1078
- async getSwapInfoToHandleUnused(considerRebalance: boolean = true, newBounds: EkuboBounds | null = null, maxIterations = 20, priceRatioPrecision = 4): Promise<SwapInfo> {
1350
+ async getSwapInfoToHandleUnused(
1351
+ considerRebalance: boolean = true,
1352
+ newBounds: EkuboBounds | null = null,
1353
+ maxIterations = 20, priceRatioPrecision = 4,
1354
+ getQuoteCallback: (tokenToSell: string, tokenToBuy: string, amountWei: string, beneficiary: string) => Promise<Quote> = this.avnu.getQuotes
1355
+ ): Promise<SwapInfo> {
1079
1356
  const poolKey = await this.getPoolKey();
1080
1357
 
1081
1358
  // fetch current unused balances of vault
@@ -1140,7 +1417,8 @@ export class EkuboCLVault extends BaseStrategy<
1140
1417
  token1Bal,
1141
1418
  ekuboBounds,
1142
1419
  maxIterations,
1143
- priceRatioPrecision
1420
+ priceRatioPrecision,
1421
+ getQuoteCallback
1144
1422
  );
1145
1423
  }
1146
1424
 
@@ -1186,6 +1464,7 @@ export class EkuboCLVault extends BaseStrategy<
1186
1464
  const tokenToSell = expectedAmounts.amount0.lessThan(token0Bal)
1187
1465
  ? poolKey.token0
1188
1466
  : poolKey.token1;
1467
+ logger.verbose(`getSwapParams => tokenToSell: ${tokenToSell.address}, expectedAmounts: ${expectedAmounts.amount0.toString()}, bal0: ${token0Bal.toString()}`);
1189
1468
  // The other token is the one to buy
1190
1469
  const tokenToBuy =
1191
1470
  tokenToSell == poolKey.token0 ? poolKey.token1 : poolKey.token0;
@@ -1210,13 +1489,13 @@ export class EkuboCLVault extends BaseStrategy<
1210
1489
  /**
1211
1490
  * @description Calculates swap info based on given amounts of token0 and token1
1212
1491
  * Use token0 and token1 balances to determine the expected amounts for new bounds
1213
- * @param poolKey
1214
- * @param token0Bal
1215
- * @param token1Bal
1492
+ * @param poolKey
1493
+ * @param token0Bal
1494
+ * @param token1Bal
1216
1495
  * @param bounds // new bounds
1217
- * @param maxIterations
1496
+ * @param maxIterations
1218
1497
  * @returns {Promise<SwapInfo>}
1219
- *
1498
+ *
1220
1499
  */
1221
1500
  async getSwapInfoGivenAmounts(
1222
1501
  poolKey: EkuboPoolKey,
@@ -1224,7 +1503,8 @@ export class EkuboCLVault extends BaseStrategy<
1224
1503
  token1Bal: Web3Number,
1225
1504
  bounds: EkuboBounds,
1226
1505
  maxIterations: number = 20,
1227
- priceRatioPrecision: number = 4
1506
+ priceRatioPrecision: number = 4,
1507
+ getQuoteCallback: (tokenToSell: string, tokenToBuy: string, amountWei: string, beneficiary: string) => Promise<Quote> = this.avnu.getQuotes
1228
1508
  ): Promise<SwapInfo> {
1229
1509
  logger.verbose(
1230
1510
  `${
@@ -1279,12 +1559,7 @@ export class EkuboCLVault extends BaseStrategy<
1279
1559
  }
1280
1560
 
1281
1561
  // 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
- );
1562
+ const quote = await getQuoteCallback(tokenToSell.address, tokenToBuy.address, amountToSell.toWei(), this.address.address);
1288
1563
 
1289
1564
  // If all of the token is to be swapped, return the swap info directly
1290
1565
  if (remainingSellAmount.eq(0)) {
@@ -1394,8 +1669,11 @@ export class EkuboCLVault extends BaseStrategy<
1394
1669
  * @param retry - Current retry attempt number (default 0)
1395
1670
  * @param adjustmentFactor - Percentage to adjust swap amount by (default 1)
1396
1671
  * @param isToken0Deficit - Whether token0 balance needs increasing (default true)
1672
+ * @param MAX_RETRIES - Maximum number of retries (default 40)
1673
+ * @param sameErrorCount - For certain errors, we just retry with same amount again. This is the count of such retries (default { count: 0, error: null })
1674
+ * @param MAX_SAME_ERROR_COUNT - For certain errors, we just retry with same amount again. This limits such retries (default 10)
1397
1675
  * @returns Array of contract calls needed for rebalancing
1398
- * @throws Error if max retries reached without successful rebalance
1676
+ * @throws Error if max retries reached without successful rebalance or max same errors reached
1399
1677
  */
1400
1678
  async rebalanceIter(
1401
1679
  swapInfo: SwapInfo,
@@ -1405,15 +1683,23 @@ export class EkuboCLVault extends BaseStrategy<
1405
1683
  retry = 0,
1406
1684
  lowerLimit = 0n,
1407
1685
  upperLimit = 0n,
1408
- MAX_RETRIES = 40
1686
+ MAX_RETRIES = 40,
1687
+ sameErrorCount: { count: number, error: null | string } = { count: 0, error: null },
1688
+ MAX_SAME_ERROR_COUNT = 10
1409
1689
  ): Promise<Call[]> {
1410
1690
 
1411
1691
  logger.verbose(
1412
1692
  `Rebalancing ${this.metadata.name}: ` +
1413
- `retry=${retry}, lowerLimit=${lowerLimit}, upperLimit=${upperLimit}, isSellTokenToken0=${isSellTokenToken0}`
1693
+ `retry=${retry}, lowerLimit=${lowerLimit}, upperLimit=${upperLimit}, isSellTokenToken0=${isSellTokenToken0}, MAX_RETRIES=${MAX_RETRIES}, sameErrorCount=${sameErrorCount.error} (${sameErrorCount.count})`
1414
1694
  );
1415
1695
 
1696
+ if (sameErrorCount.count >= MAX_SAME_ERROR_COUNT) {
1697
+ logger.error(`Rebalance failed after ${MAX_SAME_ERROR_COUNT} same errors`);
1698
+ throw new Error(`Rebalance failed after ${MAX_SAME_ERROR_COUNT} same errors`);
1699
+ }
1700
+
1416
1701
  const fromAmount = uint256.uint256ToBN(swapInfo.token_from_amount);
1702
+ const fromTokenInfo = await Global.getTokenInfoFromAddr(ContractAddr.from(swapInfo.token_from_address));
1417
1703
  logger.verbose(
1418
1704
  `Selling ${fromAmount.toString()} of token ${swapInfo.token_from_address}`
1419
1705
  );
@@ -1433,8 +1719,8 @@ export class EkuboCLVault extends BaseStrategy<
1433
1719
  );
1434
1720
 
1435
1721
  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()}`);
1722
+ const currentAmount = Web3Number.fromWei(fromAmount.toString(), fromTokenInfo.decimals);
1723
+ logger.verbose(`Current amount: ${currentAmount.toString()}, lowerLimit: ${lowerLimit.toString()}, upperLimit: ${upperLimit.toString()}`);
1438
1724
  if (
1439
1725
  err.message.includes("invalid token0 balance") ||
1440
1726
  err.message.includes("invalid token0 amount")
@@ -1487,11 +1773,43 @@ export class EkuboCLVault extends BaseStrategy<
1487
1773
  }
1488
1774
  newSwapInfo.token_from_amount = uint256.bnToUint256(nextAmount);
1489
1775
  }
1776
+ } else if (err.message.includes("Residual tokens")) {
1777
+ logger.error("Residual tokens");
1778
+ if (sameErrorCount.error == "Residual tokens") {
1779
+ sameErrorCount.count++;
1780
+ } else {
1781
+ sameErrorCount.error = "Residual tokens";
1782
+ sameErrorCount.count = 1;
1783
+ }
1784
+ // dont do anything, just try again.
1785
+ } else if (err.message.includes("Insufficient tokens received")) {
1786
+ logger.error("Insufficient tokens received");
1787
+ if (sameErrorCount.error == "Insufficient tokens received") {
1788
+ sameErrorCount.count++;
1789
+ } else {
1790
+ sameErrorCount.error = "Insufficient tokens received";
1791
+ sameErrorCount.count = 1;
1792
+ }
1793
+ // dont do anything, just try again.
1794
+ } else if (err.message.includes("Could not reach the end of the program")) {
1795
+ logger.error("Could not reach the end of the program, may be the block is full (could be a temp/permanent gas issue)");
1796
+ if (sameErrorCount.error == "Could not reach the end of the program") {
1797
+ sameErrorCount.count++;
1798
+ } else {
1799
+ sameErrorCount.error = "Could not reach the end of the program";
1800
+ sameErrorCount.count = 1;
1801
+ }
1802
+ // just try again.
1490
1803
  } else {
1491
1804
  logger.error("Unexpected error:", err);
1492
1805
  throw err;
1493
1806
  }
1494
1807
  newSwapInfo.token_to_min_amount = uint256.bnToUint256("0");
1808
+
1809
+ // if (uint256.uint256ToBN(newSwapInfo.token_from_amount) == fromAmount && sameErrorCount.error == 'loop-stuck') {
1810
+ // logger.error("Swap amount did not change, cannot proceed");
1811
+ // sameErrorCount = { count: MAX_SAME_ERROR_COUNT, error: null };
1812
+ // }
1495
1813
  return this.rebalanceIter(
1496
1814
  newSwapInfo,
1497
1815
  acc,
@@ -1499,7 +1817,10 @@ export class EkuboCLVault extends BaseStrategy<
1499
1817
  isSellTokenToken0,
1500
1818
  retry + 1,
1501
1819
  lowerLimit,
1502
- upperLimit
1820
+ upperLimit,
1821
+ MAX_RETRIES,
1822
+ sameErrorCount,
1823
+ MAX_SAME_ERROR_COUNT
1503
1824
  );
1504
1825
  }
1505
1826
  }
@@ -1592,20 +1913,34 @@ export class EkuboCLVault extends BaseStrategy<
1592
1913
  };
1593
1914
  }
1594
1915
 
1595
- async harvest(acc: Account, maxIterations = 20, priceRatioPrecision = 4): Promise<Call[]> {
1916
+ async getPendingRewards(): Promise<HarvestInfo[]> {
1596
1917
  const ekuboHarvests = new EkuboHarvests(this.config);
1597
- const unClaimedRewards = await ekuboHarvests.getUnHarvestedRewards(
1598
- this.address
1599
- );
1918
+ return await ekuboHarvests.getUnHarvestedRewards(this.address);
1919
+ }
1920
+
1921
+ async harvest(acc: Account, maxIterations = 20, priceRatioPrecision = 4, minRewardAmount: Web3Number = new Web3Number(0, 18)): Promise<Call[]> {
1922
+ const _pendingRewards = await this.getPendingRewards();
1923
+ const pendingRewards = _pendingRewards.filter(claim => claim.actualReward.greaterThanOrEqualTo(minRewardAmount));
1924
+ if (pendingRewards.length == 0) {
1925
+ logger.verbose(`${EkuboCLVault.name}: harvest => no pending rewards found`);
1926
+ return [];
1927
+ }
1928
+ // get necessary info for the harvest
1600
1929
  const poolKey = await this.getPoolKey();
1601
1930
  const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
1602
1931
  const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
1603
1932
  const bounds = await this.getCurrentBounds();
1604
1933
  logger.verbose(
1605
- `${EkuboCLVault.name}: harvest => unClaimedRewards: ${unClaimedRewards.length}`
1934
+ `${EkuboCLVault.name}: harvest => unClaimedRewards: ${pendingRewards.length}`
1606
1935
  );
1936
+
1937
+ // execute the harvest
1607
1938
  const calls: Call[] = [];
1608
- for (let claim of unClaimedRewards) {
1939
+ // do one at a time.
1940
+ const chosenClaim = pendingRewards[0];
1941
+ logger.info(`${EkuboCLVault.name}: harvest => doing one at a time`);
1942
+ 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()}`);
1943
+ for (let claim of [chosenClaim]) {
1609
1944
  const fee = claim.claim.amount
1610
1945
  .multipliedBy(this.metadata.additionalInfo.feeBps)
1611
1946
  .dividedBy(10000);
@@ -1653,8 +1988,8 @@ export class EkuboCLVault extends BaseStrategy<
1653
1988
 
1654
1989
  /**
1655
1990
  * @description This funciton requires atleast one of the pool tokens to be reward token
1656
- * i.e. STRK.
1657
- * @param params
1991
+ * i.e. STRK.
1992
+ * @param params
1658
1993
  */
1659
1994
  async _handleRewardAndVaultTokenMatchHarvest(params: {
1660
1995
  claim: HarvestInfo;
@@ -1678,60 +2013,50 @@ export class EkuboCLVault extends BaseStrategy<
1678
2013
  logger.verbose(
1679
2014
  `${
1680
2015
  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)}`
2016
+ }: harvest => token0Amt: ${token0Amt.toFixed(18)}, token1Amt: ${token1Amt.toFixed(18)}`
1697
2017
  );
1698
2018
 
1699
2019
  logger.verbose(
1700
2020
  `${EkuboCLVault.name}: harvest => claim: ${JSON.stringify(claim)}`
1701
2021
  );
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?
2022
+ const claimTokenInfo = await Global.getTokenInfoFromAddr(claim.token);
2023
+ const harvestEstimateCall = async (baseSwapInfo: SwapInfo) => {
2024
+ // - the base swap if actual swap from claim token to non-claim token
2025
+ // - the other swap is just claim token to claim token (e.g. STRK to STRK)
2026
+ // which is just dummy
2027
+ let baseSwapAmount = Web3Number.fromWei(
2028
+ uint256.uint256ToBN(baseSwapInfo.token_from_amount).toString(),
2029
+ claimTokenInfo.decimals
1706
2030
  ).minimum(
1707
- postFeeAmount.toFixed(18) // cause always strk
2031
+ postFeeAmount.toFixed(claimTokenInfo.decimals)
1708
2032
  ); // 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
2033
+ if (baseSwapAmount.lt(0.0001)) {
2034
+ baseSwapAmount = new Web3Number(0, claimTokenInfo.decimals);
2035
+ }
2036
+ baseSwapInfo.token_from_amount = uint256.bnToUint256(baseSwapAmount.toWei());
1713
2037
 
2038
+ const isToken0ClaimToken = claim.token.eq(poolKey.token0);
1714
2039
  logger.verbose(
1715
- `${EkuboCLVault.name}: harvest => swap1Amount: ${swap1Amount}`
2040
+ `${EkuboCLVault.name}: harvest => isToken0ClaimToken: ${isToken0ClaimToken}, baseSwapAmount: ${baseSwapAmount}`
1716
2041
  );
1717
2042
 
1718
- const remainingAmount = postFeeAmount.minus(swap1Amount).maximum(0);
2043
+ const remainingAmount = postFeeAmount.minus(baseSwapAmount).maximum(0);
1719
2044
  logger.verbose(
1720
2045
  `${EkuboCLVault.name}: harvest => remainingAmount: ${remainingAmount}`
1721
2046
  );
1722
- const swapInfo2 = {
1723
- ...swapInfo,
1724
- token_from_amount: uint256.bnToUint256(remainingAmount.toWei()),
1725
- };
1726
- swapInfo2.token_to_address = token1Info.address.address;
2047
+
2048
+ // obv, same to same
2049
+ let dummySwapInfo = AvnuWrapper.buildZeroSwap(claim.token, this.address.address, claim.token);
2050
+ dummySwapInfo.token_from_amount = uint256.bnToUint256(remainingAmount.toWei());
2051
+
1727
2052
  logger.verbose(
1728
- `${EkuboCLVault.name}: harvest => swapInfo: ${JSON.stringify(
1729
- swapInfo
2053
+ `${EkuboCLVault.name}: harvest => dummySwapInfo: ${JSON.stringify(
2054
+ dummySwapInfo
1730
2055
  )}`
1731
2056
  );
1732
2057
  logger.verbose(
1733
- `${EkuboCLVault.name}: harvest => swapInfo2: ${JSON.stringify(
1734
- swapInfo2
2058
+ `${EkuboCLVault.name}: harvest => baseSwapInfo: ${JSON.stringify(
2059
+ baseSwapInfo
1735
2060
  )}`
1736
2061
  );
1737
2062
  const calldata = [
@@ -1742,18 +2067,41 @@ export class EkuboCLVault extends BaseStrategy<
1742
2067
  claimee: claim.claim.claimee.address,
1743
2068
  },
1744
2069
  claim.proof.map((p) => num.getDecimalString(p)),
1745
- swapInfo,
1746
- swapInfo2,
2070
+ isToken0ClaimToken ? dummySwapInfo : baseSwapInfo, // is token0 claim token, its just dummy swap
2071
+ isToken0ClaimToken ? baseSwapInfo : dummySwapInfo,
1747
2072
  ];
1748
- logger.verbose(
1749
- `${EkuboCLVault.name}: harvest => calldata: ${JSON.stringify(
1750
- calldata
1751
- )}`
1752
- );
1753
2073
  return [this.contract.populate("harvest", calldata)];
1754
2074
  };
2075
+
2076
+ // if token0 == claim token, then the base swapInfo is from claim token to token1
2077
+ // if token1 == claim token, then the base swapInfo is from claim token to token0
2078
+ const isToken0ClaimToken = claim.token.eq(poolKey.token0);
2079
+ let baseSwapInfo = AvnuWrapper.buildZeroSwap(claim.token, this.address.address, isToken0ClaimToken ? token1Info.address : token0Info.address);
2080
+ baseSwapInfo.token_from_amount = uint256.bnToUint256(postFeeAmount.toWei()); // we try to swap all to start with
2081
+
2082
+ // if token0 != claim token, then we swap from claim token to token0
2083
+ if (postFeeAmount.greaterThan(0) && !isToken0ClaimToken) {
2084
+ const avnuWrapper = new AvnuWrapper();
2085
+ const quote = await avnuWrapper.getQuotes(
2086
+ claim.token.address,
2087
+ token0Info.address.address,
2088
+ postFeeAmount.toWei(),
2089
+ this.address.address
2090
+ );
2091
+ baseSwapInfo = await avnuWrapper.getSwapInfo(quote, this.address.address, 0, this.address.address);
2092
+ } else if (postFeeAmount.greaterThan(0) && isToken0ClaimToken) {
2093
+ // if token0 == claim token, then we swap from claim token to token1
2094
+ const avnuWrapper = new AvnuWrapper();
2095
+ const quote = await avnuWrapper.getQuotes(
2096
+ claim.token.address,
2097
+ token1Info.address.address,
2098
+ postFeeAmount.toWei(),
2099
+ this.address.address
2100
+ );
2101
+ baseSwapInfo = await avnuWrapper.getSwapInfo(quote, this.address.address, 0, this.address.address);
2102
+ }
1755
2103
  const _callsFinal = await this.rebalanceIter(
1756
- swapInfo,
2104
+ baseSwapInfo,
1757
2105
  acc,
1758
2106
  harvestEstimateCall,
1759
2107
  claim.token.eq(poolKey.token0),
@@ -1772,9 +2120,9 @@ export class EkuboCLVault extends BaseStrategy<
1772
2120
 
1773
2121
  /**
1774
2122
  * @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
2123
+ * i.e. STRK is not part of vault tokens like BTC/ETH
2124
+ * @param params
2125
+ * @returns
1778
2126
  */
1779
2127
  async _handleRewardAndVaultTokenMismatchHarvest(params: {
1780
2128
  claim: HarvestInfo;
@@ -1812,7 +2160,7 @@ export class EkuboCLVault extends BaseStrategy<
1812
2160
  return [harvestCall];
1813
2161
  }
1814
2162
 
1815
- // given an amount (i.e. portion of reward to use to swap to token0), returns info on increasing or decreasing
2163
+ // given an amount (i.e. portion of reward to use to swap to token0), returns info on increasing or decreasing
1816
2164
  // amount for binary search
1817
2165
  async harvestMismatchEstimateCallFn(params: {
1818
2166
  postFeeAmount: Web3Number;
@@ -1823,39 +2171,47 @@ export class EkuboCLVault extends BaseStrategy<
1823
2171
  }) {
1824
2172
  const { postFeeAmount, claim, token0Info, token1Info, acc } = params;
1825
2173
  let harvestCall: Call | null = null;
2174
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => postFeeAmount: ${postFeeAmount.toString()}`);
1826
2175
 
2176
+ let attempt = 0;
2177
+ let MAX_ATTEMPTS = 50;
1827
2178
  const binarySearchCallbackFn = async (mid: bigint) => {
2179
+ attempt++;
2180
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => mid: ${mid}, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1828
2181
  const rewardPart2 = BigInt(postFeeAmount.toWei()) - mid;
1829
2182
  const avnuWrapper = new AvnuWrapper();
1830
2183
  const beneficiary = this.address.address;
1831
2184
 
1832
2185
  // get quote for 1st part
1833
- const quote1 = await avnuWrapper.getQuotes(
2186
+ const quote1Prom = avnuWrapper.getQuotes(
1834
2187
  claim.token.address,
1835
2188
  token0Info.address.address,
1836
2189
  mid.toString(),
1837
2190
  beneficiary
1838
2191
  );
2192
+ const quote2Prom = avnuWrapper.getQuotes(
2193
+ claim.token.address,
2194
+ token1Info.address.address,
2195
+ rewardPart2.toString(),
2196
+ beneficiary
2197
+ );
2198
+ const [quote1, quote2] = await Promise.all([quote1Prom, quote2Prom]);
2199
+
1839
2200
  // default min amount is ok
1840
2201
  const swapInfo1 = await avnuWrapper.getSwapInfo(
1841
2202
  quote1,
1842
2203
  beneficiary,
1843
- 0,
2204
+ 0, // fee bps
1844
2205
  beneficiary
1845
2206
  );
1846
2207
 
1847
2208
  // 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
- );
2209
+
1854
2210
  // default min amount is ok
1855
2211
  const swapInfo2 = await avnuWrapper.getSwapInfo(
1856
2212
  quote2,
1857
2213
  beneficiary,
1858
- 0,
2214
+ 0, // fee bps
1859
2215
  beneficiary
1860
2216
  );
1861
2217
 
@@ -1874,27 +2230,36 @@ export class EkuboCLVault extends BaseStrategy<
1874
2230
  ];
1875
2231
  harvestCall = this.contract.populate("harvest", calldata)
1876
2232
  const gas = await acc.estimateInvokeFee(harvestCall);
2233
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => gas: ${gas.overall_fee.toString()}, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1877
2234
  return 'found';
1878
2235
  } catch (err: any) {
1879
2236
  if (err.message.includes('invalid token0 amount')) {
2237
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => invalid token0 amount, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1880
2238
  // too much token0 amount left, may be swap less to token0
1881
2239
  return 'go_low';
1882
2240
  } else if (err.message.includes('invalid token1 amount')) {
2241
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => invalid token1 amount, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1883
2242
  // too much token1 balance left, may be swap more to token0
1884
2243
  return 'go_high';
1885
2244
  }
2245
+ logger.verbose(`${EkuboCLVault.name}: harvestMismatchEstimateCallFn => error: ${err.message}, attempt: ${attempt}/${MAX_ATTEMPTS}`);
1886
2246
  return 'retry';
1887
2247
  }
1888
2248
  }
1889
2249
 
1890
2250
  // run the binary search
1891
2251
  await binarySearch(0n, BigInt(postFeeAmount.toWei()), binarySearchCallbackFn);
1892
-
2252
+
1893
2253
  return harvestCall;
1894
2254
  }
1895
2255
 
1896
2256
  async getInvestmentFlows() {
1897
- const netYield = await this.netAPY();
2257
+ // for LSTs, we use 30d, else 7d for the yield calculation
2258
+ // TODO Make the block compute more dynamic
2259
+ const blocksDiff = this.metadata.additionalInfo.lstContract
2260
+ ? 600000
2261
+ : 600000 / 4;
2262
+ const netYield = await this.netAPY("latest", blocksDiff, "7d" as any);
1898
2263
  const poolKey = await this.getPoolKey();
1899
2264
 
1900
2265
  const linkedFlow: IInvestmentFlow = {
@@ -1915,7 +2280,7 @@ export class EkuboCLVault extends BaseStrategy<
1915
2280
  id: "base",
1916
2281
  title: "Your Deposit",
1917
2282
  subItems: [
1918
- { key: `Net yield`, value: `${(netYield.net * 100).toFixed(2)}%` },
2283
+ { key: `Net yield`, value: `${(netYield * 100).toFixed(2)}%` },
1919
2284
  {
1920
2285
  key: `Performance Fee`,
1921
2286
  value: `${(this.metadata.additionalInfo.feeBps / 100).toFixed(2)}%`,
@@ -1931,8 +2296,8 @@ export class EkuboCLVault extends BaseStrategy<
1931
2296
  subItems: [
1932
2297
  {
1933
2298
  key: "Range selection",
1934
- value: (typeof this.metadata.additionalInfo.newBounds == 'string') ?
1935
- this.metadata.additionalInfo.newBounds :
2299
+ value: (typeof this.metadata.additionalInfo.newBounds == 'string') ?
2300
+ this.metadata.additionalInfo.newBounds :
1936
2301
  `${
1937
2302
  this.metadata.additionalInfo.newBounds.lower *
1938
2303
  Number(poolKey.tick_spacing)
@@ -2053,7 +2418,11 @@ function getLSTFAQs(lstSymbol: string): FAQ[] {
2053
2418
  ]
2054
2419
  }
2055
2420
 
2421
+ const vaultTypeDescription = 'Automatically collects fees and rebalances positions on Ekubo to optimize yield';
2422
+ const vaultType = VaultType.AUTOMATED_LP;
2423
+
2056
2424
  const xSTRKSTRK: IStrategyMetadata<CLVaultStrategySettings> = {
2425
+ id: "ekubo_cl_xstrkstrk",
2057
2426
  name: "Ekubo xSTRK/STRK",
2058
2427
  description: <></>,
2059
2428
  address: ContractAddr.from(
@@ -2061,6 +2430,10 @@ const xSTRKSTRK: IStrategyMetadata<CLVaultStrategySettings> = {
2061
2430
  ),
2062
2431
  launchBlock: 1209881,
2063
2432
  type: "Other",
2433
+ vaultType: {
2434
+ type: vaultType,
2435
+ description: vaultTypeDescription
2436
+ },
2064
2437
  // must be same order as poolKey token0 and token1
2065
2438
  depositTokens: [
2066
2439
  Global.getDefaultTokens().find((t) => t.symbol === "xSTRK")!,
@@ -2068,7 +2441,7 @@ const xSTRKSTRK: IStrategyMetadata<CLVaultStrategySettings> = {
2068
2441
  ],
2069
2442
  protocols: [_protocol],
2070
2443
  auditUrl: AUDIT_URL,
2071
- maxTVL: Web3Number.fromWei("0", 18),
2444
+ curator: UnwrapLabsCurator,
2072
2445
  risk: {
2073
2446
  riskFactor: _lstPoolRiskFactors,
2074
2447
  netRisk:
@@ -2078,6 +2451,10 @@ const xSTRKSTRK: IStrategyMetadata<CLVaultStrategySettings> = {
2078
2451
  },
2079
2452
  apyMethodology:
2080
2453
  "APY based on 30-day historical performance, including fees and rewards.",
2454
+ realizedApyMethodology: "The realizedAPY is based on past 14 days performance by the vault",
2455
+ feeBps: {
2456
+ performanceFeeBps: 1000,
2457
+ },
2081
2458
  additionalInfo: {
2082
2459
  newBounds: {
2083
2460
  lower: -1,
@@ -2094,270 +2471,589 @@ const xSTRKSTRK: IStrategyMetadata<CLVaultStrategySettings> = {
2094
2471
  },
2095
2472
  quoteAsset: Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
2096
2473
  },
2474
+ settings: {
2475
+ isAudited: true,
2476
+ isPaused: false,
2477
+ liveStatus: StrategyLiveStatus.ACTIVE,
2478
+ isInstantWithdrawal: true,
2479
+ hideNetEarnings: true,
2480
+ isTransactionHistDisabled: true,
2481
+ quoteToken: Global.getDefaultTokens().find((t) => t.symbol === "STRK")!,
2482
+ alerts: [
2483
+ {
2484
+ type: "info",
2485
+ text: (
2486
+ <p>
2487
+ Depending on the current position range and price, your
2488
+ input amounts are automatically adjusted to nearest
2489
+ required amounts. If you have insufficient tokens, you
2490
+ can acquire the required tokens on{" "}
2491
+ <a
2492
+ href="https://avnu.fi"
2493
+ target="_blank"
2494
+ rel="noopener noreferrer"
2495
+ >
2496
+ Avnu
2497
+ </a>
2498
+ </p>
2499
+ ),
2500
+ tab: "deposit"
2501
+ },
2502
+ {
2503
+ type: "info",
2504
+ text: (
2505
+ <>
2506
+ Depending on the current position range and price, you
2507
+ may receive both of the tokens or one of the tokens
2508
+ depending on the price
2509
+ </>
2510
+ ),
2511
+ tab: "withdraw"
2512
+ }
2513
+ ],
2514
+ tags: [StrategyTag.AUTOMATED_LP]
2515
+ },
2097
2516
  faqs: getLSTFAQs("xSTRK"),
2098
2517
  points: [{
2099
- multiplier: 1,
2518
+ multiplier: 15,
2100
2519
  logo: 'https://endur.fi/favicon.ico',
2101
2520
  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
2521
  }],
2103
2522
  contractDetails: [],
2104
- investmentSteps: []
2523
+ investmentSteps: [],
2524
+ tags: [StrategyTag.AUTOMATED_LP],
2525
+ security: {
2526
+ auditStatus: AuditStatus.AUDITED,
2527
+ sourceCode: {
2528
+ type: SourceCodeType.OPEN_SOURCE,
2529
+ contractLink: "https://github.com/trovesfi/troves-contracts",
2530
+ },
2531
+ accessControl: {
2532
+ type: AccessControlType.ROLE_BASED_ACCESS,
2533
+ addresses: [MY_ACCESS_CONTROL.address],
2534
+ },
2535
+ },
2536
+ redemptionInfo: {
2537
+ instantWithdrawalVault: InstantWithdrawalVault.YES,
2538
+ redemptionsInfo: [],
2539
+ alerts: [],
2540
+ },
2541
+ usualTimeToEarnings: null,
2542
+ usualTimeToEarningsDescription: null,
2105
2543
  };
2106
2544
 
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,
2545
+ // Helper to create common LST alerts
2546
+ const getLSTAlerts = () => [
2547
+ {
2548
+ tab: "deposit" as const,
2549
+ text: (
2550
+ <>
2551
+ To acquire the LST, please visit{" "}
2552
+ <a
2553
+ href="https://app.endur.fi"
2554
+ target="_blank"
2555
+ rel="noopener noreferrer"
2556
+ >
2557
+ endur.fi
2558
+ </a>
2559
+ </>
2560
+ ),
2561
+ type: "info" as const
2126
2562
  },
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,
2563
+ {
2564
+ type: "info" as const,
2565
+ text: (
2566
+ <p>
2567
+ Depending on the current position range and price, your input
2568
+ amounts are automatically adjusted to nearest required amounts.
2569
+ If you have insufficient tokens, you can acquire the required
2570
+ tokens on{" "}
2571
+ <a
2572
+ href="https://avnu.fi"
2573
+ target="_blank"
2574
+ rel="noopener noreferrer"
2575
+ >
2576
+ Avnu
2577
+ </a>
2578
+ </p>
2579
+ ),
2580
+ tab: "deposit" as const
2149
2581
  },
2150
- faqs: getLSTFAQs("xtBTC"),
2151
- points: [],
2152
- contractDetails: [],
2153
- investmentSteps: []
2154
- },
2155
- {
2582
+ {
2583
+ type: "info" as const,
2584
+ text: (
2585
+ <>
2586
+ Depending on the current position range and price, you may
2587
+ receive both of the tokens or one of the tokens depending on the
2588
+ price
2589
+ </>
2590
+ ),
2591
+ tab: "withdraw" as const
2592
+ }
2593
+ ];
2594
+
2595
+ // Helper to create LST strategy settings
2596
+ const createLSTSettings = (quoteTokenSymbol: string) => ({
2597
+ ...xSTRKSTRK.settings,
2598
+ isAudited: true,
2599
+ liveStatus: StrategyLiveStatus.ACTIVE,
2600
+ isInstantWithdrawal: true,
2601
+ hideNetEarnings: true,
2602
+ isTransactionHistDisabled: true,
2603
+ quoteToken: Global.getDefaultTokens().find(
2604
+ (t) => t.symbol === quoteTokenSymbol
2605
+ )!,
2606
+ alerts: getLSTAlerts(),
2607
+ tags: [StrategyTag.AUTOMATED_LP] as StrategyTag[],
2608
+ });
2609
+
2610
+ // Helper to create an LST strategy
2611
+ const createLSTStrategy = (params: {
2612
+ id: string;
2613
+ name: string;
2614
+ address: string;
2615
+ launchBlock: number;
2616
+ depositToken0Symbol: string;
2617
+ depositToken1Symbol: string;
2618
+ quoteTokenSymbol: string;
2619
+ lstSymbol: string;
2620
+ lstContractAddress?: string;
2621
+ }): IStrategyMetadata<CLVaultStrategySettings> => ({
2156
2622
  ...xSTRKSTRK,
2157
- name: "Ekubo xsBTC/solvBTC",
2623
+ id: params.id,
2624
+ name: params.name,
2158
2625
  description: <></>,
2159
- address: ContractAddr.from(
2160
- "0x3af1c7faa7c464cf2c494e988972ad1939f1103dbfb6e47e9bf0c47e49b14ef"
2161
- ),
2162
- launchBlock: 2344809,
2163
2626
  // 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,
2627
+ address: ContractAddr.from(params.address),
2628
+ launchBlock: params.launchBlock,
2629
+ vaultType: {
2630
+ type: vaultType,
2631
+ description: vaultTypeDescription
2172
2632
  },
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
2633
  depositTokens: [
2188
- Global.getDefaultTokens().find((t) => t.symbol === "LBTC")!,
2189
- Global.getDefaultTokens().find((t) => t.symbol === "xLBTC")!,
2634
+ Global.getDefaultTokens().find(
2635
+ (t) => t.symbol === params.depositToken0Symbol
2636
+ )!,
2637
+ Global.getDefaultTokens().find((t) => t.symbol === params.depositToken1Symbol)!
2190
2638
  ],
2639
+ realizedApyMethodology: "The realizedAPY is based on past 14 days performance by the vault",
2191
2640
  additionalInfo: {
2192
- ...xSTRKSTRK.additionalInfo,
2193
- quoteAsset: Global.getDefaultTokens().find((t) => t.symbol === "LBTC")!,
2194
- lstContract: Global.getDefaultTokens().find((t) => t.symbol === "xLBTC")!.address,
2641
+ ...xSTRKSTRK.additionalInfo,
2642
+ quoteAsset: Global.getDefaultTokens().find(
2643
+ (t) => t.symbol === params.quoteTokenSymbol
2644
+ )!,
2645
+ lstContract: params.lstContractAddress
2646
+ ? ContractAddr.from(params.lstContractAddress)
2647
+ : Global.getDefaultTokens().find((t) => t.symbol === params.lstSymbol)!
2648
+ .address
2195
2649
  },
2196
- faqs: getLSTFAQs("xLBTC"),
2650
+ settings: createLSTSettings(params.quoteTokenSymbol),
2651
+ faqs: getLSTFAQs(params.lstSymbol),
2197
2652
  points: [],
2198
2653
  contractDetails: [],
2199
- investmentSteps: []
2200
- }
2654
+ investmentSteps: [],
2655
+ tags: params.id.toLowerCase().includes('btc') ? [StrategyTag.BTC, StrategyTag.AUTOMATED_LP] : [StrategyTag.AUTOMATED_LP]
2656
+ });
2657
+
2658
+ const lstStrategies: IStrategyMetadata<CLVaultStrategySettings>[] = [
2659
+ xSTRKSTRK,
2660
+ createLSTStrategy({
2661
+ id: "ekubo_cl_xstrkbtcstrkbtc",
2662
+ name: "Ekubo xstrkBTC/strkBTC",
2663
+ address: "0x03d1d1932ef6882d4acf763dd0430f4abed3e2a9da28e028f1e2e8dd934b8bf7",
2664
+ launchBlock: 9651140,
2665
+ depositToken0Symbol: "xstrkBTC",
2666
+ depositToken1Symbol: "strkBTC",
2667
+ quoteTokenSymbol: "strkBTC",
2668
+ lstSymbol: "xstrkBTC",
2669
+ }),
2670
+ createLSTStrategy({
2671
+ id: "ekubo_cl_xwbtcwbtc",
2672
+ name: "Ekubo xWBTC/WBTC",
2673
+ address: "0x2ea99b4971d3c277fa4a9b4beb7d4d7d169e683393a29eef263d5d57b4380a",
2674
+ launchBlock: 2338309,
2675
+ depositToken0Symbol: "WBTC",
2676
+ depositToken1Symbol: "xWBTC",
2677
+ quoteTokenSymbol: "WBTC",
2678
+ lstSymbol: "xWBTC",
2679
+ }),
2680
+ createLSTStrategy({
2681
+ id: "ekubo_cl_xtbtctbtc",
2682
+ name: "Ekubo xtBTC/tBTC",
2683
+ address: "0x785dc3dfc4e80ef2690a99512481e3ed3a5266180adda5a47e856245d68a4af",
2684
+ launchBlock: 2415667,
2685
+ depositToken0Symbol: "xtBTC",
2686
+ depositToken1Symbol: "tBTC",
2687
+ quoteTokenSymbol: "tBTC",
2688
+ lstSymbol: "xtBTC",
2689
+ }),
2690
+ createLSTStrategy({
2691
+ id: "ekubo_cl_xsbtcsolvbtc",
2692
+ name: "Ekubo xsBTC/solvBTC",
2693
+ address: "0x3af1c7faa7c464cf2c494e988972ad1939f1103dbfb6e47e9bf0c47e49b14ef",
2694
+ launchBlock: 2344809,
2695
+ depositToken0Symbol: "xsBTC",
2696
+ depositToken1Symbol: "solvBTC",
2697
+ quoteTokenSymbol: "solvBTC",
2698
+ lstSymbol: "xsBTC",
2699
+ }),
2700
+ createLSTStrategy({
2701
+ id: "ekubo_cl_xlbtclbtc",
2702
+ name: "Ekubo xLBTC/LBTC",
2703
+ address: "0x314c4653ab1aa01f5465773cb879f525d7e369a137bc3ae084761aee99a1712",
2704
+ launchBlock: 2412442,
2705
+ depositToken0Symbol: "LBTC",
2706
+ depositToken1Symbol: "xLBTC",
2707
+ quoteTokenSymbol: "LBTC",
2708
+ lstSymbol: "xLBTC",
2709
+ })
2201
2710
  ];
2202
2711
 
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"
2712
+ const getRe7Alerts = () => [
2713
+ {
2714
+ type: "info" as const,
2715
+ text: (
2716
+ <p>
2717
+ Depending on the current position range and price, your input
2718
+ amounts are automatically adjusted to nearest required amounts.
2719
+ If you have insufficient tokens, you can acquire the required
2720
+ tokens on{" "}
2721
+ <a
2722
+ href="https://avnu.fi"
2723
+ target="_blank"
2724
+ rel="noopener noreferrer"
2725
+ >
2726
+ Avnu
2727
+ </a>
2728
+ </p>
2729
+ ),
2730
+ tab: "deposit" as const
2227
2731
  },
2228
- quoteAsset: Global.getDefaultTokens().find((t) => t.symbol === "USDC")!,
2229
- },
2230
- faqs: [
2732
+ {
2733
+ type: "info" as const,
2734
+ text: (
2735
+ <>
2736
+ Depending on the current position range and price, you may
2737
+ receive both of the tokens or one of the tokens depending on the
2738
+ price
2739
+ </>
2740
+ ),
2741
+ tab: "withdraw" as const
2742
+ }
2743
+ ];
2744
+
2745
+ // Helper to create Re7 strategy settings
2746
+ const createRe7Settings = (quoteTokenSymbol: string, isBTC: boolean, isDeprecated: boolean) => ({
2747
+ ...xSTRKSTRK.settings,
2748
+ isAudited: true,
2749
+ liveStatus: isDeprecated ? StrategyLiveStatus.DEPRECATED : StrategyLiveStatus.ACTIVE,
2750
+ isInstantWithdrawal: true,
2751
+ hideNetEarnings: true,
2752
+ isTransactionHistDisabled: false,
2753
+ quoteToken: Global.getDefaultTokens().find(
2754
+ (t) => t.symbol === quoteTokenSymbol
2755
+ )!,
2756
+ alerts: getRe7Alerts(),
2757
+ tags: isBTC ? [StrategyTag.BTC, StrategyTag.AUTOMATED_LP] : [StrategyTag.AUTOMATED_LP] as StrategyTag[]
2758
+ });
2759
+
2760
+ // Helper to create Re7 FAQs
2761
+ const getRe7FAQs = () => [
2231
2762
  ...faqs,
2232
2763
  {
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>
2764
+ question: "Who is the curator of this strategy?",
2765
+ answer: (
2766
+ <div>
2767
+ Re7 Labs is the curator of this strategy. Re7 Labs is a
2768
+ well-known Web3 asset management firm. This strategy is
2769
+ completely managed by them, including ownership of the vault.
2770
+ Troves is developer of the smart contracts and maintains
2771
+ infrastructure to help users access these strategies. You can
2772
+ find more information about them on their website{" "}
2773
+ <a
2774
+ href="https://www.re7labs.xyz"
2775
+ style={{
2776
+ textDecoration: "underline",
2777
+ marginLeft: "2px"
2778
+ }}
2779
+ target="_blank"
2780
+ >
2781
+ here
2782
+ </a>
2783
+ .
2784
+ </div>
2785
+ )
2236
2786
  },
2237
2787
  {
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" }
2788
+ question: "How is the APY calculated?",
2789
+ answer: (
2790
+ <div>
2791
+ It's an annualized fee APY, calculated as fees earned in the
2792
+ last 24h divided by TVL. Factors like impermanent loss are not
2793
+ considered.
2794
+ </div>
2795
+ )
2796
+ }
2797
+ ];
2798
+
2799
+ // Helper to create a Re7 strategy
2800
+ const createRe7Strategy = (
2801
+ id: string,
2802
+ name: string,
2803
+ address: string,
2804
+ launchBlock: number,
2805
+ depositToken0Symbol: string,
2806
+ depositToken1Symbol: string,
2807
+ quoteTokenSymbol: string,
2808
+ risk:
2809
+ | typeof highRisk
2810
+ | typeof mediumRisk
2811
+ | { riskFactor: RiskFactor[]; netRisk: number; notARisks: RiskType[] },
2812
+ isBTC: boolean
2813
+ ): IStrategyMetadata<CLVaultStrategySettings> => {
2814
+ const isDeprecated = name.toLowerCase().includes('usdc.e');
2815
+ return {
2816
+ ...xSTRKSTRK,
2817
+ id,
2818
+ name,
2819
+ description: <></>,
2820
+ address: ContractAddr.from(address),
2821
+ launchBlock,
2822
+ vaultType: {
2823
+ type: vaultType,
2824
+ description: vaultTypeDescription
2825
+ },
2826
+ depositTokens: [
2827
+ Global.getDefaultTokens().find(
2828
+ (t) => t.symbol === depositToken0Symbol
2829
+ )!,
2830
+ Global.getDefaultTokens().find((t) => t.symbol === depositToken1Symbol)!
2831
+ ],
2832
+ apyMethodology:
2833
+ "Annualized fee APY, calculated as fees earned in the last 7d divided by TVL",
2834
+ additionalInfo: {
2835
+ newBounds: "Managed by Re7",
2836
+ truePrice: 1,
2837
+ feeBps: 1000,
2838
+ rebalanceConditions: {
2839
+ customShouldRebalance: async (currentPrice: number) =>
2840
+ currentPrice > 0.99 && currentPrice < 1.01,
2841
+ minWaitHours: 6,
2842
+ direction: "any" as const
2843
+ },
2844
+ quoteAsset: Global.getDefaultTokens().find(
2845
+ (t) => t.symbol === quoteTokenSymbol
2846
+ )!
2847
+ },
2848
+ settings: createRe7Settings(quoteTokenSymbol, isBTC, isDeprecated),
2849
+ faqs: getRe7FAQs(),
2850
+ risk,
2851
+ points: [],
2852
+ curator: { name: "Re7 Labs", logo: "https://www.re7labs.xyz/favicon.ico" },
2853
+ tags: isBTC ? [StrategyTag.BTC, StrategyTag.AUTOMATED_LP] : [StrategyTag.AUTOMATED_LP] as StrategyTag[],
2854
+ discontinuationInfo: isDeprecated ? {
2855
+ info: "This strategy has been deprecated and is no longer accepting new deposits."
2856
+ } : undefined,
2857
+ security: {
2858
+ ...xSTRKSTRK.security,
2859
+ accessControl: {
2860
+ ...xSTRKSTRK.security.accessControl,
2861
+ addresses: [ContractAddr.from("0x707bf89863473548fb2844c9f3f96d83fe2394453259035a5791e4b1490642")],
2862
+ },
2863
+ },
2864
+ };
2865
+ };
2866
+
2867
+ const ETHUSDCRe7Strategy = createRe7Strategy(
2868
+ "ekubo_cl_ethusdc",
2869
+ "Ekubo ETH/USDC.e",
2870
+ "0x160d8fa4569ef6a12e6bf47cb943d7b5ebba8a41a69a14c1d943050ba5ff947",
2871
+ 1504232,
2872
+ "ETH",
2873
+ "USDC.e",
2874
+ "USDC.e",
2875
+ highRisk,
2876
+ false // isBTC
2877
+ );
2878
+
2879
+ const stableCoinRisk = {
2880
+ riskFactor: _stableCoinPoolRiskFactors,
2881
+ netRisk:
2882
+ _stableCoinPoolRiskFactors.reduce(
2883
+ (acc, curr) => acc + curr.value * curr.weight,
2884
+ 0
2885
+ ) /
2886
+ _stableCoinPoolRiskFactors.reduce((acc, curr) => acc + curr.weight, 0),
2887
+ notARisks: getNoRiskTags(_stableCoinPoolRiskFactors)
2246
2888
  };
2247
2889
 
2248
2890
  const RE7Strategies: IStrategyMetadata<CLVaultStrategySettings>[] = [
2249
- ETHUSDCRe7Strategy,
2250
- {
2251
- ...ETHUSDCRe7Strategy,
2252
- name: "Ekubo USDC/USDT",
2253
- description: <></>,
2254
- address: ContractAddr.from(
2255
- "0x3a4f8debaf12af97bb911099bc011d63d6c208d4c5ba8e15d7f437785b0aaa2"
2891
+ ETHUSDCRe7Strategy,
2892
+ createRe7Strategy(
2893
+ "ekubo_cl_usdcusdt",
2894
+ "Ekubo USDC.e/USDT",
2895
+ "0x3a4f8debaf12af97bb911099bc011d63d6c208d4c5ba8e15d7f437785b0aaa2",
2896
+ 1506139,
2897
+ "USDC.e",
2898
+ "USDT",
2899
+ "USDC.e",
2900
+ stableCoinRisk,
2901
+ false // isBTC
2256
2902
  ),
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"
2903
+ createRe7Strategy(
2904
+ "ekubo_cl_strkusdc",
2905
+ "Ekubo STRK/USDC.e",
2906
+ "0x351b36d0d9d8b40010658825adeeddb1397436cd41acd0ff6c6e23aaa8b5b30",
2907
+ 1504079,
2908
+ "STRK",
2909
+ "USDC.e",
2910
+ "USDC.e",
2911
+ highRisk,
2912
+ false // isBTC
2277
2913
  ),
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"
2914
+ createRe7Strategy(
2915
+ "ekubo_cl_strketh",
2916
+ "Ekubo STRK/ETH",
2917
+ "0x4ce3024b0ee879009112d7b0e073f8a87153dd35b029347d4247ffe48d28f51",
2918
+ 1504149,
2919
+ "STRK",
2920
+ "ETH",
2921
+ "USDC",
2922
+ highRisk,
2923
+ false // isBTC
2292
2924
  ),
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"
2925
+ createRe7Strategy(
2926
+ "ekubo_cl_wbtcusdc",
2927
+ "Ekubo WBTC/USDC.e",
2928
+ "0x2bcaef2eb7706875a5fdc6853dd961a0590f850bc3a031c59887189b5e84ba1",
2929
+ 1506144,
2930
+ "WBTC",
2931
+ "USDC.e",
2932
+ "USDC.e",
2933
+ mediumRisk,
2934
+ true // isBTC
2307
2935
  ),
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"
2936
+ // createRe7Strategy(
2937
+ // "ekubo_cl_tbtcusdce",
2938
+ // "Ekubo tBTC/USDC.e",
2939
+ // "0x4aad891a2d4432fba06b6558631bb13f6bbd7f6f33ab8c3111e344889ea4456",
2940
+ // 1501764,
2941
+ // "tBTC",
2942
+ // "USDC.e",
2943
+ // "USDC.e",
2944
+ // mediumRisk,
2945
+ // ),
2946
+ createRe7Strategy(
2947
+ "ekubo_cl_wbtceth",
2948
+ "Ekubo WBTC/ETH",
2949
+ "0x1c9232b8186d9317652f05055615f18a120c2ad9e5ee96c39e031c257fb945b",
2950
+ 1506145,
2951
+ "WBTC",
2952
+ "ETH",
2953
+ "USDC",
2954
+ mediumRisk,
2955
+ true // isBTC
2322
2956
  ),
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"
2957
+ createRe7Strategy(
2958
+ "ekubo_cl_wbtcstrk",
2959
+ "Ekubo WBTC/STRK",
2960
+ "0x1248e385c23a929a015ec298a26560fa7745bbd6e41a886550e337b02714b1b",
2961
+ 1506147,
2962
+ "WBTC",
2963
+ "STRK",
2964
+ "USDC",
2965
+ highRisk,
2966
+ true // isBTC
2337
2967
  ),
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"
2968
+ createRe7Strategy(
2969
+ "ekubo_cl_usdc_v2usdt",
2970
+ "Ekubo USDC/USDT",
2971
+ "0x5203a08b471e46bf33990ac83aff577bbe5a5d789e61de2c6531e3c4773d1c9",
2972
+ 3998018,
2973
+ "USDC",
2974
+ "USDT",
2975
+ "USDC",
2976
+ stableCoinRisk,
2977
+ false // isBTC
2978
+ ),
2979
+ createRe7Strategy(
2980
+ "ekubo_cl_ethusdc_v2",
2981
+ "Ekubo ETH/USDC",
2982
+ "0x4d00c7423b3c0fae3640f6099ac97acbfd8708f099e09bfe3a7a6a680399228",
2983
+ 3998025,
2984
+ "USDC",
2985
+ "ETH",
2986
+ "USDC",
2987
+ highRisk,
2988
+ false // isBTC
2989
+ ),
2990
+ createRe7Strategy(
2991
+ "ekubo_cl_strkusdc_v2",
2992
+ "Ekubo STRK/USDC",
2993
+ "0x4de22bd0a8eb4d0a18736e66dd36d20ba50bc106346bbfac3dbeaac1ab37ce1",
2994
+ 3998030,
2995
+ "USDC",
2996
+ "STRK",
2997
+ "USDC",
2998
+ highRisk,
2999
+ false // isBTC
3000
+ ),
3001
+ createRe7Strategy(
3002
+ "ekubo_cl_wbtcusdc_v2",
3003
+ "Ekubo WBTC/USDC",
3004
+ "0x76101c3b80af1103c9c6d541ca627f61b5ae7ae79d7fce96ccdf7bdb648450d",
3005
+ 3998034,
3006
+ "USDC",
3007
+ "WBTC",
3008
+ "USDC",
3009
+ mediumRisk,
3010
+ true // isBTC
3011
+ ),
3012
+ createRe7Strategy(
3013
+ "ekubo_cl_strkbtcusdc",
3014
+ "Ekubo strkBTC/USDC",
3015
+ "0x02dfe5af1665a7adf549008161c818eb18dcf89fc9518ab812294f2b691b2845",
3016
+ 9650986,
3017
+ "USDC",
3018
+ "strkBTC",
3019
+ "USDC",
3020
+ mediumRisk,
3021
+ true // isBTC
3022
+ ),
3023
+ createRe7Strategy(
3024
+ "ekubo_cl_strkbtcstrk",
3025
+ "Ekubo strkBTC/STRK",
3026
+ "0x04784e62a4847484528ba65f500b37a9347e88632e90d866e213f2c2651be828",
3027
+ 9650592,
3028
+ "STRK",
3029
+ "strkBTC",
3030
+ "USDC",
3031
+ mediumRisk,
3032
+ true // isBTC
3033
+ ),
3034
+ createRe7Strategy(
3035
+ "ekubo_cl_strkbtceth",
3036
+ "Ekubo strkBTC/ETH",
3037
+ "0x07118ecd7dece83462b0ac8302c682fb17c7e18b0be13d81867c5bf3f80933ef",
3038
+ 9650986,
3039
+ "ETH",
3040
+ "strkBTC",
3041
+ "USDC",
3042
+ mediumRisk,
3043
+ true // isBTC
3044
+ ),
3045
+ // wbtc/strkBTC
3046
+ createRe7Strategy(
3047
+ "ekubo_cl_wbtcstrkbtc",
3048
+ "Ekubo WBTC/strkBTC",
3049
+ "0x07e927222730899442b2438bfd6218ff8ac44bd7a3420646fca359b8392e42c1",
3050
+ 9650986,
3051
+ "WBTC",
3052
+ "strkBTC",
3053
+ "strkBTC",
3054
+ stableCoinRisk,
3055
+ true // isBTC
2352
3056
  ),
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
- }
2361
3057
  ];
2362
3058
 
2363
3059
  /**
@@ -2375,7 +3071,7 @@ EkuboCLVaultStrategies.forEach((s) => {
2375
3071
  address: s.address,
2376
3072
  name: "Vault",
2377
3073
  sourceCodeUrl: "https://github.com/strkfarm/strkfarm-contracts/tree/main/src/strategies/cl_vault"
2378
- },
3074
+ },
2379
3075
  // ...COMMON_CONTRACTS
2380
3076
  ];
2381
3077
  // set docs link
@@ -2419,4 +3115,4 @@ EkuboCLVaultStrategies.forEach((s) => {
2419
3115
  "Monitor and Rebalance position to optimize yield",
2420
3116
  "Harvest and re-invest any rewards every week (Auto-compound)",
2421
3117
  ]
2422
- });
3118
+ });