@strkfarm/sdk 2.0.0-dev.7 → 2.0.0-dev.8

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.
@@ -4,7 +4,7 @@ import {
4
4
  WithdrawParams,
5
5
  BaseAdapterConfig,
6
6
  } from "./baseAdapter";
7
- import { SIMPLE_SANITIZER_V2, toBigInt } from "./adapter-utils";
7
+ import { toBigInt } from "./adapter-utils";
8
8
  import { Protocols } from "@/interfaces";
9
9
  import { MAX_DELAY } from "../vesu-extended-strategy/utils/constants";
10
10
  import { SupportedPosition } from "./baseAdapter";
@@ -13,14 +13,13 @@ import { Web3Number } from "@/dataTypes";
13
13
  import { PositionInfo } from "./baseAdapter";
14
14
  import { ManageCall } from "./baseAdapter";
15
15
  import { ContractAddr } from "@/dataTypes";
16
- import { TokenInfo } from "@/interfaces";
17
16
  import { AVNU_EXCHANGE } from "./adapter-utils";
18
17
  import { MAX_RETRIES } from "../vesu-extended-strategy/utils/constants";
19
18
  import { Quote } from "@avnu/avnu-sdk";
20
19
  import { hash, uint256 } from "starknet";
21
20
  import { AvnuWrapper } from "@/modules/avnu";
22
21
  import axios from "axios";
23
- import { AVNU_MIDDLEWARE, SIMPLE_SANITIZER } from "./adapter-utils";
22
+ import {SIMPLE_SANITIZER } from "./adapter-utils";
24
23
  import { returnFormattedAmount } from "../vesu-extended-strategy/utils/helper";
25
24
  import { logger } from "@/utils";
26
25
  export interface AvnuAdapterConfig extends BaseAdapterConfig {
@@ -67,9 +67,11 @@ export class ExtendedAdapter extends BaseAdapter<
67
67
  timeout: this.config.extendedTimeout,
68
68
  retries: this.config.extendedRetries,
69
69
  });
70
- this.minimumExtendedMovementAmount = this.config.minimumExtendedMovementAmount ?? 5; //5 usdc
70
+ this.minimumExtendedMovementAmount =
71
+ this.config.minimumExtendedMovementAmount ?? 5; //5 usdc
71
72
  this.client = client;
72
- this.retryDelayForOrderStatus = this.config.retryDelayForOrderStatus ?? 3000;
73
+ this.retryDelayForOrderStatus =
74
+ this.config.retryDelayForOrderStatus ?? 3000;
73
75
  }
74
76
  //abstract means the method has no implementation in this class; instead, child classes must implement it.
75
77
  protected async getAPY(
@@ -416,11 +418,58 @@ export class ExtendedAdapter extends BaseAdapter<
416
418
  async withdrawFromExtended(amount: Web3Number): Promise<boolean> {
417
419
  try {
418
420
  if (!this.client) {
419
- throw new Error("Client not initialized");
421
+ logger.error("Client not initialized");
422
+ return false;
423
+ }
424
+ if (amount.lessThanOrEqualTo(0)) {
425
+ logger.error(
426
+ `Invalid withdrawal amount: ${amount.toNumber()}. Amount must be positive.`
427
+ );
428
+ return false;
429
+ }
430
+ if (amount.lessThanOrEqualTo(this.minimumExtendedMovementAmount)) {
431
+ logger.warn(
432
+ `Withdrawal amount ${amount.toNumber()} is below minimum Extended movement amount ${this.minimumExtendedMovementAmount}. Skipping withdrawal.`
433
+ );
434
+ return false;
435
+ }
436
+ const holdings = await this.getExtendedDepositAmount();
437
+ if (!holdings) {
438
+ logger.error(
439
+ "Cannot get holdings - unable to validate withdrawal amount"
440
+ );
441
+ return false;
442
+ }
443
+
444
+ const availableForWithdrawal = parseFloat(
445
+ holdings.availableForWithdrawal
446
+ );
447
+ if (
448
+ !Number.isFinite(availableForWithdrawal) ||
449
+ availableForWithdrawal < 0
450
+ ) {
451
+ logger.error(
452
+ `Invalid availableForWithdrawal: ${holdings.availableForWithdrawal}. Expected a finite, non-negative number.`
453
+ );
454
+ return false;
420
455
  }
456
+
457
+ const withdrawalAmount = amount.toNumber();
458
+ if (withdrawalAmount > availableForWithdrawal) {
459
+ logger.error(
460
+ `Withdrawal amount ${withdrawalAmount} exceeds available balance ${availableForWithdrawal}`
461
+ );
462
+ return false;
463
+ }
464
+
465
+ logger.info(
466
+ `Withdrawing ${withdrawalAmount} from Extended. Available balance: ${availableForWithdrawal}`
467
+ );
468
+
421
469
  const withdrawalRequest = await this.client.withdrawUSDC(
422
470
  amount.toFixed(2)
423
471
  );
472
+
424
473
  if (withdrawalRequest.status === "OK") {
425
474
  const withdrawalStatus = await this.getDepositOrWithdrawalStatus(
426
475
  withdrawalRequest.data,
@@ -428,6 +477,10 @@ export class ExtendedAdapter extends BaseAdapter<
428
477
  );
429
478
  return withdrawalStatus;
430
479
  }
480
+
481
+ logger.error(
482
+ `Withdrawal request failed with status: ${withdrawalRequest.status}`
483
+ );
431
484
  return false;
432
485
  } catch (error) {
433
486
  logger.error(`Error creating Withdraw Call: ${error}`);
@@ -440,21 +493,47 @@ export class ExtendedAdapter extends BaseAdapter<
440
493
  }
441
494
 
442
495
  async getExtendedDepositAmount(): Promise<Balance | undefined> {
443
- if (this.client === null) {
444
- logger.error("error initializing client");
445
- return undefined;
446
- }
447
- const result = await this.client.getHoldings();
448
- if (!result) {
449
- logger.error(`error getting holdings: ${result}`);
450
- return undefined;
451
- }
452
- const holdings = result.data;
453
- if (!holdings) {
454
- logger.error(`error getting holdings: ${holdings}`);
496
+ try {
497
+ if (this.client === null) {
498
+ logger.error("error initializing client - client is null");
499
+ return undefined; // Error: client not initialized
500
+ }
501
+
502
+ const result = await this.client.getHoldings();
503
+ if (!result) {
504
+ logger.error("error getting holdings - API returned null/undefined");
505
+ return undefined;
506
+ }
507
+
508
+ if (result.status && result.status !== "OK") {
509
+ logger.error(
510
+ `error getting holdings - API returned status: ${result.status}`
511
+ );
512
+ return undefined;
513
+ }
514
+
515
+ const holdings = result.data;
516
+ if (!holdings) {
517
+ logger.warn(
518
+ "holdings data is null/undefined - treating as zero balance"
519
+ );
520
+ return {
521
+ collateral_name: "",
522
+ balance: "0",
523
+ equity: "0",
524
+ availableForTrade: "0",
525
+ availableForWithdrawal: "0",
526
+ unrealisedPnl: "0",
527
+ initialMargin: "0",
528
+ marginRatio: "0",
529
+ updatedTime: Date.now(),
530
+ };
531
+ }
532
+ return holdings;
533
+ } catch (error) {
534
+ logger.error(`error getting holdings - exception: ${error}`);
455
535
  return undefined;
456
536
  }
457
- return holdings;
458
537
  }
459
538
 
460
539
  async setLeverage(leverage: string, marketName: string): Promise<boolean> {
@@ -503,50 +582,29 @@ export class ExtendedAdapter extends BaseAdapter<
503
582
  orderId: string,
504
583
  marketName: string
505
584
  ): Promise<OpenOrder | null> {
506
- if (this.client === null) {
507
- logger.error("error initializing client");
508
- return null;
509
- }
510
- let orderhistory = await this.getOrderHistory(marketName);
511
-
512
- if (!orderhistory || orderhistory.length === 0) {
513
- logger.error(`error getting order history: ${orderId}`);
514
- } else {
515
- const order = orderhistory
516
- .slice(0, 10)
517
- .find((order) => order.id.toString() === orderId);
518
- if (order) {
519
- return order;
585
+ try {
586
+ if (this.client === null) {
587
+ logger.error("error initializing client");
588
+ return null;
520
589
  }
521
- }
522
-
523
- // Retry logic: 3 more attempts with 3 second delay each
524
- for (let attempt = 1; attempt <= 5; attempt++) {
525
- await new Promise((resolve) => setTimeout(resolve, this.retryDelayForOrderStatus));
526
- orderhistory = await this.getOrderHistory(marketName);
590
+ const orderhistory = await this.getOrderHistory(marketName);
527
591
 
528
592
  if (!orderhistory || orderhistory.length === 0) {
529
- logger.error(
530
- `error getting order history on retry ${attempt}: ${orderId}`
531
- );
532
- continue;
593
+ return null;
533
594
  }
534
-
535
595
  const order = orderhistory
536
- .slice(0, 5)
596
+ .slice(0, 20)
537
597
  .find((order) => order.id.toString() === orderId);
538
598
 
539
- if (order && order.status === OrderStatus.FILLED) {
599
+ if (order) {
540
600
  return order;
541
601
  }
542
602
 
543
- logger.error(
544
- `order not found in top 15 entries on retry ${attempt}: ${orderId}`
545
- );
603
+ return null; // Order not found
604
+ } catch (error) {
605
+ logger.error(`error getting order status: ${error}`);
606
+ return null;
546
607
  }
547
-
548
- logger.error(`error getting order after all retries: ${orderId}`);
549
- return null;
550
608
  }
551
609
 
552
610
  async fetchOrderBookBTCUSDC(): Promise<{
@@ -608,16 +666,60 @@ export class ExtendedAdapter extends BaseAdapter<
608
666
  logger.error("error depositing or setting leverage");
609
667
  return null;
610
668
  }
611
- const positions = await this.getAllOpenPositions();
612
- if (positions === null) {
669
+ const { ask, bid } = await this.fetchOrderBookBTCUSDC();
670
+ if (
671
+ !ask ||
672
+ !bid ||
673
+ ask.lessThanOrEqualTo(0) ||
674
+ bid.lessThanOrEqualTo(0)
675
+ ) {
676
+ logger.error(
677
+ `Invalid orderbook prices: ask=${ask?.toNumber()}, bid=${bid?.toNumber()}`
678
+ );
613
679
  return null;
614
680
  }
615
- const { ask, bid } = await this.fetchOrderBookBTCUSDC();
681
+
616
682
  const spread = ask.minus(bid);
617
- let price = ask.plus(bid).div(2);
618
- side === OrderSide.SELL
619
- ? (price = price.minus(spread.times(0.2 * attempt)))
620
- : (price = price.plus(spread.times(0.2 * attempt)));
683
+ const midPrice = ask.plus(bid).div(2);
684
+ /** Maximum deviation: 50% of spread (to prevent extremely unfavorable prices) */
685
+ const MAX_PRICE_DEVIATION_MULTIPLIER = 0.5;
686
+ const priceAdjustmentMultiplier = Math.min(
687
+ 0.2 * attempt,
688
+ MAX_PRICE_DEVIATION_MULTIPLIER
689
+ );
690
+ const priceAdjustment = spread.times(priceAdjustmentMultiplier);
691
+
692
+ let price = midPrice;
693
+ if (side === OrderSide.SELL) {
694
+ price = midPrice.minus(priceAdjustment);
695
+ } else {
696
+ price = midPrice.plus(priceAdjustment);
697
+ }
698
+
699
+ /** Validate price is still reasonable (within 50% of mid price) */
700
+ const maxDeviation = midPrice.times(0.5);
701
+ if (price.minus(midPrice).abs().greaterThan(maxDeviation)) {
702
+ logger.error(
703
+ `Price deviation too large on attempt ${attempt}: price=${price.toNumber()}, midPrice=${midPrice.toNumber()}, deviation=${price
704
+ .minus(midPrice)
705
+ .abs()
706
+ .toNumber()}`
707
+ );
708
+ if (attempt >= maxAttempts) {
709
+ return null;
710
+ }
711
+ price =
712
+ side === OrderSide.SELL
713
+ ? midPrice.minus(maxDeviation)
714
+ : midPrice.plus(maxDeviation);
715
+ }
716
+
717
+ logger.info(
718
+ `createOrder attempt ${attempt}/${maxAttempts}: side=${side}, midPrice=${midPrice.toNumber()}, adjustedPrice=${price.toNumber()}, adjustment=${
719
+ priceAdjustmentMultiplier * 100
720
+ }%`
721
+ );
722
+
621
723
  const amount_in_token = (btcAmount * parseInt(leverage)).toFixed(
622
724
  this.config.extendedPrecision
623
725
  ); // gives the amount of wbtc
@@ -629,17 +731,66 @@ export class ExtendedAdapter extends BaseAdapter<
629
731
  price.toFixed(0),
630
732
  side
631
733
  );
632
- if (!result) {
734
+ if (!result || !result.position_id) {
735
+ logger.error("Failed to create order - no position_id returned");
633
736
  return null;
634
737
  }
635
- await new Promise((resolve) => setTimeout(resolve, 5000));
636
- const openOrder = await this.getOrderStatus(
637
- result.position_id,
738
+
739
+ const positionId = result.position_id;
740
+ logger.info(
741
+ `Order created with position_id: ${positionId}. Waiting for API to update...`
742
+ );
743
+
744
+ let openOrder = await this.getOrderStatus(
745
+ positionId,
638
746
  this.config.extendedMarketName
639
747
  );
748
+ const maxStatusRetries = 3;
749
+ const statusRetryDelay = 5000;
750
+
640
751
  if (!openOrder) {
752
+ logger.warn(
753
+ `Order ${positionId} not found in API yet. Retrying status fetch (max ${maxStatusRetries} times)...`
754
+ );
755
+ for (
756
+ let statusRetry = 1;
757
+ statusRetry <= maxStatusRetries;
758
+ statusRetry++
759
+ ) {
760
+ await new Promise((resolve) => setTimeout(resolve, statusRetryDelay));
761
+ openOrder = await this.getOrderStatus(
762
+ positionId,
763
+ this.config.extendedMarketName
764
+ );
765
+ if (openOrder) {
766
+ logger.info(
767
+ `Order ${positionId} found after ${statusRetry} status retry(ies)`
768
+ );
769
+ break;
770
+ }
771
+ logger.warn(
772
+ `Order ${positionId} still not found after ${statusRetry}/${maxStatusRetries} status retries`
773
+ );
774
+ }
775
+ }
776
+
777
+ if (openOrder && openOrder.status === OrderStatus.FILLED) {
778
+ logger.info(
779
+ `Order ${positionId} successfully filled with quantity ${openOrder.qty}`
780
+ );
781
+ return {
782
+ position_id: positionId,
783
+ btc_exposure: openOrder.qty,
784
+ };
785
+ } else if (openOrder && openOrder.status !== OrderStatus.FILLED) {
786
+ // Order found but NOT FILLED - retry (recreate)
787
+ logger.warn(
788
+ `Order ${positionId} found but status is ${openOrder.status}, not FILLED. Retrying order creation...`
789
+ );
641
790
  if (attempt >= maxAttempts) {
642
- logger.error("Max retries reached — could not verify open position");
791
+ logger.error(
792
+ `Max retries reached — order ${positionId} status is ${openOrder.status}, not FILLED`
793
+ );
643
794
  return null;
644
795
  } else {
645
796
  const backoff = 2000 * attempt;
@@ -653,9 +804,12 @@ export class ExtendedAdapter extends BaseAdapter<
653
804
  );
654
805
  }
655
806
  } else {
807
+ logger.warn(
808
+ `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.`
809
+ );
656
810
  return {
657
- position_id: result.position_id,
658
- btc_exposure: openOrder.qty,
811
+ position_id: positionId,
812
+ btc_exposure: amount_in_token,
659
813
  };
660
814
  }
661
815
  } catch (err: any) {