@strkfarm/sdk 2.0.0-dev.35 → 2.0.0-dev.37

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 (50) hide show
  1. package/dist/cli.js +2 -2
  2. package/dist/cli.mjs +2 -2
  3. package/dist/index.browser.global.js +19596 -28786
  4. package/dist/index.browser.mjs +8559 -17931
  5. package/dist/index.d.ts +578 -2746
  6. package/dist/index.js +8649 -18059
  7. package/dist/index.mjs +8577 -17949
  8. package/package.json +3 -3
  9. package/src/data/universal-vault.abi.json +8 -7
  10. package/src/dataTypes/bignumber.browser.ts +5 -1
  11. package/src/dataTypes/bignumber.node.ts +5 -0
  12. package/src/global.ts +21 -1
  13. package/src/interfaces/common.tsx +39 -4
  14. package/src/modules/avnu.ts +19 -10
  15. package/src/modules/index.ts +1 -1
  16. package/src/strategies/base-strategy.ts +92 -8
  17. package/src/strategies/constants.ts +8 -3
  18. package/src/strategies/ekubo-cl-vault.tsx +150 -16
  19. package/src/strategies/factory.ts +21 -1
  20. package/src/strategies/index.ts +2 -7
  21. package/src/strategies/registry.ts +28 -5
  22. package/src/strategies/sensei.ts +29 -13
  23. package/src/strategies/svk-strategy.ts +26 -2
  24. package/src/strategies/token-boosted-xstrk-carry-strategy.tsx +1223 -0
  25. package/src/strategies/universal-adapters/avnu-adapter.ts +16 -8
  26. package/src/strategies/universal-adapters/index.ts +1 -2
  27. package/src/strategies/universal-adapters/svk-troves-adapter.ts +19 -6
  28. package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +22 -3
  29. package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +75 -52
  30. package/src/strategies/universal-adapters/vesu-position-common.ts +38 -31
  31. package/src/strategies/universal-lst-muliplier-strategy.tsx +222 -269
  32. package/src/strategies/universal-strategy.tsx +166 -105
  33. package/src/strategies/vesu-rebalance.tsx +3 -6
  34. package/src/strategies/yoloVault.ts +1084 -0
  35. package/src/utils/health-factor-math.ts +29 -0
  36. package/src/modules/ExtendedWrapperSDk/index.ts +0 -62
  37. package/src/modules/ExtendedWrapperSDk/types.ts +0 -334
  38. package/src/modules/ExtendedWrapperSDk/wrapper.ts +0 -611
  39. package/src/strategies/universal-adapters/extended-adapter.ts +0 -860
  40. package/src/strategies/universal-adapters/usdc<>usdce-adapter.ts +0 -200
  41. package/src/strategies/usdc-boosted-strategy.tsx +0 -693
  42. package/src/strategies/vesu-extended-strategy/services/executionService.ts +0 -2234
  43. package/src/strategies/vesu-extended-strategy/services/extended-vesu-state-manager.ts +0 -4254
  44. package/src/strategies/vesu-extended-strategy/services/ltv-imbalance-rebalance-math.ts +0 -783
  45. package/src/strategies/vesu-extended-strategy/services/operationService.ts +0 -56
  46. package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +0 -88
  47. package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +0 -78
  48. package/src/strategies/vesu-extended-strategy/utils/constants.ts +0 -48
  49. package/src/strategies/vesu-extended-strategy/utils/helper.ts +0 -528
  50. package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +0 -1014
@@ -1,860 +0,0 @@
1
- import {
2
- BaseAdapter,
3
- DepositParams,
4
- WithdrawParams,
5
- BaseAdapterConfig,
6
- } from "./baseAdapter";
7
- import { Protocols } from "@/interfaces";
8
- import { SupportedPosition } from "./baseAdapter";
9
- import { PositionAPY, APYType, PositionAmount } from "./baseAdapter";
10
- import { Web3Number } from "@/dataTypes";
11
- import { PositionInfo } from "./baseAdapter";
12
- import { ManageCall } from "./baseAdapter";
13
- import { ContractAddr } from "@/dataTypes";
14
- import { AVNU_EXCHANGE_FOR_LEGACY_USDC } from "./adapter-utils";
15
- import { StandardMerkleTree } from "@/utils";
16
- import { hash, uint256 } from "starknet";
17
- import { AdapterLeafType, GenerateCallFn } from "./baseAdapter";
18
- import {
19
- AVNU_LEGACY_SANITIZER,
20
- EXTENDED_SANITIZER,
21
- SIMPLE_SANITIZER,
22
- toBigInt,
23
- } from "./adapter-utils";
24
- import ExtendedWrapper, {
25
- AssetOperationStatus,
26
- AssetOperationType,
27
- FundingPayment,
28
- FundingRate,
29
- OrderSide,
30
- Position,
31
- TimeInForce,
32
- } from "@/modules/ExtendedWrapperSDk";
33
- import { Balance, OpenOrder, OrderStatus } from "@/modules/ExtendedWrapperSDk";
34
- import axios from "axios";
35
- import { AvnuAdapter } from "./avnu-adapter";
36
- import { logger } from "@/utils";
37
- import { TokenInfo } from "@/interfaces";
38
-
39
- export interface ExtendedAdapterConfig extends BaseAdapterConfig {
40
- vaultIdExtended: number;
41
- extendedContract: ContractAddr; //0x04270219d365d6b017231b52e92b3fb5d7c8378b05e9abc97724537a80e93b0f
42
- extendedBackendReadUrl: string;
43
- extendedBackendWriteUrl: string;
44
- extendedTimeout: number;
45
- extendedRetries: number;
46
- extendedBaseUrl: string;
47
- extendedMarketName: string;
48
- extendedPrecision: number;
49
- avnuAdapter: AvnuAdapter;
50
- retryDelayForOrderStatus: number;
51
- minimumExtendedMovementAmount: number;
52
- }
53
-
54
- export class ExtendedAdapter extends BaseAdapter<
55
- DepositParams,
56
- WithdrawParams
57
- > {
58
- readonly config: ExtendedAdapterConfig;
59
- readonly client: ExtendedWrapper;
60
- readonly usdcToken: TokenInfo;
61
- readonly retryDelayForOrderStatus: number;
62
- readonly minimumExtendedMovementAmount: number;
63
-
64
- constructor(config: ExtendedAdapterConfig) {
65
- super(config, ExtendedAdapter.name, Protocols.EXTENDED);
66
- this.config = config as ExtendedAdapterConfig;
67
- const client = new ExtendedWrapper({
68
- writeUrl: this.config.extendedBackendWriteUrl,
69
- readUrl: this.config.extendedBackendReadUrl,
70
- apiKey: "", // todo add auth
71
- timeout: this.config.extendedTimeout,
72
- retries: this.config.extendedRetries,
73
- });
74
- this.minimumExtendedMovementAmount =
75
- this.config.minimumExtendedMovementAmount ?? 5; //5 usdc
76
- this.client = client;
77
- this.retryDelayForOrderStatus =
78
- this.config.retryDelayForOrderStatus ?? 3000;
79
- this.usdcToken = this.config.supportedPositions[0].asset;
80
- }
81
-
82
- private _depositApproveProofReadableId(): string {
83
- return `extended_approve_${this.usdcToken.symbol}`;
84
- }
85
-
86
- private _depositCallProofReadableId(): string {
87
- return `extended_deposit_${this.usdcToken.symbol}`;
88
- }
89
- //abstract means the method has no implementation in this class; instead, child classes must implement it.
90
- protected async getAPY(
91
- supportedPosition: SupportedPosition,
92
- ): Promise<PositionAPY> {
93
- /** Considering supportedPosiiton.isDebt as side parameter to get the funding rates */
94
- const side = supportedPosition.isDebt ? "LONG" : "SHORT";
95
- const fundingRates = await this.getFundingRates(side);
96
- if (!fundingRates.success) {
97
- logger.error("error getting funding rates", fundingRates);
98
- return { apy: 0, type: APYType.BASE };
99
- }
100
- const fundingRate: FundingRate = fundingRates.data[0];
101
- const apy = Number(fundingRate.f) * 365 * 24;
102
- return { apy: apy, type: APYType.BASE };
103
- }
104
-
105
- public async getFundingRates(
106
- side: string,
107
- startTime?: number,
108
- endTime?: number,
109
- ): Promise<{ success: boolean; data: FundingRate[] }> {
110
- try {
111
- const response = await this.client.getFundingRates(
112
- this.config.extendedMarketName,
113
- side,
114
- startTime,
115
- endTime,
116
- );
117
- if (response.status !== "OK") {
118
- logger.error("error getting funding rates", response.data);
119
- return { success: false, data: [] };
120
- }
121
- logger.debug(
122
- `success getting funding rates market=${this.config.extendedMarketName} side=${side} count=${response.data.length}`,
123
- );
124
- return { success: true, data: response.data };
125
- } catch (err) {
126
- logger.error("error getting funding rates", err);
127
- return { success: false, data: [] };
128
- }
129
- }
130
-
131
- /** Account funding payment history via `GET /api/v1/account/funding-payments` (USDC amounts). */
132
- public async getFundingPayments(
133
- side: string,
134
- startTime?: number,
135
- limit?: number,
136
- ): Promise<{ success: boolean; data: FundingPayment[] }> {
137
- try {
138
- const response = await this.client.getUserFundingPayments(
139
- this.config.extendedMarketName,
140
- side,
141
- startTime ?? Date.now() - 30 * 24 * 60 * 60 * 1000,
142
- limit ?? 200,
143
- );
144
- if (response.status !== "OK") {
145
- logger.error("error getting funding payments", response.data);
146
- return { success: false, data: [] };
147
- }
148
- return { success: true, data: response.data ?? [] };
149
- } catch (err) {
150
- logger.error("error getting funding payments", err);
151
- return { success: false, data: [] };
152
- }
153
- }
154
-
155
- protected async getPosition(
156
- supportedPosition: SupportedPosition,
157
- ): Promise<PositionAmount> {
158
- const holdings = await this.getExtendedDepositAmount();
159
- if (!holdings) {
160
- throw new Error("No position found");
161
- }
162
- // equity is the total amount on extended
163
- const amount = holdings.equity;
164
- return Promise.resolve({
165
- amount: new Web3Number(amount, 0),
166
- remarks: `Extended Equity`,
167
- });
168
- }
169
-
170
- async maxDeposit(amount?: Web3Number): Promise<PositionInfo> {
171
- return Promise.resolve({
172
- tokenInfo: this.config.baseToken,
173
- amount: new Web3Number(0, 0),
174
- usdValue: 0,
175
- apy: { apy: 0, type: APYType.BASE },
176
- protocol: Protocols.EXTENDED,
177
- remarks: "",
178
- });
179
- }
180
-
181
- async maxWithdraw(): Promise<PositionInfo> {
182
- return Promise.resolve({
183
- tokenInfo: this.config.baseToken,
184
- amount: new Web3Number(0, 0),
185
- usdValue: 0,
186
- apy: { apy: 0, type: APYType.BASE },
187
- protocol: Protocols.EXTENDED,
188
- remarks: "",
189
- });
190
- }
191
-
192
- protected _getDepositLeaf(): {
193
- target: ContractAddr;
194
- method: string;
195
- packedArguments: bigint[];
196
- sanitizer: ContractAddr;
197
- id: string;
198
- }[] {
199
- return [
200
- {
201
- target: this.usdcToken.address,
202
- method: "approve",
203
- packedArguments: [this.config.extendedContract.toBigInt()],
204
- id: this._depositApproveProofReadableId(),
205
- sanitizer: SIMPLE_SANITIZER,
206
- },
207
- {
208
- target: this.config.extendedContract,
209
- method: "deposit",
210
- packedArguments: [BigInt(this.config.vaultIdExtended)],
211
- sanitizer: EXTENDED_SANITIZER,
212
- id: this._depositCallProofReadableId(),
213
- },
214
- ];
215
- }
216
-
217
- protected _getWithdrawLeaf(): {
218
- target: ContractAddr;
219
- method: string;
220
- packedArguments: bigint[];
221
- sanitizer: ContractAddr;
222
- id: string;
223
- }[] {
224
- /**
225
- * Withdraw is done via extended wrapper
226
- */
227
- return [];
228
- }
229
-
230
- async getDepositCall(params: DepositParams): Promise<ManageCall[]> {
231
- try {
232
- const salt = Math.floor(Math.random() * 10 ** this.usdcToken.decimals);
233
- const amount = uint256.bnToUint256(params.amount.toWei());
234
- return [
235
- {
236
- proofReadableId: this._depositApproveProofReadableId(),
237
- sanitizer: SIMPLE_SANITIZER,
238
- call: {
239
- contractAddress: this.usdcToken.address,
240
- selector: hash.getSelectorFromName("approve"),
241
- calldata: [
242
- this.config.extendedContract.toBigInt(),
243
- toBigInt(amount.low.toString()), // amount low
244
- toBigInt(amount.high.toString()), // amount high
245
- ],
246
- },
247
- },
248
- {
249
- proofReadableId: this._depositCallProofReadableId(),
250
- sanitizer: EXTENDED_SANITIZER,
251
- call: {
252
- contractAddress: this.config.extendedContract,
253
- selector: hash.getSelectorFromName("deposit"),
254
- calldata: [
255
- BigInt(this.config.vaultIdExtended),
256
- BigInt(params.amount.toWei()),
257
- BigInt(salt),
258
- ],
259
- },
260
- },
261
- ];
262
- } catch (error) {
263
- logger.error(`Error creating Deposit Call: ${error}`);
264
- return [];
265
- }
266
- }
267
-
268
- async getWithdrawCall(params: WithdrawParams): Promise<ManageCall[]> {
269
- try {
270
- return [];
271
- } catch (error) {
272
- logger.error(`Error creating Withdraw Call: ${error}`);
273
- return [];
274
- }
275
- }
276
-
277
- async withdrawFromExtended(amount: Web3Number): Promise<{
278
- status: boolean;
279
- receivedTxnHash: boolean;
280
- }> {
281
- try {
282
- if (!this.client) {
283
- logger.error("Client not initialized");
284
- return {
285
- status: false,
286
- receivedTxnHash: false,
287
- };
288
- }
289
- if (amount.lessThanOrEqualTo(0)) {
290
- logger.error(
291
- `Invalid withdrawal amount: ${amount.toNumber()}. Amount must be positive.`,
292
- );
293
- return {
294
- status: false,
295
- receivedTxnHash: false,
296
- };
297
- }
298
- if (amount.lessThanOrEqualTo(this.minimumExtendedMovementAmount)) {
299
- logger.warn(
300
- `Withdrawal amount ${amount.toNumber()} is below minimum Extended movement amount ${this.minimumExtendedMovementAmount}. Skipping withdrawal.`,
301
- );
302
- return {
303
- status: false,
304
- receivedTxnHash: false,
305
- };
306
- }
307
- const holdings = await this.getExtendedDepositAmount();
308
- if (!holdings) {
309
- logger.error(
310
- "Cannot get holdings - unable to validate withdrawal amount",
311
- );
312
- return {
313
- status: false,
314
- receivedTxnHash: false,
315
- };
316
- }
317
-
318
- const availableForWithdrawal = parseFloat(
319
- holdings.availableForWithdrawal,
320
- );
321
- if (
322
- !Number.isFinite(availableForWithdrawal) ||
323
- availableForWithdrawal < 0
324
- ) {
325
- logger.error(
326
- `Invalid availableForWithdrawal: ${holdings.availableForWithdrawal}. Expected a finite, non-negative number.`,
327
- );
328
- return {
329
- status: false,
330
- receivedTxnHash: false,
331
- };
332
- }
333
-
334
- const withdrawalAmount = amount.toNumber();
335
- if (withdrawalAmount > availableForWithdrawal) {
336
- logger.error(
337
- `Withdrawal amount ${withdrawalAmount} exceeds available balance ${availableForWithdrawal}`,
338
- );
339
- return {
340
- status: false,
341
- receivedTxnHash: false,
342
- };
343
- }
344
-
345
- logger.info(
346
- `Withdrawing ${withdrawalAmount} from Extended. Available balance: ${availableForWithdrawal}`,
347
- );
348
-
349
- const withdrawalRequest = await this.client.withdrawUSDC(
350
- amount.toFixed(2),
351
- );
352
-
353
- if (withdrawalRequest.status === "OK") {
354
- const withdrawalStatus = await this.getDepositOrWithdrawalStatus(
355
- withdrawalRequest.data,
356
- AssetOperationType.WITHDRAWAL,
357
- );
358
- return {
359
- status: true,
360
- receivedTxnHash: withdrawalStatus,
361
- };
362
- }
363
- logger.error(
364
- `Withdrawal request failed with status: ${withdrawalRequest.status}`,
365
- );
366
- return {
367
- status: false,
368
- receivedTxnHash: false,
369
- };
370
- } catch (error) {
371
- logger.error(`Error creating Withdraw Call: ${error}`);
372
- return {
373
- status: false,
374
- receivedTxnHash: false,
375
- };
376
- }
377
- }
378
-
379
- async getHealthFactor(): Promise<number> {
380
- return Promise.resolve(1);
381
- }
382
-
383
- async getExtendedDepositAmount(): Promise<Balance | undefined> {
384
- const failResult = {
385
- collateral_name: "",
386
- balance: "0",
387
- equity: "0",
388
- availableForTrade: "0",
389
- availableForWithdrawal: "0",
390
- unrealisedPnl: "0",
391
- initialMargin: "0",
392
- marginRatio: "0",
393
- updatedTime: Date.now(),
394
- };
395
- const fail = (message: string) => {
396
- logger.error(message);
397
- return failResult;
398
- };
399
-
400
- try {
401
- const result = await this.client.getHoldings();
402
- if (result.status !== "OK") {
403
- return fail("error getting holdings - API returned null/undefined");
404
- }
405
- const holdings = result.data;
406
- if (!holdings) {
407
- return fail(
408
- "holdings data is null/undefined - treating as zero balance",
409
- );
410
- }
411
- return holdings;
412
- } catch (error) {
413
- return fail(`error getting holdings - exception: ${error}`);
414
- }
415
- }
416
-
417
- async setLeverage(leverage: string, marketName: string): Promise<boolean> {
418
- if (this.client === null) {
419
- logger.error("error initializing client");
420
- return false;
421
- }
422
- const res = await this.client.updateLeverage({
423
- leverage: leverage,
424
- market: marketName,
425
- });
426
- if (res.status === "OK") {
427
- return true;
428
- }
429
- return false;
430
- }
431
-
432
- async getAllOpenPositions(): Promise<Position[] | null> {
433
- try {
434
- const response = await this.client.getPositions(
435
- this.config.extendedMarketName,
436
- );
437
- if (response.status === "OK") {
438
- if (response.data.length === 0) {
439
- return [];
440
- } else {
441
- return response.data;
442
- }
443
- }
444
- return null;
445
- } catch (err) {
446
- logger.error("error getting all open positions", err);
447
- return null;
448
- }
449
- }
450
-
451
- // async getOrderHistory(marketName: string): Promise<OpenOrder[] | null> {
452
- // try {
453
- // const result = await axios.get<ApiResponse<OpenOrder[]>>(`${this.config.extendedBackendReadUrl}/marketOrders/${marketName}`);
454
- // if (!result.data.success) {
455
- // return null;
456
- // }
457
- // return result.data.data;
458
- // } catch (err) {
459
- // logger.error("error getting order history", err);
460
- // return null;
461
- // }
462
- // }
463
-
464
- async getOrderStatus(
465
- orderId: string,
466
- marketName: string,
467
- ): Promise<OpenOrder | null> {
468
- try {
469
- if (this.client === null) {
470
- logger.error("error initializing client");
471
- return null;
472
- }
473
- const orderhistoryResponse =
474
- await this.client.getOrderHistory(marketName);
475
-
476
- if (
477
- orderhistoryResponse.status !== "OK" ||
478
- orderhistoryResponse.data.length === 0
479
- ) {
480
- return null;
481
- }
482
- const orderhistory = orderhistoryResponse.data;
483
- const order = orderhistory
484
- .slice(0, 20)
485
- .find((order) => order.id.toString() === orderId);
486
- if (order) {
487
- return order;
488
- }
489
- return null; // Order not found
490
- } catch (error) {
491
- logger.error(`error getting order status: ${error}`);
492
- return null;
493
- }
494
- }
495
-
496
- async fetchOrderBookFromExtended(): Promise<{
497
- status: boolean;
498
- bid: Web3Number;
499
- ask: Web3Number;
500
- }> {
501
- try {
502
- const res = await axios.get(
503
- `${this.config.extendedBaseUrl}/api/v1/info/markets/${this.config.extendedMarketName}/orderbook`,
504
- );
505
- let ask_price = new Web3Number(0, 0);
506
- let bid_price = new Web3Number(0, 0);
507
- if (res.data.status !== "OK") {
508
- return {
509
- status: false,
510
- bid: bid_price,
511
- ask: ask_price,
512
- };
513
- }
514
- const data = res.data.data;
515
- const bid = data.bid[0];
516
- const ask = data.ask[0];
517
- ask_price = new Web3Number(ask.price, 0);
518
- bid_price = new Web3Number(bid.price, 0);
519
-
520
- return {
521
- status: true,
522
- bid: bid_price, // check for quantity as well, not at the moment though
523
- ask: ask_price, // check for quantity as well, not at the moment though
524
- };
525
- } catch (err) {
526
- logger.error(`extended orderbook fetch failed: ${err}`);
527
- return {
528
- status: false,
529
- bid: new Web3Number(0, 0),
530
- ask: new Web3Number(0, 0),
531
- };
532
- }
533
- }
534
-
535
- async createOrder(
536
- leverage: string,
537
- btcAmount: number,
538
- side: OrderSide,
539
- attempt = 1,
540
- maxAttempts = 5,
541
- ): Promise<{ position_id: string; btc_exposure: string } | null> {
542
- try {
543
- if (this.client === null) {
544
- logger.error("error initializing client");
545
- return null;
546
- }
547
- // todo, instead get levgerage and log errors if not ideal lever already
548
- // const setLeverage = await this.setLeverage(
549
- // leverage,
550
- // this.config.extendedMarketName,
551
- // );
552
- // if (!setLeverage) {
553
- // logger.error("error depositing or setting leverage");
554
- // return null;
555
- // }
556
- const { ask, bid } = await this.fetchOrderBookFromExtended();
557
- if (
558
- !ask ||
559
- !bid ||
560
- ask.lessThanOrEqualTo(0) ||
561
- bid.lessThanOrEqualTo(0)
562
- ) {
563
- logger.error(
564
- `Invalid orderbook prices: ask=${ask?.toNumber()}, bid=${bid?.toNumber()}`,
565
- );
566
- return null;
567
- }
568
-
569
- const spread = ask.minus(bid);
570
- const midPrice = ask.plus(bid).div(2);
571
- /** Maximum deviation: 50% of spread (to prevent extremely unfavorable prices) */
572
- const MAX_PRICE_DEVIATION_MULTIPLIER = 0.5;
573
- const priceAdjustmentMultiplier = Math.min(
574
- 0.2 * attempt,
575
- MAX_PRICE_DEVIATION_MULTIPLIER,
576
- );
577
- const priceAdjustment = spread.times(priceAdjustmentMultiplier);
578
-
579
- let price = midPrice;
580
- if (side === OrderSide.SELL) {
581
- price = midPrice.minus(priceAdjustment);
582
- } else {
583
- price = midPrice.plus(priceAdjustment);
584
- }
585
-
586
- /** Validate price is still reasonable (within 50% of mid price) */
587
- const maxDeviation = midPrice.times(0.5);
588
- if (price.minus(midPrice).abs().greaterThan(maxDeviation)) {
589
- logger.error(
590
- `Price deviation too large on attempt ${attempt}: price=${price.toNumber()}, midPrice=${midPrice.toNumber()}, deviation=${price
591
- .minus(midPrice)
592
- .abs()
593
- .toNumber()}`,
594
- );
595
- if (attempt >= maxAttempts) {
596
- return null;
597
- }
598
- price =
599
- side === OrderSide.SELL
600
- ? midPrice.minus(maxDeviation)
601
- : midPrice.plus(maxDeviation);
602
- }
603
-
604
- logger.info(
605
- `createOrder attempt ${attempt}/${maxAttempts}: side=${side}, midPrice=${midPrice.toNumber()}, adjustedPrice=${price.toNumber()}, adjustment=${priceAdjustmentMultiplier * 100
606
- }%`,
607
- );
608
-
609
- const amount_in_token = (btcAmount * parseInt(leverage)).toFixed(
610
- this.config.extendedPrecision,
611
- ); // gives the amount of wbtc
612
-
613
- const result = await this.createExtendedPositon(
614
- this.client,
615
- this.config.extendedMarketName,
616
- amount_in_token,
617
- price.toFixed(0),
618
- side,
619
- );
620
- if (!result || !result.position_id) {
621
- logger.error("Failed to create order - no position_id returned");
622
- return null;
623
- }
624
-
625
- const positionId = result.position_id;
626
- logger.info(
627
- `Order created with position_id: ${positionId}. Waiting for API to update...`,
628
- );
629
-
630
- let openOrder = await this.getOrderStatus(
631
- positionId,
632
- this.config.extendedMarketName,
633
- );
634
- const maxStatusRetries = 3;
635
- const statusRetryDelay = 5000;
636
-
637
- if (!openOrder) {
638
- logger.warn(
639
- `Order ${positionId} not found in API yet. Retrying status fetch (max ${maxStatusRetries} times)...`,
640
- );
641
- for (
642
- let statusRetry = 1;
643
- statusRetry <= maxStatusRetries;
644
- statusRetry++
645
- ) {
646
- await new Promise((resolve) => setTimeout(resolve, statusRetryDelay));
647
- openOrder = await this.getOrderStatus(
648
- positionId,
649
- this.config.extendedMarketName,
650
- );
651
- if (openOrder) {
652
- logger.info(
653
- `Order ${positionId} found after ${statusRetry} status retry(ies)`,
654
- );
655
- break;
656
- }
657
- logger.warn(
658
- `Order ${positionId} still not found after ${statusRetry}/${maxStatusRetries} status retries`,
659
- );
660
- }
661
- }
662
-
663
- if (openOrder && openOrder.status === OrderStatus.FILLED) {
664
- logger.info(
665
- `Order ${positionId} successfully filled with quantity ${openOrder.qty}`,
666
- );
667
- return {
668
- position_id: positionId,
669
- btc_exposure: openOrder.qty,
670
- };
671
- } else if (openOrder && openOrder.status !== OrderStatus.FILLED) {
672
- // Order found but NOT FILLED - retry (recreate)
673
- logger.warn(
674
- `Order ${positionId} found but status is ${openOrder.status}, not FILLED. Retrying order creation...`,
675
- );
676
- if (attempt >= maxAttempts) {
677
- logger.error(
678
- `Max retries reached — order ${positionId} status is ${openOrder.status}, not FILLED`,
679
- );
680
- return null;
681
- } else {
682
- const backoff = 2000 * attempt;
683
- await new Promise((resolve) => setTimeout(resolve, backoff));
684
- return this.createOrder(
685
- leverage,
686
- btcAmount,
687
- side,
688
- attempt + 1,
689
- maxAttempts,
690
- );
691
- }
692
- } else {
693
- logger.warn(
694
- `Order ${positionId} not found in API after ${maxStatusRetries} status retries (API update delayed ~30s). We got position_id from creation, so order exists. Returning position_id - status will be checked in next loop iteration.`,
695
- );
696
- return {
697
- position_id: positionId,
698
- btc_exposure: amount_in_token,
699
- };
700
- }
701
- } catch (err: any) {
702
- logger.error(
703
- `createShortOrder failed on attempt ${attempt}: ${err.message}`,
704
- );
705
-
706
- if (attempt < maxAttempts) {
707
- const backoff = 1200 * attempt;
708
- logger.info(`Retrying after ${backoff}ms...`);
709
- await new Promise((resolve) => setTimeout(resolve, backoff));
710
- return this.createOrder(
711
- leverage,
712
- btcAmount,
713
- side,
714
- attempt + 1,
715
- maxAttempts,
716
- );
717
- }
718
- logger.error("Max retry attempts reached — aborting createShortOrder");
719
- return null;
720
- }
721
- }
722
-
723
- async createExtendedPositon(
724
- client: ExtendedWrapper,
725
- marketName: string,
726
- amount: string,
727
- price: string,
728
- side: OrderSide,
729
- ) {
730
- try {
731
- const result =
732
- side === OrderSide.SELL
733
- ? await client.createSellOrder(marketName, amount, price, {
734
- post_only: false,
735
- reduce_only: false,
736
- time_in_force: TimeInForce.IOC,
737
- })
738
- : await client.createBuyOrder(marketName, amount, price, {
739
- post_only: false,
740
- reduce_only: true,
741
- time_in_force: TimeInForce.IOC,
742
- });
743
- if (result.data.id) {
744
- const position_id = result.data.id.toString();
745
- return {
746
- position_id,
747
- };
748
- }
749
- return null;
750
- } catch (err) {
751
- logger.error(`Error opening short extended position, ${err}`);
752
- return null;
753
- }
754
- }
755
-
756
- async getDepositOrWithdrawalStatus(
757
- orderId: number | string, // for deposits, send txn hash as string
758
- operationsType: AssetOperationType,
759
- ): Promise<boolean> {
760
- const maxAttempts = 15;
761
- const retryDelayMs = 30000; // 60 seconds
762
-
763
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
764
- try {
765
- let transferHistory = await this.client.getAssetOperations({
766
- operationsType: [operationsType],
767
- operationsStatus: [AssetOperationStatus.COMPLETED],
768
- });
769
- if (operationsType === AssetOperationType.DEPOSIT) {
770
- const myTransferStatus = transferHistory.data.find(
771
- (operation) =>
772
- operation.transactionHash?.toLowerCase() ===
773
- orderId.toString().toLowerCase(),
774
- );
775
- if (!myTransferStatus) {
776
- if (attempt < maxAttempts) {
777
- logger.info(
778
- `Deposit operation not found for transactionHash ${orderId}, retrying (attempt ${attempt}/${maxAttempts})...`,
779
- );
780
- await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
781
- continue;
782
- }
783
- logger.warn(
784
- `Deposit operation not found for transactionHash ${orderId} after ${maxAttempts} attempts`,
785
- );
786
- return false;
787
- }
788
- // Check if status is COMPLETED
789
- if (myTransferStatus.status === AssetOperationStatus.COMPLETED) {
790
- logger.info(`Deposit operation ${orderId} completed successfully`);
791
- return true;
792
- } else {
793
- if (attempt < maxAttempts) {
794
- logger.info(
795
- `Deposit operation ${orderId} found but status is ${myTransferStatus.status}, not COMPLETED. Retrying (attempt ${attempt}/${maxAttempts})...`,
796
- );
797
- await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
798
- continue;
799
- }
800
- logger.warn(
801
- `Deposit operation ${orderId} status is ${myTransferStatus.status} after ${maxAttempts} attempts, expected COMPLETED`,
802
- );
803
- return false;
804
- }
805
- } else {
806
- const myTransferStatus = transferHistory.data.find(
807
- (operation) => operation.id === orderId.toString(),
808
- );
809
- if (!myTransferStatus) {
810
- if (attempt < maxAttempts) {
811
- logger.info(
812
- `Withdrawal status not found for orderId ${orderId} in completed operations, retrying (attempt ${attempt}/${maxAttempts})...`,
813
- );
814
- await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
815
- continue;
816
- }
817
- logger.warn(
818
- `Withdrawal operation not found for orderId ${orderId} after ${maxAttempts} attempts`,
819
- );
820
- return false;
821
- }
822
- // Check if status is COMPLETED
823
- if (myTransferStatus.status === AssetOperationStatus.COMPLETED) {
824
- logger.info(
825
- `Withdrawal operation ${orderId} completed successfully`,
826
- );
827
- return true;
828
- } else {
829
- if (attempt < maxAttempts) {
830
- logger.info(
831
- `Withdrawal operation ${orderId} found but status is ${myTransferStatus.status}, not COMPLETED. Retrying (attempt ${attempt}/${maxAttempts})...`,
832
- );
833
- await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
834
- continue;
835
- }
836
- logger.warn(
837
- `Withdrawal operation ${orderId} status is ${myTransferStatus.status} after ${maxAttempts} attempts, expected COMPLETED`,
838
- );
839
- return false;
840
- }
841
- }
842
- } catch (err) {
843
- logger.error(
844
- `error getting deposit or withdrawal status (attempt ${attempt}/${maxAttempts}): ${err}`,
845
- );
846
- if (attempt < maxAttempts) {
847
- logger.info(`Retrying after ${retryDelayMs}ms...`);
848
- await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
849
- continue;
850
- }
851
- logger.error(
852
- `Max retry attempts reached for getDepositOrWithdrawalStatus`,
853
- );
854
- return false;
855
- }
856
- }
857
-
858
- return false;
859
- }
860
- }