async-hyperliquid 0.3.10__tar.gz → 0.4.0__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: async-hyperliquid
3
- Version: 0.3.10
3
+ Version: 0.4.0
4
4
  Summary: Async Hyperliquid client using aiohttp
5
5
  Keywords: dex,hyperliquid,async,aiohttp,trading,cryptocurrency,defi
6
6
  Author: Yuki
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "async-hyperliquid"
3
- version = "0.3.10"
3
+ version = "0.4.0"
4
4
  description = "Async Hyperliquid client using aiohttp"
5
5
  authors = [{ name = "Yuki", email = "yuqi.lyle@gmail.com" }]
6
6
  readme = "README.md"
@@ -1,5 +1,7 @@
1
+ import re
1
2
  import math
2
3
  import asyncio
4
+ import warnings
3
5
  from typing import Literal
4
6
 
5
7
  from aiohttp import ClientSession, ClientTimeout
@@ -22,12 +24,12 @@ from async_hyperliquid.utils.miscs import (
22
24
  from async_hyperliquid.utils.types import (
23
25
  Cloid,
24
26
  Metas,
27
+ LimitTif,
25
28
  PerpMeta,
26
29
  Position,
27
30
  SpotMeta,
28
31
  OrderType,
29
32
  Portfolio,
30
- LimitOrder,
31
33
  Abstraction,
32
34
  UserDeposit,
33
35
  AccountState,
@@ -38,12 +40,15 @@ from async_hyperliquid.utils.types import (
38
40
  SpotTokenMeta,
39
41
  UserOpenOrders,
40
42
  OrderWithStatus,
43
+ AgentAbstraction,
41
44
  PlaceOrderRequest,
42
45
  BatchCancelRequest,
43
46
  ClearinghouseState,
47
+ UserSetAbstraction,
44
48
  UserNonFundingDelta,
45
49
  BatchPlaceOrderRequest,
46
50
  SpotClearinghouseState,
51
+ limit_order_type,
47
52
  )
48
53
  from async_hyperliquid.utils.signing import (
49
54
  encode_order,
@@ -501,17 +506,12 @@ class AsyncHyperliquid(AsyncAPI):
501
506
  positions.extend(result)
502
507
  return positions
503
508
 
504
- async def get_user_dex_abstraction(self, address: str | None) -> bool:
505
- if not address:
506
- address = self.address
507
- return await self.info.get_user_dex_abstraction(address)
508
-
509
- async def get_user_abstraction_state(
509
+ async def get_user_abstraction(
510
510
  self, address: str | None = None
511
- ) -> str:
511
+ ) -> Abstraction:
512
512
  if not address:
513
513
  address = self.address
514
- return await self.info.get_user_abstraction_state(address)
514
+ return await self.info.get_user_abstraction(address)
515
515
 
516
516
  # Exchange API
517
517
  async def _round_sz_px(self, coin: str, sz: float, px: float):
@@ -521,26 +521,46 @@ class AsyncHyperliquid(AsyncAPI):
521
521
  px_decimals = (6 if not is_spot else 8) - sz_decimals
522
522
  return asset, round_float(sz, sz_decimals), round_px(px, px_decimals)
523
523
 
524
- async def place_order(
524
+ async def place_market_order(
525
525
  self,
526
526
  coin: str,
527
527
  is_buy: bool,
528
528
  sz: float,
529
- px: float,
530
- is_market: bool = True,
531
529
  *,
532
530
  ro: bool = False,
533
- order_type: OrderType = LimitOrder.IOC.value, # type: ignore
534
531
  cloid: Cloid | None = None,
535
532
  slippage: float = 0.05, # Default slippage is 5%
536
533
  builder: OrderBuilder | None = None,
537
534
  ):
538
- if is_market:
539
- mid_px = await self.get_mid_price(coin)
540
- slippage_factor = (1 + slippage) if is_buy else (1 - slippage)
541
- px = mid_px * slippage_factor
542
- # Market order is an aggressive Limit Order IoC
543
- order_type = LimitOrder.IOC.value # type: ignore
535
+ mid_px = await self.get_mid_price(coin)
536
+ slippage_factor = (1 + slippage) if is_buy else (1 - slippage)
537
+ px = mid_px * slippage_factor
538
+ # Market order is an aggressive Limit Order IoC
539
+ return await self.place_typed_order(
540
+ coin=coin,
541
+ is_buy=is_buy,
542
+ sz=sz,
543
+ px=px,
544
+ ro=ro,
545
+ order_type=limit_order_type(LimitTif.IOC),
546
+ cloid=cloid,
547
+ builder=builder,
548
+ )
549
+
550
+ async def place_typed_order(
551
+ self,
552
+ coin: str,
553
+ is_buy: bool,
554
+ sz: float,
555
+ px: float,
556
+ *,
557
+ ro: bool = False,
558
+ order_type: OrderType | None = None,
559
+ cloid: Cloid | None = None,
560
+ builder: OrderBuilder | None = None,
561
+ ):
562
+ if order_type is None:
563
+ order_type = limit_order_type(LimitTif.IOC)
544
564
 
545
565
  asset, sz, px = await self._round_sz_px(coin, sz, px)
546
566
 
@@ -556,6 +576,42 @@ class AsyncHyperliquid(AsyncAPI):
556
576
 
557
577
  return await self.place_orders([order_req], builder=builder)
558
578
 
579
+ async def place_order(
580
+ self,
581
+ coin: str,
582
+ is_buy: bool,
583
+ sz: float,
584
+ px: float,
585
+ is_market: bool = True,
586
+ *,
587
+ ro: bool = False,
588
+ order_type: OrderType | None = None,
589
+ cloid: Cloid | None = None,
590
+ slippage: float = 0.05, # Default slippage is 5%
591
+ builder: OrderBuilder | None = None,
592
+ ):
593
+ if is_market:
594
+ return await self.place_market_order(
595
+ coin=coin,
596
+ is_buy=is_buy,
597
+ sz=sz,
598
+ ro=ro,
599
+ cloid=cloid,
600
+ slippage=slippage,
601
+ builder=builder,
602
+ )
603
+
604
+ return await self.place_typed_order(
605
+ coin=coin,
606
+ is_buy=is_buy,
607
+ sz=sz,
608
+ px=px,
609
+ ro=ro,
610
+ order_type=order_type,
611
+ cloid=cloid,
612
+ builder=builder,
613
+ )
614
+
559
615
  async def place_orders(
560
616
  self,
561
617
  orders: list[PlaceOrderRequest],
@@ -603,7 +659,7 @@ class AsyncHyperliquid(AsyncAPI):
603
659
  reqs = []
604
660
  dexs = list(set(get_coin_dex(o["coin"]) for o in orders))
605
661
  all_mids = await self.get_dexs_mids(dexs)
606
- order_type = LimitOrder.IOC.value
662
+ order_type = limit_order_type(LimitTif.IOC)
607
663
  for o in orders:
608
664
  coin = o["coin"]
609
665
  market_price = all_mids[coin]
@@ -1014,6 +1070,12 @@ class AsyncHyperliquid(AsyncAPI):
1014
1070
  async def user_dex_abstraction(
1015
1071
  self, user: str | None = None, enabled: bool = True
1016
1072
  ):
1073
+ warnings.warn(
1074
+ "user_dex_abstraction is deprecated and may be removed in a "
1075
+ "future release.",
1076
+ DeprecationWarning,
1077
+ stacklevel=2,
1078
+ )
1017
1079
  nonce = get_timestamp_ms()
1018
1080
  if user is None:
1019
1081
  user = self.address
@@ -1028,30 +1090,44 @@ class AsyncHyperliquid(AsyncAPI):
1028
1090
  )
1029
1091
  return await self.exchange.post_action_with_sig(action, sig, nonce)
1030
1092
 
1031
- async def agent_enable_dex_abstraction(self):
1032
- action = {"type": "agentEnableDexAbstraction"}
1033
- return await self.exchange.post_action(
1034
- action, vault=self.vault, expires=self.expires
1035
- )
1036
-
1037
1093
  async def user_set_abstraction(
1038
- self, abstraction: Abstraction, address: str | None = None
1094
+ self, abstraction: UserSetAbstraction, user: str | None = None
1039
1095
  ):
1040
- if address is None:
1041
- address = self.address
1042
-
1043
1096
  nonce = get_timestamp_ms()
1097
+ if user is None:
1098
+ user = self.address
1099
+ if re.fullmatch(r"0x[a-fA-F0-9]{40}", user) is None:
1100
+ raise ValueError(
1101
+ f"user must be a 42-char hex address, got: {user!r}"
1102
+ )
1044
1103
  action = {
1045
1104
  "type": "userSetAbstraction",
1046
- "user": address.lower(),
1105
+ "user": user.lower(),
1047
1106
  "abstraction": abstraction,
1048
1107
  "nonce": nonce,
1049
1108
  }
1050
1109
  sig = sign_user_set_abstraction_action(
1051
1110
  self.account, action, self.is_mainnet
1052
1111
  )
1053
-
1054
1112
  return await self.exchange.post_action_with_sig(action, sig, nonce)
1055
1113
 
1114
+ async def agent_enable_dex_abstraction(self):
1115
+ warnings.warn(
1116
+ "agent_enable_dex_abstraction is deprecated and may be removed "
1117
+ "in a future release.",
1118
+ DeprecationWarning,
1119
+ stacklevel=2,
1120
+ )
1121
+ action = {"type": "agentEnableDexAbstraction"}
1122
+ return await self.exchange.post_action(
1123
+ action, vault=self.vault, expires=self.expires
1124
+ )
1125
+
1126
+ async def agent_set_abstraction(self, abstraction: AgentAbstraction):
1127
+ action = {"type": "agentSetAbstraction", "abstraction": abstraction}
1128
+ return await self.exchange.post_action(
1129
+ action, vault=self.vault, expires=self.expires
1130
+ )
1131
+
1056
1132
 
1057
1133
  AsyncHyper = AsyncHyperliquid
@@ -4,6 +4,7 @@ from aiohttp import ClientSession
4
4
 
5
5
  from async_hyperliquid.async_api import AsyncAPI
6
6
  from async_hyperliquid.utils.types import (
7
+ Abstraction,
7
8
  Depth,
8
9
  Candles,
9
10
  Endpoint,
@@ -183,7 +184,7 @@ class InfoAPI(AsyncAPI):
183
184
  payload = {"type": "userDexAbstraction", "user": address}
184
185
  return await self.post(payload)
185
186
 
186
- async def get_user_abstraction_state(self, address: str) -> str:
187
+ async def get_user_abstraction(self, address: str) -> Abstraction:
187
188
  payload = {"type": "userAbstraction", "user": address}
188
189
  return await self.post(payload)
189
190
 
@@ -8,6 +8,7 @@ from eth_utils.conversions import to_hex
8
8
  from eth_account.signers.local import LocalAccount
9
9
 
10
10
  from async_hyperliquid.utils.types import (
11
+ LimitTif,
11
12
  OrderType,
12
13
  OrderAction,
13
14
  EncodedOrder,
@@ -15,6 +16,9 @@ from async_hyperliquid.utils.types import (
15
16
  OrderBuilder,
16
17
  SignedAction,
17
18
  PlaceOrderRequest,
19
+ is_limit_order_type,
20
+ is_trigger_order_type,
21
+ limit_order_type,
18
22
  )
19
23
  from async_hyperliquid.utils.constants import (
20
24
  SIGNATURE_CHAIN_ID,
@@ -27,9 +31,9 @@ from async_hyperliquid.utils.constants import (
27
31
  APPROVE_BUILDER_FEE_TYPES,
28
32
  STAKING_TRANSFER_SIGN_TYPES,
29
33
  MULTI_SIG_ENVELOPE_SIGN_TYPES,
34
+ USER_SET_ABSTRACTION_SIGN_TYPES,
30
35
  USD_CLASS_TRANSFER_SIGN_TYPES,
31
36
  USER_DEX_ABSTRACTION_SIGN_TYPES,
32
- USER_SET_ABSTRACTION_SIGN_TYPES,
33
37
  CONVERT_TO_MULTI_SIG_USER_SIGN_TYPES,
34
38
  )
35
39
 
@@ -114,9 +118,9 @@ def round_float(x: float) -> str:
114
118
 
115
119
 
116
120
  def ensure_order_type(order_type: OrderType) -> OrderType:
117
- if "limit" in order_type:
118
- return {"limit": order_type["limit"]} # type: ignore
119
- elif "trigger" in order_type:
121
+ if is_limit_order_type(order_type):
122
+ return limit_order_type(LimitTif(order_type["limit"]["tif"]))
123
+ if is_trigger_order_type(order_type):
120
124
  return {
121
125
  "trigger": {
122
126
  "isMarket": order_type["trigger"]["isMarket"],
@@ -1,5 +1,5 @@
1
1
  from enum import Enum
2
- from typing import Any, Literal, TypedDict
2
+ from typing import Any, Literal, TypeGuard, TypedDict
3
3
 
4
4
  from typing_extensions import NotRequired
5
5
 
@@ -47,12 +47,22 @@ class LimitOrderType(TypedDict):
47
47
  limit: LimitOrderOptions
48
48
 
49
49
 
50
+ class LimitTif(str, Enum):
51
+ ALO = "Alo"
52
+ IOC = "Ioc"
53
+ GTC = "Gtc"
54
+
55
+
50
56
  class LimitOrder(Enum):
51
57
  ALO = {"limit": {"tif": "Alo"}}
52
58
  IOC = {"limit": {"tif": "Ioc"}}
53
59
  GTC = {"limit": {"tif": "Gtc"}}
54
60
 
55
61
 
62
+ def limit_order_type(tif: LimitTif) -> LimitOrderType:
63
+ return {"limit": {"tif": tif.value}}
64
+
65
+
56
66
  class TriggerOrderOptions(TypedDict):
57
67
  isMarket: bool
58
68
  triggerPx: str
@@ -63,9 +73,39 @@ class TriggerOrderType(TypedDict):
63
73
  trigger: TriggerOrderOptions
64
74
 
65
75
 
76
+ class TriggerTpsl(str, Enum):
77
+ TP = "tp"
78
+ SL = "sl"
79
+
80
+
81
+ def trigger_order_type(
82
+ *, is_market: bool, trigger_px: float | str, tpsl: TriggerTpsl
83
+ ) -> TriggerOrderType:
84
+ return {
85
+ "trigger": {
86
+ "isMarket": is_market,
87
+ "triggerPx": str(trigger_px),
88
+ "tpsl": tpsl.value,
89
+ }
90
+ }
91
+
92
+
66
93
  OrderType = LimitOrderType | TriggerOrderType
67
94
 
68
95
  GroupOptions = Literal["na", "normalTpsl", "positionTpsl"]
96
+ Abstraction = Literal[
97
+ "unifiedAccount", "portfolioMargin", "disabled", "default", "dexAbstraction"
98
+ ]
99
+ UserSetAbstraction = Literal["disabled", "unifiedAccount", "portfolioMargin"]
100
+ AgentAbstraction = Literal["i", "u", "p"]
101
+
102
+
103
+ def is_limit_order_type(order_type: OrderType) -> TypeGuard[LimitOrderType]:
104
+ return "limit" in order_type and "trigger" not in order_type
105
+
106
+
107
+ def is_trigger_order_type(order_type: OrderType) -> TypeGuard[TriggerOrderType]:
108
+ return "trigger" in order_type and "limit" not in order_type
69
109
 
70
110
 
71
111
  class BasicPlaceOrderRequest(TypedDict):