@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,5 +1,6 @@
1
1
  import { logger } from "@/utils/logger";
2
2
  import BigNumber from "bignumber.js";
3
+ import { uint256 } from "starknet";
3
4
 
4
5
  export class _Web3Number<T extends _Web3Number<T>> extends BigNumber {
5
6
  decimals: number;
@@ -107,6 +108,10 @@ export class _Web3Number<T extends _Web3Number<T>> extends BigNumber {
107
108
  sign: sign,
108
109
  }
109
110
  }
111
+
112
+ toUint256() {
113
+ return uint256.bnToUint256(this.toWei());
114
+ }
110
115
  }
111
116
 
112
117
  BigNumber.config({ DECIMAL_PLACES: 18, ROUNDING_MODE: BigNumber.ROUND_DOWN });
@@ -1,3 +1,4 @@
1
+ import { Uint256, uint256 } from "starknet";
1
2
  import { _Web3Number } from "./_bignumber";
2
3
 
3
4
  export class Web3Number extends _Web3Number<Web3Number> {
@@ -5,4 +6,8 @@ export class Web3Number extends _Web3Number<Web3Number> {
5
6
  const bn = (new Web3Number(weiNumber, decimals)).dividedBy(10 ** decimals)
6
7
  return new Web3Number(bn.toString(), decimals);
7
8
  }
9
+
10
+ static fromUint256(uint256Value: Uint256): Web3Number {
11
+ return this.fromWei(uint256.uint256ToBN(uint256Value).toString(), 18);
12
+ }
8
13
  }
@@ -1,5 +1,6 @@
1
1
  import util from 'util';
2
2
  import { _Web3Number } from "./_bignumber";
3
+ import { Uint256, uint256 } from 'starknet';
3
4
 
4
5
  export class Web3Number extends _Web3Number<Web3Number> {
5
6
 
@@ -8,6 +9,10 @@ export class Web3Number extends _Web3Number<Web3Number> {
8
9
  return new Web3Number(bn.toString(), decimals);
9
10
  }
10
11
 
12
+ static fromUint256(uint256Value: Uint256): Web3Number {
13
+ return this.fromWei(uint256.uint256ToBN(uint256Value).toString(), 18);
14
+ }
15
+
11
16
  [util.inspect.custom](depth: any, opts: any): string {
12
17
  return this.toString();
13
18
  }
@@ -1,2 +1,3 @@
1
- export * from '@/dataTypes/bignumber';
2
- export * from './address';
1
+ export * from "@/dataTypes/bignumber";
2
+ export * from "./address";
3
+ export * from "./mynumber";
@@ -0,0 +1,141 @@
1
+ import BigNumber from "bignumber.js";
2
+ import { ethers } from "ethers";
3
+
4
+ const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom");
5
+
6
+ BigNumber.config({
7
+ DECIMAL_PLACES: 18
8
+ });
9
+
10
+ export class MyNumber {
11
+ bigNumber: BigNumber;
12
+ decimals: number;
13
+
14
+ constructor(bigNumber: string, decimals: number) {
15
+ this.bigNumber = new BigNumber(bigNumber);
16
+ this.decimals = decimals;
17
+ }
18
+
19
+ static fromEther(num: string, decimals: number) {
20
+ try {
21
+ return new MyNumber(
22
+ Number(
23
+ ethers.parseUnits(Number(num).toFixed(10), decimals)
24
+ ).toFixed(6),
25
+ decimals
26
+ );
27
+ } catch (e) {
28
+ console.error("fromEther", e, num, decimals);
29
+ throw e;
30
+ }
31
+ }
32
+
33
+ static fromZero() {
34
+ return new MyNumber("0", 0);
35
+ }
36
+
37
+ toString() {
38
+ return this.bigNumber.toFixed();
39
+ }
40
+
41
+ toEtherStr() {
42
+ return ethers.formatUnits(this.bigNumber.toFixed(), this.decimals);
43
+ }
44
+
45
+ toFixedStr(decimals: number) {
46
+ return Number(this.toEtherStr()).toFixed(decimals);
47
+ }
48
+
49
+ toEtherToFixedDecimals(decimals: number) {
50
+ // rounding down
51
+ if (this.bigNumber.isNaN()) {
52
+ return "NaN";
53
+ }
54
+ return (
55
+ Math.floor(parseFloat(this.toEtherStr()) * 10 ** decimals) /
56
+ 10 ** decimals
57
+ ).toFixed(decimals);
58
+ }
59
+
60
+ isZero() {
61
+ return this.bigNumber.eq("0");
62
+ }
63
+
64
+ /**
65
+ *
66
+ * @param amountEther in token terms without decimal e.g. 1 for 1 STRK
67
+ * @param command BigNumber compare funds. e.g. gte, gt, lt
68
+ * @returns
69
+ * @dev Add more commands as needed
70
+ */
71
+ compare(amountEther: string, command: "gte" | "gt" | "lt") {
72
+ const fullNum = new BigNumber(
73
+ ethers.parseUnits(amountEther, this.decimals).toString()
74
+ );
75
+ return this.bigNumber[command](fullNum);
76
+ }
77
+
78
+ operate(command: "div" | "plus" | "mul", value: string | number) {
79
+ const bn = new BigNumber(Number(value).toFixed(6));
80
+ return new MyNumber(
81
+ this.bigNumber[command](bn).toFixed(0),
82
+ this.decimals
83
+ );
84
+ }
85
+
86
+ subtract(value: MyNumber) {
87
+ const bn = this.bigNumber.minus(value.bigNumber);
88
+ return new MyNumber(bn.toString(), this.decimals);
89
+ }
90
+
91
+ static min(a: MyNumber, b: MyNumber) {
92
+ if (a.decimals !== b.decimals) {
93
+ const diff = Math.abs(a.decimals - b.decimals);
94
+ if (a.decimals > b.decimals) {
95
+ b = new MyNumber(
96
+ b.bigNumber.times(10 ** diff).toString(),
97
+ a.decimals
98
+ );
99
+ } else {
100
+ a = new MyNumber(
101
+ a.bigNumber.times(10 ** diff).toString(),
102
+ b.decimals
103
+ );
104
+ }
105
+ }
106
+ const bn = BigNumber.min(a.bigNumber, b.bigNumber);
107
+ return new MyNumber(
108
+ bn.toString(),
109
+ a.decimals > b.decimals ? a.decimals : b.decimals
110
+ );
111
+ }
112
+
113
+ static max(a: MyNumber, b: MyNumber) {
114
+ if (a.decimals !== b.decimals) {
115
+ const diff = Math.abs(a.decimals - b.decimals);
116
+ if (a.decimals > b.decimals) {
117
+ b = new MyNumber(
118
+ b.bigNumber.times(10 ** diff).toString(),
119
+ a.decimals
120
+ );
121
+ } else {
122
+ a = new MyNumber(
123
+ a.bigNumber.times(10 ** diff).toString(),
124
+ b.decimals
125
+ );
126
+ }
127
+ }
128
+ const bn = BigNumber.max(a.bigNumber, b.bigNumber);
129
+ return new MyNumber(
130
+ bn.toString(),
131
+ a.decimals > b.decimals ? a.decimals : b.decimals
132
+ );
133
+ }
134
+
135
+ [customInspectSymbol](depth: any, inspectOptions: any, inspect: any) {
136
+ return JSON.stringify({
137
+ raw: this.toString(),
138
+ decimals: this.decimals
139
+ });
140
+ }
141
+ }
package/src/global.ts CHANGED
@@ -165,6 +165,33 @@ const defaultTokens: TokenInfo[] = [{
165
165
  coingeckId: undefined,
166
166
  displayDecimals: 2,
167
167
  priceCheckAmount: 100,
168
+ }, {
169
+ name: "fyeWBTC",
170
+ symbol: "fyeWBTC",
171
+ logo: 'https://assets.strkfarm.com/integrations/tokens/wbtc.svg',
172
+ address: ContractAddr.from('0x04dd39de0a588f5e1c7a8377e1bef2c49caaee49a11433429d2c48f587b3a492'),
173
+ decimals: 8,
174
+ coingeckId: undefined,
175
+ displayDecimals: 6,
176
+ priceCheckAmount: 0.001, // 112000 * 0.0001 = $110.2
177
+ }, {
178
+ name: "fyETH",
179
+ symbol: "fyETH",
180
+ logo: 'https://assets.strkfarm.com/integrations/tokens/eth.svg',
181
+ address: ContractAddr.from('0x050707bC3b8730022F10530C2c6f6b9467644129C50C2868Ad0036c5e4E9e616'),
182
+ decimals: 18,
183
+ coingeckId: undefined,
184
+ displayDecimals: 4,
185
+ priceCheckAmount: 0.1,
186
+ }, {
187
+ name: "fyeUSDC",
188
+ symbol: "fyeUSDC",
189
+ logo: 'https://assets.strkfarm.com/integrations/tokens/usdc.svg',
190
+ address: ContractAddr.from('0x07fdcec0cef01294c9c3d52415215949805c77bae8003702a7928fd6d2c36bc1'),
191
+ decimals: 6,
192
+ coingeckId: undefined,
193
+ displayDecimals: 2,
194
+ priceCheckAmount: 100,
168
195
  }]
169
196
  const tokens: TokenInfo[] = defaultTokens;
170
197
 
@@ -255,6 +282,21 @@ export class Global {
255
282
  return token;
256
283
  }
257
284
 
285
+ static async getTokenInfoFromName(tokenName: string) {
286
+ // if tokens are not loaded, load them
287
+ if (tokens.length == defaultTokens.length) {
288
+ await Global.getTokens();
289
+ }
290
+
291
+ const token = tokens.find(
292
+ (token) => token.name.toLowerCase() === tokenName.toLowerCase()
293
+ );
294
+ if (!token) {
295
+ throw new FatalError(`Token not found: ${tokenName}`);
296
+ }
297
+ return token;
298
+ }
299
+
258
300
  static setGlobalCache(key: string, data: any, ttl: number = 60000) {
259
301
  Global.cache[key] = {
260
302
  value: data,
@@ -3,4 +3,5 @@ export * from './modules';
3
3
  export * from './interfaces';
4
4
  export * from './dataTypes';
5
5
  export * from './global';
6
- export * from './strategies';
6
+ export * from './strategies';
7
+ export * from "./utils";
@@ -55,6 +55,69 @@ export interface IProtocol {
55
55
  logo: string;
56
56
  }
57
57
 
58
+ export enum StrategyTag {
59
+ META_VAULT = "Meta Vaults",
60
+ LEVERED = "Maxx",
61
+ AUTOMATED_LP = "Ekubo",
62
+ BTC = "BTC"
63
+ }
64
+
65
+ export enum VaultType {
66
+ LOOPING = "Looping",
67
+ META_VAULT = "Meta Vault",
68
+ DELTA_NEUTRAL = "Delta Neutral",
69
+ AUTOMATED_LP = "Automated LP",
70
+ Other = "Other",
71
+ }
72
+
73
+ // Security metadata enums
74
+ export enum AuditStatus {
75
+ AUDITED = "Audited",
76
+ NOT_AUDITED = "Not Audited",
77
+ }
78
+
79
+ export enum SourceCodeType {
80
+ OPEN_SOURCE = "Open Source",
81
+ CLOSED_SOURCE = "Closed Source",
82
+ }
83
+
84
+ export enum AccessControlType {
85
+ MULTISIG_ACCOUNT = "Multisig Account",
86
+ STANDARD_ACCOUNT = "Standard Account",
87
+ }
88
+
89
+ export enum InstantWithdrawalVault {
90
+ YES = "Yes",
91
+ NO = "No",
92
+ }
93
+
94
+ // Security metadata interfaces
95
+ export interface SourceCodeInfo {
96
+ type: SourceCodeType;
97
+ contractLink: string;
98
+ }
99
+
100
+ export interface AccessControlInfo {
101
+ type: AccessControlType;
102
+ addresses: ContractAddr[];
103
+ timeLock: string;
104
+ }
105
+
106
+ export interface SecurityMetadata {
107
+ auditStatus: AuditStatus;
108
+ sourceCode: SourceCodeInfo;
109
+ accessControl: AccessControlInfo;
110
+ }
111
+
112
+ export interface RedemptionInfo {
113
+ instantWithdrawalVault: InstantWithdrawalVault;
114
+ redemptionsInfo: {
115
+ title: string; // e.g. Upto $1M
116
+ description: string; // e.g. "1-2 hours"
117
+ }[],
118
+ alerts: StrategyAlert[];
119
+ }
120
+
58
121
  export enum FlowChartColors {
59
122
  Green = "purple",
60
123
  Blue = "#35484f",
@@ -66,26 +129,65 @@ export interface FAQ {
66
129
  answer: string | React.ReactNode;
67
130
  }
68
131
 
132
+ export enum StrategyLiveStatus {
133
+ ACTIVE = "Active",
134
+ NEW = "New",
135
+ COMING_SOON = "Coming Soon",
136
+ DEPRECATED = "Deprecated", // active but not recommended
137
+ RETIRED = "Retired", // not active anymore
138
+ HOT = "Hot & New 🔥"
139
+ }
140
+
141
+ export interface StrategyAlert {
142
+ type: "warning" | "info";
143
+ text: string | React.ReactNode;
144
+ tab: "all" | "deposit" | "withdraw";
145
+ }
146
+
147
+ export interface StrategySettings {
148
+ maxTVL?: Web3Number;
149
+ liveStatus?: StrategyLiveStatus;
150
+ isPaused?: boolean;
151
+ isInMaintenance?: boolean;
152
+ isAudited: boolean;
153
+ isInstantWithdrawal?: boolean;
154
+ hideHarvestInfo?: boolean;
155
+ is_promoted?: boolean;
156
+ isTransactionHistDisabled?: boolean;
157
+ quoteToken: TokenInfo;
158
+ hideNetEarnings?: boolean;
159
+ showWithdrawalWarningModal?: boolean;
160
+ alerts?: StrategyAlert[];
161
+ tags?: StrategyTag[];
162
+ }
163
+
69
164
  /**
70
165
  * @property risk.riskFactor.factor - The risk factors that are considered for the strategy.
71
166
  * @property risk.riskFactor.factor - The value of the risk factor from 0 to 10, 0 being the lowest and 10 being the highest.
167
+ * @property security - Security-related metadata including audit status, source code information, and access control details.
168
+ * @property redemptionInfo - Redemption information including instant withdrawal availability and expected redemption times.
72
169
  */
73
170
  export interface IStrategyMetadata<T> {
171
+ id: string;
74
172
  name: string;
75
173
  description: string | React.ReactNode;
76
174
  address: ContractAddr;
77
175
  launchBlock: number;
78
176
  type: "ERC4626" | "ERC721" | "Other";
177
+ vaultType: {
178
+ type: VaultType;
179
+ description: string;
180
+ };
79
181
  depositTokens: TokenInfo[];
80
182
  protocols: IProtocol[];
81
183
  auditUrl?: string;
82
- maxTVL: Web3Number;
83
184
  risk: {
84
185
  riskFactor: RiskFactor[];
85
186
  netRisk: number;
86
187
  notARisks: RiskType[];
87
188
  };
88
189
  apyMethodology?: string;
190
+ realizedAPYMethodology?: string;
89
191
  additionalInfo: T;
90
192
  contractDetails: {
91
193
  address: ContractAddr;
@@ -97,7 +199,30 @@ export interface IStrategyMetadata<T> {
97
199
  docs?: string;
98
200
  investmentSteps: string[];
99
201
  curator?: { name: string, logo: string },
100
- isPreview?: boolean
202
+ isPreview?: boolean;
203
+ tags?: StrategyTag[];
204
+ security: SecurityMetadata;
205
+ redemptionInfo: RedemptionInfo;
206
+ usualTimeToEarnings: null | string; // e.g. "2 weeks" // some strats grow like step functions
207
+ usualTimeToEarningsDescription: null | string; // e.g. "LSTs price on DEX goes up roughly every 2 weeks"
208
+ discontinuationInfo?: {
209
+ date?: Date;
210
+ reason?: React.ReactNode | string;
211
+ info?: React.ReactNode | string;
212
+ };
213
+ settings?: StrategySettings;
214
+ // Legacy field for multi-step strategies (deprecated, use investmentFlows instead)
215
+ actions?: Array<{
216
+ name?: string;
217
+ pool?: {
218
+ protocol?: { name: string; logo: string };
219
+ pool?: { name: string; logos?: string[] };
220
+ apr?: number;
221
+ borrow?: { apr?: number };
222
+ };
223
+ amount?: string | number;
224
+ isDeposit?: boolean;
225
+ }>;
101
226
  }
102
227
 
103
228
  export interface IInvestmentFlow {
@@ -124,6 +249,23 @@ export function getMainnetConfig(
124
249
  };
125
250
  }
126
251
 
252
+ export const getStrategyTagDesciption = (tag: StrategyTag): string => {
253
+ switch (tag) {
254
+ case StrategyTag.META_VAULT:
255
+ return "A meta vault is a vault that auto allocates funds to multiple vaults based on optimal yield opportunities";
256
+ case StrategyTag.LEVERED:
257
+ return "Looping vaults on Endur LSTs with leveraged borrowing of STRK or BTC to increase yield (2-4x higher yield than simply staking)";
258
+ case StrategyTag.AUTOMATED_LP:
259
+ return "Automated LP vaults on Ekubo that rebalance position automatically, ensuring you earn fees efficiently";
260
+ case StrategyTag.BTC:
261
+ return "BTC linked vaults";
262
+ }
263
+ }
264
+
265
+ export const getAllStrategyTags = (): StrategyTag[] => {
266
+ return Object.values(StrategyTag);
267
+ }
268
+
127
269
  export const getRiskExplaination = (riskType: RiskType) => {
128
270
  switch (riskType) {
129
271
  case RiskType.MARKET_RISK:
@@ -214,6 +356,30 @@ export interface VaultPosition {
214
356
  remarks: string
215
357
  }
216
358
 
359
+ export interface AmountInfo {
360
+ amount: Web3Number;
361
+ usdValue: number;
362
+ tokenInfo: TokenInfo;
363
+ }
364
+
365
+ export interface AmountsInfo {
366
+ usdValue: number;
367
+ amounts: AmountInfo[];
368
+ }
369
+
370
+ /**
371
+ * Strategy capabilities interface
372
+ * Describes what optional methods a strategy instance supports
373
+ */
374
+ export interface StrategyCapabilities {
375
+ hasMatchInputAmounts: boolean;
376
+ hasNetAPY: boolean;
377
+ hasGetInvestmentFlows: boolean;
378
+ hasGetPendingRewards: boolean;
379
+ hasHarvest: boolean;
380
+ hasRebalance: boolean;
381
+ }
382
+
217
383
  const VesuProtocol: IProtocol = {
218
384
  name: "Vesu",
219
385
  logo: "https://static-assets-8zct.onrender.com/integrations/vesu/logo.png"
@@ -0,0 +1,28 @@
1
+ import { ApolloClient, InMemoryCache } from '@apollo/client';
2
+ import { IConfig } from '@/interfaces';
3
+
4
+ /**
5
+ * Creates an Apollo Client instance configured for the appropriate environment
6
+ * @param config - The application config containing network and stage information
7
+ * @returns Configured Apollo Client instance
8
+ */
9
+ export function createApolloClient(config: IConfig) {
10
+ // Determine the URI based on the environment
11
+ const uri = config.stage === 'production'
12
+ ? 'https://api.troves.fi/'
13
+ : 'http://localhost:4000';
14
+
15
+ return new ApolloClient({
16
+ uri,
17
+ cache: new InMemoryCache(),
18
+ });
19
+ }
20
+
21
+ // Default client for backward compatibility
22
+ const apolloClient = new ApolloClient({
23
+ uri: 'https://api.troves.fi/',
24
+ cache: new InMemoryCache(),
25
+ });
26
+
27
+ export default apolloClient;
28
+
@@ -37,7 +37,7 @@ export class AvnuWrapper {
37
37
  excludeSources = ['Haiko(Solvers)']
38
38
  ): Promise<Quote> {
39
39
  const MAX_RETRY = 5;
40
- // logger.verbose(`${AvnuWrapper.name}: getQuotes => Getting quotes for ${fromToken} -> ${toToken}, amount: ${amountWei}, taker: ${taker}, retry: ${retry}`);
40
+ logger.verbose(`${AvnuWrapper.name}: getQuotes => Getting quotes for ${fromToken} -> ${toToken}, amount: ${amountWei}, taker: ${taker}, retry: ${retry}`);
41
41
  const params: any = {
42
42
  sellTokenAddress: fromToken,
43
43
  buyTokenAddress: toToken,
@@ -0,0 +1,79 @@
1
+ import { Contract, RpcProvider, BlockIdentifier } from "starknet";
2
+ import EkuboPricerAbi from '@/data/ekubo-price-fethcer.abi.json';
3
+ import { PricerBase } from "./pricerBase";
4
+ import { IConfig, TokenInfo } from "@/interfaces";
5
+ import { PriceInfo } from "./pricer";
6
+
7
+ export class EkuboPricer extends PricerBase {
8
+ EKUBO_PRICE_FETCHER_ADDRESS = '0x04946fb4ad5237d97bbb1256eba2080c4fe1de156da6a7f83e3b4823bb6d7da1';
9
+ readonly contract: Contract;
10
+ private readonly USDC_ADDRESS = '0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8';
11
+ private readonly USDC_DECIMALS = 6;
12
+
13
+ constructor(config: IConfig, tokens: TokenInfo[]) {
14
+ super(config, tokens);
15
+ this.contract = new Contract({
16
+ abi: EkuboPricerAbi,
17
+ address: this.EKUBO_PRICE_FETCHER_ADDRESS,
18
+ providerOrAccount: config.provider as RpcProvider
19
+ });
20
+ }
21
+
22
+ private div2Power128(num: bigint): number {
23
+ return Number((num * BigInt(1e18)) / BigInt(2 ** 128)) / 1e18;
24
+ }
25
+
26
+ async getPrice(tokenAddr: string, blockIdentifier: BlockIdentifier = 'latest'): Promise<PriceInfo> {
27
+ if (!tokenAddr) {
28
+ throw new Error(`EkuboPricer:getPrice - no token`);
29
+ }
30
+
31
+ // get_prices arguments in order:
32
+ // - quote_token: USDC address (quote token for price calculation)
33
+ // - base_tokens: array containing the base token address/addresses
34
+ // - period: time period in seconds for TWAP (3600 = 1 hour)
35
+ // - min_token: minimum token amount threshold (min liquidity) in 6 Decimals = 1000000)
36
+ const result: any = await this.contract.call(
37
+ 'get_prices',
38
+ [this.USDC_ADDRESS, [tokenAddr], 3600, 1000000],
39
+ { blockIdentifier }
40
+ );
41
+
42
+ if (!result || result.length === 0) {
43
+ throw new Error(`EkuboPricer: No price result returned for ${tokenAddr}`);
44
+ }
45
+
46
+ const priceResult = result[0];
47
+
48
+ if (!priceResult?.variant?.Price) {
49
+ const variant = priceResult?.variant ? Object.keys(priceResult.variant)[0] : 'Unknown';
50
+ throw new Error(`EkuboPricer: Price fetch failed with variant: ${variant}`);
51
+ }
52
+
53
+ const rawPrice = typeof priceResult.variant.Price === 'string'
54
+ ? BigInt(priceResult.variant.Price)
55
+ : priceResult.variant.Price;
56
+
57
+ // Get token info to determine decimals from configured tokens
58
+ const tokenInfo = this.tokens.find(t =>
59
+ t.address.address.toLowerCase() === tokenAddr.toLowerCase()
60
+ );
61
+
62
+ if (!tokenInfo) {
63
+ throw new Error(`Token ${tokenAddr} not found in global tokens`);
64
+ }
65
+
66
+ // Convert from x128 format
67
+ const priceAfterX128 = this.div2Power128(rawPrice);
68
+
69
+ // Adjust for token decimals
70
+ const decimalAdjustment = 10 ** (tokenInfo.decimals - this.USDC_DECIMALS);
71
+ const price = priceAfterX128 * decimalAdjustment;
72
+
73
+ return {
74
+ price,
75
+ timestamp: new Date()
76
+ };
77
+ }
78
+ }
79
+
@@ -2,7 +2,7 @@ import { ContractAddr, Web3Number } from "@/dataTypes";
2
2
  import { IConfig } from "@/interfaces";
3
3
  import { Contract } from "starknet";
4
4
  import ERC20Abi from '@/data/erc20.abi.json';
5
-
5
+ import { uint256 } from "starknet";
6
6
  export class ERC20 {
7
7
  readonly config: IConfig;
8
8
 
@@ -12,7 +12,7 @@ export class ERC20 {
12
12
 
13
13
  contract(addr: string | ContractAddr) {
14
14
  const _addr = typeof addr === 'string' ? addr : addr.address;
15
- return new Contract({abi: ERC20Abi, address: _addr, providerOrAccount: this.config.provider});
15
+ return new Contract({ abi: ERC20Abi, address: _addr, providerOrAccount: this.config.provider });
16
16
  }
17
17
 
18
18
  async balanceOf(token: string | ContractAddr, address: string | ContractAddr, tokenDecimals: number) {
@@ -26,4 +26,20 @@ export class ERC20 {
26
26
  const allowance = await contract.call('allowance', [owner.toString(), spender.toString()]);
27
27
  return Web3Number.fromWei(allowance.toString(), tokenDecimals);
28
28
  }
29
+
30
+ approve(
31
+ token: string | ContractAddr,
32
+ spender: string | ContractAddr,
33
+ amount: Web3Number
34
+ ) {
35
+ const contract = this.contract(token);
36
+ const amountUint256 = uint256.bnToUint256(amount.toWei());
37
+ const approveCall = contract.populate("approve", [
38
+ spender.toString(),
39
+ amountUint256.low.toString(),
40
+ amountUint256.high.toString(),
41
+ ]);
42
+ return approveCall;
43
+ }
44
+
29
45
  }
@@ -1,22 +1,37 @@
1
- import { Contract, RpcProvider } from "starknet";
1
+ import { Contract, RpcProvider, BlockIdentifier } from "starknet";
2
2
  import PragmaAbi from '@/data/pragma.abi.json';
3
3
  import { logger } from "@/utils/logger";
4
+ import { PricerBase } from "./pricerBase";
5
+ import { IConfig, TokenInfo } from "@/interfaces";
6
+ import { PriceInfo } from "./pricer";
4
7
 
5
- export class Pragma {
8
+ export class Pragma extends PricerBase {
6
9
  contractAddr = '0x023fb3afbff2c0e3399f896dcf7400acf1a161941cfb386e34a123f228c62832';
7
10
  readonly contract: Contract;
8
11
 
9
- constructor(provider: RpcProvider) {
10
- this.contract = new Contract({abi: PragmaAbi, address: this.contractAddr, providerOrAccount: provider});
12
+ constructor(config: IConfig, tokens: TokenInfo[]) {
13
+ super(config, tokens);
14
+ this.contract = new Contract({
15
+ abi: PragmaAbi,
16
+ address: this.contractAddr,
17
+ providerOrAccount: config.provider as RpcProvider
18
+ });
11
19
  }
12
20
 
13
- async getPrice(tokenAddr: string) {
21
+ async getPrice(tokenAddr: string, blockIdentifier: BlockIdentifier = 'latest'): Promise<PriceInfo> {
14
22
  if (!tokenAddr) {
15
23
  throw new Error(`Pragma:getPrice - no token`)
16
24
  }
17
- const result: any = await this.contract.call('get_price', [tokenAddr]);
25
+ const result: any = await this.contract.call(
26
+ 'get_price',
27
+ [tokenAddr],
28
+ { blockIdentifier }
29
+ );
18
30
  const price = Number(result.price) / 10**8;
19
- logger.verbose(`Pragma:${tokenAddr}: ${price}`);
20
- return price;
31
+ logger.verbose(`Pragma:${tokenAddr}: ${price} at block ${blockIdentifier}`);
32
+ return {
33
+ price,
34
+ timestamp: new Date()
35
+ };
21
36
  }
22
37
  }