@strkfarm/sdk 2.0.0-dev.3 → 2.0.0-dev.30
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/cli.js +190 -36
- package/dist/cli.mjs +188 -34
- package/dist/index.browser.global.js +78478 -45620
- package/dist/index.browser.mjs +19583 -9901
- package/dist/index.d.ts +3763 -1424
- package/dist/index.js +20980 -11063
- package/dist/index.mjs +20948 -11087
- package/package.json +1 -1
- package/src/data/avnu.abi.json +840 -0
- package/src/data/ekubo-price-fethcer.abi.json +265 -0
- package/src/dataTypes/_bignumber.ts +13 -4
- package/src/dataTypes/bignumber.browser.ts +6 -1
- package/src/dataTypes/bignumber.node.ts +5 -1
- package/src/dataTypes/index.ts +3 -2
- package/src/dataTypes/mynumber.ts +141 -0
- package/src/global.ts +76 -41
- package/src/index.browser.ts +2 -1
- package/src/interfaces/common.tsx +175 -3
- package/src/modules/ExtendedWrapperSDk/types.ts +28 -5
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +275 -59
- package/src/modules/apollo-client-config.ts +28 -0
- package/src/modules/avnu.ts +4 -4
- package/src/modules/ekubo-pricer.ts +79 -0
- package/src/modules/ekubo-quoter.ts +48 -30
- package/src/modules/erc20.ts +17 -0
- package/src/modules/harvests.ts +43 -29
- package/src/modules/pragma.ts +23 -8
- package/src/modules/pricer-from-api.ts +156 -15
- package/src/modules/pricer-lst.ts +1 -1
- package/src/modules/pricer.ts +40 -4
- package/src/modules/pricerBase.ts +2 -1
- package/src/node/deployer.ts +36 -1
- package/src/node/pricer-redis.ts +2 -1
- package/src/strategies/base-strategy.ts +78 -10
- package/src/strategies/ekubo-cl-vault.tsx +906 -347
- package/src/strategies/factory.ts +159 -0
- package/src/strategies/index.ts +7 -1
- package/src/strategies/registry.ts +239 -0
- package/src/strategies/sensei.ts +335 -7
- package/src/strategies/svk-strategy.ts +97 -27
- package/src/strategies/types.ts +4 -0
- package/src/strategies/universal-adapters/adapter-utils.ts +2 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +180 -265
- package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
- package/src/strategies/universal-adapters/common-adapter.ts +206 -203
- package/src/strategies/universal-adapters/extended-adapter.ts +490 -316
- package/src/strategies/universal-adapters/index.ts +11 -8
- package/src/strategies/universal-adapters/svk-troves-adapter.ts +364 -0
- package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
- package/src/strategies/universal-adapters/usdc<>usdce-adapter.ts +200 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +120 -82
- package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +476 -0
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1067 -704
- package/src/strategies/universal-adapters/vesu-position-common.ts +251 -0
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
- package/src/strategies/universal-lst-muliplier-strategy.tsx +397 -204
- package/src/strategies/universal-strategy.tsx +1426 -1173
- package/src/strategies/vesu-extended-strategy/services/executionService.ts +2233 -0
- package/src/strategies/vesu-extended-strategy/services/extended-vesu-state-manager.ts +4087 -0
- package/src/strategies/vesu-extended-strategy/services/ltv-imbalance-rebalance-math.ts +783 -0
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +38 -16
- package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +88 -0
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +1 -0
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +5 -6
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +259 -103
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +688 -817
- package/src/strategies/vesu-rebalance.tsx +255 -152
- package/src/utils/cacheClass.ts +11 -2
- package/src/utils/health-factor-math.ts +4 -1
- package/src/utils/index.ts +3 -1
- package/src/utils/logger.browser.ts +22 -4
- package/src/utils/logger.node.ts +259 -24
- package/src/utils/starknet-call-parser.ts +1036 -0
- package/src/utils/strategy-utils.ts +61 -0
- 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
|
|
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
|
-
|
|
126
|
+
isRead: boolean,
|
|
127
|
+
options: RequestInit = {},
|
|
47
128
|
): Promise<ExtendedApiResponse<T>> {
|
|
48
|
-
const url = `${this.
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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(
|
|
367
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
+
|
package/src/modules/avnu.ts
CHANGED
|
@@ -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
|
+
|