@strkfarm/sdk 2.0.0-dev.5 → 2.0.0-dev.51

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