@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.
- package/dist/index.browser.global.js +418 -99
- package/dist/index.browser.mjs +418 -99
- package/dist/index.js +418 -99
- package/dist/index.mjs +418 -99
- package/package.json +1 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +2 -3
- package/src/strategies/universal-adapters/extended-adapter.ts +217 -63
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +284 -50
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
WithdrawParams,
|
|
5
5
|
BaseAdapterConfig,
|
|
6
6
|
} from "./baseAdapter";
|
|
7
|
-
import {
|
|
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 {
|
|
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 =
|
|
70
|
+
this.minimumExtendedMovementAmount =
|
|
71
|
+
this.config.minimumExtendedMovementAmount ?? 5; //5 usdc
|
|
71
72
|
this.client = client;
|
|
72
|
-
this.retryDelayForOrderStatus =
|
|
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
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
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,
|
|
596
|
+
.slice(0, 20)
|
|
537
597
|
.find((order) => order.id.toString() === orderId);
|
|
538
598
|
|
|
539
|
-
if (order
|
|
599
|
+
if (order) {
|
|
540
600
|
return order;
|
|
541
601
|
}
|
|
542
602
|
|
|
543
|
-
|
|
544
|
-
|
|
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
|
|
612
|
-
if (
|
|
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
|
-
|
|
681
|
+
|
|
616
682
|
const spread = ask.minus(bid);
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
-
|
|
636
|
-
const
|
|
637
|
-
|
|
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(
|
|
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:
|
|
658
|
-
btc_exposure:
|
|
811
|
+
position_id: positionId,
|
|
812
|
+
btc_exposure: amount_in_token,
|
|
659
813
|
};
|
|
660
814
|
}
|
|
661
815
|
} catch (err: any) {
|