@strkfarm/sdk 2.0.0-dev.3 → 2.0.0-dev.31

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 (75) hide show
  1. package/dist/cli.js +190 -36
  2. package/dist/cli.mjs +188 -34
  3. package/dist/index.browser.global.js +78475 -45620
  4. package/dist/index.browser.mjs +19580 -9901
  5. package/dist/index.d.ts +3763 -1424
  6. package/dist/index.js +20977 -11063
  7. package/dist/index.mjs +20945 -11087
  8. package/package.json +1 -1
  9. package/src/data/avnu.abi.json +840 -0
  10. package/src/data/ekubo-price-fethcer.abi.json +265 -0
  11. package/src/dataTypes/_bignumber.ts +13 -4
  12. package/src/dataTypes/bignumber.browser.ts +6 -1
  13. package/src/dataTypes/bignumber.node.ts +5 -1
  14. package/src/dataTypes/index.ts +3 -2
  15. package/src/dataTypes/mynumber.ts +141 -0
  16. package/src/global.ts +76 -41
  17. package/src/index.browser.ts +2 -1
  18. package/src/interfaces/common.tsx +175 -3
  19. package/src/modules/ExtendedWrapperSDk/types.ts +28 -5
  20. package/src/modules/ExtendedWrapperSDk/wrapper.ts +275 -59
  21. package/src/modules/apollo-client-config.ts +28 -0
  22. package/src/modules/avnu.ts +4 -4
  23. package/src/modules/ekubo-pricer.ts +79 -0
  24. package/src/modules/ekubo-quoter.ts +48 -30
  25. package/src/modules/erc20.ts +17 -0
  26. package/src/modules/harvests.ts +43 -29
  27. package/src/modules/pragma.ts +23 -8
  28. package/src/modules/pricer-from-api.ts +156 -15
  29. package/src/modules/pricer-lst.ts +1 -1
  30. package/src/modules/pricer.ts +40 -4
  31. package/src/modules/pricerBase.ts +2 -1
  32. package/src/node/deployer.ts +36 -1
  33. package/src/node/pricer-redis.ts +2 -1
  34. package/src/strategies/base-strategy.ts +78 -10
  35. package/src/strategies/ekubo-cl-vault.tsx +906 -347
  36. package/src/strategies/factory.ts +159 -0
  37. package/src/strategies/index.ts +7 -1
  38. package/src/strategies/registry.ts +239 -0
  39. package/src/strategies/sensei.ts +335 -7
  40. package/src/strategies/svk-strategy.ts +97 -27
  41. package/src/strategies/types.ts +4 -0
  42. package/src/strategies/universal-adapters/adapter-utils.ts +2 -1
  43. package/src/strategies/universal-adapters/avnu-adapter.ts +180 -265
  44. package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
  45. package/src/strategies/universal-adapters/common-adapter.ts +206 -203
  46. package/src/strategies/universal-adapters/extended-adapter.ts +490 -316
  47. package/src/strategies/universal-adapters/index.ts +11 -8
  48. package/src/strategies/universal-adapters/svk-troves-adapter.ts +364 -0
  49. package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
  50. package/src/strategies/universal-adapters/usdc<>usdce-adapter.ts +200 -0
  51. package/src/strategies/universal-adapters/vesu-adapter.ts +120 -82
  52. package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +476 -0
  53. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1067 -704
  54. package/src/strategies/universal-adapters/vesu-position-common.ts +251 -0
  55. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
  56. package/src/strategies/universal-lst-muliplier-strategy.tsx +397 -204
  57. package/src/strategies/universal-strategy.tsx +1426 -1173
  58. package/src/strategies/vesu-extended-strategy/services/executionService.ts +2233 -0
  59. package/src/strategies/vesu-extended-strategy/services/extended-vesu-state-manager.ts +4087 -0
  60. package/src/strategies/vesu-extended-strategy/services/ltv-imbalance-rebalance-math.ts +783 -0
  61. package/src/strategies/vesu-extended-strategy/services/operationService.ts +38 -16
  62. package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +88 -0
  63. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +1 -0
  64. package/src/strategies/vesu-extended-strategy/utils/constants.ts +5 -6
  65. package/src/strategies/vesu-extended-strategy/utils/helper.ts +259 -103
  66. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +688 -817
  67. package/src/strategies/vesu-rebalance.tsx +255 -152
  68. package/src/utils/cacheClass.ts +11 -2
  69. package/src/utils/health-factor-math.ts +4 -1
  70. package/src/utils/index.ts +3 -1
  71. package/src/utils/logger.browser.ts +22 -4
  72. package/src/utils/logger.node.ts +259 -24
  73. package/src/utils/starknet-call-parser.ts +1036 -0
  74. package/src/utils/strategy-utils.ts +61 -0
  75. package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
@@ -3,6 +3,7 @@
3
3
  * Provides a clean interface to interact with the Extended Exchange trading API
4
4
  */
5
5
 
6
+ import BigNumber from "bignumber.js";
6
7
  import {
7
8
  CreateOrderRequest,
8
9
  WithdrawRequest,
@@ -22,20 +23,99 @@ import {
22
23
  AssetOperationType,
23
24
  AssetOperationStatus,
24
25
  FundingRate,
26
+ FundingPayment,
25
27
  UpdateLeverageRequest,
26
28
  } from "./types";
27
29
 
30
+ type ExtendedTradingRules = {
31
+ minOrderSize: BigNumber;
32
+ qtyStep: BigNumber;
33
+ priceStep: BigNumber;
34
+ };
35
+
36
+ function asRecord(v: unknown): Record<string, unknown> | null {
37
+ return v !== null && typeof v === "object" && !Array.isArray(v)
38
+ ? (v as Record<string, unknown>)
39
+ : null;
40
+ }
41
+
42
+ function pickTradingConfig(row: Record<string, unknown>): Record<string, unknown> | null {
43
+ const tc = row.trading_config ?? row.tradingConfig;
44
+ return asRecord(tc);
45
+ }
46
+
47
+ function readTcString(
48
+ tc: Record<string, unknown>,
49
+ snake: string,
50
+ camel: string,
51
+ ): string | undefined {
52
+ const v = tc[snake] ?? tc[camel];
53
+ if (v === undefined || v === null) return undefined;
54
+ return String(v).trim();
55
+ }
56
+
57
+ function tradingRulesFromMarketRow(
58
+ marketName: string,
59
+ row: unknown,
60
+ ): ExtendedTradingRules {
61
+ const r = asRecord(row);
62
+ if (!r) {
63
+ throw new Error(`ExtendedWrapper: invalid market payload for ${marketName}`);
64
+ }
65
+ const tc = pickTradingConfig(r);
66
+ if (!tc) {
67
+ throw new Error(`ExtendedWrapper: missing tradingConfig for market ${marketName}`);
68
+ }
69
+ const minS = readTcString(tc, "min_order_size", "minOrderSize");
70
+ const qtyStep = readTcString(tc, "min_order_size_change", "minOrderSizeChange");
71
+ const pxStep = readTcString(tc, "min_price_change", "minPriceChange");
72
+ if (!minS || !qtyStep || !pxStep) {
73
+ throw new Error(`ExtendedWrapper: incomplete tradingConfig for market ${marketName}`);
74
+ }
75
+ const minOrderSize = new BigNumber(minS);
76
+ const qty = new BigNumber(qtyStep);
77
+ const px = new BigNumber(pxStep);
78
+ if (!minOrderSize.isFinite() || minOrderSize.lte(0)) {
79
+ throw new Error(`ExtendedWrapper: invalid minOrderSize for ${marketName}`);
80
+ }
81
+ if (!qty.isFinite() || qty.lte(0)) {
82
+ throw new Error(`ExtendedWrapper: invalid minOrderSizeChange for ${marketName}`);
83
+ }
84
+ if (!px.isFinite() || px.lte(0)) {
85
+ throw new Error(`ExtendedWrapper: invalid minPriceChange for ${marketName}`);
86
+ }
87
+ return { minOrderSize, qtyStep: qty, priceStep: px };
88
+ }
89
+
90
+ function roundToStepBn(value: BigNumber, step: BigNumber): BigNumber {
91
+ if (step.lte(0)) return value;
92
+ return value.div(step).round(0, BigNumber.ROUND_HALF_UP).times(step);
93
+ }
94
+
95
+ function formatBnForApi(bn: BigNumber, step: BigNumber): string {
96
+ const dp = Math.max(step.decimalPlaces() ?? 0, bn.decimalPlaces() ?? 0, 0);
97
+ return Number(bn.toFixed(Math.min(80, dp))).toString();
98
+ }
99
+
28
100
  export class ExtendedWrapper {
29
- private baseUrl: string;
101
+ private readUrl: string;
102
+ private writeUrl: string;
30
103
  private apiKey?: string;
31
104
  private timeout: number;
32
105
  private retries: number;
106
+ /** Per-market rules from GET /markets (tradingConfig); retained for process lifetime (no TTL). */
107
+ private marketTradingRulesCache = new Map<string, ExtendedTradingRules>();
108
+ private marketTradingRulesInflight = new Map<
109
+ string,
110
+ Promise<ExtendedTradingRules>
111
+ >();
33
112
 
34
113
  constructor(config: ExtendedWrapperConfig) {
35
- this.baseUrl = config.baseUrl.replace(/\/$/, ""); // Remove trailing slash
36
114
  this.apiKey = config.apiKey;
37
115
  this.timeout = config.timeout || 30000; // 30 seconds default
38
116
  this.retries = config.retries || 3;
117
+ this.readUrl = config.readUrl.replace(/\/$/, "");
118
+ this.writeUrl = config.writeUrl.replace(/\/$/, "");
39
119
  }
40
120
 
41
121
  /**
@@ -43,15 +123,14 @@ export class ExtendedWrapper {
43
123
  */
44
124
  private async makeRequest<T>(
45
125
  endpoint: string,
46
- options: RequestInit = {}
126
+ isRead: boolean,
127
+ options: RequestInit = {},
47
128
  ): Promise<ExtendedApiResponse<T>> {
48
- const url = `${this.baseUrl}${endpoint}`;
49
-
129
+ const url = `${isRead ? this.readUrl : this.writeUrl}${endpoint}`;
50
130
  const headers: Record<string, any> = {
51
131
  "Content-Type": "application/json",
52
132
  ...options.headers,
53
133
  };
54
-
55
134
  if (this.apiKey) {
56
135
  headers["X-API-Key"] = this.apiKey;
57
136
  }
@@ -73,11 +152,42 @@ export class ExtendedWrapper {
73
152
  throw new Error(
74
153
  `HTTP ${response.status}: ${
75
154
  errorData.detail || response.statusText
76
- }`
155
+ }`,
77
156
  );
78
157
  }
79
158
 
80
- const data = await response.json();
159
+ const text = await response.text();
160
+
161
+ // Replace large integers (greater than MAX_SAFE_INTEGER) with quoted strings
162
+ // This regex finds numbers that are likely to be large integers in the "data" field
163
+ const MAX_SAFE_INTEGER_STR = "9007199254740991";
164
+ const largeIntegerRegex = /"data"\s*:\s*(\d{16,})/g;
165
+
166
+ const modifiedText = text.replace(
167
+ largeIntegerRegex,
168
+ (match, largeInt) => {
169
+ // Compare as strings to avoid precision loss
170
+ if (
171
+ largeInt.length > MAX_SAFE_INTEGER_STR.length ||
172
+ (largeInt.length === MAX_SAFE_INTEGER_STR.length &&
173
+ largeInt > MAX_SAFE_INTEGER_STR)
174
+ ) {
175
+ // Replace the number with a quoted string to preserve precision
176
+ return `"data":"${largeInt}"`;
177
+ }
178
+ return match; // Keep original if it's a safe integer
179
+ },
180
+ );
181
+
182
+ const data = JSON.parse(modifiedText);
183
+
184
+ if (data && typeof data.data === "string" && /^\d+$/.test(data.data)) {
185
+ const numValue = Number(data.data);
186
+ if (Number.isSafeInteger(numValue)) {
187
+ data.data = numValue;
188
+ }
189
+ }
190
+
81
191
  return data;
82
192
  } catch (error) {
83
193
  lastError = error as Error;
@@ -93,15 +203,80 @@ export class ExtendedWrapper {
93
203
  throw lastError || new Error("Request failed after all retries");
94
204
  }
95
205
 
206
+ private async resolveTradingRules(
207
+ marketName: string,
208
+ ): Promise<ExtendedTradingRules> {
209
+ const cached = this.marketTradingRulesCache.get(marketName);
210
+ if (cached) return cached;
211
+ const existing = this.marketTradingRulesInflight.get(marketName);
212
+ if (existing) return existing;
213
+ const inflight = (async () => {
214
+ const res = await this.getMarkets(marketName);
215
+ if (res.status !== "OK") {
216
+ throw new Error(
217
+ `ExtendedWrapper: getMarkets failed for ${marketName}: ${res.message}`,
218
+ );
219
+ }
220
+ const rows = res.data;
221
+ if (!Array.isArray(rows) || rows.length === 0) {
222
+ throw new Error(
223
+ `ExtendedWrapper: empty markets response for ${marketName}`,
224
+ );
225
+ }
226
+ const row = rows.find((m) => asRecord(m)?.name === marketName);
227
+ if (!row) {
228
+ throw new Error(
229
+ `ExtendedWrapper: market ${marketName} not found in markets list`,
230
+ );
231
+ }
232
+ const rules = tradingRulesFromMarketRow(marketName, row);
233
+ this.marketTradingRulesCache.set(marketName, rules);
234
+ return rules;
235
+ })();
236
+ this.marketTradingRulesInflight.set(marketName, inflight);
237
+ void inflight.finally(() => {
238
+ if (this.marketTradingRulesInflight.get(marketName) === inflight) {
239
+ this.marketTradingRulesInflight.delete(marketName);
240
+ }
241
+ });
242
+ return inflight;
243
+ }
244
+
96
245
  /**
97
246
  * Create a new order on Extended Exchange
98
247
  */
99
248
  async createOrder(
100
- request: CreateOrderRequest
249
+ request: CreateOrderRequest,
101
250
  ): Promise<ExtendedApiResponse<PlacedOrder>> {
102
- return this.makeRequest<PlacedOrder>("/api/v1/orders", {
251
+ const rules = await this.resolveTradingRules(request.market_name);
252
+ const amountBn = new BigNumber(String(request.amount).trim());
253
+ const priceBn = new BigNumber(String(request.price).trim());
254
+ if (!amountBn.isFinite() || amountBn.lte(0)) {
255
+ throw new Error(`ExtendedWrapper: invalid order amount=${request.amount}`);
256
+ }
257
+ if (!priceBn.isFinite() || priceBn.lte(0)) {
258
+ throw new Error(`ExtendedWrapper: invalid order price=${request.price}`);
259
+ }
260
+ if (amountBn.lt(rules.minOrderSize)) {
261
+ throw new Error(
262
+ `ExtendedWrapper: order amount ${request.amount} is below minOrderSize ${rules.minOrderSize.toFixed()} for ${request.market_name}`,
263
+ );
264
+ }
265
+ const adjAmount = roundToStepBn(amountBn, rules.qtyStep);
266
+ const adjPrice = roundToStepBn(priceBn, rules.priceStep);
267
+ if (adjAmount.lt(rules.minOrderSize)) {
268
+ throw new Error(
269
+ `ExtendedWrapper: amount after tick rounding ${formatBnForApi(adjAmount, rules.qtyStep)} is below minOrderSize ${rules.minOrderSize.toFixed()} (${request.market_name})`,
270
+ );
271
+ }
272
+ const payload: CreateOrderRequest = {
273
+ ...request,
274
+ amount: formatBnForApi(adjAmount, rules.qtyStep),
275
+ price: formatBnForApi(adjPrice, rules.priceStep),
276
+ };
277
+ return this.makeRequest<PlacedOrder>("/api/v1/orders", false, {
103
278
  method: "POST",
104
- body: JSON.stringify(request),
279
+ body: JSON.stringify(payload),
105
280
  });
106
281
  }
107
282
 
@@ -109,32 +284,35 @@ export class ExtendedWrapper {
109
284
  * Get all markets
110
285
  */
111
286
  async getMarkets(
112
- marketNames?: string
287
+ marketNames?: string,
113
288
  ): Promise<ExtendedApiResponse<Market[]>> {
114
289
  const params = marketNames
115
290
  ? `?market_names=${encodeURIComponent(marketNames)}`
116
291
  : "";
117
- return this.makeRequest<Market[]>(`/api/v1/markets${params}`);
292
+ return this.makeRequest<Market[]>(`/api/v1/markets${params}`, false);
118
293
  }
119
294
 
120
- /**
121
- *
295
+ /**
296
+ *
122
297
  * @param orderId - The ID of the order to get
123
298
  * @returns The order
124
299
  */
125
- async getOrderByOrderId(orderId: string): Promise<ExtendedApiResponse<OpenOrder>> {
300
+ async getOrderByOrderId(
301
+ orderId: string,
302
+ ): Promise<ExtendedApiResponse<OpenOrder>> {
126
303
  const orderIdInt = parseInt(orderId);
127
- return this.makeRequest<OpenOrder>(`/api/v1/orderId/${orderIdInt}`);
304
+ return this.makeRequest<OpenOrder>(`/api/v1/orderId/${orderIdInt}`, false);
128
305
  }
129
306
 
130
307
  /**
131
308
  * Get market statistics for a specific market
132
309
  */
133
310
  async getMarketStatistics(
134
- marketName: string
311
+ marketName: string,
135
312
  ): Promise<ExtendedApiResponse<MarketStats>> {
136
313
  return this.makeRequest<MarketStats>(
137
- `/api/v1/markets/statistics?market_name=${encodeURIComponent(marketName)}`
314
+ `/api/v1/markets/statistics?market_name=${encodeURIComponent(marketName)}`,
315
+ false,
138
316
  );
139
317
  }
140
318
 
@@ -142,28 +320,30 @@ export class ExtendedWrapper {
142
320
  * Get current trading positions
143
321
  */
144
322
  async getPositions(
145
- marketNames?: string
323
+ marketNames?: string,
146
324
  ): Promise<ExtendedApiResponse<Position[]>> {
147
325
  const params = marketNames
148
326
  ? `?market_names=${encodeURIComponent(marketNames)}`
149
327
  : "";
150
- return this.makeRequest<Position[]>(`/api/v1/positions${params}`);
328
+ const response = await this.makeRequest<Position[]>(`/api/v1/positions${params}`, true);
329
+ return response;
151
330
  }
152
331
 
153
332
  /**
154
333
  * Get account balance and holdings
155
334
  */
156
335
  async getHoldings(): Promise<ExtendedApiResponse<Balance>> {
157
- return this.makeRequest<Balance>("/api/v1/holdings");
336
+ return this.makeRequest<Balance>("/api/v1/holdings", true);
158
337
  }
159
338
 
160
339
  /**
161
340
  * Initiate a withdrawal from Extended Exchange
341
+ * Returns data as number | string to preserve precision for large integers
162
342
  */
163
343
  async withdraw(
164
- request: WithdrawRequest
165
- ): Promise<ExtendedApiResponse<number>> {
166
- return this.makeRequest<number>("/api/v1/withdraw", {
344
+ request: WithdrawRequest,
345
+ ): Promise<ExtendedApiResponse<number | string>> {
346
+ return this.makeRequest<number | string>("/api/v1/withdraw", false, {
167
347
  method: "POST",
168
348
  body: JSON.stringify(request),
169
349
  });
@@ -181,7 +361,7 @@ export class ExtendedWrapper {
181
361
  };
182
362
  }>
183
363
  > {
184
- return this.makeRequest("/api/v1/withdraw/sign", {
364
+ return this.makeRequest("/api/v1/withdraw/sign", false, {
185
365
  method: "POST",
186
366
  body: JSON.stringify(request),
187
367
  });
@@ -191,25 +371,24 @@ export class ExtendedWrapper {
191
371
  * Cancel an existing order
192
372
  */
193
373
  async cancelOrder(
194
- request: CancelOrderRequest
374
+ request: CancelOrderRequest,
195
375
  ): Promise<ExtendedApiResponse<{}>> {
196
- return this.makeRequest<{}>("/api/v1/orders/cancel", {
376
+ return this.makeRequest<{}>("/api/v1/orders/cancel", false, {
197
377
  method: "POST",
198
378
  body: JSON.stringify(request),
199
379
  });
200
380
  }
201
381
 
202
-
203
382
  /**
204
383
  * Get all open orders
205
384
  */
206
385
  async getOpenOrders(
207
- marketName?: string
386
+ marketName?: string,
208
387
  ): Promise<ExtendedApiResponse<OpenOrder[]>> {
209
388
  const endpoint = marketName
210
389
  ? `/api/v1/marketOrders/${marketName}`
211
390
  : "/api/v1/marketOrders";
212
- return this.makeRequest<OpenOrder[]>(endpoint,{
391
+ return this.makeRequest<OpenOrder[]>(endpoint, false, {
213
392
  method: "GET",
214
393
  headers: {
215
394
  "Content-Type": "application/json",
@@ -223,9 +402,9 @@ export class ExtendedWrapper {
223
402
  * @returns
224
403
  */
225
404
  async updateLeverage(
226
- request: UpdateLeverageRequest
405
+ request: UpdateLeverageRequest,
227
406
  ): Promise<ExtendedApiResponse<{}>> {
228
- return this.makeRequest<{}>("/api/v1/leverage", {
407
+ return this.makeRequest<{}>("/api/v1/leverage", false, {
229
408
  method: "POST",
230
409
  body: JSON.stringify(request),
231
410
  });
@@ -243,7 +422,7 @@ export class ExtendedWrapper {
243
422
  endTime?: number;
244
423
  cursor?: number;
245
424
  limit?: number;
246
- } = {}
425
+ } = {},
247
426
  ): Promise<ExtendedApiResponse<AssetOperation[]>> {
248
427
  const params = new URLSearchParams();
249
428
 
@@ -268,14 +447,14 @@ export class ExtendedWrapper {
268
447
  queryString ? `?${queryString}` : ""
269
448
  }`;
270
449
 
271
- return this.makeRequest<AssetOperation[]>(endpoint);
450
+ return this.makeRequest<AssetOperation[]>(endpoint, false);
272
451
  }
273
452
 
274
453
  /**
275
454
  * Health check endpoint
276
455
  */
277
456
  async healthCheck(): Promise<ExtendedApiResponse<MarketStats>> {
278
- return this.makeRequest<MarketStats>("/api/v1/health");
457
+ return this.makeRequest<MarketStats>("/api/v1/health", false);
279
458
  }
280
459
 
281
460
  /**
@@ -286,11 +465,12 @@ export class ExtendedWrapper {
286
465
  amount: string,
287
466
  price: string,
288
467
  options: {
289
- postOnly?: boolean;
290
- previousOrderId?: number;
291
- externalId?: string;
292
- timeInForce?: TimeInForce;
293
- } = {}
468
+ post_only?: boolean;
469
+ reduce_only?: boolean;
470
+ previous_order_id?: number;
471
+ external_id?: string;
472
+ time_in_force?: TimeInForce;
473
+ } = {},
294
474
  ): Promise<ExtendedApiResponse<PlacedOrder>> {
295
475
  return this.createOrder({
296
476
  market_name: marketName,
@@ -301,13 +481,13 @@ export class ExtendedWrapper {
301
481
  });
302
482
  }
303
483
 
304
- /**
484
+ /**
305
485
  * Get order by ID
306
486
  * @param orderId - The ID of the order to get
307
487
  * @returns The order
308
488
  */
309
- async getOrderById(orderId: number): Promise<ExtendedApiResponse<OpenOrder>> {
310
- return this.makeRequest<OpenOrder>(`/api/v1/orderId/${orderId}`);
489
+ async getOrderById(orderId: number): Promise<ExtendedApiResponse<OpenOrder>> {
490
+ return this.makeRequest<OpenOrder>(`/api/v1/orderId/${orderId}`, false);
311
491
  }
312
492
 
313
493
  /**
@@ -318,11 +498,12 @@ export class ExtendedWrapper {
318
498
  amount: string,
319
499
  price: string,
320
500
  options: {
321
- postOnly?: boolean;
322
- previousOrderId?: number;
323
- externalId?: string;
324
- timeInForce?: TimeInForce;
325
- } = {}
501
+ post_only?: boolean;
502
+ reduce_only?: boolean;
503
+ previous_order_id?: number;
504
+ external_id?: string;
505
+ time_in_force?: TimeInForce;
506
+ } = {},
326
507
  ): Promise<ExtendedApiResponse<PlacedOrder>> {
327
508
  return this.createOrder({
328
509
  market_name: marketName,
@@ -337,7 +518,7 @@ export class ExtendedWrapper {
337
518
  * Get positions for a specific market
338
519
  */
339
520
  async getPositionsForMarket(
340
- marketName: string
521
+ marketName: string,
341
522
  ): Promise<ExtendedApiResponse<Position[]>> {
342
523
  return this.getPositions(marketName);
343
524
  }
@@ -346,7 +527,7 @@ export class ExtendedWrapper {
346
527
  * Get open orders for a specific market
347
528
  */
348
529
  async getOpenOrdersForMarket(
349
- marketName: string
530
+ marketName: string,
350
531
  ): Promise<ExtendedApiResponse<OpenOrder[]>> {
351
532
  return this.getOpenOrders(marketName);
352
533
  }
@@ -357,20 +538,25 @@ export class ExtendedWrapper {
357
538
  async cancelOrderById(orderId: number): Promise<ExtendedApiResponse<{}>> {
358
539
  return this.cancelOrder({ order_id: orderId });
359
540
  }
360
-
541
+
361
542
  /**
362
543
  * Get order history for a specific market
363
544
  * @param marketName - The name of the market to get order history for
364
545
  * @returns The order history for the specified market
365
546
  */
366
- async getOrderHistory(marketName: string): Promise<ExtendedApiResponse<OpenOrder[]>> {
367
- return this.makeRequest<OpenOrder[]>(`/api/v1/marketOrders/${marketName}`);
547
+ async getOrderHistory(
548
+ marketName: string,
549
+ ): Promise<ExtendedApiResponse<OpenOrder[]>> {
550
+ return this.makeRequest<OpenOrder[]>(`/api/v1/marketOrders/${marketName}`, false);
368
551
  }
369
552
 
370
553
  /**
371
554
  * Withdraw USDC (convenience method)
555
+ * Returns data as number | string to preserve precision for large integers
372
556
  */
373
- async withdrawUSDC(amount: string): Promise<ExtendedApiResponse<number>> {
557
+ async withdrawUSDC(
558
+ amount: string,
559
+ ): Promise<ExtendedApiResponse<number | string>> {
374
560
  return this.withdraw({ amount, asset: "USDC" });
375
561
  }
376
562
 
@@ -381,15 +567,45 @@ export class ExtendedWrapper {
381
567
  */
382
568
  async getFundingRates(
383
569
  marketName: string,
384
- side: string
570
+ side: string,
571
+ startTime?: number,
572
+ endTime?: number,
573
+ // in epoch milliseconds
385
574
  ): Promise<ExtendedApiResponse<FundingRate[]>> {
386
- return this.makeRequest<FundingRate[]>(
575
+ const endTimeParam = endTime !== undefined ? `&end_time=${endTime}` : "";
576
+ const startTimeParam =
577
+ startTime !== undefined ? `&start_time=${startTime}` : "";
578
+ const response = await this.makeRequest<FundingRate[]>(
387
579
  `/api/v1/markets/funding-rates?market_name=${encodeURIComponent(
388
- marketName
389
- )}&side=${encodeURIComponent(side)}`
580
+ marketName,
581
+ )}&side=${encodeURIComponent(side)}${startTimeParam}${endTimeParam}`,
582
+ true
390
583
  );
584
+ return response;
391
585
  }
392
586
 
587
+ /**
588
+ * Get funding payments for a specific market.
589
+ */
590
+ async getUserFundingPayments(
591
+ marketName: string,
592
+ side: string,
593
+ startTime: number,
594
+ limit?: number,
595
+ cursor?: number,
596
+ ): Promise<ExtendedApiResponse<FundingPayment[]>> {
597
+ const params = new URLSearchParams();
598
+ params.append("start_time", String(startTime));
599
+ params.append("market_names", marketName);
600
+ params.append("side", side);
601
+ if (limit !== undefined) params.append("limit", String(limit));
602
+ if (cursor !== undefined) params.append("cursor", String(cursor));
603
+
604
+ return this.makeRequest<FundingPayment[]>(
605
+ `/api/v1/account/funding-payments?${params.toString()}`,
606
+ true,
607
+ );
608
+ }
393
609
  }
394
610
 
395
611
  export default ExtendedWrapper;
@@ -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,
@@ -100,9 +100,9 @@ export class AvnuWrapper {
100
100
  // swapInfo as expected by the strategy
101
101
  // fallback, max 1% slippage
102
102
  const _minAmount = minAmount || (quote.buyAmount * 95n / 100n).toString();
103
- logger.verbose(`${AvnuWrapper.name}: getSwapInfo => sellToken: ${quote.sellTokenAddress}, sellAmount: ${quote.sellAmount}`);
104
- logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyToken: ${quote.buyTokenAddress}`);
105
- logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyAmount: ${quote.buyAmount}, minAmount: ${_minAmount}`);
103
+ // logger.verbose(`${AvnuWrapper.name}: getSwapInfo => sellToken: ${quote.sellTokenAddress}, sellAmount: ${quote.sellAmount}`);
104
+ // logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyToken: ${quote.buyTokenAddress}`);
105
+ // logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyAmount: ${quote.buyAmount}, minAmount: ${_minAmount}`);
106
106
  const swapInfo: SwapInfo = {
107
107
  token_from_address: quote.sellTokenAddress,
108
108
  token_from_amount: uint256.bnToUint256(quote.sellAmount),
@@ -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
+