@strkfarm/sdk 2.0.0-dev.2 → 2.0.0-dev.21

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.
@@ -1,8 +1,14 @@
1
- import { BaseAdapter, DepositParams, WithdrawParams, BaseAdapterConfig } from "./baseAdapter";
1
+ import {
2
+ BaseAdapter,
3
+ DepositParams,
4
+ WithdrawParams,
5
+ BaseAdapterConfig,
6
+ } from "./baseAdapter";
2
7
  import { Protocols } from "@/interfaces";
3
8
  import { SupportedPosition } from "./baseAdapter";
4
9
  import { PositionAPY, APYType, PositionAmount } from "./baseAdapter";
5
10
  import { Web3Number } from "@/dataTypes";
11
+ import { ApiResponse } from "@/modules/ExtendedWrapperSDk";
6
12
  import { PositionInfo } from "./baseAdapter";
7
13
  import { ManageCall } from "./baseAdapter";
8
14
  import { ContractAddr } from "@/dataTypes";
@@ -11,7 +17,12 @@ import { StandardMerkleTree } from "@/utils";
11
17
  import { hash, uint256 } from "starknet";
12
18
  import { Global } from "@/global";
13
19
  import { AdapterLeafType, GenerateCallFn } from "./baseAdapter";
14
- import { AVNU_LEGACY_SANITIZER, EXTENDED_SANITIZER, SIMPLE_SANITIZER, toBigInt } from "./adapter-utils";
20
+ import {
21
+ AVNU_LEGACY_SANITIZER,
22
+ EXTENDED_SANITIZER,
23
+ SIMPLE_SANITIZER,
24
+ toBigInt,
25
+ } from "./adapter-utils";
15
26
  import ExtendedWrapper, {
16
27
  AssetOperationStatus,
17
28
  AssetOperationType,
@@ -24,17 +35,21 @@ import { Balance, OpenOrder, OrderStatus } from "@/modules/ExtendedWrapperSDk";
24
35
  import axios from "axios";
25
36
  import { AvnuAdapter } from "./avnu-adapter";
26
37
  import { logger } from "@/utils";
38
+
39
+
27
40
  export interface ExtendedAdapterConfig extends BaseAdapterConfig {
28
41
  vaultIdExtended: number;
29
42
  extendedContract: ContractAddr; //0x04270219d365d6b017231b52e92b3fb5d7c8378b05e9abc97724537a80e93b0f
30
- extendedBackendUrl: string;
31
- extendedApiKey: string;
43
+ extendedBackendReadUrl: string;
44
+ extendedBackendWriteUrl: string;
32
45
  extendedTimeout: number;
33
46
  extendedRetries: number;
34
47
  extendedBaseUrl: string;
35
48
  extendedMarketName: string;
36
49
  extendedPrecision: number;
37
50
  avnuAdapter: AvnuAdapter;
51
+ retryDelayForOrderStatus: number;
52
+ minimumExtendedMovementAmount: number;
38
53
  }
39
54
 
40
55
  export class ExtendedAdapter extends BaseAdapter<
@@ -43,17 +58,23 @@ export class ExtendedAdapter extends BaseAdapter<
43
58
  > {
44
59
  readonly config: ExtendedAdapterConfig;
45
60
  readonly client: ExtendedWrapper;
61
+ readonly retryDelayForOrderStatus: number;
62
+ readonly minimumExtendedMovementAmount: number;
46
63
 
47
64
  constructor(config: ExtendedAdapterConfig) {
48
65
  super(config, ExtendedAdapter.name, Protocols.EXTENDED);
49
66
  this.config = config as ExtendedAdapterConfig;
50
67
  const client = new ExtendedWrapper({
51
- baseUrl: this.config.extendedBackendUrl,
52
- apiKey: this.config.extendedApiKey,
68
+ baseUrl: this.config.extendedBackendWriteUrl,
69
+ apiKey: "",
53
70
  timeout: this.config.extendedTimeout,
54
71
  retries: this.config.extendedRetries,
55
72
  });
73
+ this.minimumExtendedMovementAmount =
74
+ this.config.minimumExtendedMovementAmount ?? 5; //5 usdc
56
75
  this.client = client;
76
+ this.retryDelayForOrderStatus =
77
+ this.config.retryDelayForOrderStatus ?? 3000;
57
78
  }
58
79
  //abstract means the method has no implementation in this class; instead, child classes must implement it.
59
80
  protected async getAPY(
@@ -61,23 +82,40 @@ export class ExtendedAdapter extends BaseAdapter<
61
82
  ): Promise<PositionAPY> {
62
83
  /** Considering supportedPosiiton.isDebt as side parameter to get the funding rates */
63
84
  const side = supportedPosition.isDebt ? "LONG" : "SHORT";
64
- const fundingRates = await this.client.getFundingRates(this.config.extendedMarketName, side);
65
- if (fundingRates.status !== "OK") {
85
+ const fundingRates = await this.getFundingRates(side);
86
+ if (!fundingRates.success) {
66
87
  logger.error("error getting funding rates", fundingRates);
67
88
  return { apy: 0, type: APYType.BASE };
68
89
  }
69
- const fundingRate:FundingRate = fundingRates.data[0];
90
+ const fundingRate: FundingRate = fundingRates.data[0];
70
91
  const apy = Number(fundingRate.f) * 365 * 24;
71
92
  return { apy: apy, type: APYType.BASE };
72
93
  }
73
94
 
95
+ public async getFundingRates(side: string, startTime?: number, endTime?: number): Promise<{ success: boolean, data: FundingRate[] }> {
96
+ try {
97
+ const response = await axios.get<ApiResponse<FundingRate[]>>(`${this.config.extendedBackendReadUrl}/fundingRates/${this.config.extendedMarketName}/${side}`, {
98
+ params: {
99
+ startTime: startTime ? startTime.toString() : undefined,
100
+ endTime: endTime ? endTime.toString() : undefined,
101
+ }
102
+ });
103
+ if (!response.data.success) {
104
+ logger.error("error getting funding rates", response.data);
105
+ return { success: false, data: [] };
106
+ }
107
+ logger.info("success getting funding rates", response.data.data);
108
+ return { success: true, data: response.data.data };
109
+ } catch (err) {
110
+ logger.error("error getting funding rates", err);
111
+ return { success: false, data: [] };
112
+ }
113
+ }
114
+
74
115
  protected async getPosition(
75
116
  supportedPosition: SupportedPosition
76
117
  ): Promise<PositionAmount> {
77
- if (!this.client) {
78
- throw new Error("Client not initialized");
79
- }
80
- const holdings = await this.getExtendedDepositAmount();
118
+ const holdings = await this.getExtendedDepositAmount()
81
119
  if (!holdings) {
82
120
  throw new Error("No position found");
83
121
  }
@@ -85,7 +123,7 @@ export class ExtendedAdapter extends BaseAdapter<
85
123
  const amount = holdings.equity;
86
124
  return Promise.resolve({
87
125
  amount: new Web3Number(amount, 0),
88
- remarks: `${holdings.availableForWithdrawal} ${holdings.equity}`,
126
+ remarks: `Extended Equity`,
89
127
  });
90
128
  }
91
129
 
@@ -118,14 +156,14 @@ export class ExtendedAdapter extends BaseAdapter<
118
156
  sanitizer: ContractAddr;
119
157
  id: string;
120
158
  }[] {
121
- const usdceToken = Global.getDefaultTokens().find(token => token.symbol === "USDCe");
159
+ const usdceToken = Global.getDefaultTokens().find(
160
+ (token) => token.symbol === "USDCe"
161
+ );
122
162
  return [
123
163
  {
124
164
  target: this.config.supportedPositions[0].asset.address,
125
165
  method: "approve",
126
- packedArguments: [
127
- AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt(),
128
- ],
166
+ packedArguments: [AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt()],
129
167
  id: `extended_approve_${this.config.supportedPositions[0].asset.symbol}`,
130
168
  sanitizer: AVNU_LEGACY_SANITIZER,
131
169
  },
@@ -155,19 +193,27 @@ export class ExtendedAdapter extends BaseAdapter<
155
193
 
156
194
  getSwapFromLegacyLeaf(): AdapterLeafType<DepositParams> {
157
195
  const leafConfigs = this._getSwapFromLegacyLeaf();
158
- const leaves = leafConfigs.map(config => {
196
+ const leaves = leafConfigs.map((config) => {
159
197
  const { target, method, packedArguments, sanitizer, id } = config;
160
- const leaf = this.constructSimpleLeafData({
161
- id: id,
162
- target,
163
- method,
164
- packedArguments
165
- }, sanitizer);
198
+ const leaf = this.constructSimpleLeafData(
199
+ {
200
+ id: id,
201
+ target,
202
+ method,
203
+ packedArguments,
204
+ },
205
+ sanitizer
206
+ );
166
207
  return leaf;
167
208
  });
168
- return { leaves, callConstructor: this.getSwapFromLegacyCall.bind(this) as unknown as GenerateCallFn<DepositParams> };
209
+ return {
210
+ leaves,
211
+ callConstructor: this.getSwapFromLegacyCall.bind(
212
+ this
213
+ ) as unknown as GenerateCallFn<DepositParams>,
214
+ };
169
215
  }
170
-
216
+
171
217
  protected _getSwapFromLegacyLeaf(): {
172
218
  target: ContractAddr;
173
219
  method: string;
@@ -175,14 +221,14 @@ export class ExtendedAdapter extends BaseAdapter<
175
221
  sanitizer: ContractAddr;
176
222
  id: string;
177
223
  }[] {
178
- const usdceToken = Global.getDefaultTokens().find(token => token.symbol === "USDCe");
224
+ const usdceToken = Global.getDefaultTokens().find(
225
+ (token) => token.symbol === "USDCe"
226
+ );
179
227
  return [
180
228
  {
181
229
  target: usdceToken!.address,
182
230
  method: "approve",
183
- packedArguments: [
184
- AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt(),
185
- ],
231
+ packedArguments: [AVNU_EXCHANGE_FOR_LEGACY_USDC.toBigInt()],
186
232
  id: `extendedswaplegacyapprove_${usdceToken!.symbol}`,
187
233
  sanitizer: AVNU_LEGACY_SANITIZER,
188
234
  },
@@ -193,10 +239,9 @@ export class ExtendedAdapter extends BaseAdapter<
193
239
  id: `extended_swap_to_new_${usdceToken!.symbol}`,
194
240
  sanitizer: AVNU_LEGACY_SANITIZER,
195
241
  },
196
- ]
242
+ ];
197
243
  }
198
244
 
199
-
200
245
  protected _getWithdrawLeaf(): {
201
246
  target: ContractAddr;
202
247
  method: string;
@@ -213,12 +258,14 @@ export class ExtendedAdapter extends BaseAdapter<
213
258
  async getDepositCall(params: DepositParams): Promise<ManageCall[]> {
214
259
  try {
215
260
  const usdcToken = this.config.supportedPositions[0].asset;
216
- const usdceToken = Global.getDefaultTokens().find(token => token.symbol === "USDCe");
217
- const salt = Math.floor(
218
- Math.random() * 10 ** usdcToken.decimals
261
+ const usdceToken = Global.getDefaultTokens().find(
262
+ (token) => token.symbol === "USDCe"
219
263
  );
264
+ const salt = Math.floor(Math.random() * 10 ** usdcToken.decimals);
220
265
  // Give approval for more amount than the required amount
221
- const amount = uint256.bnToUint256(params.amount.multipliedBy(10).toWei());
266
+ const amount = uint256.bnToUint256(
267
+ params.amount.multipliedBy(10).toWei()
268
+ );
222
269
  const quotes = await this.config.avnuAdapter.getQuotesAvnu(
223
270
  usdcToken.address.toString(),
224
271
  usdceToken!.address.toString(),
@@ -226,15 +273,17 @@ export class ExtendedAdapter extends BaseAdapter<
226
273
  this.config.avnuAdapter.config.vaultAllocator.address.toString(),
227
274
  usdceToken!.decimals,
228
275
  false
229
- )
276
+ );
230
277
 
231
278
  if (!quotes) {
232
279
  logger.error("error getting quotes from avnu");
233
280
  return [];
234
281
  }
235
- const getCalldata = await this.config.avnuAdapter.getSwapCallData(quotes!);
282
+ const getCalldata = await this.config.avnuAdapter.getSwapCallData(
283
+ quotes!
284
+ );
236
285
  const swapCallData = getCalldata[0];
237
- //change extended sanitizer here
286
+ //change extended sanitizer here
238
287
  return [
239
288
  {
240
289
  sanitizer: AVNU_LEGACY_SANITIZER,
@@ -287,35 +336,44 @@ export class ExtendedAdapter extends BaseAdapter<
287
336
  }
288
337
  }
289
338
 
290
- getProofsForFromLegacySwap<T>(tree: StandardMerkleTree): { proofs: string[][], callConstructor: GenerateCallFn<DepositParams> | GenerateCallFn<WithdrawParams> } {
339
+ getProofsForFromLegacySwap<T>(tree: StandardMerkleTree): {
340
+ proofs: string[][];
341
+ callConstructor:
342
+ | GenerateCallFn<DepositParams>
343
+ | GenerateCallFn<WithdrawParams>;
344
+ } {
291
345
  let proofGroups: string[][] = [];
292
346
 
293
- const ids = this.getSwapFromLegacyLeaf().leaves.map(l => l.readableId)
347
+ const ids = this.getSwapFromLegacyLeaf().leaves.map((l) => l.readableId);
294
348
  // console.log(`${this.name}::getProofs ids: ${ids}`);
295
349
  for (const [i, v] of tree.entries()) {
296
- // console.log(`${this.name}::getProofs v: ${v.readableId}`);
350
+ // console.log(`${this.name}::getProofs v: ${v.readableId}`);
297
351
  if (ids.includes(v.readableId)) {
298
- //console.log(`${this.name}::getProofs found id: ${v.readableId}`);
352
+ //console.log(`${this.name}::getProofs found id: ${v.readableId}`);
299
353
  proofGroups.push(tree.getProof(i));
300
354
  }
301
355
  }
302
356
  if (proofGroups.length != ids.length) {
303
- throw new Error(`Not all proofs found for IDs: ${ids.join(', ')}`);
357
+ throw new Error(`Not all proofs found for IDs: ${ids.join(", ")}`);
304
358
  }
305
359
 
306
360
  // find leaf adapter
307
361
  return {
308
362
  proofs: proofGroups,
309
- callConstructor: this.getSwapFromLegacyCall.bind(this)
310
- };
311
- }
363
+ callConstructor: this.getSwapFromLegacyCall.bind(this),
364
+ };
365
+ }
312
366
 
313
367
  async getSwapFromLegacyCall(params: DepositParams): Promise<ManageCall[]> {
314
368
  try {
315
369
  const usdcToken = this.config.supportedPositions[0].asset;
316
- const usdceToken = Global.getDefaultTokens().find(token => token.symbol === "USDCe");
370
+ const usdceToken = Global.getDefaultTokens().find(
371
+ (token) => token.symbol === "USDCe"
372
+ );
317
373
  // Give approval for more amount than the required amount
318
- const amount = uint256.bnToUint256(params.amount.multipliedBy(10).toWei());
374
+ const amount = uint256.bnToUint256(
375
+ params.amount.multipliedBy(10).toWei()
376
+ );
319
377
  const quotes = await this.config.avnuAdapter.getQuotesAvnu(
320
378
  usdceToken!.address.toString(),
321
379
  usdcToken!.address.toString(),
@@ -323,15 +381,17 @@ export class ExtendedAdapter extends BaseAdapter<
323
381
  this.config.avnuAdapter.config.vaultAllocator.address.toString(),
324
382
  usdcToken!.decimals,
325
383
  false
326
- )
384
+ );
327
385
 
328
386
  if (!quotes) {
329
387
  logger.error("error getting quotes from avnu");
330
388
  return [];
331
389
  }
332
- const getCalldata = await this.config.avnuAdapter.getSwapCallData(quotes!);
390
+ const getCalldata = await this.config.avnuAdapter.getSwapCallData(
391
+ quotes!
392
+ );
333
393
  const swapCallData = getCalldata[0];
334
- //change extended sanitizer here
394
+ //change extended sanitizer here
335
395
  return [
336
396
  {
337
397
  sanitizer: AVNU_LEGACY_SANITIZER,
@@ -352,7 +412,7 @@ export class ExtendedAdapter extends BaseAdapter<
352
412
  selector: hash.getSelectorFromName("swap_to_new"),
353
413
  calldata: swapCallData,
354
414
  },
355
- }
415
+ },
356
416
  ];
357
417
  } catch (error) {
358
418
  logger.error(`Error creating Deposit Call: ${error}`);
@@ -372,20 +432,105 @@ export class ExtendedAdapter extends BaseAdapter<
372
432
  }
373
433
  }
374
434
 
375
- async withdrawFromExtended(amount: Web3Number): Promise<boolean> {
435
+ async withdrawFromExtended(amount: Web3Number): Promise<{
436
+ status: boolean;
437
+ receivedTxnHash: boolean;
438
+ }> {
376
439
  try {
377
440
  if (!this.client) {
378
- throw new Error("Client not initialized");
441
+ logger.error("Client not initialized");
442
+ return {
443
+ status: false,
444
+ receivedTxnHash: false,
445
+ }
446
+ }
447
+ if (amount.lessThanOrEqualTo(0)) {
448
+ logger.error(
449
+ `Invalid withdrawal amount: ${amount.toNumber()}. Amount must be positive.`
450
+ );
451
+ return {
452
+ status: false,
453
+ receivedTxnHash: false,
454
+ }
455
+ }
456
+ if (amount.lessThanOrEqualTo(this.minimumExtendedMovementAmount)) {
457
+ logger.warn(
458
+ `Withdrawal amount ${amount.toNumber()} is below minimum Extended movement amount ${this.minimumExtendedMovementAmount}. Skipping withdrawal.`
459
+ );
460
+ return {
461
+ status: false,
462
+ receivedTxnHash: false,
463
+ }
464
+ }
465
+ const holdings = await this.getExtendedDepositAmount();
466
+ if (!holdings) {
467
+ logger.error(
468
+ "Cannot get holdings - unable to validate withdrawal amount"
469
+ );
470
+ return {
471
+ status: false,
472
+ receivedTxnHash: false,
473
+ }
379
474
  }
380
- const withdrawalRequest = await this.client.withdrawUSDC(amount.toFixed(2));
475
+
476
+ const availableForWithdrawal = parseFloat(
477
+ holdings.availableForWithdrawal
478
+ );
479
+ if (
480
+ !Number.isFinite(availableForWithdrawal) ||
481
+ availableForWithdrawal < 0
482
+ ) {
483
+ logger.error(
484
+ `Invalid availableForWithdrawal: ${holdings.availableForWithdrawal}. Expected a finite, non-negative number.`
485
+ );
486
+ return {
487
+ status: false,
488
+ receivedTxnHash: false,
489
+ }
490
+ }
491
+
492
+ const withdrawalAmount = amount.toNumber();
493
+ if (withdrawalAmount > availableForWithdrawal) {
494
+ logger.error(
495
+ `Withdrawal amount ${withdrawalAmount} exceeds available balance ${availableForWithdrawal}`
496
+ );
497
+ return {
498
+ status: false,
499
+ receivedTxnHash: false,
500
+ }
501
+ }
502
+
503
+ logger.info(
504
+ `Withdrawing ${withdrawalAmount} from Extended. Available balance: ${availableForWithdrawal}`
505
+ );
506
+
507
+ const withdrawalRequest = await this.client.withdrawUSDC(
508
+ amount.toFixed(2)
509
+ );
510
+
381
511
  if (withdrawalRequest.status === "OK") {
382
- const withdrawalStatus = await this.getDepositOrWithdrawalStatus(withdrawalRequest.data, AssetOperationType.WITHDRAWAL);
383
- return withdrawalStatus;
512
+ const withdrawalStatus = await this.getDepositOrWithdrawalStatus(
513
+ withdrawalRequest.data,
514
+ AssetOperationType.WITHDRAWAL
515
+ );
516
+ return {
517
+ status: true,
518
+ receivedTxnHash: withdrawalStatus,
519
+ }
520
+ }
521
+ logger.error(
522
+ `Withdrawal request failed with status: ${withdrawalRequest.status}`
523
+ );
524
+ return {
525
+ status: false,
526
+ receivedTxnHash: false,
384
527
  }
385
- return false;
386
528
  } catch (error) {
387
529
  logger.error(`Error creating Withdraw Call: ${error}`);
388
- return false;
530
+ return {
531
+ status: false,
532
+ receivedTxnHash: false,
533
+ }
389
534
  }
390
535
  }
391
536
 
@@ -394,21 +539,42 @@ export class ExtendedAdapter extends BaseAdapter<
394
539
  }
395
540
 
396
541
  async getExtendedDepositAmount(): Promise<Balance | undefined> {
397
- if (this.client === null) {
398
- logger.error("error initializing client");
399
- return undefined;
400
- }
401
- const result = await this.client.getHoldings();
402
- if (!result) {
403
- logger.error(`error getting holdings: ${result}`);
404
- return undefined;
405
- }
406
- const holdings = result.data;
407
- if (!holdings) {
408
- logger.error(`error getting holdings: ${holdings}`);
542
+ try {
543
+ const result = await axios.get<ApiResponse<Balance>>(`${this.config.extendedBackendReadUrl}/holdings`);
544
+ if (!result.data) {
545
+ logger.error("error getting holdings - API returned null/undefined");
546
+ return undefined;
547
+ }
548
+
549
+ if (!result.data.success) {
550
+ logger.error(
551
+ `error getting holdings - API returned status: ${result.status}`
552
+ );
553
+ return undefined;
554
+ }
555
+
556
+ const holdings = result.data.data;
557
+ if (!holdings) {
558
+ logger.warn(
559
+ "holdings data is null/undefined - treating as zero balance"
560
+ );
561
+ return {
562
+ collateral_name: "",
563
+ balance: "0",
564
+ equity: "0",
565
+ availableForTrade: "0",
566
+ availableForWithdrawal: "0",
567
+ unrealisedPnl: "0",
568
+ initialMargin: "0",
569
+ marginRatio: "0",
570
+ updatedTime: Date.now(),
571
+ };
572
+ }
573
+ return holdings;
574
+ } catch (error) {
575
+ logger.error(`error getting holdings - exception: ${error}`);
409
576
  return undefined;
410
577
  }
411
- return holdings;
412
578
  }
413
579
 
414
580
  async setLeverage(leverage: string, marketName: string): Promise<boolean> {
@@ -427,54 +593,62 @@ export class ExtendedAdapter extends BaseAdapter<
427
593
  }
428
594
 
429
595
  async getAllOpenPositions(): Promise<Position[] | null> {
430
- if (this.client === null) {
431
- logger.error("error initializing client");
432
- return null;
433
- }
434
- const response = await this.client.getPositionsForMarket(
435
- this.config.extendedMarketName
436
- );
437
- if (response.status === "OK") {
438
- if (response.data.length === 0) {
439
- return [];
440
- } else {
441
- return response.data;
596
+ try {
597
+ const response = await axios.get<ApiResponse<Position[]>>(`${this.config.extendedBackendReadUrl}/positions`);
598
+ if (response.data.success) {
599
+ if (response.data.data.length === 0) {
600
+ return [];
601
+ } else {
602
+ return response.data.data;
603
+ }
442
604
  }
605
+ return null;
606
+ } catch (err) {
607
+ logger.error("error getting all open positions", err);
608
+ return null;
443
609
  }
444
- return null;
445
610
  }
446
611
 
447
612
  async getOrderHistory(marketName: string): Promise<OpenOrder[] | null> {
448
- if (this.client === null) {
449
- logger.error("error initializing client");
613
+ try {
614
+ const result = await axios.get<ApiResponse<OpenOrder[]>>(`${this.config.extendedBackendReadUrl}/marketOrders/${marketName}`);
615
+ if (!result.data.success) {
616
+ return null;
617
+ }
618
+ return result.data.data;
619
+ } catch (err) {
620
+ logger.error("error getting order history", err);
450
621
  return null;
451
622
  }
452
- const result = await this.client.getOrderHistory(marketName);
453
- return result.data;
454
623
  }
455
624
 
456
625
  async getOrderStatus(
457
626
  orderId: string,
458
627
  marketName: string
459
628
  ): Promise<OpenOrder | null> {
460
- if (this.client === null) {
461
- logger.error("error initializing client");
462
- return null;
463
- }
464
- const orderhistory = await this.getOrderHistory(marketName);
629
+ try {
630
+ if (this.client === null) {
631
+ logger.error("error initializing client");
632
+ return null;
633
+ }
634
+ const orderhistory = await this.getOrderHistory(marketName);
465
635
 
466
- if (!orderhistory || orderhistory.length === 0) {
467
- logger.error(`error getting order: ${orderId}`);
468
- return null;
469
- }
470
- const order = orderhistory
471
- .slice(0, 3)
472
- .find((order) => order.id.toString() === orderId);
473
- if (!order) {
474
- logger.error(`error getting order: ${order}`);
636
+ if (!orderhistory || orderhistory.length === 0) {
637
+ return null;
638
+ }
639
+ const order = orderhistory
640
+ .slice(0, 20)
641
+ .find((order) => order.id.toString() === orderId);
642
+
643
+ if (order) {
644
+ return order;
645
+ }
646
+
647
+ return null; // Order not found
648
+ } catch (error) {
649
+ logger.error(`error getting order status: ${error}`);
475
650
  return null;
476
651
  }
477
- return order;
478
652
  }
479
653
 
480
654
  async fetchOrderBookBTCUSDC(): Promise<{
@@ -536,16 +710,59 @@ export class ExtendedAdapter extends BaseAdapter<
536
710
  logger.error("error depositing or setting leverage");
537
711
  return null;
538
712
  }
539
- const positions = await this.getAllOpenPositions();
540
- if (positions === null) {
713
+ const { ask, bid } = await this.fetchOrderBookBTCUSDC();
714
+ if (
715
+ !ask ||
716
+ !bid ||
717
+ ask.lessThanOrEqualTo(0) ||
718
+ bid.lessThanOrEqualTo(0)
719
+ ) {
720
+ logger.error(
721
+ `Invalid orderbook prices: ask=${ask?.toNumber()}, bid=${bid?.toNumber()}`
722
+ );
541
723
  return null;
542
724
  }
543
- const { ask, bid } = await this.fetchOrderBookBTCUSDC();
725
+
544
726
  const spread = ask.minus(bid);
545
- let price = ask
546
- .plus(bid)
547
- .div(2)
548
- side === OrderSide.SELL ? price = price.minus(spread.times(0.2 * attempt)) : price = price.plus(spread.times(0.2 * attempt));
727
+ const midPrice = ask.plus(bid).div(2);
728
+ /** Maximum deviation: 50% of spread (to prevent extremely unfavorable prices) */
729
+ const MAX_PRICE_DEVIATION_MULTIPLIER = 0.5;
730
+ const priceAdjustmentMultiplier = Math.min(
731
+ 0.2 * attempt,
732
+ MAX_PRICE_DEVIATION_MULTIPLIER
733
+ );
734
+ const priceAdjustment = spread.times(priceAdjustmentMultiplier);
735
+
736
+ let price = midPrice;
737
+ if (side === OrderSide.SELL) {
738
+ price = midPrice.minus(priceAdjustment);
739
+ } else {
740
+ price = midPrice.plus(priceAdjustment);
741
+ }
742
+
743
+ /** Validate price is still reasonable (within 50% of mid price) */
744
+ const maxDeviation = midPrice.times(0.5);
745
+ if (price.minus(midPrice).abs().greaterThan(maxDeviation)) {
746
+ logger.error(
747
+ `Price deviation too large on attempt ${attempt}: price=${price.toNumber()}, midPrice=${midPrice.toNumber()}, deviation=${price
748
+ .minus(midPrice)
749
+ .abs()
750
+ .toNumber()}`
751
+ );
752
+ if (attempt >= maxAttempts) {
753
+ return null;
754
+ }
755
+ price =
756
+ side === OrderSide.SELL
757
+ ? midPrice.minus(maxDeviation)
758
+ : midPrice.plus(maxDeviation);
759
+ }
760
+
761
+ logger.info(
762
+ `createOrder attempt ${attempt}/${maxAttempts}: side=${side}, midPrice=${midPrice.toNumber()}, adjustedPrice=${price.toNumber()}, adjustment=${priceAdjustmentMultiplier * 100
763
+ }%`
764
+ );
765
+
549
766
  const amount_in_token = (btcAmount * parseInt(leverage)).toFixed(
550
767
  this.config.extendedPrecision
551
768
  ); // gives the amount of wbtc
@@ -557,14 +774,66 @@ export class ExtendedAdapter extends BaseAdapter<
557
774
  price.toFixed(0),
558
775
  side
559
776
  );
560
- if (!result) {
777
+ if (!result || !result.position_id) {
778
+ logger.error("Failed to create order - no position_id returned");
561
779
  return null;
562
780
  }
563
- await new Promise((resolve) => setTimeout(resolve, 5000));
564
- const openOrder = await this.getOrderStatus(result.position_id, this.config.extendedMarketName);
565
- if (!openOrder || openOrder.status !== OrderStatus.FILLED) {
781
+
782
+ const positionId = result.position_id;
783
+ logger.info(
784
+ `Order created with position_id: ${positionId}. Waiting for API to update...`
785
+ );
786
+
787
+ let openOrder = await this.getOrderStatus(
788
+ positionId,
789
+ this.config.extendedMarketName
790
+ );
791
+ const maxStatusRetries = 3;
792
+ const statusRetryDelay = 5000;
793
+
794
+ if (!openOrder) {
795
+ logger.warn(
796
+ `Order ${positionId} not found in API yet. Retrying status fetch (max ${maxStatusRetries} times)...`
797
+ );
798
+ for (
799
+ let statusRetry = 1;
800
+ statusRetry <= maxStatusRetries;
801
+ statusRetry++
802
+ ) {
803
+ await new Promise((resolve) => setTimeout(resolve, statusRetryDelay));
804
+ openOrder = await this.getOrderStatus(
805
+ positionId,
806
+ this.config.extendedMarketName
807
+ );
808
+ if (openOrder) {
809
+ logger.info(
810
+ `Order ${positionId} found after ${statusRetry} status retry(ies)`
811
+ );
812
+ break;
813
+ }
814
+ logger.warn(
815
+ `Order ${positionId} still not found after ${statusRetry}/${maxStatusRetries} status retries`
816
+ );
817
+ }
818
+ }
819
+
820
+ if (openOrder && openOrder.status === OrderStatus.FILLED) {
821
+ logger.info(
822
+ `Order ${positionId} successfully filled with quantity ${openOrder.qty}`
823
+ );
824
+ return {
825
+ position_id: positionId,
826
+ btc_exposure: openOrder.qty,
827
+ };
828
+ } else if (openOrder && openOrder.status !== OrderStatus.FILLED) {
829
+ // Order found but NOT FILLED - retry (recreate)
830
+ logger.warn(
831
+ `Order ${positionId} found but status is ${openOrder.status}, not FILLED. Retrying order creation...`
832
+ );
566
833
  if (attempt >= maxAttempts) {
567
- logger.error("Max retries reached — could not verify open position");
834
+ logger.error(
835
+ `Max retries reached — order ${positionId} status is ${openOrder.status}, not FILLED`
836
+ );
568
837
  return null;
569
838
  } else {
570
839
  const backoff = 2000 * attempt;
@@ -578,13 +847,18 @@ export class ExtendedAdapter extends BaseAdapter<
578
847
  );
579
848
  }
580
849
  } else {
850
+ logger.warn(
851
+ `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.`
852
+ );
581
853
  return {
582
- position_id: result.position_id,
583
- btc_exposure: openOrder.qty,
854
+ position_id: positionId,
855
+ btc_exposure: amount_in_token,
584
856
  };
585
857
  }
586
858
  } catch (err: any) {
587
- logger.error(`createShortOrder failed on attempt ${attempt}: ${err.message}`);
859
+ logger.error(
860
+ `createShortOrder failed on attempt ${attempt}: ${err.message}`
861
+ );
588
862
 
589
863
  if (attempt < maxAttempts) {
590
864
  const backoff = 1200 * attempt;
@@ -614,13 +888,13 @@ export class ExtendedAdapter extends BaseAdapter<
614
888
  const result =
615
889
  side === OrderSide.SELL
616
890
  ? await client.createSellOrder(marketName, amount, price, {
617
- postOnly: false,
618
- timeInForce: TimeInForce.IOC,
619
- })
891
+ postOnly: false,
892
+ timeInForce: TimeInForce.IOC,
893
+ })
620
894
  : await client.createBuyOrder(marketName, amount, price, {
621
- postOnly: false,
622
- timeInForce: TimeInForce.IOC,
623
- });
895
+ postOnly: false,
896
+ timeInForce: TimeInForce.IOC,
897
+ });
624
898
  if (result.data.id) {
625
899
  const position_id = result.data.id.toString();
626
900
  return {
@@ -634,28 +908,108 @@ export class ExtendedAdapter extends BaseAdapter<
634
908
  }
635
909
  }
636
910
 
637
- async getDepositOrWithdrawalStatus(orderId: number | string, operationsType: AssetOperationType): Promise<boolean> {
638
- try{
639
- let transferHistory = await this.client.getAssetOperations({
640
- operationsType:[operationsType],
641
- operationsStatus:[AssetOperationStatus.COMPLETED]
642
- })
643
- if(operationsType === AssetOperationType.DEPOSIT){
644
- const myTransferStatus = transferHistory.data.find(operation => operation.transactionHash === orderId);
645
- if (!myTransferStatus) {
646
- return true;
911
+ async getDepositOrWithdrawalStatus(
912
+ orderId: number | string, // for deposits, send txn hash as string
913
+ operationsType: AssetOperationType
914
+ ): Promise<boolean> {
915
+ const maxAttempts = 15;
916
+ const retryDelayMs = 30000; // 60 seconds
917
+
918
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
919
+ try {
920
+ let transferHistory = await this.client.getAssetOperations({
921
+ operationsType: [operationsType],
922
+ operationsStatus: [AssetOperationStatus.COMPLETED],
923
+ });
924
+ if (operationsType === AssetOperationType.DEPOSIT) {
925
+ const myTransferStatus = transferHistory.data.find(
926
+ (operation) => operation.transactionHash?.toLowerCase() === orderId.toString().toLowerCase()
927
+ );
928
+ if (!myTransferStatus) {
929
+ if (attempt < maxAttempts) {
930
+ logger.info(
931
+ `Deposit operation not found for transactionHash ${orderId}, retrying (attempt ${attempt}/${maxAttempts})...`
932
+ );
933
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
934
+ continue;
935
+ }
936
+ logger.warn(
937
+ `Deposit operation not found for transactionHash ${orderId} after ${maxAttempts} attempts`
938
+ );
939
+ return false;
940
+ }
941
+ // Check if status is COMPLETED
942
+ if (myTransferStatus.status === AssetOperationStatus.COMPLETED) {
943
+ logger.info(
944
+ `Deposit operation ${orderId} completed successfully`
945
+ );
946
+ return true;
947
+ } else {
948
+ if (attempt < maxAttempts) {
949
+ logger.info(
950
+ `Deposit operation ${orderId} found but status is ${myTransferStatus.status}, not COMPLETED. Retrying (attempt ${attempt}/${maxAttempts})...`
951
+ );
952
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
953
+ continue;
954
+ }
955
+ logger.warn(
956
+ `Deposit operation ${orderId} status is ${myTransferStatus.status} after ${maxAttempts} attempts, expected COMPLETED`
957
+ );
958
+ return false;
959
+ }
960
+ } else {
961
+ const myTransferStatus = transferHistory.data.find(
962
+ (operation) => operation.id === orderId.toString()
963
+ );
964
+ if (!myTransferStatus) {
965
+ if (attempt < maxAttempts) {
966
+ logger.info(
967
+ `Withdrawal status not found for orderId ${orderId} in completed operations, retrying (attempt ${attempt}/${maxAttempts})...`
968
+ );
969
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
970
+ continue;
971
+ }
972
+ logger.warn(
973
+ `Withdrawal operation not found for orderId ${orderId} after ${maxAttempts} attempts`
974
+ );
975
+ return false;
976
+ }
977
+ // Check if status is COMPLETED
978
+ if (myTransferStatus.status === AssetOperationStatus.COMPLETED) {
979
+ logger.info(
980
+ `Withdrawal operation ${orderId} completed successfully`
981
+ );
982
+ return true;
983
+ } else {
984
+ if (attempt < maxAttempts) {
985
+ logger.info(
986
+ `Withdrawal operation ${orderId} found but status is ${myTransferStatus.status}, not COMPLETED. Retrying (attempt ${attempt}/${maxAttempts})...`
987
+ );
988
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
989
+ continue;
990
+ }
991
+ logger.warn(
992
+ `Withdrawal operation ${orderId} status is ${myTransferStatus.status} after ${maxAttempts} attempts, expected COMPLETED`
993
+ );
994
+ return false;
995
+ }
647
996
  }
648
- return true;
649
- }else{
650
- const myTransferStatus = transferHistory.data.find(operation => operation.id.toString() === orderId.toString());
651
- if (!myTransferStatus) {
652
- return true;
997
+ } catch (err) {
998
+ logger.error(
999
+ `error getting deposit or withdrawal status (attempt ${attempt}/${maxAttempts}): ${err}`
1000
+ );
1001
+ if (attempt < maxAttempts) {
1002
+ logger.info(`Retrying after ${retryDelayMs}ms...`);
1003
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
1004
+ continue;
653
1005
  }
654
- return true;
1006
+ logger.error(
1007
+ `Max retry attempts reached for getDepositOrWithdrawalStatus`
1008
+ );
1009
+ return false;
655
1010
  }
656
- }catch(err){
657
- logger.error(`error getting deposit or withdrawal status: ${err}`);
658
- return false;
659
1011
  }
1012
+
1013
+ return false;
660
1014
  }
661
1015
  }