@strkfarm/sdk 1.1.21 → 1.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -9690,15 +9690,15 @@ var VesuRebalance = class _VesuRebalance extends BaseStrategy {
9690
9690
  }
9691
9691
  async getPoolInfo(p, pools, vesuPositions, totalAssets, isErrorPositionsAPI, isErrorPoolsAPI) {
9692
9692
  const vesuPosition = vesuPositions.find(
9693
- (d) => d.pool.id.toString() === num4.getDecimalString(p.pool_id.address.toString())
9693
+ (d) => ContractAddr.from(d.id).eq(p.pool_id)
9694
9694
  );
9695
9695
  const _pool = pools.find((d) => {
9696
9696
  logger.verbose(
9697
- `pool check: ${d.id == num4.getDecimalString(p.pool_id.address.toString())}, id: ${d.id}, pool_id: ${num4.getDecimalString(
9697
+ `pool check: ${ContractAddr.from(d.id).eq(p.pool_id)}, id: ${d.id}, pool_id: ${num4.getDecimalString(
9698
9698
  p.pool_id.address.toString()
9699
9699
  )}`
9700
9700
  );
9701
- return d.id == num4.getDecimalString(p.pool_id.address.toString());
9701
+ return ContractAddr.from(d.id).eq(p.pool_id);
9702
9702
  });
9703
9703
  logger.verbose(`pool: ${JSON.stringify(_pool)}`);
9704
9704
  logger.verbose(typeof _pool);
@@ -15288,6 +15288,17 @@ var erc4626_abi_default = [
15288
15288
  }
15289
15289
  ];
15290
15290
 
15291
+ // src/strategies/ekubo-cl-vault.tsx
15292
+ import { gql } from "@apollo/client";
15293
+
15294
+ // src/modules/apollo-client.ts
15295
+ import { ApolloClient, InMemoryCache } from "@apollo/client";
15296
+ var apolloClient = new ApolloClient({
15297
+ uri: "https://api.troves.fi/",
15298
+ cache: new InMemoryCache()
15299
+ });
15300
+ var apollo_client_default = apolloClient;
15301
+
15291
15302
  // src/strategies/ekubo-cl-vault.tsx
15292
15303
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
15293
15304
  var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
@@ -15445,11 +15456,75 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
15445
15456
  handleFeesCall() {
15446
15457
  return [this.contract.populate("handle_fees", [])];
15447
15458
  }
15448
- /**
15449
- * Calculates assets before and now in a given token of TVL per share to observe growth
15450
- * @returns {Promise<number>} The weighted average APY across all pools
15451
- */
15452
- async netAPY(blockIdentifier = "latest", sinceBlocks = 6e5) {
15459
+ async getFeeHistory(timePeriod = "24h") {
15460
+ const { data } = await apollo_client_default.query({
15461
+ query: gql`
15462
+ query ContractFeeEarnings(
15463
+ $timeframe: String!
15464
+ $contract: String!
15465
+ ) {
15466
+ contractFeeEarnings(timeframe: $timeframe, contract: $contract) {
15467
+ contract
15468
+ dailyEarnings {
15469
+ date
15470
+ tokenAddress
15471
+ amount
15472
+ }
15473
+ totalCollections
15474
+ }
15475
+ }
15476
+ `,
15477
+ variables: {
15478
+ timeframe: timePeriod,
15479
+ contract: this.address.address
15480
+ },
15481
+ fetchPolicy: "no-cache"
15482
+ });
15483
+ const poolKey = await this.getPoolKey();
15484
+ const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
15485
+ const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
15486
+ const price0 = await this.pricer.getPrice(token0Info.symbol);
15487
+ const price1 = await this.pricer.getPrice(token1Info.symbol);
15488
+ let totalToken0Amount = Web3Number.fromWei(0, token0Info.decimals);
15489
+ let totalToken1Amount = Web3Number.fromWei(0, token1Info.decimals);
15490
+ let totalToken0Usd = 0;
15491
+ let totalToken1Usd = 0;
15492
+ const parsedFeeInfo = [];
15493
+ const feeInfo = data.contractFeeEarnings.dailyEarnings;
15494
+ for (const d of feeInfo) {
15495
+ const tokenInfo = await Global.getTokenInfoFromAddr(ContractAddr.from(d.tokenAddress));
15496
+ const amount = Web3Number.fromWei(d.amount, tokenInfo.decimals);
15497
+ if (tokenInfo.address.eq(poolKey.token0)) {
15498
+ totalToken0Amount = totalToken0Amount.plus(amount);
15499
+ totalToken0Usd = totalToken0Usd + amount.multipliedBy(price0.price).toNumber();
15500
+ } else {
15501
+ totalToken1Amount = totalToken1Amount.plus(amount);
15502
+ totalToken1Usd = totalToken1Usd + amount.multipliedBy(price1.price).toNumber();
15503
+ }
15504
+ parsedFeeInfo.push({
15505
+ date: d.date,
15506
+ tokenInfo,
15507
+ amount: Web3Number.fromWei(d.amount, tokenInfo.decimals)
15508
+ });
15509
+ }
15510
+ return {
15511
+ summary: {
15512
+ usdValue: totalToken0Usd + totalToken1Usd,
15513
+ token0: {
15514
+ tokenInfo: token0Info,
15515
+ amount: totalToken0Amount,
15516
+ usdValue: totalToken0Usd
15517
+ },
15518
+ token1: {
15519
+ tokenInfo: token1Info,
15520
+ amount: totalToken1Amount,
15521
+ usdValue: totalToken1Usd
15522
+ }
15523
+ },
15524
+ history: parsedFeeInfo
15525
+ };
15526
+ }
15527
+ async netSharesBasedTrueAPY(blockIdentifier = "latest", sinceBlocks = 6e5) {
15453
15528
  const tvlNow = await this._getTVL(blockIdentifier);
15454
15529
  const supplyNow = await this.totalSupply(blockIdentifier);
15455
15530
  const priceNow = await this.getCurrentPrice(blockIdentifier);
@@ -15487,6 +15562,23 @@ var EkuboCLVault = class _EkuboCLVault extends BaseStrategy {
15487
15562
  ) / 1e4;
15488
15563
  return apyForGivenBlocks * (365 * 24 * 3600) / timeDiffSeconds;
15489
15564
  }
15565
+ async feeBasedAPY(blockIdentifier = "latest", sinceBlocks = 6e5) {
15566
+ const feeInfo = await this.getFeeHistory("24h");
15567
+ const tvlNow = await this.getTVL("latest");
15568
+ return feeInfo.summary.usdValue * 365 / tvlNow.usdValue;
15569
+ }
15570
+ /**
15571
+ * Calculates assets before and now in a given token of TVL per share to observe growth
15572
+ * @returns {Promise<number>} The weighted average APY across all pools
15573
+ */
15574
+ async netAPY(blockIdentifier = "latest", sinceBlocks = 6e5) {
15575
+ const isUSDCQouteToken = this.metadata.additionalInfo.quoteAsset.symbol === "USDC";
15576
+ if (!isUSDCQouteToken) {
15577
+ return this.netSharesBasedTrueAPY(blockIdentifier, sinceBlocks);
15578
+ } else {
15579
+ return this.feeBasedAPY(blockIdentifier, sinceBlocks);
15580
+ }
15581
+ }
15490
15582
  async getHarvestRewardShares(fromBlock, toBlock) {
15491
15583
  const len = Number(await this.contract.call("get_total_rewards"));
15492
15584
  let shares = Web3Number.fromWei(0, 18);
@@ -16700,7 +16792,7 @@ var ETHUSDCRe7Strategy = {
16700
16792
  Global.getDefaultTokens().find((t) => t.symbol === "ETH"),
16701
16793
  Global.getDefaultTokens().find((t) => t.symbol === "USDC")
16702
16794
  ],
16703
- apyMethodology: "APY based on 7-day historical performance, including fees and rewards.",
16795
+ apyMethodology: "Annualized fee APY, calculated as fees earned in the last 24h divided by TVL",
16704
16796
  additionalInfo: {
16705
16797
  newBounds: "Managed by Re7",
16706
16798
  truePrice: 1,
@@ -16721,6 +16813,10 @@ var ETHUSDCRe7Strategy = {
16721
16813
  /* @__PURE__ */ jsx3("a", { href: "https://www.re7labs.xyz", style: { textDecoration: "underline", marginLeft: "2px" }, target: "_blank", children: "here" }),
16722
16814
  "."
16723
16815
  ] })
16816
+ },
16817
+ {
16818
+ question: "How is the APY calculated?",
16819
+ answer: /* @__PURE__ */ jsx3("div", { children: "It's an annualized fee APY, calculated as fees earned in the last 24h divided by TVL. Factors like impermanent loss are not considered." })
16724
16820
  }
16725
16821
  ],
16726
16822
  risk: highRisk,
@@ -26689,7 +26785,7 @@ var UniversalStrategy = class _UniversalStrategy extends BaseStrategy {
26689
26785
  if (legAUM[0].token.address.eq(underlying.address)) {
26690
26786
  vesuAum = vesuAum.plus(legAUM[0].amount);
26691
26787
  } else {
26692
- vesuAum = vesuAum.plus(legAUM[1].usdValue / tokenUnderlyingPrice.price);
26788
+ vesuAum = vesuAum.plus(legAUM[0].usdValue / tokenUnderlyingPrice.price);
26693
26789
  }
26694
26790
  if (legAUM[1].token.address.eq(underlying.address)) {
26695
26791
  vesuAum = vesuAum.minus(legAUM[1].amount);
@@ -26722,7 +26818,8 @@ var UniversalStrategy = class _UniversalStrategy extends BaseStrategy {
26722
26818
  amount: zeroAmt,
26723
26819
  usdValue: 0
26724
26820
  };
26725
- if (vesuAum.isZero()) {
26821
+ const aumToken = vesuAum.plus(balance.amount);
26822
+ if (aumToken.isZero()) {
26726
26823
  return { net, splits: [{
26727
26824
  aum: zeroAmt,
26728
26825
  id: "finalised" /* FINALISED */
@@ -26731,7 +26828,6 @@ var UniversalStrategy = class _UniversalStrategy extends BaseStrategy {
26731
26828
  id: "defispring" /* DEFISPRING */
26732
26829
  }], prevAum };
26733
26830
  }
26734
- const aumToken = vesuAum.plus(balance.amount);
26735
26831
  logger.verbose(`${this.getTag()} Actual AUM: ${aumToken}`);
26736
26832
  const rewardAssets = await this.getRewardsAUM(prevAum);
26737
26833
  const newAUM = aumToken.plus(rewardAssets);
@@ -27932,6 +28028,92 @@ var TelegramNotif = class {
27932
28028
  }
27933
28029
  };
27934
28030
 
28031
+ // src/notifs/telegram-group.ts
28032
+ import TelegramBot2 from "node-telegram-bot-api";
28033
+ var TelegramGroupNotif = class {
28034
+ constructor(token, groupId, topicId) {
28035
+ this.bot = new TelegramBot2(token, { polling: false });
28036
+ this.groupId = groupId;
28037
+ this.topicId = topicId;
28038
+ }
28039
+ // listen to start msgs, register chatId and send registered msg
28040
+ activateChatBot() {
28041
+ this.bot.on("message", (msg) => {
28042
+ console.log(`Tg: Message received: `, msg);
28043
+ const chatId = msg.chat.id;
28044
+ let text = msg.text.toLowerCase().trim();
28045
+ console.log(`Tg: IncomingMsg: ID: ${chatId}, msg: ${text}`, msg);
28046
+ logger.verbose(`Tg: IncomingMsg: ID: ${chatId}, msg: ${text}`);
28047
+ });
28048
+ }
28049
+ // send a message to the group
28050
+ sendMessage(msg) {
28051
+ logger.verbose(`Tg Group: Sending message to group ${this.groupId}: ${msg}`);
28052
+ const messageOptions = {};
28053
+ if (this.topicId !== void 0) {
28054
+ messageOptions.message_thread_id = this.topicId;
28055
+ }
28056
+ this.bot.sendMessage(this.groupId, msg, messageOptions).catch((err) => {
28057
+ logger.error(`Tg Group: Error sending message to group ${this.groupId}`);
28058
+ logger.error(`Tg Group: Error details: ${err.message}`);
28059
+ }).then(() => {
28060
+ logger.verbose(`Tg Group: Message sent to group ${this.groupId}${this.topicId ? ` (topic: ${this.topicId})` : ""}`);
28061
+ });
28062
+ }
28063
+ // send a message with specific formatting options
28064
+ sendFormattedMessage(msg, parseMode) {
28065
+ logger.verbose(`Tg Group: Sending formatted message to group ${this.groupId}: ${msg}`);
28066
+ const messageOptions = {};
28067
+ if (this.topicId !== void 0) {
28068
+ messageOptions.message_thread_id = this.topicId;
28069
+ }
28070
+ if (parseMode) {
28071
+ messageOptions.parse_mode = parseMode;
28072
+ }
28073
+ this.bot.sendMessage(this.groupId, msg, messageOptions).catch((err) => {
28074
+ logger.error(`Tg Group: Error sending formatted message to group ${this.groupId}`);
28075
+ logger.error(`Tg Group: Error details: ${err.message}`);
28076
+ }).then(() => {
28077
+ logger.verbose(`Tg Group: Formatted message sent to group ${this.groupId}${this.topicId ? ` (topic: ${this.topicId})` : ""}`);
28078
+ });
28079
+ }
28080
+ // send a photo to the group
28081
+ sendPhoto(photo, caption) {
28082
+ logger.verbose(`Tg Group: Sending photo to group ${this.groupId}`);
28083
+ const messageOptions = {};
28084
+ if (this.topicId !== void 0) {
28085
+ messageOptions.message_thread_id = this.topicId;
28086
+ }
28087
+ if (caption) {
28088
+ messageOptions.caption = caption;
28089
+ }
28090
+ this.bot.sendPhoto(this.groupId, photo, messageOptions).catch((err) => {
28091
+ logger.error(`Tg Group: Error sending photo to group ${this.groupId}`);
28092
+ logger.error(`Tg Group: Error details: ${err.message}`);
28093
+ }).then(() => {
28094
+ logger.verbose(`Tg Group: Photo sent to group ${this.groupId}${this.topicId ? ` (topic: ${this.topicId})` : ""}`);
28095
+ });
28096
+ }
28097
+ // get group information
28098
+ getGroupInfo() {
28099
+ return {
28100
+ groupId: this.groupId,
28101
+ topicId: this.topicId,
28102
+ hasTopic: this.topicId !== void 0
28103
+ };
28104
+ }
28105
+ // update group ID (useful if group ID changes)
28106
+ updateGroupId(newGroupId) {
28107
+ this.groupId = newGroupId;
28108
+ logger.verbose(`Tg Group: Updated group ID to ${newGroupId}`);
28109
+ }
28110
+ // update topic ID (useful for switching topics)
28111
+ updateTopicId(newTopicId) {
28112
+ this.topicId = newTopicId;
28113
+ logger.verbose(`Tg Group: Updated topic ID to ${newTopicId || "none"}`);
28114
+ }
28115
+ };
28116
+
27935
28117
  // src/node/pricer-redis.ts
27936
28118
  import { createClient } from "redis";
27937
28119
  var PricerRedis = class extends Pricer {
@@ -28349,6 +28531,7 @@ export {
28349
28531
  SenseiVault,
28350
28532
  StandardMerkleTree,
28351
28533
  Store,
28534
+ TelegramGroupNotif,
28352
28535
  TelegramNotif,
28353
28536
  UNIVERSAL_ADAPTERS,
28354
28537
  UNIVERSAL_MANAGE_IDS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strkfarm/sdk",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
4
4
  "description": "STRKFarm TS SDK (Meant for our internal use, but feel free to use it)",
5
5
  "typings": "dist/index.d.ts",
6
6
  "types": "dist/index.d.ts",
@@ -57,6 +57,7 @@
57
57
  "starknet": "8.5.2"
58
58
  },
59
59
  "dependencies": {
60
+ "@apollo/client": "3.11.8",
60
61
  "@avnu/avnu-sdk": "3.0.2",
61
62
  "@ericnordelo/strk-merkle-tree": "^1.0.0",
62
63
  "@noble/curves": "^1.0.0",
@@ -68,6 +69,7 @@
68
69
  "chalk": "^4.1.2",
69
70
  "commander": "^12.1.0",
70
71
  "ethers": "^6.13.5",
72
+ "graphql": "16.9.0",
71
73
  "inquirer": "^10.1.2",
72
74
  "node-telegram-bot-api": "^0.66.0",
73
75
  "proxy-from-env": "^1.1.0",
@@ -0,0 +1,8 @@
1
+ import { ApolloClient, InMemoryCache } from '@apollo/client';
2
+
3
+ const apolloClient = new ApolloClient({
4
+ uri: 'https://api.troves.fi/',
5
+ cache: new InMemoryCache(),
6
+ });
7
+
8
+ export default apolloClient;
@@ -1 +1,2 @@
1
- export * from './telegram';
1
+ export * from './telegram';
2
+ export * from './telegram-group';
@@ -0,0 +1,115 @@
1
+ import TelegramBot from "node-telegram-bot-api";
2
+ import { logger } from "@/utils/logger";
3
+
4
+ export class TelegramGroupNotif {
5
+ readonly bot: TelegramBot;
6
+ private groupId: string;
7
+ private topicId?: number;
8
+
9
+ constructor(token: string, groupId: string, topicId?: number) {
10
+ this.bot = new TelegramBot(token, { polling: false });
11
+ this.groupId = groupId;
12
+ this.topicId = topicId;
13
+ }
14
+
15
+ // listen to start msgs, register chatId and send registered msg
16
+ activateChatBot() {
17
+ this.bot.on('message', (msg: any) => {
18
+ console.log(`Tg: Message received: `, msg)
19
+ const chatId = msg.chat.id;
20
+ let text = msg.text.toLowerCase().trim()
21
+ console.log(`Tg: IncomingMsg: ID: ${chatId}, msg: ${text}`, msg)
22
+ logger.verbose(`Tg: IncomingMsg: ID: ${chatId}, msg: ${text}`)
23
+ });
24
+ }
25
+
26
+ // send a message to the group
27
+ sendMessage(msg: string) {
28
+ logger.verbose(`Tg Group: Sending message to group ${this.groupId}: ${msg}`);
29
+
30
+ const messageOptions: any = {};
31
+
32
+ // If topicId is provided, add it to the message options for supergroups with topics
33
+ if (this.topicId !== undefined) {
34
+ messageOptions.message_thread_id = this.topicId;
35
+ }
36
+
37
+ this.bot.sendMessage(this.groupId, msg, messageOptions)
38
+ .catch((err: any) => {
39
+ logger.error(`Tg Group: Error sending message to group ${this.groupId}`);
40
+ logger.error(`Tg Group: Error details: ${err.message}`);
41
+ })
42
+ .then(() => {
43
+ logger.verbose(`Tg Group: Message sent to group ${this.groupId}${this.topicId ? ` (topic: ${this.topicId})` : ''}`);
44
+ });
45
+ }
46
+
47
+ // send a message with specific formatting options
48
+ sendFormattedMessage(msg: string, parseMode?: 'HTML' | 'Markdown' | 'MarkdownV2') {
49
+ logger.verbose(`Tg Group: Sending formatted message to group ${this.groupId}: ${msg}`);
50
+
51
+ const messageOptions: any = {};
52
+
53
+ if (this.topicId !== undefined) {
54
+ messageOptions.message_thread_id = this.topicId;
55
+ }
56
+
57
+ if (parseMode) {
58
+ messageOptions.parse_mode = parseMode;
59
+ }
60
+
61
+ this.bot.sendMessage(this.groupId, msg, messageOptions)
62
+ .catch((err: any) => {
63
+ logger.error(`Tg Group: Error sending formatted message to group ${this.groupId}`);
64
+ logger.error(`Tg Group: Error details: ${err.message}`);
65
+ })
66
+ .then(() => {
67
+ logger.verbose(`Tg Group: Formatted message sent to group ${this.groupId}${this.topicId ? ` (topic: ${this.topicId})` : ''}`);
68
+ });
69
+ }
70
+
71
+ // send a photo to the group
72
+ sendPhoto(photo: string | Buffer, caption?: string) {
73
+ logger.verbose(`Tg Group: Sending photo to group ${this.groupId}`);
74
+
75
+ const messageOptions: any = {};
76
+
77
+ if (this.topicId !== undefined) {
78
+ messageOptions.message_thread_id = this.topicId;
79
+ }
80
+
81
+ if (caption) {
82
+ messageOptions.caption = caption;
83
+ }
84
+
85
+ this.bot.sendPhoto(this.groupId, photo, messageOptions)
86
+ .catch((err: any) => {
87
+ logger.error(`Tg Group: Error sending photo to group ${this.groupId}`);
88
+ logger.error(`Tg Group: Error details: ${err.message}`);
89
+ })
90
+ .then(() => {
91
+ logger.verbose(`Tg Group: Photo sent to group ${this.groupId}${this.topicId ? ` (topic: ${this.topicId})` : ''}`);
92
+ });
93
+ }
94
+
95
+ // get group information
96
+ getGroupInfo() {
97
+ return {
98
+ groupId: this.groupId,
99
+ topicId: this.topicId,
100
+ hasTopic: this.topicId !== undefined
101
+ };
102
+ }
103
+
104
+ // update group ID (useful if group ID changes)
105
+ updateGroupId(newGroupId: string) {
106
+ this.groupId = newGroupId;
107
+ logger.verbose(`Tg Group: Updated group ID to ${newGroupId}`);
108
+ }
109
+
110
+ // update topic ID (useful for switching topics)
111
+ updateTopicId(newTopicId?: number) {
112
+ this.topicId = newTopicId;
113
+ logger.verbose(`Tg Group: Updated topic ID to ${newTopicId || 'none'}`);
114
+ }
115
+ }
@@ -37,6 +37,8 @@ import { EkuboHarvests } from "@/modules/harvests";
37
37
  import { logger } from "@/utils/logger";
38
38
  import { COMMON_CONTRACTS } from "./constants";
39
39
  import { DepegRiskLevel, ImpermanentLossLevel, MarketRiskLevel, SmartContractRiskLevel } from "@/interfaces/risks";
40
+ import { gql } from "@apollo/client";
41
+ import apolloClient from "@/modules/apollo-client";
40
42
 
41
43
  export interface EkuboPoolKey {
42
44
  token0: ContractAddr;
@@ -51,6 +53,12 @@ export interface EkuboBounds {
51
53
  upperTick: bigint;
52
54
  }
53
55
 
56
+ interface FeeHistory {
57
+ date: string;
58
+ tokenInfo: TokenInfo;
59
+ amount: Web3Number;
60
+ }
61
+
54
62
  /**
55
63
  * Settings for the CLVaultStrategy
56
64
  *
@@ -285,11 +293,97 @@ export class EkuboCLVault extends BaseStrategy<
285
293
  return [this.contract.populate("handle_fees", [])];
286
294
  }
287
295
 
288
- /**
289
- * Calculates assets before and now in a given token of TVL per share to observe growth
290
- * @returns {Promise<number>} The weighted average APY across all pools
291
- */
292
- async netAPY(
296
+ async getFeeHistory(timePeriod: '24h' | '30d' | '3m' = '24h'): Promise<{
297
+ summary: DualTokenInfo,
298
+ history: FeeHistory[]
299
+ }> {
300
+ const { data } = await apolloClient.query({
301
+ query: gql`
302
+ query ContractFeeEarnings(
303
+ $timeframe: String!
304
+ $contract: String!
305
+ ) {
306
+ contractFeeEarnings(timeframe: $timeframe, contract: $contract) {
307
+ contract
308
+ dailyEarnings {
309
+ date
310
+ tokenAddress
311
+ amount
312
+ }
313
+ totalCollections
314
+ }
315
+ }
316
+ `,
317
+ variables: {
318
+ timeframe: timePeriod,
319
+ contract: this.address.address
320
+ },
321
+ fetchPolicy: 'no-cache',
322
+ });
323
+
324
+ // Sample response type:
325
+ // {
326
+ // contractFeeEarnings: {
327
+ // contract: '0x2bcaef2eb7706875a5fdc6853dd961a0590f850bc3a031c59887189b5e84ba1',
328
+ // dailyEarnings: [ [Object], [Object], [Object], [Object] ],
329
+ // totalCollections: 3,
330
+ // __typename: 'FeeSummary'
331
+ // }
332
+ // }
333
+ // dailyEarnings: {
334
+ // date: '2025-09-27',
335
+ // tokenAddress: '0x3fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac',
336
+ // amount: '15404',
337
+ // __typename: 'DailyFeeEarnings'
338
+ // }[]
339
+
340
+ // get pool key and token info
341
+ const poolKey = await this.getPoolKey();
342
+ const token0Info = await Global.getTokenInfoFromAddr(poolKey.token0);
343
+ const token1Info = await Global.getTokenInfoFromAddr(poolKey.token1);
344
+ const price0 = await this.pricer.getPrice(token0Info.symbol);
345
+ const price1 = await this.pricer.getPrice(token1Info.symbol);
346
+ let totalToken0Amount = Web3Number.fromWei(0, token0Info.decimals);
347
+ let totalToken1Amount = Web3Number.fromWei(0, token1Info.decimals);
348
+ let totalToken0Usd = 0;
349
+ let totalToken1Usd = 0;
350
+ const parsedFeeInfo: FeeHistory[] = [];
351
+ const feeInfo = data.contractFeeEarnings.dailyEarnings;
352
+ for (const d of feeInfo) {
353
+ const tokenInfo = await Global.getTokenInfoFromAddr(ContractAddr.from(d.tokenAddress));
354
+ const amount = Web3Number.fromWei(d.amount, tokenInfo.decimals);
355
+ if (tokenInfo.address.eq(poolKey.token0)) {
356
+ totalToken0Amount = totalToken0Amount.plus(amount);
357
+ totalToken0Usd = totalToken0Usd + amount.multipliedBy(price0.price).toNumber();
358
+ } else {
359
+ totalToken1Amount = totalToken1Amount.plus(amount);
360
+ totalToken1Usd = totalToken1Usd + amount.multipliedBy(price1.price).toNumber();
361
+ }
362
+ parsedFeeInfo.push({
363
+ date: d.date,
364
+ tokenInfo,
365
+ amount: Web3Number.fromWei(d.amount, tokenInfo.decimals)
366
+ });
367
+ }
368
+ return {
369
+ summary: {
370
+ usdValue: totalToken0Usd + totalToken1Usd,
371
+ token0: {
372
+ tokenInfo: token0Info,
373
+ amount: totalToken0Amount,
374
+ usdValue: totalToken0Usd
375
+ },
376
+ token1: {
377
+ tokenInfo: token1Info,
378
+ amount: totalToken1Amount,
379
+ usdValue: totalToken1Usd
380
+ }
381
+ },
382
+ history: parsedFeeInfo
383
+ }
384
+ }
385
+
386
+ async netSharesBasedTrueAPY(
293
387
  blockIdentifier: BlockIdentifier = "latest",
294
388
  sinceBlocks = 600000
295
389
  ): Promise<number> {
@@ -354,6 +448,33 @@ export class EkuboCLVault extends BaseStrategy<
354
448
  return (apyForGivenBlocks * (365 * 24 * 3600)) / timeDiffSeconds;
355
449
  }
356
450
 
451
+ async feeBasedAPY(
452
+ blockIdentifier: BlockIdentifier = "latest",
453
+ sinceBlocks = 600000
454
+ ): Promise<number> {
455
+ const feeInfo = await this.getFeeHistory('24h');
456
+ const tvlNow = await this.getTVL('latest');
457
+ return feeInfo.summary.usdValue * 365 / tvlNow.usdValue;
458
+ }
459
+
460
+ /**
461
+ * Calculates assets before and now in a given token of TVL per share to observe growth
462
+ * @returns {Promise<number>} The weighted average APY across all pools
463
+ */
464
+ async netAPY(
465
+ blockIdentifier: BlockIdentifier = "latest",
466
+ sinceBlocks = 600000
467
+ ): Promise<number> {
468
+ const isUSDCQouteToken = this.metadata.additionalInfo.quoteAsset.symbol === "USDC";
469
+ if (!isUSDCQouteToken) {
470
+ // good for LSTs and stables
471
+ return this.netSharesBasedTrueAPY(blockIdentifier, sinceBlocks);
472
+ } else {
473
+ // good for non-stables
474
+ return this.feeBasedAPY(blockIdentifier, sinceBlocks);
475
+ }
476
+ }
477
+
357
478
  async getHarvestRewardShares(fromBlock: number, toBlock: number) {
358
479
  const len = Number(await this.contract.call("get_total_rewards"));
359
480
  let shares = Web3Number.fromWei(0, 18);
@@ -1893,7 +2014,7 @@ const ETHUSDCRe7Strategy: IStrategyMetadata<CLVaultStrategySettings> = {
1893
2014
  Global.getDefaultTokens().find((t) => t.symbol === "USDC")!
1894
2015
  ],
1895
2016
  apyMethodology:
1896
- "APY based on 7-day historical performance, including fees and rewards.",
2017
+ "Annualized fee APY, calculated as fees earned in the last 24h divided by TVL",
1897
2018
  additionalInfo: {
1898
2019
  newBounds: "Managed by Re7",
1899
2020
  truePrice: 1,
@@ -1913,6 +2034,11 @@ const ETHUSDCRe7Strategy: IStrategyMetadata<CLVaultStrategySettings> = {
1913
2034
  answer:
1914
2035
  <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>
1915
2036
  },
2037
+ {
2038
+ question: "How is the APY calculated?",
2039
+ answer:
2040
+ <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>
2041
+ },
1916
2042
  ],
1917
2043
  risk: highRisk,
1918
2044
  points: [],
@@ -317,7 +317,7 @@ export class UniversalStrategy<
317
317
  if (legAUM[0].token.address.eq(underlying.address)) {
318
318
  vesuAum = vesuAum.plus(legAUM[0].amount);
319
319
  } else {
320
- vesuAum = vesuAum.plus(legAUM[1].usdValue / tokenUnderlyingPrice.price);
320
+ vesuAum = vesuAum.plus(legAUM[0].usdValue / tokenUnderlyingPrice.price);
321
321
  }
322
322
 
323
323
  // handle debt
@@ -360,14 +360,14 @@ export class UniversalStrategy<
360
360
  amount: zeroAmt,
361
361
  usdValue: 0
362
362
  };
363
- if (vesuAum.isZero()) {
363
+ const aumToken = vesuAum.plus(balance.amount);
364
+ if (aumToken.isZero()) {
364
365
  return { net, splits: [{
365
366
  aum: zeroAmt, id: AUMTypes.FINALISED
366
367
  }, {
367
368
  aum: zeroAmt, id: AUMTypes.DEFISPRING
368
369
  }], prevAum};
369
370
  }
370
- const aumToken = vesuAum.plus(balance.amount);
371
371
  logger.verbose(`${this.getTag()} Actual AUM: ${aumToken}`);
372
372
 
373
373
  // compute rewards contribution to AUM
@@ -252,18 +252,17 @@ export class VesuRebalance extends BaseStrategy<
252
252
  ) {
253
253
  const vesuPosition = vesuPositions.find(
254
254
  (d: any) =>
255
- d.pool.id.toString() ===
256
- num.getDecimalString(p.pool_id.address.toString())
255
+ ContractAddr.from(d.id).eq(p.pool_id)
257
256
  );
258
257
  const _pool = pools.find((d: any) => {
259
258
  logger.verbose(
260
259
  `pool check: ${
261
- d.id == num.getDecimalString(p.pool_id.address.toString())
260
+ ContractAddr.from(d.id).eq(p.pool_id)
262
261
  }, id: ${d.id}, pool_id: ${num.getDecimalString(
263
262
  p.pool_id.address.toString()
264
263
  )}`
265
264
  );
266
- return d.id == num.getDecimalString(p.pool_id.address.toString());
265
+ return ContractAddr.from(d.id).eq(p.pool_id);
267
266
  });
268
267
  logger.verbose(`pool: ${JSON.stringify(_pool)}`);
269
268
  logger.verbose(typeof _pool);