@strkfarm/sdk 1.2.0 → 2.0.0-dev-strategy2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.global.js +76556 -66640
- package/dist/index.browser.mjs +34235 -24392
- package/dist/index.d.ts +2372 -793
- package/dist/index.js +31967 -22084
- package/dist/index.mjs +25545 -15719
- package/package.json +86 -76
- package/readme.md +56 -1
- package/src/data/extended-deposit.abi.json +3613 -0
- package/src/data/universal-vault.abi.json +135 -20
- package/src/dataTypes/_bignumber.ts +11 -0
- package/src/dataTypes/address.ts +7 -0
- package/src/global.ts +240 -193
- package/src/interfaces/common.tsx +26 -2
- package/src/modules/ExtendedWrapperSDk/index.ts +62 -0
- package/src/modules/ExtendedWrapperSDk/types.ts +311 -0
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +448 -0
- package/src/modules/avnu.ts +17 -4
- package/src/modules/ekubo-quoter.ts +89 -10
- package/src/modules/erc20.ts +67 -21
- package/src/modules/harvests.ts +29 -43
- package/src/modules/index.ts +5 -1
- package/src/modules/lst-apr.ts +36 -0
- package/src/modules/midas.ts +159 -0
- package/src/modules/pricer-from-api.ts +2 -2
- package/src/modules/pricer-lst.ts +1 -1
- package/src/modules/pricer.ts +3 -38
- package/src/modules/token-market-data.ts +202 -0
- package/src/node/deployer.ts +1 -36
- package/src/strategies/autoCompounderStrk.ts +1 -1
- package/src/strategies/base-strategy.ts +20 -3
- package/src/strategies/btc-vesu-extended-strategy/core-strategy.tsx +1486 -0
- package/src/strategies/btc-vesu-extended-strategy/services/operationService.ts +32 -0
- package/src/strategies/btc-vesu-extended-strategy/utils/constants.ts +3 -0
- package/src/strategies/btc-vesu-extended-strategy/utils/helper.ts +396 -0
- package/src/strategies/btc-vesu-extended-strategy/utils/types.ts +5 -0
- package/src/strategies/ekubo-cl-vault.tsx +123 -306
- package/src/strategies/index.ts +7 -1
- package/src/strategies/svk-strategy.ts +247 -0
- package/src/strategies/universal-adapters/adapter-optimizer.ts +65 -0
- package/src/strategies/universal-adapters/adapter-utils.ts +5 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +432 -0
- package/src/strategies/universal-adapters/baseAdapter.ts +181 -153
- package/src/strategies/universal-adapters/common-adapter.ts +98 -77
- package/src/strategies/universal-adapters/extended-adapter.ts +976 -0
- package/src/strategies/universal-adapters/index.ts +7 -1
- package/src/strategies/universal-adapters/unused-balance-adapter.ts +109 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +230 -230
- package/src/strategies/universal-adapters/vesu-borrow-adapter.ts +1247 -0
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1306 -0
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +58 -51
- package/src/strategies/universal-lst-muliplier-strategy.tsx +716 -844
- package/src/strategies/universal-strategy.tsx +1103 -1181
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +34 -0
- package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +25 -0
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +77 -0
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +50 -0
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +367 -0
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +1420 -0
- package/src/strategies/vesu-rebalance.tsx +16 -20
- package/src/utils/health-factor-math.ts +11 -5
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExtendedWrapper - TypeScript wrapper for Extended Exchange API
|
|
3
|
+
* Provides a clean interface to interact with the Extended Exchange trading API
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
CreateOrderRequest,
|
|
8
|
+
WithdrawRequest,
|
|
9
|
+
SignedWithdrawRequest,
|
|
10
|
+
CancelOrderRequest,
|
|
11
|
+
PlacedOrder,
|
|
12
|
+
OpenOrder,
|
|
13
|
+
Position,
|
|
14
|
+
Balance,
|
|
15
|
+
Market,
|
|
16
|
+
MarketStats,
|
|
17
|
+
AssetOperation,
|
|
18
|
+
ExtendedApiResponse,
|
|
19
|
+
ExtendedWrapperConfig,
|
|
20
|
+
OrderSide,
|
|
21
|
+
TimeInForce,
|
|
22
|
+
AssetOperationType,
|
|
23
|
+
AssetOperationStatus,
|
|
24
|
+
FundingRate,
|
|
25
|
+
UpdateLeverageRequest,
|
|
26
|
+
} from "./types";
|
|
27
|
+
|
|
28
|
+
export class ExtendedWrapper {
|
|
29
|
+
private baseUrl: string;
|
|
30
|
+
private apiKey?: string;
|
|
31
|
+
private timeout: number;
|
|
32
|
+
private retries: number;
|
|
33
|
+
|
|
34
|
+
constructor(config: ExtendedWrapperConfig) {
|
|
35
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, ""); // Remove trailing slash
|
|
36
|
+
this.apiKey = config.apiKey;
|
|
37
|
+
this.timeout = config.timeout || 30000; // 30 seconds default
|
|
38
|
+
this.retries = config.retries || 3;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Make HTTP request with retry logic and error handling
|
|
43
|
+
*/
|
|
44
|
+
private async makeRequest<T>(
|
|
45
|
+
endpoint: string,
|
|
46
|
+
options: RequestInit = {}
|
|
47
|
+
): Promise<ExtendedApiResponse<T>> {
|
|
48
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
49
|
+
|
|
50
|
+
const headers: Record<string, any> = {
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
...options.headers,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (this.apiKey) {
|
|
56
|
+
headers["X-API-Key"] = this.apiKey;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const requestOptions: RequestInit = {
|
|
60
|
+
...options,
|
|
61
|
+
headers,
|
|
62
|
+
signal: AbortSignal.timeout(this.timeout),
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
let lastError: Error | null = null;
|
|
66
|
+
|
|
67
|
+
for (let attempt = 1; attempt <= this.retries; attempt++) {
|
|
68
|
+
try {
|
|
69
|
+
const response = await fetch(url, requestOptions);
|
|
70
|
+
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
const errorData = await response.json().catch(() => ({}));
|
|
73
|
+
throw new Error(
|
|
74
|
+
`HTTP ${response.status}: ${
|
|
75
|
+
errorData.detail || response.statusText
|
|
76
|
+
}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const text = await response.text();
|
|
81
|
+
|
|
82
|
+
// Replace large integers (greater than MAX_SAFE_INTEGER) with quoted strings
|
|
83
|
+
// This regex finds numbers that are likely to be large integers in the "data" field
|
|
84
|
+
const MAX_SAFE_INTEGER_STR = "9007199254740991";
|
|
85
|
+
const largeIntegerRegex = /"data"\s*:\s*(\d{16,})/g;
|
|
86
|
+
|
|
87
|
+
const modifiedText = text.replace(
|
|
88
|
+
largeIntegerRegex,
|
|
89
|
+
(match, largeInt) => {
|
|
90
|
+
// Compare as strings to avoid precision loss
|
|
91
|
+
if (
|
|
92
|
+
largeInt.length > MAX_SAFE_INTEGER_STR.length ||
|
|
93
|
+
(largeInt.length === MAX_SAFE_INTEGER_STR.length &&
|
|
94
|
+
largeInt > MAX_SAFE_INTEGER_STR)
|
|
95
|
+
) {
|
|
96
|
+
// Replace the number with a quoted string to preserve precision
|
|
97
|
+
return `"data":"${largeInt}"`;
|
|
98
|
+
}
|
|
99
|
+
return match; // Keep original if it's a safe integer
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const data = JSON.parse(modifiedText);
|
|
104
|
+
|
|
105
|
+
if (data && typeof data.data === "string" && /^\d+$/.test(data.data)) {
|
|
106
|
+
const numValue = Number(data.data);
|
|
107
|
+
if (Number.isSafeInteger(numValue)) {
|
|
108
|
+
data.data = numValue;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return data;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
lastError = error as Error;
|
|
115
|
+
|
|
116
|
+
if (attempt < this.retries) {
|
|
117
|
+
// Exponential backoff
|
|
118
|
+
const delay = Math.pow(2, attempt) * 1000;
|
|
119
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw lastError || new Error("Request failed after all retries");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Create a new order on Extended Exchange
|
|
129
|
+
*/
|
|
130
|
+
async createOrder(
|
|
131
|
+
request: CreateOrderRequest
|
|
132
|
+
): Promise<ExtendedApiResponse<PlacedOrder>> {
|
|
133
|
+
return this.makeRequest<PlacedOrder>("/api/v1/orders", {
|
|
134
|
+
method: "POST",
|
|
135
|
+
body: JSON.stringify(request),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get all markets
|
|
141
|
+
*/
|
|
142
|
+
async getMarkets(
|
|
143
|
+
marketNames?: string
|
|
144
|
+
): Promise<ExtendedApiResponse<Market[]>> {
|
|
145
|
+
const params = marketNames
|
|
146
|
+
? `?market_names=${encodeURIComponent(marketNames)}`
|
|
147
|
+
: "";
|
|
148
|
+
return this.makeRequest<Market[]>(`/api/v1/markets${params}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
*
|
|
153
|
+
* @param orderId - The ID of the order to get
|
|
154
|
+
* @returns The order
|
|
155
|
+
*/
|
|
156
|
+
async getOrderByOrderId(
|
|
157
|
+
orderId: string
|
|
158
|
+
): Promise<ExtendedApiResponse<OpenOrder>> {
|
|
159
|
+
const orderIdInt = parseInt(orderId);
|
|
160
|
+
return this.makeRequest<OpenOrder>(`/api/v1/orderId/${orderIdInt}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get market statistics for a specific market
|
|
165
|
+
*/
|
|
166
|
+
async getMarketStatistics(
|
|
167
|
+
marketName: string
|
|
168
|
+
): Promise<ExtendedApiResponse<MarketStats>> {
|
|
169
|
+
return this.makeRequest<MarketStats>(
|
|
170
|
+
`/api/v1/markets/statistics?market_name=${encodeURIComponent(marketName)}`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get current trading positions
|
|
176
|
+
*/
|
|
177
|
+
async getPositions(
|
|
178
|
+
marketNames?: string
|
|
179
|
+
): Promise<ExtendedApiResponse<Position[]>> {
|
|
180
|
+
const params = marketNames
|
|
181
|
+
? `?market_names=${encodeURIComponent(marketNames)}`
|
|
182
|
+
: "";
|
|
183
|
+
return this.makeRequest<Position[]>(`/api/v1/positions${params}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get account balance and holdings
|
|
188
|
+
*/
|
|
189
|
+
async getHoldings(): Promise<ExtendedApiResponse<Balance>> {
|
|
190
|
+
return this.makeRequest<Balance>("/api/v1/holdings");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Initiate a withdrawal from Extended Exchange
|
|
195
|
+
* Returns data as number | string to preserve precision for large integers
|
|
196
|
+
*/
|
|
197
|
+
async withdraw(
|
|
198
|
+
request: WithdrawRequest
|
|
199
|
+
): Promise<ExtendedApiResponse<number | string>> {
|
|
200
|
+
return this.makeRequest<number | string>("/api/v1/withdraw", {
|
|
201
|
+
method: "POST",
|
|
202
|
+
body: JSON.stringify(request),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Create and sign a withdrawal request hash
|
|
208
|
+
*/
|
|
209
|
+
async signWithdrawalRequest(request: SignedWithdrawRequest): Promise<
|
|
210
|
+
ExtendedApiResponse<{
|
|
211
|
+
withdraw_request_hash: string;
|
|
212
|
+
signature: {
|
|
213
|
+
r: string;
|
|
214
|
+
s: string;
|
|
215
|
+
};
|
|
216
|
+
}>
|
|
217
|
+
> {
|
|
218
|
+
return this.makeRequest("/api/v1/withdraw/sign", {
|
|
219
|
+
method: "POST",
|
|
220
|
+
body: JSON.stringify(request),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Cancel an existing order
|
|
226
|
+
*/
|
|
227
|
+
async cancelOrder(
|
|
228
|
+
request: CancelOrderRequest
|
|
229
|
+
): Promise<ExtendedApiResponse<{}>> {
|
|
230
|
+
return this.makeRequest<{}>("/api/v1/orders/cancel", {
|
|
231
|
+
method: "POST",
|
|
232
|
+
body: JSON.stringify(request),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Get all open orders
|
|
238
|
+
*/
|
|
239
|
+
async getOpenOrders(
|
|
240
|
+
marketName?: string
|
|
241
|
+
): Promise<ExtendedApiResponse<OpenOrder[]>> {
|
|
242
|
+
const endpoint = marketName
|
|
243
|
+
? `/api/v1/marketOrders/${marketName}`
|
|
244
|
+
: "/api/v1/marketOrders";
|
|
245
|
+
return this.makeRequest<OpenOrder[]>(endpoint, {
|
|
246
|
+
method: "GET",
|
|
247
|
+
headers: {
|
|
248
|
+
"Content-Type": "application/json",
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Update leverage on the market
|
|
255
|
+
* @param request
|
|
256
|
+
* @returns
|
|
257
|
+
*/
|
|
258
|
+
async updateLeverage(
|
|
259
|
+
request: UpdateLeverageRequest
|
|
260
|
+
): Promise<ExtendedApiResponse<{}>> {
|
|
261
|
+
return this.makeRequest<{}>("/api/v1/leverage", {
|
|
262
|
+
method: "POST",
|
|
263
|
+
body: JSON.stringify(request),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get asset operations with optional filtering
|
|
269
|
+
*/
|
|
270
|
+
async getAssetOperations(
|
|
271
|
+
options: {
|
|
272
|
+
id?: number;
|
|
273
|
+
operationsType?: AssetOperationType[];
|
|
274
|
+
operationsStatus?: AssetOperationStatus[];
|
|
275
|
+
startTime?: number;
|
|
276
|
+
endTime?: number;
|
|
277
|
+
page?: number;
|
|
278
|
+
} = {}
|
|
279
|
+
): Promise<ExtendedApiResponse<AssetOperation[]>> {
|
|
280
|
+
const params = new URLSearchParams();
|
|
281
|
+
|
|
282
|
+
if (options.id !== undefined) params.append("id", options.id.toString());
|
|
283
|
+
if (options.operationsType) {
|
|
284
|
+
params.append("operations_type", options.operationsType.join(","));
|
|
285
|
+
}
|
|
286
|
+
if (options.operationsStatus) {
|
|
287
|
+
params.append("operations_status", options.operationsStatus.join(","));
|
|
288
|
+
}
|
|
289
|
+
if (options.startTime !== undefined)
|
|
290
|
+
params.append("start_time", options.startTime.toString());
|
|
291
|
+
if (options.endTime !== undefined)
|
|
292
|
+
params.append("end_time", options.endTime.toString());
|
|
293
|
+
if (options.page !== undefined)
|
|
294
|
+
params.append("page", options.page.toString());
|
|
295
|
+
|
|
296
|
+
const queryString = params.toString();
|
|
297
|
+
const endpoint = `/api/v1/asset-operations${
|
|
298
|
+
queryString ? `?${queryString}` : ""
|
|
299
|
+
}`;
|
|
300
|
+
|
|
301
|
+
return this.makeRequest<AssetOperation[]>(endpoint);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Health check endpoint
|
|
306
|
+
*/
|
|
307
|
+
async healthCheck(): Promise<ExtendedApiResponse<MarketStats>> {
|
|
308
|
+
return this.makeRequest<MarketStats>("/api/v1/health");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Convenience method to create a buy order
|
|
313
|
+
*/
|
|
314
|
+
async createBuyOrder(
|
|
315
|
+
marketName: string,
|
|
316
|
+
amount: string,
|
|
317
|
+
price: string,
|
|
318
|
+
options: {
|
|
319
|
+
postOnly?: boolean;
|
|
320
|
+
previousOrderId?: number;
|
|
321
|
+
externalId?: string;
|
|
322
|
+
timeInForce?: TimeInForce;
|
|
323
|
+
} = {}
|
|
324
|
+
): Promise<ExtendedApiResponse<PlacedOrder>> {
|
|
325
|
+
return this.createOrder({
|
|
326
|
+
market_name: marketName,
|
|
327
|
+
amount,
|
|
328
|
+
price,
|
|
329
|
+
side: OrderSide.BUY,
|
|
330
|
+
...options,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Get order by ID
|
|
336
|
+
* @param orderId - The ID of the order to get
|
|
337
|
+
* @returns The order
|
|
338
|
+
*/
|
|
339
|
+
async getOrderById(orderId: number): Promise<ExtendedApiResponse<OpenOrder>> {
|
|
340
|
+
return this.makeRequest<OpenOrder>(`/api/v1/orderId/${orderId}`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Convenience method to create a sell order
|
|
345
|
+
*/
|
|
346
|
+
async createSellOrder(
|
|
347
|
+
marketName: string,
|
|
348
|
+
amount: string,
|
|
349
|
+
price: string,
|
|
350
|
+
options: {
|
|
351
|
+
postOnly?: boolean;
|
|
352
|
+
previousOrderId?: number;
|
|
353
|
+
externalId?: string;
|
|
354
|
+
timeInForce?: TimeInForce;
|
|
355
|
+
} = {}
|
|
356
|
+
): Promise<ExtendedApiResponse<PlacedOrder>> {
|
|
357
|
+
return this.createOrder({
|
|
358
|
+
market_name: marketName,
|
|
359
|
+
amount,
|
|
360
|
+
price,
|
|
361
|
+
side: OrderSide.SELL,
|
|
362
|
+
...options,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Get positions for a specific market
|
|
368
|
+
*/
|
|
369
|
+
async getPositionsForMarket(
|
|
370
|
+
marketName: string
|
|
371
|
+
): Promise<ExtendedApiResponse<Position[]>> {
|
|
372
|
+
return this.getPositions(marketName);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Get open orders for a specific market
|
|
377
|
+
*/
|
|
378
|
+
async getOpenOrdersForMarket(
|
|
379
|
+
marketName: string
|
|
380
|
+
): Promise<ExtendedApiResponse<OpenOrder[]>> {
|
|
381
|
+
return this.getOpenOrders(marketName);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Cancel order by ID (convenience method)
|
|
386
|
+
*/
|
|
387
|
+
async cancelOrderById(orderId: number): Promise<ExtendedApiResponse<{}>> {
|
|
388
|
+
return this.cancelOrder({ order_id: orderId });
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Get order history for a specific market
|
|
393
|
+
* @param marketName - The name of the market to get order history for
|
|
394
|
+
* @returns The order history for the specified market
|
|
395
|
+
*/
|
|
396
|
+
async getOrderHistory(
|
|
397
|
+
marketName: string
|
|
398
|
+
): Promise<ExtendedApiResponse<OpenOrder[]>> {
|
|
399
|
+
return this.makeRequest<OpenOrder[]>(`/api/v1/marketOrders/${marketName}`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Withdraw USDC (convenience method)
|
|
404
|
+
* Returns data as number | string to preserve precision for large integers
|
|
405
|
+
*/
|
|
406
|
+
async withdrawUSDC(
|
|
407
|
+
amount: string
|
|
408
|
+
): Promise<ExtendedApiResponse<number | string>> {
|
|
409
|
+
return this.withdraw({ amount, asset: "USDC" });
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get funding rates for a specific market
|
|
414
|
+
* @param marketName - The name of the market to get funding rates for
|
|
415
|
+
* @param side - The side (BUY/SELL)
|
|
416
|
+
* @param options - Optional parameters for filtering and pagination
|
|
417
|
+
* @param options.startTime - Start timestamp for filtering (ISO format datetime string)
|
|
418
|
+
* @param options.endTime - End timestamp for filtering (ISO format datetime string)
|
|
419
|
+
* @param options.page - Page number (default: 1, 10 entries per page)
|
|
420
|
+
* @returns The funding rates for the specified market
|
|
421
|
+
*/
|
|
422
|
+
async getFundingRates(
|
|
423
|
+
marketName: string,
|
|
424
|
+
side: string,
|
|
425
|
+
options: {
|
|
426
|
+
startTime?: string;
|
|
427
|
+
endTime?: string;
|
|
428
|
+
page?: number;
|
|
429
|
+
} = {}
|
|
430
|
+
): Promise<ExtendedApiResponse<FundingRate[]>> {
|
|
431
|
+
const params = new URLSearchParams();
|
|
432
|
+
params.append("market_name", marketName);
|
|
433
|
+
params.append("side", side);
|
|
434
|
+
if (options.startTime !== undefined)
|
|
435
|
+
params.append("start_time", options.startTime);
|
|
436
|
+
if (options.endTime !== undefined)
|
|
437
|
+
params.append("end_time", options.endTime);
|
|
438
|
+
if (options.page !== undefined)
|
|
439
|
+
params.append("page", options.page.toString());
|
|
440
|
+
|
|
441
|
+
const queryString = params.toString();
|
|
442
|
+
const endpoint = `/api/v1/markets/funding-rates?${queryString}`;
|
|
443
|
+
|
|
444
|
+
return this.makeRequest<FundingRate[]>(endpoint);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export default ExtendedWrapper;
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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),
|
|
@@ -135,4 +135,17 @@ export class AvnuWrapper {
|
|
|
135
135
|
routes: [],
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
async getSwapCallData(
|
|
141
|
+
quote: Pick<Quote, 'quoteId' | 'buyTokenAddress' | 'buyAmount' | 'sellTokenAddress' | 'sellAmount'>,
|
|
142
|
+
taker: string,
|
|
143
|
+
) {
|
|
144
|
+
const calldata = await fetchBuildExecuteTransaction(quote.quoteId, taker, undefined, false);
|
|
145
|
+
const result = calldata.calls.map((call: Call) => {
|
|
146
|
+
const data = call.calldata as string[];
|
|
147
|
+
return data.map(x => BigInt(x));
|
|
148
|
+
});
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
138
151
|
}
|
|
@@ -2,6 +2,12 @@ import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
|
2
2
|
import { IConfig, TokenInfo } from "@/interfaces/common";
|
|
3
3
|
import { Swap } from "@/strategies";
|
|
4
4
|
import axios from "axios";
|
|
5
|
+
import { logger } from "@/utils";
|
|
6
|
+
import { uint256 } from "starknet";
|
|
7
|
+
import { Contract } from "starknet";
|
|
8
|
+
import ERC4626Abi from "@/data/erc4626.abi.json";
|
|
9
|
+
import { TokenMarketData } from "./token-market-data";
|
|
10
|
+
import { PricerBase } from "./pricerBase";
|
|
5
11
|
|
|
6
12
|
export interface EkuboRouteNode {
|
|
7
13
|
pool_key: {
|
|
@@ -29,8 +35,11 @@ export interface EkuboQuote {
|
|
|
29
35
|
|
|
30
36
|
export class EkuboQuoter {
|
|
31
37
|
ENDPOINT = 'https://prod-api-quoter.ekubo.org/23448594291968334/{{AMOUNT}}/{{TOKEN_FROM_ADDRESS}}/{{TOKEN_TO_ADDRESS}}'; // e.g. ETH/USDC'
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
tokenMarketData: TokenMarketData;
|
|
39
|
+
|
|
40
|
+
constructor(private readonly config: IConfig, pricer: PricerBase) {
|
|
41
|
+
this.tokenMarketData = new TokenMarketData(pricer, config);
|
|
42
|
+
}
|
|
34
43
|
|
|
35
44
|
/**
|
|
36
45
|
*
|
|
@@ -40,16 +49,17 @@ export class EkuboQuoter {
|
|
|
40
49
|
* @returns
|
|
41
50
|
*/
|
|
42
51
|
async getQuote(fromToken: string, toToken: string, amount: Web3Number, retry = 0): Promise<EkuboQuote> {
|
|
43
|
-
let _fromToken = amount.gt(0) ? fromToken : toToken;
|
|
44
|
-
let _toToken = amount.gt(0) ? toToken : fromToken;
|
|
52
|
+
// let _fromToken = amount.gt(0) ? fromToken : toToken;
|
|
53
|
+
// let _toToken = amount.gt(0) ? toToken : fromToken;
|
|
45
54
|
|
|
46
55
|
try {
|
|
47
|
-
const
|
|
48
|
-
console.log(
|
|
49
|
-
|
|
56
|
+
const url = this.ENDPOINT.replace("{{AMOUNT}}", amount.toFixed(0)).replace("{{TOKEN_FROM_ADDRESS}}", fromToken).replace("{{TOKEN_TO_ADDRESS}}", toToken);
|
|
57
|
+
console.log("url", url);
|
|
58
|
+
const quote = await axios.get(url);
|
|
59
|
+
return quote.data as EkuboQuote;
|
|
50
60
|
} catch (error: any) {
|
|
51
|
-
|
|
52
|
-
if (retry <
|
|
61
|
+
logger.error(`${error.message} dassf ${error.data}`);
|
|
62
|
+
if (retry < 3) {
|
|
53
63
|
await new Promise((resolve) => setTimeout(resolve, (retry + 1) * 5000));
|
|
54
64
|
return await this.getQuote(fromToken, toToken, amount, retry + 1);
|
|
55
65
|
}
|
|
@@ -57,6 +67,72 @@ export class EkuboQuoter {
|
|
|
57
67
|
}
|
|
58
68
|
}
|
|
59
69
|
|
|
70
|
+
async getDexPrice(baseToken: TokenInfo, quoteToken: TokenInfo, amount: Web3Number) {
|
|
71
|
+
const lstTokenInfo = baseToken;
|
|
72
|
+
const lstUnderlyingTokenInfo = quoteToken;
|
|
73
|
+
const quote = await this.getQuote(
|
|
74
|
+
lstTokenInfo.address.address,
|
|
75
|
+
lstUnderlyingTokenInfo.address.address,
|
|
76
|
+
amount
|
|
77
|
+
);
|
|
78
|
+
// in Underlying
|
|
79
|
+
const outputAmount = Web3Number.fromWei(quote.total_calculated, lstUnderlyingTokenInfo.decimals);
|
|
80
|
+
const price = outputAmount.toNumber() / amount.toNumber();
|
|
81
|
+
logger.verbose(`${EkuboQuoter.name}:: LST Dex Price: ${price}`);
|
|
82
|
+
return price;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async getLSTTrueExchangeRate(baseToken: TokenInfo, quoteToken: TokenInfo, amount: Web3Number) {
|
|
86
|
+
const lstTokenInfo = baseToken;
|
|
87
|
+
const lstABI = new Contract({
|
|
88
|
+
abi: ERC4626Abi,
|
|
89
|
+
address: lstTokenInfo.address.address,
|
|
90
|
+
providerOrAccount: this.config.provider
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const price: any = await lstABI.call('convert_to_assets', [uint256.bnToUint256((new Web3Number(1, lstTokenInfo.decimals)).toWei())]);
|
|
94
|
+
const exchangeRate = Number(uint256.uint256ToBN(price).toString()) / Math.pow(10, lstTokenInfo.decimals);
|
|
95
|
+
logger.verbose(`${EkuboQuoter.name}:: LST true Exchange Rate: ${exchangeRate}`);
|
|
96
|
+
return exchangeRate;
|
|
97
|
+
}
|
|
98
|
+
// debt collateral
|
|
99
|
+
async getSwapLimitAmount(fromToken: TokenInfo, toToken: TokenInfo, amount: Web3Number, max_slippage: number = 0.002): Promise<Web3Number> {
|
|
100
|
+
const isExactAmountIn = amount.greaterThanOrEqualTo(0);
|
|
101
|
+
logger.verbose(`${EkuboQuoter.name}::getSwapLimitAmount isExactAmountIn: ${isExactAmountIn}, fromToken: ${fromToken.symbol}, toToken: ${toToken.symbol}, amount: ${amount}`);
|
|
102
|
+
const isYieldToken = this.tokenMarketData.isAPYSupported(toToken);
|
|
103
|
+
console.log("isYieldToken", isYieldToken);
|
|
104
|
+
|
|
105
|
+
// if LST, get true exchange rate else use dex price
|
|
106
|
+
// wbtc
|
|
107
|
+
const baseToken = isExactAmountIn ? toToken : fromToken; // fromToken -> wbtc,
|
|
108
|
+
const quoteToken = isExactAmountIn ? fromToken : toToken; // toToken -> usdc,
|
|
109
|
+
// need dex price of from token in toToken
|
|
110
|
+
// from baseToken to underlying token
|
|
111
|
+
// for withdraw, usdc to btc with amount negative
|
|
112
|
+
const dexPrice = await this.getDexPrice(baseToken, quoteToken, amount);
|
|
113
|
+
const trueExchangeRate = isYieldToken ? await this.tokenMarketData.getTruePrice(baseToken) : dexPrice;
|
|
114
|
+
console.log("trueExchangeRate", trueExchangeRate);
|
|
115
|
+
if (isExactAmountIn) {
|
|
116
|
+
let minLSTReceived = amount.dividedBy(dexPrice).multipliedBy(1 - max_slippage); // used for increase
|
|
117
|
+
console.log("minLSTReceived", minLSTReceived);
|
|
118
|
+
const minLSTReceivedAsPerTruePrice = amount.dividedBy(trueExchangeRate); // execution output to be <= True LST price
|
|
119
|
+
if (minLSTReceived < minLSTReceivedAsPerTruePrice) {
|
|
120
|
+
minLSTReceived = minLSTReceivedAsPerTruePrice; // the execution shouldn't be bad than True price logi
|
|
121
|
+
}
|
|
122
|
+
logger.verbose(`${EkuboQuoter.name}::getModifyLeverCall minLSTReceivedAsPerTruePrice: ${minLSTReceivedAsPerTruePrice}, minLSTReceived: ${minLSTReceived}`);
|
|
123
|
+
return minLSTReceived;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let maxUsedCollateral = amount.abs().dividedBy(dexPrice).multipliedBy(1 + max_slippage); // +ve for exact amount out, used for decrease
|
|
127
|
+
const maxUsedCollateralInLST = amount.abs().dividedBy(trueExchangeRate).multipliedBy(1.005); // 0.5% slippage, worst case based on true price
|
|
128
|
+
logger.verbose(`${EkuboQuoter.name}::getModifyLeverCall maxUsedCollateralInLST: ${maxUsedCollateralInLST}, maxUsedCollateral: ${maxUsedCollateral}`);
|
|
129
|
+
if (maxUsedCollateralInLST > maxUsedCollateral) {
|
|
130
|
+
maxUsedCollateral = maxUsedCollateralInLST;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return maxUsedCollateral;
|
|
134
|
+
}
|
|
135
|
+
|
|
60
136
|
/**
|
|
61
137
|
* Formats Ekubo response for Vesu multiple use
|
|
62
138
|
* @param quote
|
|
@@ -65,7 +141,7 @@ export class EkuboQuoter {
|
|
|
65
141
|
*/
|
|
66
142
|
getVesuMultiplyQuote(quote: EkuboQuote, fromTokenInfo: TokenInfo, toTokenInfo: TokenInfo): Swap[] {
|
|
67
143
|
return quote.splits.map(split => {
|
|
68
|
-
const isNegativeAmount = BigInt(split.amount_specified)
|
|
144
|
+
const isNegativeAmount = BigInt(split.amount_specified) <= 0n;
|
|
69
145
|
const token = isNegativeAmount ? toTokenInfo : fromTokenInfo;
|
|
70
146
|
return {
|
|
71
147
|
route: split.route.map(_route => ({
|
|
@@ -81,6 +157,9 @@ export class EkuboQuoter {
|
|
|
81
157
|
})),
|
|
82
158
|
token_amount: {
|
|
83
159
|
token: token.address,
|
|
160
|
+
/**
|
|
161
|
+
* For closing position, put this as 0
|
|
162
|
+
*/
|
|
84
163
|
amount: Web3Number.fromWei(split.amount_specified, token.decimals)
|
|
85
164
|
}
|
|
86
165
|
};
|