async-hyperliquid 0.3.9__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.9
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.9"
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,13 @@ 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,
33
+ Abstraction,
31
34
  UserDeposit,
32
35
  AccountState,
33
36
  GroupOptions,
@@ -37,12 +40,15 @@ from async_hyperliquid.utils.types import (
37
40
  SpotTokenMeta,
38
41
  UserOpenOrders,
39
42
  OrderWithStatus,
43
+ AgentAbstraction,
40
44
  PlaceOrderRequest,
41
45
  BatchCancelRequest,
42
46
  ClearinghouseState,
47
+ UserSetAbstraction,
43
48
  UserNonFundingDelta,
44
49
  BatchPlaceOrderRequest,
45
50
  SpotClearinghouseState,
51
+ limit_order_type,
46
52
  )
47
53
  from async_hyperliquid.utils.signing import (
48
54
  encode_order,
@@ -58,6 +64,7 @@ from async_hyperliquid.utils.signing import (
58
64
  sign_usd_class_transfer_action,
59
65
  sign_approve_builder_fee_action,
60
66
  sign_user_dex_abstraction_action,
67
+ sign_user_set_abstraction_action,
61
68
  sign_convert_to_multi_sig_user_action,
62
69
  )
63
70
  from async_hyperliquid.utils.constants import (
@@ -499,17 +506,12 @@ class AsyncHyperliquid(AsyncAPI):
499
506
  positions.extend(result)
500
507
  return positions
501
508
 
502
- async def get_user_dex_abstraction(self, address: str | None) -> bool:
503
- if not address:
504
- address = self.address
505
- return await self.info.get_user_dex_abstraction(address)
506
-
507
- async def get_user_abstraction_state(
509
+ async def get_user_abstraction(
508
510
  self, address: str | None = None
509
- ) -> str:
511
+ ) -> Abstraction:
510
512
  if not address:
511
513
  address = self.address
512
- return await self.info.get_user_abstraction_state(address)
514
+ return await self.info.get_user_abstraction(address)
513
515
 
514
516
  # Exchange API
515
517
  async def _round_sz_px(self, coin: str, sz: float, px: float):
@@ -519,26 +521,46 @@ class AsyncHyperliquid(AsyncAPI):
519
521
  px_decimals = (6 if not is_spot else 8) - sz_decimals
520
522
  return asset, round_float(sz, sz_decimals), round_px(px, px_decimals)
521
523
 
522
- async def place_order(
524
+ async def place_market_order(
523
525
  self,
524
526
  coin: str,
525
527
  is_buy: bool,
526
528
  sz: float,
527
- px: float,
528
- is_market: bool = True,
529
529
  *,
530
530
  ro: bool = False,
531
- order_type: OrderType = LimitOrder.IOC.value, # type: ignore
532
531
  cloid: Cloid | None = None,
533
532
  slippage: float = 0.05, # Default slippage is 5%
534
533
  builder: OrderBuilder | None = None,
535
534
  ):
536
- if is_market:
537
- mid_px = await self.get_mid_price(coin)
538
- slippage_factor = (1 + slippage) if is_buy else (1 - slippage)
539
- px = mid_px * slippage_factor
540
- # Market order is an aggressive Limit Order IoC
541
- 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)
542
564
 
543
565
  asset, sz, px = await self._round_sz_px(coin, sz, px)
544
566
 
@@ -554,6 +576,42 @@ class AsyncHyperliquid(AsyncAPI):
554
576
 
555
577
  return await self.place_orders([order_req], builder=builder)
556
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
+
557
615
  async def place_orders(
558
616
  self,
559
617
  orders: list[PlaceOrderRequest],
@@ -601,7 +659,7 @@ class AsyncHyperliquid(AsyncAPI):
601
659
  reqs = []
602
660
  dexs = list(set(get_coin_dex(o["coin"]) for o in orders))
603
661
  all_mids = await self.get_dexs_mids(dexs)
604
- order_type = LimitOrder.IOC.value
662
+ order_type = limit_order_type(LimitTif.IOC)
605
663
  for o in orders:
606
664
  coin = o["coin"]
607
665
  market_price = all_mids[coin]
@@ -1012,6 +1070,12 @@ class AsyncHyperliquid(AsyncAPI):
1012
1070
  async def user_dex_abstraction(
1013
1071
  self, user: str | None = None, enabled: bool = True
1014
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
+ )
1015
1079
  nonce = get_timestamp_ms()
1016
1080
  if user is None:
1017
1081
  user = self.address
@@ -1026,11 +1090,44 @@ class AsyncHyperliquid(AsyncAPI):
1026
1090
  )
1027
1091
  return await self.exchange.post_action_with_sig(action, sig, nonce)
1028
1092
 
1093
+ async def user_set_abstraction(
1094
+ self, abstraction: UserSetAbstraction, user: str | None = None
1095
+ ):
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
+ )
1103
+ action = {
1104
+ "type": "userSetAbstraction",
1105
+ "user": user.lower(),
1106
+ "abstraction": abstraction,
1107
+ "nonce": nonce,
1108
+ }
1109
+ sig = sign_user_set_abstraction_action(
1110
+ self.account, action, self.is_mainnet
1111
+ )
1112
+ return await self.exchange.post_action_with_sig(action, sig, nonce)
1113
+
1029
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
+ )
1030
1121
  action = {"type": "agentEnableDexAbstraction"}
1031
1122
  return await self.exchange.post_action(
1032
1123
  action, vault=self.vault, expires=self.expires
1033
1124
  )
1034
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
+
1035
1132
 
1036
1133
  AsyncHyper = AsyncHyperliquid
@@ -25,7 +25,7 @@ class ExchangeAPI(AsyncAPI):
25
25
  address: str | None = None,
26
26
  ):
27
27
  self.account = account
28
- self.address = address or account.address # type: ignore
28
+ self.address = address or account.address
29
29
  self.is_mainnet = base_url == MAINNET_API_URL
30
30
  super().__init__(Endpoint.EXCHANGE, base_url, session)
31
31
 
@@ -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
 
@@ -99,3 +99,10 @@ USER_DEX_ABSTRACTION_SIGN_TYPES = [
99
99
  {"name": "enabled", "type": "bool"},
100
100
  {"name": "nonce", "type": "uint64"},
101
101
  ]
102
+
103
+ USER_SET_ABSTRACTION_SIGN_TYPES = [
104
+ {"name": "hyperliquidChain", "type": "string"},
105
+ {"name": "user", "type": "address"},
106
+ {"name": "abstraction", "type": "string"},
107
+ {"name": "nonce", "type": "uint64"},
108
+ ]
@@ -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,10 @@ 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
- CONVERT_TO_MULTI_SIG_USER_SIGN_TYPES,
32
36
  USER_DEX_ABSTRACTION_SIGN_TYPES,
37
+ CONVERT_TO_MULTI_SIG_USER_SIGN_TYPES,
33
38
  )
34
39
 
35
40
 
@@ -40,7 +45,7 @@ def address_to_bytes(address: str) -> bytes:
40
45
  def hash_action(
41
46
  action: dict, vault: str | None, nonce: int, expires: int | None = None
42
47
  ) -> bytes:
43
- data: bytes = msgpack.packb(action) # type: ignore
48
+ data: bytes = msgpack.packb(action)
44
49
  data += nonce.to_bytes(8, "big")
45
50
 
46
51
  if vault is None:
@@ -113,9 +118,9 @@ def round_float(x: float) -> str:
113
118
 
114
119
 
115
120
  def ensure_order_type(order_type: OrderType) -> OrderType:
116
- if "limit" in order_type:
117
- return {"limit": order_type["limit"]}
118
- 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):
119
124
  return {
120
125
  "trigger": {
121
126
  "isMarket": order_type["trigger"]["isMarket"],
@@ -357,3 +362,15 @@ def sign_user_dex_abstraction_action(
357
362
  "HyperliquidTransaction:UserDexAbstraction",
358
363
  is_mainnet,
359
364
  )
365
+
366
+
367
+ def sign_user_set_abstraction_action(
368
+ wallet: LocalAccount, action: dict, is_mainnet: bool
369
+ ):
370
+ return sign_user_signed_action(
371
+ wallet,
372
+ action,
373
+ USER_SET_ABSTRACTION_SIGN_TYPES,
374
+ "HyperliquidTransaction:UserSetAbstraction",
375
+ is_mainnet,
376
+ )
@@ -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):
@@ -837,3 +877,7 @@ class Metas(TypedDict):
837
877
  class SignType(str, Enum):
838
878
  SINGLE_SIG = "singleSig"
839
879
  MULTI_SIG = "multiSig"
880
+
881
+
882
+ Abstraction = Literal["unifiedAccount", "portfolioMargin", "disabled"]
883
+ AgentAbstraction = Literal["u", "p", "i"]