@strkfarm/sdk 2.0.0-dev.6 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strkfarm/sdk",
3
- "version": "2.0.0-dev.6",
3
+ "version": "2.0.0-dev.8",
4
4
  "description": "STRKFarm TS SDK (Meant for our internal use, but feel free to use it)",
5
5
  "typings": "dist/index.d.ts",
6
6
  "types": "dist/index.d.ts",
@@ -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 {
@@ -45,6 +45,8 @@ export interface ExtendedAdapterConfig extends BaseAdapterConfig {
45
45
  extendedMarketName: string;
46
46
  extendedPrecision: number;
47
47
  avnuAdapter: AvnuAdapter;
48
+ retryDelayForOrderStatus: number;
49
+ minimumExtendedMovementAmount: number;
48
50
  }
49
51
 
50
52
  export class ExtendedAdapter extends BaseAdapter<
@@ -53,6 +55,8 @@ export class ExtendedAdapter extends BaseAdapter<
53
55
  > {
54
56
  readonly config: ExtendedAdapterConfig;
55
57
  readonly client: ExtendedWrapper;
58
+ readonly retryDelayForOrderStatus: number;
59
+ readonly minimumExtendedMovementAmount: number;
56
60
 
57
61
  constructor(config: ExtendedAdapterConfig) {
58
62
  super(config, ExtendedAdapter.name, Protocols.EXTENDED);
@@ -63,7 +67,11 @@ export class ExtendedAdapter extends BaseAdapter<
63
67
  timeout: this.config.extendedTimeout,
64
68
  retries: this.config.extendedRetries,
65
69
  });
70
+ this.minimumExtendedMovementAmount =
71
+ this.config.minimumExtendedMovementAmount ?? 5; //5 usdc
66
72
  this.client = client;
73
+ this.retryDelayForOrderStatus =
74
+ this.config.retryDelayForOrderStatus ?? 3000;
67
75
  }
68
76
  //abstract means the method has no implementation in this class; instead, child classes must implement it.
69
77
  protected async getAPY(
@@ -410,11 +418,58 @@ export class ExtendedAdapter extends BaseAdapter<
410
418
  async withdrawFromExtended(amount: Web3Number): Promise<boolean> {
411
419
  try {
412
420
  if (!this.client) {
413
- 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;
414
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
+
415
469
  const withdrawalRequest = await this.client.withdrawUSDC(
416
470
  amount.toFixed(2)
417
471
  );
472
+
418
473
  if (withdrawalRequest.status === "OK") {
419
474
  const withdrawalStatus = await this.getDepositOrWithdrawalStatus(
420
475
  withdrawalRequest.data,
@@ -422,6 +477,10 @@ export class ExtendedAdapter extends BaseAdapter<
422
477
  );
423
478
  return withdrawalStatus;
424
479
  }
480
+
481
+ logger.error(
482
+ `Withdrawal request failed with status: ${withdrawalRequest.status}`
483
+ );
425
484
  return false;
426
485
  } catch (error) {
427
486
  logger.error(`Error creating Withdraw Call: ${error}`);
@@ -434,21 +493,47 @@ export class ExtendedAdapter extends BaseAdapter<
434
493
  }
435
494
 
436
495
  async getExtendedDepositAmount(): Promise<Balance | undefined> {
437
- if (this.client === null) {
438
- logger.error("error initializing client");
439
- return undefined;
440
- }
441
- const result = await this.client.getHoldings();
442
- if (!result) {
443
- logger.error(`error getting holdings: ${result}`);
444
- return undefined;
445
- }
446
- const holdings = result.data;
447
- if (!holdings) {
448
- 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}`);
449
535
  return undefined;
450
536
  }
451
- return holdings;
452
537
  }
453
538
 
454
539
  async setLeverage(leverage: string, marketName: string): Promise<boolean> {
@@ -497,50 +582,29 @@ export class ExtendedAdapter extends BaseAdapter<
497
582
  orderId: string,
498
583
  marketName: string
499
584
  ): Promise<OpenOrder | null> {
500
- if (this.client === null) {
501
- logger.error("error initializing client");
502
- return null;
503
- }
504
- let orderhistory = await this.getOrderHistory(marketName);
505
-
506
- if (!orderhistory || orderhistory.length === 0) {
507
- logger.error(`error getting order history: ${orderId}`);
508
- } else {
509
- const order = orderhistory
510
- .slice(0, 5)
511
- .find((order) => order.id.toString() === orderId);
512
- if (order) {
513
- return order;
585
+ try {
586
+ if (this.client === null) {
587
+ logger.error("error initializing client");
588
+ return null;
514
589
  }
515
- }
516
-
517
- // Retry logic: 3 more attempts with 3 second delay each
518
- for (let attempt = 1; attempt <= 3; attempt++) {
519
- await new Promise((resolve) => setTimeout(resolve, 3000));
520
- orderhistory = await this.getOrderHistory(marketName);
590
+ const orderhistory = await this.getOrderHistory(marketName);
521
591
 
522
592
  if (!orderhistory || orderhistory.length === 0) {
523
- logger.error(
524
- `error getting order history on retry ${attempt}: ${orderId}`
525
- );
526
- continue;
593
+ return null;
527
594
  }
528
-
529
595
  const order = orderhistory
530
- .slice(0, 5)
596
+ .slice(0, 20)
531
597
  .find((order) => order.id.toString() === orderId);
532
598
 
533
599
  if (order) {
534
600
  return order;
535
601
  }
536
602
 
537
- logger.error(
538
- `order not found in top 5 entries on retry ${attempt}: ${orderId}`
539
- );
603
+ return null; // Order not found
604
+ } catch (error) {
605
+ logger.error(`error getting order status: ${error}`);
606
+ return null;
540
607
  }
541
-
542
- logger.error(`error getting order after all retries: ${orderId}`);
543
- return null;
544
608
  }
545
609
 
546
610
  async fetchOrderBookBTCUSDC(): Promise<{
@@ -602,16 +666,60 @@ export class ExtendedAdapter extends BaseAdapter<
602
666
  logger.error("error depositing or setting leverage");
603
667
  return null;
604
668
  }
605
- const positions = await this.getAllOpenPositions();
606
- 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
+ );
607
679
  return null;
608
680
  }
609
- const { ask, bid } = await this.fetchOrderBookBTCUSDC();
681
+
610
682
  const spread = ask.minus(bid);
611
- let price = ask.plus(bid).div(2);
612
- side === OrderSide.SELL
613
- ? (price = price.minus(spread.times(0.2 * attempt)))
614
- : (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
+
615
723
  const amount_in_token = (btcAmount * parseInt(leverage)).toFixed(
616
724
  this.config.extendedPrecision
617
725
  ); // gives the amount of wbtc
@@ -623,17 +731,66 @@ export class ExtendedAdapter extends BaseAdapter<
623
731
  price.toFixed(0),
624
732
  side
625
733
  );
626
- if (!result) {
734
+ if (!result || !result.position_id) {
735
+ logger.error("Failed to create order - no position_id returned");
627
736
  return null;
628
737
  }
629
- await new Promise((resolve) => setTimeout(resolve, 5000));
630
- const openOrder = await this.getOrderStatus(
631
- 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,
632
746
  this.config.extendedMarketName
633
747
  );
634
- if (!openOrder || openOrder.status !== OrderStatus.FILLED) {
748
+ const maxStatusRetries = 3;
749
+ const statusRetryDelay = 5000;
750
+
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
+ );
635
790
  if (attempt >= maxAttempts) {
636
- 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
+ );
637
794
  return null;
638
795
  } else {
639
796
  const backoff = 2000 * attempt;
@@ -647,9 +804,12 @@ export class ExtendedAdapter extends BaseAdapter<
647
804
  );
648
805
  }
649
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
+ );
650
810
  return {
651
- position_id: result.position_id,
652
- btc_exposure: openOrder.qty,
811
+ position_id: positionId,
812
+ btc_exposure: amount_in_token,
653
813
  };
654
814
  }
655
815
  } catch (err: any) {
@@ -50,6 +50,7 @@ export interface VesuMultiplyAdapterConfig extends BaseAdapterConfig {
50
50
  targetHealthFactor: number;
51
51
  minHealthFactor: number;
52
52
  quoteAmountToFetchPrice: Web3Number;
53
+ minimumVesuMovementAmount: number;
53
54
  }
54
55
 
55
56
  export class VesuMultiplyAdapter extends BaseAdapter<
@@ -59,7 +60,7 @@ export class VesuMultiplyAdapter extends BaseAdapter<
59
60
  readonly config: VesuMultiplyAdapterConfig;
60
61
  readonly vesuAdapter: VesuAdapter;
61
62
  readonly tokenMarketData: TokenMarketData;
62
-
63
+ readonly minimumVesuMovementAmount: number;
63
64
  constructor(config: VesuMultiplyAdapterConfig) {
64
65
  super(config, VesuMultiplyAdapter.name, Protocols.VESU);
65
66
  this.config = config;
@@ -70,6 +71,7 @@ export class VesuMultiplyAdapter extends BaseAdapter<
70
71
  vaultAllocator: config.vaultAllocator,
71
72
  id: "",
72
73
  });
74
+ this.minimumVesuMovementAmount = config.minimumVesuMovementAmount ?? 5; //5 usdc
73
75
  this.tokenMarketData = new TokenMarketData(
74
76
  this.config.pricer,
75
77
  this.config.networkConfig
@@ -786,7 +786,8 @@ function getLooperSettings(
786
786
  minHealthFactor: vaultSettings.minHealthFactor,
787
787
  quoteAmountToFetchPrice: vaultSettings.quoteAmountToFetchPrice,
788
788
  ...baseAdapterConfig,
789
- supportedPositions: [{asset: lstToken, isDebt: false}, {asset: Global.getDefaultTokens().find(token => token.symbol === position)!, isDebt: true}]
789
+ supportedPositions: [{asset: lstToken, isDebt: false}, {asset: Global.getDefaultTokens().find(token => token.symbol === position)!, isDebt: true}],
790
+ minimumVesuMovementAmount: 0
790
791
  }));
791
792
 
792
793
  const unusedBalanceAdapter = new UnusedBalanceAdapter({
@@ -22,7 +22,7 @@ export const STRK_API_RPC = process.env.STRK_API_RPC ?? "https://mainnet.starkne
22
22
  export const MAX_RETRIES = Number(process.env.MAX_RETRIES ?? 3);
23
23
  export const MAX_DELAY = Number(process.env.MAX_DELAY ?? 100);
24
24
  export const EXTEND_MARKET_NAME = "BTC-USD";
25
- export const LIMIT_BALANCE = Number(process.env.LIMIT_BALANCE ?? 10);
25
+ export const LIMIT_BALANCE = Number(process.env.LIMIT_BALANCE ?? 0.05);
26
26
  export const REBALANCER_INTERVAL = Number(process.env.REBALANCER_INTERVAL ?? 180000); //3 mins
27
27
  export const WITHDRAWAL_INTERVAL = Number(process.env.WITHDRAWAL_INTERVAL ?? 18000000); //5 hours
28
28
  export const INVESTING_INTERVAL = Number(process.env.INVESTING_INTERVAL ?? 180000); //3 mins