@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.browser.global.js +14103 -1672
- package/dist/index.browser.mjs +108 -12
- package/dist/index.d.ts +30 -1
- package/dist/index.js +196 -12
- package/dist/index.mjs +195 -12
- package/package.json +3 -1
- package/src/modules/apollo-client.ts +8 -0
- package/src/notifs/index.ts +2 -1
- package/src/notifs/telegram-group.ts +115 -0
- package/src/strategies/ekubo-cl-vault.tsx +132 -6
- package/src/strategies/universal-strategy.tsx +3 -3
- package/src/strategies/vesu-rebalance.tsx +3 -4
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
15450
|
-
|
|
15451
|
-
|
|
15452
|
-
|
|
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
|
|
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[
|
|
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
|
-
|
|
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.
|
|
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",
|
package/src/notifs/index.ts
CHANGED
|
@@ -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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
|
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[
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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);
|