async-hyperliquid 0.3.10__tar.gz → 0.4.1__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.1
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.1"
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,
@@ -163,6 +168,7 @@ class AsyncHyperliquid(AsyncAPI):
163
168
  self.asset_sz_decimals[asset] = info["szDecimals"]
164
169
 
165
170
  def _init_spot_meta(self, meta: SpotMeta) -> None:
171
+ total_tokens = len(meta["tokens"])
166
172
  for info in meta["universe"]:
167
173
  asset = info["index"] + SPOT_OFFSET
168
174
  asset_name = info["name"]
@@ -172,6 +178,10 @@ class AsyncHyperliquid(AsyncAPI):
172
178
  self.coin_names[asset_name] = asset_name
173
179
  # For token pairs
174
180
  base, quote = info["tokens"]
181
+ if base >= total_tokens or quote >= total_tokens:
182
+ print("Unreconized token index for: ", info)
183
+ continue
184
+
175
185
  base_info = meta["tokens"][base]
176
186
  base_name = base_info["name"]
177
187
  quote_name = meta["tokens"][quote]["name"]
@@ -501,17 +511,12 @@ class AsyncHyperliquid(AsyncAPI):
501
511
  positions.extend(result)
502
512
  return positions
503
513
 
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(
514
+ async def get_user_abstraction(
510
515
  self, address: str | None = None
511
- ) -> str:
516
+ ) -> Abstraction:
512
517
  if not address:
513
518
  address = self.address
514
- return await self.info.get_user_abstraction_state(address)
519
+ return await self.info.get_user_abstraction(address)
515
520
 
516
521
  # Exchange API
517
522
  async def _round_sz_px(self, coin: str, sz: float, px: float):
@@ -521,26 +526,46 @@ class AsyncHyperliquid(AsyncAPI):
521
526
  px_decimals = (6 if not is_spot else 8) - sz_decimals
522
527
  return asset, round_float(sz, sz_decimals), round_px(px, px_decimals)
523
528
 
524
- async def place_order(
529
+ async def place_market_order(
525
530
  self,
526
531
  coin: str,
527
532
  is_buy: bool,
528
533
  sz: float,
529
- px: float,
530
- is_market: bool = True,
531
534
  *,
532
535
  ro: bool = False,
533
- order_type: OrderType = LimitOrder.IOC.value, # type: ignore
534
536
  cloid: Cloid | None = None,
535
537
  slippage: float = 0.05, # Default slippage is 5%
536
538
  builder: OrderBuilder | None = None,
537
539
  ):
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
540
+ mid_px = await self.get_mid_price(coin)
541
+ slippage_factor = (1 + slippage) if is_buy else (1 - slippage)
542
+ px = mid_px * slippage_factor
543
+ # Market order is an aggressive Limit Order IoC
544
+ return await self.place_typed_order(
545
+ coin=coin,
546
+ is_buy=is_buy,
547
+ sz=sz,
548
+ px=px,
549
+ ro=ro,
550
+ order_type=limit_order_type(LimitTif.IOC),
551
+ cloid=cloid,
552
+ builder=builder,
553
+ )
554
+
555
+ async def place_typed_order(
556
+ self,
557
+ coin: str,
558
+ is_buy: bool,
559
+ sz: float,
560
+ px: float,
561
+ *,
562
+ ro: bool = False,
563
+ order_type: OrderType | None = None,
564
+ cloid: Cloid | None = None,
565
+ builder: OrderBuilder | None = None,
566
+ ):
567
+ if order_type is None:
568
+ order_type = limit_order_type(LimitTif.IOC)
544
569
 
545
570
  asset, sz, px = await self._round_sz_px(coin, sz, px)
546
571
 
@@ -556,6 +581,42 @@ class AsyncHyperliquid(AsyncAPI):
556
581
 
557
582
  return await self.place_orders([order_req], builder=builder)
558
583
 
584
+ async def place_order(
585
+ self,
586
+ coin: str,
587
+ is_buy: bool,
588
+ sz: float,
589
+ px: float,
590
+ is_market: bool = True,
591
+ *,
592
+ ro: bool = False,
593
+ order_type: OrderType | None = None,
594
+ cloid: Cloid | None = None,
595
+ slippage: float = 0.05, # Default slippage is 5%
596
+ builder: OrderBuilder | None = None,
597
+ ):
598
+ if is_market:
599
+ return await self.place_market_order(
600
+ coin=coin,
601
+ is_buy=is_buy,
602
+ sz=sz,
603
+ ro=ro,
604
+ cloid=cloid,
605
+ slippage=slippage,
606
+ builder=builder,
607
+ )
608
+
609
+ return await self.place_typed_order(
610
+ coin=coin,
611
+ is_buy=is_buy,
612
+ sz=sz,
613
+ px=px,
614
+ ro=ro,
615
+ order_type=order_type,
616
+ cloid=cloid,
617
+ builder=builder,
618
+ )
619
+
559
620
  async def place_orders(
560
621
  self,
561
622
  orders: list[PlaceOrderRequest],
@@ -603,7 +664,7 @@ class AsyncHyperliquid(AsyncAPI):
603
664
  reqs = []
604
665
  dexs = list(set(get_coin_dex(o["coin"]) for o in orders))
605
666
  all_mids = await self.get_dexs_mids(dexs)
606
- order_type = LimitOrder.IOC.value
667
+ order_type = limit_order_type(LimitTif.IOC)
607
668
  for o in orders:
608
669
  coin = o["coin"]
609
670
  market_price = all_mids[coin]
@@ -1014,6 +1075,12 @@ class AsyncHyperliquid(AsyncAPI):
1014
1075
  async def user_dex_abstraction(
1015
1076
  self, user: str | None = None, enabled: bool = True
1016
1077
  ):
1078
+ warnings.warn(
1079
+ "user_dex_abstraction is deprecated and may be removed in a "
1080
+ "future release.",
1081
+ DeprecationWarning,
1082
+ stacklevel=2,
1083
+ )
1017
1084
  nonce = get_timestamp_ms()
1018
1085
  if user is None:
1019
1086
  user = self.address
@@ -1028,30 +1095,44 @@ class AsyncHyperliquid(AsyncAPI):
1028
1095
  )
1029
1096
  return await self.exchange.post_action_with_sig(action, sig, nonce)
1030
1097
 
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
1098
  async def user_set_abstraction(
1038
- self, abstraction: Abstraction, address: str | None = None
1099
+ self, abstraction: UserSetAbstraction, user: str | None = None
1039
1100
  ):
1040
- if address is None:
1041
- address = self.address
1042
-
1043
1101
  nonce = get_timestamp_ms()
1102
+ if user is None:
1103
+ user = self.address
1104
+ if re.fullmatch(r"0x[a-fA-F0-9]{40}", user) is None:
1105
+ raise ValueError(
1106
+ f"user must be a 42-char hex address, got: {user!r}"
1107
+ )
1044
1108
  action = {
1045
1109
  "type": "userSetAbstraction",
1046
- "user": address.lower(),
1110
+ "user": user.lower(),
1047
1111
  "abstraction": abstraction,
1048
1112
  "nonce": nonce,
1049
1113
  }
1050
1114
  sig = sign_user_set_abstraction_action(
1051
1115
  self.account, action, self.is_mainnet
1052
1116
  )
1053
-
1054
1117
  return await self.exchange.post_action_with_sig(action, sig, nonce)
1055
1118
 
1119
+ async def agent_enable_dex_abstraction(self):
1120
+ warnings.warn(
1121
+ "agent_enable_dex_abstraction is deprecated and may be removed "
1122
+ "in a future release.",
1123
+ DeprecationWarning,
1124
+ stacklevel=2,
1125
+ )
1126
+ action = {"type": "agentEnableDexAbstraction"}
1127
+ return await self.exchange.post_action(
1128
+ action, vault=self.vault, expires=self.expires
1129
+ )
1130
+
1131
+ async def agent_set_abstraction(self, abstraction: AgentAbstraction):
1132
+ action = {"type": "agentSetAbstraction", "abstraction": abstraction}
1133
+ return await self.exchange.post_action(
1134
+ action, vault=self.vault, expires=self.expires
1135
+ )
1136
+
1056
1137
 
1057
1138
  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):
@@ -113,7 +153,7 @@ class EncodedOrder(TypedDict):
113
153
 
114
154
  class OrderBuilder(TypedDict):
115
155
  b: str # builder address
116
- f: float
156
+ f: int
117
157
 
118
158
 
119
159
  class OrderAction(TypedDict):