@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.
Files changed (60) hide show
  1. package/dist/index.browser.global.js +76556 -66640
  2. package/dist/index.browser.mjs +34235 -24392
  3. package/dist/index.d.ts +2372 -793
  4. package/dist/index.js +31967 -22084
  5. package/dist/index.mjs +25545 -15719
  6. package/package.json +86 -76
  7. package/readme.md +56 -1
  8. package/src/data/extended-deposit.abi.json +3613 -0
  9. package/src/data/universal-vault.abi.json +135 -20
  10. package/src/dataTypes/_bignumber.ts +11 -0
  11. package/src/dataTypes/address.ts +7 -0
  12. package/src/global.ts +240 -193
  13. package/src/interfaces/common.tsx +26 -2
  14. package/src/modules/ExtendedWrapperSDk/index.ts +62 -0
  15. package/src/modules/ExtendedWrapperSDk/types.ts +311 -0
  16. package/src/modules/ExtendedWrapperSDk/wrapper.ts +448 -0
  17. package/src/modules/avnu.ts +17 -4
  18. package/src/modules/ekubo-quoter.ts +89 -10
  19. package/src/modules/erc20.ts +67 -21
  20. package/src/modules/harvests.ts +29 -43
  21. package/src/modules/index.ts +5 -1
  22. package/src/modules/lst-apr.ts +36 -0
  23. package/src/modules/midas.ts +159 -0
  24. package/src/modules/pricer-from-api.ts +2 -2
  25. package/src/modules/pricer-lst.ts +1 -1
  26. package/src/modules/pricer.ts +3 -38
  27. package/src/modules/token-market-data.ts +202 -0
  28. package/src/node/deployer.ts +1 -36
  29. package/src/strategies/autoCompounderStrk.ts +1 -1
  30. package/src/strategies/base-strategy.ts +20 -3
  31. package/src/strategies/btc-vesu-extended-strategy/core-strategy.tsx +1486 -0
  32. package/src/strategies/btc-vesu-extended-strategy/services/operationService.ts +32 -0
  33. package/src/strategies/btc-vesu-extended-strategy/utils/constants.ts +3 -0
  34. package/src/strategies/btc-vesu-extended-strategy/utils/helper.ts +396 -0
  35. package/src/strategies/btc-vesu-extended-strategy/utils/types.ts +5 -0
  36. package/src/strategies/ekubo-cl-vault.tsx +123 -306
  37. package/src/strategies/index.ts +7 -1
  38. package/src/strategies/svk-strategy.ts +247 -0
  39. package/src/strategies/universal-adapters/adapter-optimizer.ts +65 -0
  40. package/src/strategies/universal-adapters/adapter-utils.ts +5 -1
  41. package/src/strategies/universal-adapters/avnu-adapter.ts +432 -0
  42. package/src/strategies/universal-adapters/baseAdapter.ts +181 -153
  43. package/src/strategies/universal-adapters/common-adapter.ts +98 -77
  44. package/src/strategies/universal-adapters/extended-adapter.ts +976 -0
  45. package/src/strategies/universal-adapters/index.ts +7 -1
  46. package/src/strategies/universal-adapters/unused-balance-adapter.ts +109 -0
  47. package/src/strategies/universal-adapters/vesu-adapter.ts +230 -230
  48. package/src/strategies/universal-adapters/vesu-borrow-adapter.ts +1247 -0
  49. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1306 -0
  50. package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +58 -51
  51. package/src/strategies/universal-lst-muliplier-strategy.tsx +716 -844
  52. package/src/strategies/universal-strategy.tsx +1103 -1181
  53. package/src/strategies/vesu-extended-strategy/services/operationService.ts +34 -0
  54. package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +25 -0
  55. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +77 -0
  56. package/src/strategies/vesu-extended-strategy/utils/constants.ts +50 -0
  57. package/src/strategies/vesu-extended-strategy/utils/helper.ts +367 -0
  58. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +1420 -0
  59. package/src/strategies/vesu-rebalance.tsx +16 -20
  60. 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;
@@ -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),
@@ -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
- constructor(private readonly config: IConfig) {}
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 quote = await axios.get(this.ENDPOINT.replace("{{AMOUNT}}", amount.toWei()).replace("{{TOKEN_FROM_ADDRESS}}", _fromToken).replace("{{TOKEN_TO_ADDRESS}}", _toToken));
48
- console.log(`Ekubo quote from ${_fromToken} to ${_toToken} for ${amount.toString()}: ${JSON.stringify(quote.data)}`);
49
- return quote.data as EkuboQuote;
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
- console.error(error.message, 'dassf', error.data);
52
- if (retry < 10) {
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) < 0n;
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
  };