@strkfarm/sdk 1.2.1 → 2.0.0-dca.2

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 (42) hide show
  1. package/dist/cli.js +9 -5
  2. package/dist/cli.mjs +9 -5
  3. package/dist/index.browser.global.js +67035 -40458
  4. package/dist/index.browser.mjs +5218 -1908
  5. package/dist/index.d.ts +478 -33
  6. package/dist/index.js +5500 -2157
  7. package/dist/index.mjs +5441 -2129
  8. package/package.json +4 -1
  9. package/src/data/ekubo-price-fethcer.abi.json +265 -0
  10. package/src/data/yoloVault.abi.json +777 -0
  11. package/src/dataTypes/_bignumber.ts +5 -0
  12. package/src/dataTypes/bignumber.browser.ts +5 -0
  13. package/src/dataTypes/bignumber.node.ts +5 -0
  14. package/src/dataTypes/index.ts +3 -2
  15. package/src/dataTypes/mynumber.ts +141 -0
  16. package/src/global.ts +42 -0
  17. package/src/index.browser.ts +2 -1
  18. package/src/interfaces/common.tsx +168 -2
  19. package/src/modules/apollo-client-config.ts +28 -0
  20. package/src/modules/avnu.ts +1 -1
  21. package/src/modules/ekubo-pricer.ts +79 -0
  22. package/src/modules/erc20.ts +18 -2
  23. package/src/modules/pragma.ts +23 -8
  24. package/src/modules/pricer-from-api.ts +150 -14
  25. package/src/modules/pricer.ts +2 -1
  26. package/src/modules/pricerBase.ts +2 -1
  27. package/src/node/pricer-redis.ts +2 -1
  28. package/src/strategies/base-strategy.ts +81 -2
  29. package/src/strategies/ekubo-cl-vault.tsx +686 -316
  30. package/src/strategies/factory.ts +159 -0
  31. package/src/strategies/index.ts +5 -1
  32. package/src/strategies/registry.ts +239 -0
  33. package/src/strategies/sensei.ts +361 -13
  34. package/src/strategies/types.ts +4 -0
  35. package/src/strategies/universal-adapters/vesu-adapter.ts +48 -27
  36. package/src/strategies/universal-lst-muliplier-strategy.tsx +1396 -463
  37. package/src/strategies/universal-strategy.tsx +287 -129
  38. package/src/strategies/vesu-rebalance.tsx +242 -146
  39. package/src/strategies/yoloVault.ts +463 -0
  40. package/src/utils/index.ts +1 -1
  41. package/src/utils/logger.node.ts +11 -4
  42. package/src/utils/strategy-utils.ts +61 -0
@@ -1,21 +1,59 @@
1
- import { PriceInfo } from "./pricer";
1
+ import { PriceInfo } from "./pricer";
2
2
  import axios from "axios";
3
3
  import { IConfig, TokenInfo } from "@/interfaces";
4
4
  import { PricerBase } from "./pricerBase";
5
5
  import { logger } from "@/utils/logger";
6
+ import { createApolloClient } from "./apollo-client-config";
7
+ import { gql } from "@apollo/client";
8
+ import { ContractAddr } from "@/dataTypes";
9
+ import { Pragma } from "./pragma";
10
+ import { EkuboPricer } from "./ekubo-pricer";
11
+ import { BlockIdentifier } from "starknet";
6
12
 
7
13
  export class PricerFromApi extends PricerBase {
14
+ private apolloClient: ReturnType<typeof createApolloClient>;
15
+ private pragma: Pragma;
16
+ private ekuboPricer: EkuboPricer;
17
+
18
+ // Tokens supported by Pragma and GraphQL for historical prices (standardized addresses)
19
+ // Note: xSTRK is not supported by Pragma RPC, so it will use Ekubo pricer instead
20
+ private readonly PRAGMA_SUPPORTED_TOKENS = [
21
+ ContractAddr.from('0x3fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac').address, // WBTC
22
+ ContractAddr.from('0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8').address, // USDC
23
+ ContractAddr.from('0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d').address, // STRK
24
+ // xSTRK removed - not supported by Pragma RPC, will use Ekubo pricer
25
+ // ContractAddr.from('0x28d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a').address, // xSTRK
26
+ ContractAddr.from('0x68f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8').address, // USDT
27
+ ContractAddr.from('0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7').address, // ETH
28
+ ];
29
+
8
30
  constructor(config: IConfig, tokens: TokenInfo[]) {
9
31
  super(config, tokens);
32
+ this.apolloClient = createApolloClient(config);
33
+ this.pragma = new Pragma(config, tokens);
34
+ this.ekuboPricer = new EkuboPricer(config, tokens);
10
35
  }
11
36
 
12
- async getPrice(tokenSymbol: string): Promise<PriceInfo> {
37
+ async getPrice(tokenSymbol: string, blockNumber?: BlockIdentifier): Promise<PriceInfo> {
38
+ // If blockNumber is provided, fetch historical price from GraphQL
39
+ if (blockNumber !== undefined) {
40
+ try {
41
+ return await this.getHistoricalPrice(tokenSymbol, blockNumber);
42
+ } catch (e: any) {
43
+ logger.error(`Historical price fetch failed for ${tokenSymbol}: ${e.message}`);
44
+ throw new Error('Server Error in fetching historical price')
45
+ // Fall back to current price if historical price is not available
46
+ }
47
+ }
48
+
49
+ // Otherwise, fetch current price
13
50
  try {
14
51
  return await this.getPriceFromMyAPI(tokenSymbol);
15
52
  } catch (e: any) {
16
- logger.warn('getPriceFromMyAPI error', JSON.stringify(e.message || e));
53
+ logger.error(`API price fetch failed for ${tokenSymbol}: ${e.message}`);
17
54
  }
18
- logger.info('getPrice coinbase', tokenSymbol);
55
+
56
+ logger.info(`Using Coinbase price for ${tokenSymbol}`);
19
57
  let retry = 0;
20
58
  const MAX_RETRIES = 5;
21
59
  for (retry = 1; retry < MAX_RETRIES + 1; retry++) {
@@ -41,22 +79,120 @@ export class PricerFromApi extends PricerBase {
41
79
  }
42
80
 
43
81
  async getPriceFromMyAPI(tokenSymbol: string) {
44
- logger.verbose(`getPrice from redis: ${tokenSymbol}`);
45
82
  const endpoint = 'https://proxy.api.troves.fi'
46
83
  const url = `${endpoint}/api/price/${tokenSymbol}`;
47
84
  const priceInfoRes = await fetch(url);
48
85
  const priceInfo = await priceInfoRes.json();
49
- const now = new Date();
50
- const priceTime = new Date(priceInfo.timestamp);
51
- // if (now.getTime() - priceTime.getTime() > 900000) {
52
- // // 15 mins
53
- // logger.verbose(`Price is stale: ${tokenSymbol}, timestamp: ${priceInfo.timestamp}, price: ${priceInfo.price}`);
54
- // throw new Error('Price is stale');
55
- // }
56
86
  const price = Number(priceInfo.price);
57
87
  return {
58
88
  price,
59
89
  timestamp: new Date(priceInfo.timestamp)
60
90
  }
61
- }
62
- }
91
+ }
92
+
93
+ /**
94
+ * Fetches historical price for a token at a specific block number
95
+ * @param tokenSymbol - The token symbol to get price for
96
+ * @param blockNumber - The block number to query
97
+ * @returns PriceInfo with price at the closest block <= blockNumber
98
+ */
99
+ async getHistoricalPrice(tokenSymbol: string, blockNumber: BlockIdentifier): Promise<PriceInfo> {
100
+ const tokenInfo = this.tokens.find(t => t.symbol === tokenSymbol);
101
+ if (!tokenInfo) {
102
+ throw new Error(`Token ${tokenSymbol} not found in configured tokens`);
103
+ }
104
+
105
+ // If blockNumber is not a number, fall back to current price
106
+ if (typeof blockNumber !== 'number') {
107
+ logger.info(`Non-numeric blockIdentifier '${blockNumber}' provided, fetching current price for ${tokenSymbol}`);
108
+ return await this.getPriceFromMyAPI(tokenSymbol);
109
+ }
110
+
111
+ const standardizedAddress = ContractAddr.from(tokenInfo.address.address).address;
112
+ const isPragmaSupported = this.PRAGMA_SUPPORTED_TOKENS.includes(standardizedAddress);
113
+
114
+ // Step 1: Try GraphQL first for ALL tokens
115
+ let data;
116
+ try {
117
+ const result = await this.apolloClient.query({
118
+ query: gql`
119
+ query GetHistoricalPrice($asset: String!, $blockNumber: Int!) {
120
+ findFirstPrices(
121
+ where: {
122
+ asset: { equals: $asset },
123
+ block_number: { lte: $blockNumber }
124
+ }
125
+ orderBy: { block_number: desc }
126
+ ) {
127
+ price
128
+ timestamp
129
+ block_number
130
+ }
131
+ }
132
+ `,
133
+ variables: {
134
+ asset: standardizedAddress,
135
+ blockNumber: blockNumber,
136
+ },
137
+ });
138
+ data = result.data;
139
+ } catch (graphqlError: any) {
140
+ logger.error(`GraphQL query failed for ${tokenSymbol}: ${graphqlError.message}`);
141
+ }
142
+
143
+ // Step 2: If GraphQL has data, check block difference and use it if acceptable
144
+ if (data?.findFirstPrices) {
145
+ const priceData = data.findFirstPrices;
146
+ const blockDifference = blockNumber - priceData.block_number;
147
+ const MAX_BLOCK_DIFFERENCE = 6800; // ~6 hours worth of blocks
148
+
149
+ if (blockDifference <= MAX_BLOCK_DIFFERENCE) {
150
+ logger.info(`Using GraphQL price for ${tokenSymbol} at block ${priceData.block_number}`);
151
+ return {
152
+ price: Number(priceData.price),
153
+ timestamp: new Date(priceData.timestamp * 1000),
154
+ };
155
+ }
156
+
157
+ logger.info(
158
+ `Block difference ${blockDifference} exceeds limit for ${tokenSymbol}. ` +
159
+ `Will try fallback sources at block ${blockNumber}`
160
+ );
161
+ } else {
162
+ logger.info(`No GraphQL data for ${tokenSymbol} at block ${blockNumber}, trying fallback sources`);
163
+ }
164
+
165
+ // Step 3: Fallback to Pragma (for supported tokens) or Ekubo (for non-supported tokens)
166
+ if (isPragmaSupported) {
167
+ logger.info(`Attempting Pragma RPC fetch for ${tokenSymbol} at block ${blockNumber}`);
168
+ try {
169
+ const priceInfo = await this.pragma.getPrice(tokenInfo.address.address, blockNumber);
170
+ logger.info(`Using Pragma price for ${tokenSymbol}: ${priceInfo.price}`);
171
+ return {
172
+ price: priceInfo.price,
173
+ timestamp: priceInfo.timestamp,
174
+ };
175
+ } catch (error: any) {
176
+ logger.error(`Pragma RPC failed for ${tokenSymbol} at block ${blockNumber}: ${error.message}`);
177
+ throw new Error(`Server Error at Pragma token ${tokenSymbol} at block ${blockNumber}`);
178
+ }
179
+ } else {
180
+ // For non-Pragma tokens, use Ekubo pricer
181
+ logger.info(`Attempting Ekubo price fetch for ${tokenSymbol} at block ${blockNumber}`);
182
+ try {
183
+ const priceInfo = await this.ekuboPricer.getPrice(
184
+ tokenInfo.address.address,
185
+ blockNumber
186
+ );
187
+ logger.info(`Using Ekubo price for ${tokenSymbol}: ${priceInfo.price}`);
188
+ return {
189
+ price: priceInfo.price,
190
+ timestamp: priceInfo.timestamp,
191
+ };
192
+ } catch (error: any) {
193
+ logger.error(`Ekubo RPC failed for ${tokenSymbol} at block ${blockNumber}: ${error.message}`);
194
+ throw new Error(`Server Error at Ekubo token ${tokenSymbol} at block ${blockNumber}`);
195
+ }
196
+ }
197
+ }
198
+ }
@@ -6,6 +6,7 @@ import { Web3Number } from "@/dataTypes";
6
6
  import { PricerBase } from "./pricerBase";
7
7
  import { logger } from "@/utils/logger";
8
8
  import { AvnuWrapper } from "./avnu";
9
+ import { BlockIdentifier } from "starknet";
9
10
 
10
11
  export interface PriceInfo {
11
12
  price: number,
@@ -83,7 +84,7 @@ export class Pricer extends PricerBase {
83
84
  Global.assert(!this.isStale(timestamp, tokenName), `Price of ${tokenName} is stale`);
84
85
 
85
86
  }
86
- async getPrice(tokenSymbol: string) {
87
+ async getPrice(tokenSymbol: string, blockNumber?: BlockIdentifier) {
87
88
  Global.assert(this.prices[tokenSymbol], `Price of ${tokenSymbol} not found`);
88
89
  this.assertNotStale(this.prices[tokenSymbol].timestamp, tokenSymbol);
89
90
  return this.prices[tokenSymbol];
@@ -1,5 +1,6 @@
1
1
  import { IConfig, TokenInfo } from "@/interfaces";
2
2
  import { PriceInfo } from "./pricer";
3
+ import { BlockIdentifier } from "starknet";
3
4
 
4
5
  export abstract class PricerBase {
5
6
  readonly config: IConfig;
@@ -9,7 +10,7 @@ export abstract class PricerBase {
9
10
  this.tokens = tokens;
10
11
  }
11
12
 
12
- async getPrice(tokenSymbol: string): Promise<PriceInfo> {
13
+ async getPrice(tokenSymbol: string, blockNumber?: BlockIdentifier): Promise<PriceInfo> {
13
14
  throw new Error('Method not implemented');
14
15
  }
15
16
  }
@@ -4,6 +4,7 @@ import { PriceInfo, Pricer } from '@/modules/pricer';
4
4
  import { createClient } from 'redis';
5
5
  import type { RedisClientType } from 'redis'
6
6
  import { logger } from "@/utils/logger";
7
+ import { BlockIdentifier } from "starknet";
7
8
 
8
9
  export class PricerRedis extends Pricer {
9
10
  private redisClient: RedisClientType | null = null;
@@ -51,7 +52,7 @@ export class PricerRedis extends Pricer {
51
52
  }
52
53
 
53
54
  /** Returns price from redis */
54
- async getPrice(tokenSymbol: string) {
55
+ async getPrice(tokenSymbol: string, blockNumber?: BlockIdentifier) {
55
56
  const STALE_TIME = 60000;
56
57
  if (!this.redisClient) {
57
58
  throw new FatalError(`Redis client not initialised`);
@@ -2,7 +2,7 @@ import { ContractAddr, Web3Number } from "@/dataTypes";
2
2
  import { IConfig, TokenInfo, VaultPosition } from "@/interfaces";
3
3
  import { HarvestInfo } from "@/modules/harvests";
4
4
  import { CacheClass } from "@/utils/cacheClass";
5
- import { Call } from "starknet";
5
+ import { Call, BlockIdentifier } from "starknet";
6
6
 
7
7
  export interface SingleActionAmount {
8
8
  tokenInfo: TokenInfo,
@@ -23,6 +23,16 @@ export interface DualTokenInfo {
23
23
  token1: SingleTokenInfo
24
24
  }
25
25
 
26
+ export interface NetAPYSplit {
27
+ apy: number;
28
+ id: string;
29
+ }
30
+
31
+ export interface NetAPYDetails {
32
+ net: number;
33
+ splits: NetAPYSplit[];
34
+ }
35
+
26
36
  interface CacheData {
27
37
  timestamp: number;
28
38
  ttl: number;
@@ -37,7 +47,7 @@ export class BaseStrategy<TVLInfo, ActionInfo> extends CacheClass {
37
47
  this.config = config;
38
48
  }
39
49
 
40
- async getUserTVL(user: ContractAddr): Promise<TVLInfo> {
50
+ async getUserTVL(user: ContractAddr, blockIdentifier?: BlockIdentifier): Promise<TVLInfo> {
41
51
  throw new Error("Not implemented");
42
52
  }
43
53
 
@@ -57,7 +67,76 @@ export class BaseStrategy<TVLInfo, ActionInfo> extends CacheClass {
57
67
  throw new Error("Not implemented");
58
68
  }
59
69
 
70
+ async netAPY(
71
+ blockIdentifier?: BlockIdentifier,
72
+ sinceBlocks?: number,
73
+ timeperiod?: "24h" | "7d" | "30d" | "3m"
74
+ ): Promise<number | NetAPYDetails> {
75
+ throw new Error("Not implemented");
76
+ }
77
+
60
78
  async getPendingRewards(): Promise<HarvestInfo[]> {
61
79
  return [];
62
80
  }
81
+
82
+ async getUserRealizedAPY(
83
+ blockIdentifier?: BlockIdentifier,
84
+ sinceBlocks?: number
85
+ ): Promise<number> {
86
+ throw new Error("Not implemented");
87
+ }
88
+
89
+ /**
90
+ * Calculate lifetime earnings for a user based on provided data from client
91
+ * Formula: lifetimeEarnings = currentValue + totalWithdrawals - totalDeposits
92
+ *
93
+ * @param userTVL - The user's current TVL (SingleTokenInfo with amount, usdValue, tokenInfo)
94
+ * @param investmentFlows - Array of investment flow transactions from client
95
+ * @returns Object containing lifetime earnings, current value, and total deposits/withdrawals
96
+ */
97
+ getLifetimeEarnings(
98
+ userTVL: SingleTokenInfo,
99
+ investmentFlows: Array<{ amount: string; type: string; timestamp: number; tx_hash: string }>
100
+ ): {
101
+ tokenInfo: SingleTokenInfo;
102
+ lifetimeEarnings: Web3Number;
103
+ currentValue: Web3Number;
104
+ totalDeposits: Web3Number;
105
+ totalWithdrawals: Web3Number;
106
+ } {
107
+ // Get token decimals from userTVL
108
+ const tokenDecimals = userTVL.tokenInfo.decimals;
109
+
110
+ // Initialize totals
111
+ let totalDeposits = Web3Number.fromWei("0", tokenDecimals);
112
+ let totalWithdrawals = Web3Number.fromWei("0", tokenDecimals);
113
+
114
+ // Process investment flows
115
+ for (const flow of investmentFlows) {
116
+ const amount = Web3Number.fromWei(flow.amount, tokenDecimals);
117
+
118
+ if (flow.type === 'deposit') {
119
+ totalDeposits = totalDeposits.plus(amount);
120
+ } else if (flow.type === 'withdraw' || flow.type === 'redeem') {
121
+ totalWithdrawals = totalWithdrawals.plus(amount);
122
+ }
123
+ }
124
+
125
+ // Calculate lifetime earnings: current value + withdrawals - deposits
126
+ const lifetimeEarnings = userTVL.amount
127
+ .plus(totalWithdrawals)
128
+ .minus(totalDeposits);
129
+
130
+ return {
131
+ tokenInfo: {
132
+ tokenInfo: userTVL.tokenInfo,
133
+ amount: lifetimeEarnings,
134
+ usdValue: 0, // Lifetime earnings are not converted to USD
135
+ },
136
+ lifetimeEarnings,
137
+ currentValue: userTVL.amount,
138
+ totalDeposits,
139
+ totalWithdrawals,
140
+ };
141
+ }
63
142
  }