unicex 0.13.17__py3-none-any.whl

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.
Files changed (93) hide show
  1. unicex/__init__.py +200 -0
  2. unicex/_abc/__init__.py +11 -0
  3. unicex/_abc/exchange_info.py +216 -0
  4. unicex/_abc/uni_client.py +329 -0
  5. unicex/_abc/uni_websocket_manager.py +294 -0
  6. unicex/_base/__init__.py +9 -0
  7. unicex/_base/client.py +214 -0
  8. unicex/_base/websocket.py +261 -0
  9. unicex/binance/__init__.py +27 -0
  10. unicex/binance/adapter.py +202 -0
  11. unicex/binance/client.py +1577 -0
  12. unicex/binance/exchange_info.py +62 -0
  13. unicex/binance/uni_client.py +188 -0
  14. unicex/binance/uni_websocket_manager.py +166 -0
  15. unicex/binance/user_websocket.py +186 -0
  16. unicex/binance/websocket_manager.py +912 -0
  17. unicex/bitget/__init__.py +27 -0
  18. unicex/bitget/adapter.py +188 -0
  19. unicex/bitget/client.py +2514 -0
  20. unicex/bitget/exchange_info.py +48 -0
  21. unicex/bitget/uni_client.py +198 -0
  22. unicex/bitget/uni_websocket_manager.py +275 -0
  23. unicex/bitget/user_websocket.py +7 -0
  24. unicex/bitget/websocket_manager.py +232 -0
  25. unicex/bybit/__init__.py +27 -0
  26. unicex/bybit/adapter.py +208 -0
  27. unicex/bybit/client.py +1876 -0
  28. unicex/bybit/exchange_info.py +53 -0
  29. unicex/bybit/uni_client.py +200 -0
  30. unicex/bybit/uni_websocket_manager.py +291 -0
  31. unicex/bybit/user_websocket.py +7 -0
  32. unicex/bybit/websocket_manager.py +339 -0
  33. unicex/enums.py +273 -0
  34. unicex/exceptions.py +64 -0
  35. unicex/extra.py +335 -0
  36. unicex/gate/__init__.py +27 -0
  37. unicex/gate/adapter.py +178 -0
  38. unicex/gate/client.py +1667 -0
  39. unicex/gate/exchange_info.py +55 -0
  40. unicex/gate/uni_client.py +214 -0
  41. unicex/gate/uni_websocket_manager.py +269 -0
  42. unicex/gate/user_websocket.py +7 -0
  43. unicex/gate/websocket_manager.py +513 -0
  44. unicex/hyperliquid/__init__.py +27 -0
  45. unicex/hyperliquid/adapter.py +261 -0
  46. unicex/hyperliquid/client.py +2315 -0
  47. unicex/hyperliquid/exchange_info.py +119 -0
  48. unicex/hyperliquid/uni_client.py +325 -0
  49. unicex/hyperliquid/uni_websocket_manager.py +269 -0
  50. unicex/hyperliquid/user_websocket.py +7 -0
  51. unicex/hyperliquid/websocket_manager.py +393 -0
  52. unicex/mapper.py +111 -0
  53. unicex/mexc/__init__.py +27 -0
  54. unicex/mexc/_spot_ws_proto/PrivateAccountV3Api_pb2.py +38 -0
  55. unicex/mexc/_spot_ws_proto/PrivateDealsV3Api_pb2.py +38 -0
  56. unicex/mexc/_spot_ws_proto/PrivateOrdersV3Api_pb2.py +38 -0
  57. unicex/mexc/_spot_ws_proto/PublicAggreBookTickerV3Api_pb2.py +38 -0
  58. unicex/mexc/_spot_ws_proto/PublicAggreDealsV3Api_pb2.py +40 -0
  59. unicex/mexc/_spot_ws_proto/PublicAggreDepthsV3Api_pb2.py +40 -0
  60. unicex/mexc/_spot_ws_proto/PublicBookTickerBatchV3Api_pb2.py +38 -0
  61. unicex/mexc/_spot_ws_proto/PublicBookTickerV3Api_pb2.py +38 -0
  62. unicex/mexc/_spot_ws_proto/PublicDealsV3Api_pb2.py +40 -0
  63. unicex/mexc/_spot_ws_proto/PublicFuture_pb2.py +103 -0
  64. unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsBatchV3Api_pb2.py +38 -0
  65. unicex/mexc/_spot_ws_proto/PublicIncreaseDepthsV3Api_pb2.py +40 -0
  66. unicex/mexc/_spot_ws_proto/PublicLimitDepthsV3Api_pb2.py +40 -0
  67. unicex/mexc/_spot_ws_proto/PublicMiniTickerV3Api_pb2.py +38 -0
  68. unicex/mexc/_spot_ws_proto/PublicMiniTickersV3Api_pb2.py +38 -0
  69. unicex/mexc/_spot_ws_proto/PublicSpotKlineV3Api_pb2.py +38 -0
  70. unicex/mexc/_spot_ws_proto/PushDataV3ApiWrapper_pb2.py +38 -0
  71. unicex/mexc/_spot_ws_proto/__init__.py +335 -0
  72. unicex/mexc/adapter.py +239 -0
  73. unicex/mexc/client.py +846 -0
  74. unicex/mexc/exchange_info.py +47 -0
  75. unicex/mexc/uni_client.py +211 -0
  76. unicex/mexc/uni_websocket_manager.py +269 -0
  77. unicex/mexc/user_websocket.py +7 -0
  78. unicex/mexc/websocket_manager.py +456 -0
  79. unicex/okx/__init__.py +27 -0
  80. unicex/okx/adapter.py +150 -0
  81. unicex/okx/client.py +2864 -0
  82. unicex/okx/exchange_info.py +47 -0
  83. unicex/okx/uni_client.py +202 -0
  84. unicex/okx/uni_websocket_manager.py +269 -0
  85. unicex/okx/user_websocket.py +7 -0
  86. unicex/okx/websocket_manager.py +743 -0
  87. unicex/types.py +164 -0
  88. unicex/utils.py +218 -0
  89. unicex-0.13.17.dist-info/METADATA +243 -0
  90. unicex-0.13.17.dist-info/RECORD +93 -0
  91. unicex-0.13.17.dist-info/WHEEL +5 -0
  92. unicex-0.13.17.dist-info/licenses/LICENSE +28 -0
  93. unicex-0.13.17.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2315 @@
1
+ __all__ = ["Client"]
2
+
3
+ import time
4
+ from typing import Any, Literal, Self
5
+
6
+ import aiohttp
7
+ import msgpack
8
+ from eth_account import Account
9
+ from eth_account.messages import encode_typed_data
10
+ from eth_account.signers.local import LocalAccount
11
+ from eth_utils.conversions import to_hex
12
+ from eth_utils.crypto import keccak
13
+
14
+ from unicex._base import BaseClient
15
+ from unicex.exceptions import NotAuthorized
16
+ from unicex.types import LoggerLike
17
+ from unicex.utils import filter_params
18
+
19
+ # Authentication
20
+
21
+
22
+ def _l1_payload(phantom_agent: dict[str, Any]) -> dict[str, Any]:
23
+ """Формирует EIP-712 payload для подписи "агента".
24
+
25
+ Простыми словами:
26
+ Это упаковка данных в формат, который кошелёк сможет подписать.
27
+ В Ethereum есть стандарт EIP-712 — "structured data signing".
28
+ Он позволяет подписывать не просто строку, а структуру (объект),
29
+ чтобы потом её можно было проверить.
30
+
31
+ Пример:
32
+ >>> phantom = {"source": "a", "connectionId": b"1234...."}
33
+ >>> _l1_payload(phantom)
34
+ {...сложный словарь...}
35
+
36
+ Параметры:
37
+ phantom_agent (dict): объект с полями:
38
+ - source (str): откуда пришёл агент ("a" для mainnet, "b" для testnet)
39
+ - connectionId (bytes32): уникальный ID (обычно хэш)
40
+
41
+ Возвращает:
42
+ dict: структура для подписи через EIP-712
43
+ """
44
+ return {
45
+ "domain": {
46
+ "chainId": 1337,
47
+ "name": "Exchange",
48
+ "verifyingContract": "0x0000000000000000000000000000000000000000",
49
+ "version": "1",
50
+ },
51
+ "types": {
52
+ "Agent": [
53
+ {"name": "source", "type": "string"},
54
+ {"name": "connectionId", "type": "bytes32"},
55
+ ],
56
+ "EIP712Domain": [
57
+ {"name": "name", "type": "string"},
58
+ {"name": "version", "type": "string"},
59
+ {"name": "chainId", "type": "uint256"},
60
+ {"name": "verifyingContract", "type": "address"},
61
+ ],
62
+ },
63
+ "primaryType": "Agent",
64
+ "message": phantom_agent,
65
+ }
66
+
67
+
68
+ def _address_to_bytes(address: str) -> bytes:
69
+ r"""Переводит Ethereum-адрес в байты.
70
+
71
+ Простыми словами:
72
+ Берём строку вида "0xABC123..." и превращаем её в бинарные данные.
73
+ Это нужно, потому что внутри подписи адрес должен храниться как массив байтов.
74
+
75
+ Пример:
76
+ >>> _address_to_bytes("0x0000000000000000000000000000000000000001")
77
+ b'\\x00...\\x01'
78
+
79
+ Параметры:
80
+ address (str): строковый Ethereum-адрес, с "0x" или без.
81
+
82
+ Возвращает:
83
+ bytes: бинарное представление адреса.
84
+ """
85
+ return bytes.fromhex(address[2:] if address.startswith("0x") else address)
86
+
87
+
88
+ def _construct_phantom_agent(hash: bytes, is_mainnet: bool) -> dict[str, Any]:
89
+ r"""Собирает объект "phantom_agent".
90
+
91
+ Простыми словами:
92
+ Это кусочек данных, который будет подписываться.
93
+ В нём указывается:
94
+ - источник ("a" если это mainnet, "b" если не mainnet)
95
+ - connectionId — хэш действий.
96
+
97
+ Пример:
98
+ >>> _construct_phantom_agent(b"\\x01" * 32, True)
99
+ {"source": "a", "connectionId": b"\\x01"*32}
100
+
101
+ Параметры:
102
+ hash (bytes): хэш действия (32 байта).
103
+ is_mainnet (bool): True если сеть основная, False если тестовая.
104
+
105
+ Возвращает:
106
+ dict: объект phantom_agent.
107
+ """
108
+ return {"source": "a" if is_mainnet else "b", "connectionId": hash}
109
+
110
+
111
+ def _action_hash(
112
+ action: dict[str, Any],
113
+ vault_address: str | None,
114
+ nonce: int,
115
+ expires_after: int | None,
116
+ ) -> bytes:
117
+ r"""Строит хэш действия.
118
+
119
+ Простыми словами:
120
+ Берём действие (например, ордер), сериализуем его через msgpack,
121
+ добавляем nonce, адрес хранилища (если есть), срок действия,
122
+ и всё это хэшируем keccak256.
123
+ Получается уникальный "отпечаток" действия.
124
+
125
+ Пример:
126
+ >>> action = {"type": "order", "amount": 1}
127
+ >>> _action_hash(action, None, 42, None)
128
+ b"\\xab...\\xff" # 32 байта
129
+
130
+ Параметры:
131
+ action (dict): описание действия (например, ордер).
132
+ vault_address (str | None): адрес кошелька/контракта, если есть.
133
+ nonce (int): уникальный счётчик (чтобы нельзя было повторить действие).
134
+ expires_after (int | None): время (в секундах), когда действие протухает.
135
+
136
+ Возвращает:
137
+ bytes: 32-байтовый хэш (keccak256).
138
+ """
139
+ data = msgpack.packb(action)
140
+ data += nonce.to_bytes(8, "big") # type: ignore
141
+ if vault_address is None:
142
+ data += b"\x00"
143
+ else:
144
+ data += b"\x01"
145
+ data += _address_to_bytes(vault_address)
146
+ if expires_after is not None:
147
+ data += b"\x00"
148
+ data += expires_after.to_bytes(8, "big")
149
+ return keccak(data)
150
+
151
+
152
+ def _sign_inner(wallet: LocalAccount, data: dict[str, Any]) -> dict[str, Any]:
153
+ """Подписывает данные EIP-712 через кошелёк.
154
+
155
+ Простыми словами:
156
+ Берём структуру (payload), кодируем её в формат EIP-712
157
+ и просим кошелёк подписать.
158
+ Возвращаем r, s, v — стандартные параметры Ethereum-подписи.
159
+
160
+ Пример:
161
+ >>> _sign_inner(wallet, {...})
162
+ {"r": "0x...", "s": "0x...", "v": 27}
163
+
164
+ Параметры:
165
+ wallet (LocalAccount): объект кошелька.
166
+ data (dict): структура для подписи (EIP-712).
167
+
168
+ Возвращает:
169
+ dict:
170
+ - r (str): часть подписи
171
+ - s (str): часть подписи
172
+ - v (int): "восстановитель" (27 или 28 обычно)
173
+ """
174
+ structured_data = encode_typed_data(full_message=data)
175
+ signed = wallet.sign_message(structured_data)
176
+ return {"r": to_hex(signed["r"]), "s": to_hex(signed["s"]), "v": signed["v"]}
177
+
178
+
179
+ def _sign_l1_action(
180
+ wallet: LocalAccount,
181
+ action: dict[str, Any],
182
+ active_pool: str | None,
183
+ nonce: int,
184
+ expires_after: int | None,
185
+ is_mainnet: bool = True,
186
+ ) -> dict[str, Any]:
187
+ """Подписывает действие для L1 (основного уровня).
188
+
189
+ Простыми словами:
190
+ Это конечная функция, которая собирает всё:
191
+ - делает хэш действия
192
+ - строит phantom_agent
193
+ - формирует payload для подписи
194
+ - подписывает его через кошелёк
195
+ Возвращает r, s, v.
196
+
197
+ Пример:
198
+ >>> _sign_l1_action(wallet, {"type": "order"}, None, 1, None, True)
199
+ {"r": "0x...", "s": "0x...", "v": 27}
200
+
201
+ Параметры:
202
+ wallet (LocalAccount): объект кошелька.
203
+ action (dict): действие (например, ордер).
204
+ active_pool (str | None): адрес пула (если нужен).
205
+ nonce (int): уникальный номер действия.
206
+ expires_after (int | None): срок жизни действия.
207
+ is_mainnet (bool): True — основная сеть, False — тестовая.
208
+
209
+ Возвращает:
210
+ dict:
211
+ - r (str)
212
+ - s (str)
213
+ - v (int)
214
+ """
215
+ hash = _action_hash(action, active_pool, nonce, expires_after)
216
+ phantom_agent = _construct_phantom_agent(hash, is_mainnet)
217
+ data = _l1_payload(phantom_agent)
218
+ return _sign_inner(wallet, data)
219
+
220
+
221
+ def _user_signed_payload(
222
+ primary_type: str,
223
+ payload_types: list[dict[str, str]],
224
+ action: dict[str, Any],
225
+ ) -> dict[str, Any]:
226
+ """Формирует EIP-712 payload для "user-signed" подписи.
227
+
228
+ Простыми словами:
229
+ Это структура для подписи действий от лица пользователя.
230
+ В отличие от L1-подписи (где используется phantom-агент),
231
+ здесь кошелёк подписывает само действие напрямую.
232
+
233
+ ChainID и домен указываются специально, чтобы предотвратить
234
+ повторное воспроизведение (replay) на других цепях.
235
+
236
+ Пример:
237
+ >>> payload = _user_signed_payload(
238
+ ... primary_type="Withdraw",
239
+ ... payload_types=[{"name": "amount", "type": "uint256"}],
240
+ ... action={"amount": 100, "signatureChainId": "0x66eee"},
241
+ ... )
242
+ >>> payload.keys()
243
+ dict_keys(["domain", "types", "primaryType", "message"])
244
+
245
+ Параметры:
246
+ primary_type (str): Основной тип сообщения, например `"Withdraw"`, `"Transfer"`.
247
+ payload_types (list[dict]): Список полей EIP-712 типа, например:
248
+ `[{"name": "amount", "type": "uint256"}, {"name": "recipient", "type": "address"}]`
249
+ action (dict): Само сообщение (поля, которые будут подписаны).
250
+ Должно содержать `"signatureChainId"` (строку в hex).
251
+
252
+ Возвращает:
253
+ dict: Полностью готовая структура EIP-712 для подписи кошельком.
254
+ """
255
+ chain_id = int(action["signatureChainId"], 16)
256
+ return {
257
+ "domain": {
258
+ "name": "HyperliquidSignTransaction",
259
+ "version": "1",
260
+ "chainId": chain_id,
261
+ "verifyingContract": "0x0000000000000000000000000000000000000000",
262
+ },
263
+ "types": {
264
+ primary_type: payload_types,
265
+ "EIP712Domain": [
266
+ {"name": "name", "type": "string"},
267
+ {"name": "version", "type": "string"},
268
+ {"name": "chainId", "type": "uint256"},
269
+ {"name": "verifyingContract", "type": "address"},
270
+ ],
271
+ },
272
+ "primaryType": primary_type,
273
+ "message": action,
274
+ }
275
+
276
+
277
+ def _sign_user_signed_action(
278
+ wallet: LocalAccount,
279
+ action: dict[str, Any],
280
+ payload_types: list[dict[str, str]],
281
+ primary_type: str,
282
+ is_mainnet: bool,
283
+ ) -> dict[str, Any]:
284
+ """Подписывает действие "user-signed" формата (EIP-712).
285
+
286
+ Простыми словами:
287
+ Это альтернатива L1-подписи. Здесь сам пользователь (его кошелёк)
288
+ подписывает объект `action`, который будет исполнен Hyperliquid.
289
+ Отличие от L1: используется другой `chainId` (0x66eee) и поле `"hyperliquidChain"`,
290
+ чтобы действие нельзя было воспроизвести в другой сети.
291
+
292
+ Пример:
293
+ >>> action = {"amount": 100}
294
+ >>> payload_types = [{"name": "amount", "type": "uint256"}]
295
+ >>> sig = _sign_user_signed_action(wallet, action, payload_types, "Withdraw", True)
296
+ >>> sig.keys()
297
+ dict_keys(["r", "s", "v"])
298
+
299
+ Параметры:
300
+ wallet (LocalAccount): Объект кошелька, созданный через `Account.from_key(...)`.
301
+ action (dict): Содержимое действия, которое нужно подписать.
302
+ payload_types (list[dict[str, str]]): Описание полей типа для EIP-712.
303
+ primary_type (str): Основное имя типа, например `"Withdraw"`, `"Transfer"`.
304
+ is_mainnet (bool): True — подпись для основной сети, False — для тестовой.
305
+
306
+ Возвращает:
307
+ dict:
308
+ - r (str): первая часть подписи
309
+ - s (str): вторая часть подписи
310
+ - v (int): "восстановитель" подписи (27 или 28)
311
+ """
312
+ # signatureChainId — цепочка, через которую кошелёк делает подпись (не Hyperliquid chain)
313
+ action["signatureChainId"] = "0x66eee"
314
+ # hyperliquidChain — фактическая среда исполнения
315
+ action["hyperliquidChain"] = "Mainnet" if is_mainnet else "Testnet"
316
+
317
+ data = _user_signed_payload(primary_type, payload_types, action)
318
+ return _sign_inner(wallet, data)
319
+
320
+
321
+ USD_SEND_SIGN_TYPES = [
322
+ {"name": "hyperliquidChain", "type": "string"},
323
+ {"name": "destination", "type": "string"},
324
+ {"name": "amount", "type": "string"},
325
+ {"name": "time", "type": "uint64"},
326
+ ]
327
+
328
+ SPOT_TRANSFER_SIGN_TYPES = [
329
+ {"name": "hyperliquidChain", "type": "string"},
330
+ {"name": "destination", "type": "string"},
331
+ {"name": "token", "type": "string"},
332
+ {"name": "amount", "type": "string"},
333
+ {"name": "time", "type": "uint64"},
334
+ ]
335
+
336
+ WITHDRAW_SIGN_TYPES = [
337
+ {"name": "hyperliquidChain", "type": "string"},
338
+ {"name": "destination", "type": "string"},
339
+ {"name": "amount", "type": "string"},
340
+ {"name": "time", "type": "uint64"},
341
+ ]
342
+
343
+ USD_CLASS_TRANSFER_SIGN_TYPES = [
344
+ {"name": "hyperliquidChain", "type": "string"},
345
+ {"name": "amount", "type": "string"},
346
+ {"name": "toPerp", "type": "bool"},
347
+ {"name": "nonce", "type": "uint64"},
348
+ ]
349
+
350
+ SEND_ASSET_SIGN_TYPES = [
351
+ {"name": "hyperliquidChain", "type": "string"},
352
+ {"name": "destination", "type": "string"},
353
+ {"name": "sourceDex", "type": "string"},
354
+ {"name": "destinationDex", "type": "string"},
355
+ {"name": "token", "type": "string"},
356
+ {"name": "amount", "type": "string"},
357
+ {"name": "fromSubAccount", "type": "string"},
358
+ {"name": "nonce", "type": "uint64"},
359
+ ]
360
+
361
+ STAKING_SIGN_TYPES = [
362
+ {"name": "hyperliquidChain", "type": "string"},
363
+ {"name": "wei", "type": "uint64"},
364
+ {"name": "nonce", "type": "uint64"},
365
+ ]
366
+
367
+ TOKEN_DELEGATE_TYPES = [
368
+ {"name": "hyperliquidChain", "type": "string"},
369
+ {"name": "validator", "type": "address"},
370
+ {"name": "wei", "type": "uint64"},
371
+ {"name": "isUndelegate", "type": "bool"},
372
+ {"name": "nonce", "type": "uint64"},
373
+ ]
374
+
375
+ APPROVE_AGENT_SIGN_TYPES = [
376
+ {"name": "hyperliquidChain", "type": "string"},
377
+ {"name": "agentAddress", "type": "address"},
378
+ {"name": "agentName", "type": "string"},
379
+ {"name": "nonce", "type": "uint64"},
380
+ ]
381
+
382
+ APPROVE_BUILDER_FEE_SIGN_TYPES = [
383
+ {"name": "hyperliquidChain", "type": "string"},
384
+ {"name": "maxFeeRate", "type": "string"},
385
+ {"name": "builder", "type": "address"},
386
+ {"name": "nonce", "type": "uint64"},
387
+ ]
388
+
389
+
390
+ class Client(BaseClient):
391
+ """Клиент для работы с Hyperliquid API."""
392
+
393
+ _BASE_URL = "https://api.hyperliquid.xyz"
394
+ """Базовый URL для REST API Hyperliquid."""
395
+
396
+ _BASE_HEADERS = {"Content-Type": "application/json"}
397
+
398
+ def __init__(
399
+ self,
400
+ session: aiohttp.ClientSession,
401
+ private_key: str | bytes | None = None,
402
+ wallet_address: str | None = None,
403
+ vault_address: str | None = None,
404
+ logger: LoggerLike | None = None,
405
+ max_retries: int = 3,
406
+ retry_delay: int | float = 0.1,
407
+ proxies: list[str] | None = None,
408
+ timeout: int = 10,
409
+ ) -> None:
410
+ """Инициализация клиента.
411
+
412
+ Параметры:
413
+ session (`aiohttp.ClientSession`): Сессия для выполнения HTTP‑запросов.
414
+ private_key (`str | bytes | None`): Приватный ключ API для аутентификации (Hyperliquid).
415
+ wallet_address (`str | None`): Адрес кошелька для аутентификации (Hyperliquid).
416
+ vault_address (`str | None`): Адрес хранилища для аутентификации (Hyperliquid).
417
+ logger (`LoggerLike | None`): Логгер для вывода информации.
418
+ max_retries (`int`): Максимальное количество повторных попыток запроса.
419
+ retry_delay (`int | float`): Задержка между повторными попытками, сек.
420
+ proxies (`list[str] | None`): Список HTTP(S)‑прокси для циклического использования.
421
+ timeout (`int`): Максимальное время ожидания ответа от сервера, сек.
422
+ """
423
+ super().__init__(
424
+ session,
425
+ None,
426
+ None,
427
+ None,
428
+ logger,
429
+ max_retries,
430
+ retry_delay,
431
+ proxies,
432
+ timeout,
433
+ )
434
+ self._vault_address = vault_address
435
+ self._wallet_address = wallet_address
436
+ self._wallet: LocalAccount | None = None
437
+ if private_key is not None:
438
+ # private_key может быть в hex-строке ("0x...") или в байтах
439
+ self._wallet = Account.from_key(private_key)
440
+
441
+ @classmethod
442
+ async def create(
443
+ cls,
444
+ private_key: str | bytes | None = None,
445
+ wallet_address: str | None = None,
446
+ vault_address: str | None = None,
447
+ session: aiohttp.ClientSession | None = None,
448
+ logger: LoggerLike | None = None,
449
+ max_retries: int = 3,
450
+ retry_delay: int | float = 0.1,
451
+ proxies: list[str] | None = None,
452
+ timeout: int = 10,
453
+ ) -> Self:
454
+ """Создаёт инстанцию клиента.
455
+
456
+ Параметры:
457
+ private_key (`str | bytes | None`): Приватный ключ для подписи запросов.
458
+ wallet_address (`str | None`): Адрес кошелька для подписи запросов.
459
+ vault_address (`str | None`): Адрес валита для подписи запросов.
460
+ session (`aiohttp.ClientSession | None`): Сессия для HTTP‑запросов (если не передана, будет создана).
461
+ logger (`LoggerLike | None`): Логгер для вывода информации.
462
+ max_retries (`int`): Максимум повторов при ошибках запроса.
463
+ retry_delay (`int | float`): Задержка между повторами, сек.
464
+ proxies (`list[str] | None`): Список HTTP(S)‑прокси.
465
+ timeout (`int`): Таймаут ответа сервера, сек.
466
+
467
+ Возвращает:
468
+ `Self`: Созданный экземпляр клиента.
469
+ """
470
+ return cls(
471
+ private_key=private_key,
472
+ wallet_address=wallet_address,
473
+ vault_address=vault_address,
474
+ session=session or aiohttp.ClientSession(),
475
+ logger=logger,
476
+ max_retries=max_retries,
477
+ retry_delay=retry_delay,
478
+ proxies=proxies,
479
+ timeout=timeout,
480
+ )
481
+
482
+ async def _make_request(
483
+ self,
484
+ method: Literal["GET", "POST"],
485
+ endpoint: str,
486
+ *,
487
+ data: dict[str, Any] | None = None,
488
+ ) -> Any:
489
+ """Создаёт HTTP-запрос к Hyperliquid API.
490
+
491
+ Параметры:
492
+ method (`Literal["GET", "POST"]`): HTTP-метод запроса.
493
+ endpoint (`str`): Относительный путь эндпоинта.
494
+ data (`dict[str, Any] | None`): Тело запроса для POST-запросов.
495
+
496
+ Возвращает:
497
+ `Any`: Ответ Hyperliquid API.
498
+ """
499
+ filtered_data = filter_params(data) if data is not None else None
500
+
501
+ return await super()._make_request(
502
+ method=method,
503
+ url=self._BASE_URL + endpoint,
504
+ headers=self._BASE_HEADERS,
505
+ data=filtered_data,
506
+ )
507
+
508
+ async def _post_request(self, endpoint: str, data: dict) -> Any:
509
+ """Создание POST-запроса к Hyperliquid API."""
510
+ return await self._make_request("POST", endpoint, data=data)
511
+
512
+ async def request(
513
+ self,
514
+ endpoint: str,
515
+ *,
516
+ data: dict,
517
+ ) -> Any:
518
+ """Специальный метод для выполнения запросов на эндпоинты, которые не обернуты в клиенте.
519
+
520
+ Параметры:
521
+ endpoint (`str`): Относительный путь эндпоинта (например, `/info`).
522
+ data (`dict[str, Any]`): JSON-тело запроса для POST-запросов.
523
+
524
+ Возвращает:
525
+ `Any`: Ответ Hyperliquid API.
526
+ """
527
+ return await self._post_request(endpoint, data=data)
528
+
529
+ # topic: Info endpoint
530
+ # topic: Futures
531
+
532
+ async def perp_dexs(self) -> list[dict | None]:
533
+ """Получение списка доступных перпетуальных DEX.
534
+
535
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-metadata-universe-and-margin-tables
536
+ """
537
+ payload = {"type": "perpDexs"}
538
+
539
+ return await self._post_request("/info", data=payload)
540
+
541
+ async def perp_metadata(self, dex: str | None = None) -> dict:
542
+ """Получение метаданных по перпетуальным контрактам.
543
+
544
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-metadata-universe-and-margin-tables
545
+ """
546
+ payload = {
547
+ "type": "meta",
548
+ "dex": dex,
549
+ }
550
+
551
+ return await self._post_request("/info", data=payload)
552
+
553
+ async def perp_meta_and_asset_contexts(self) -> list[Any]:
554
+ """Получение метаданных и контекстов активов перпетуальных контрактов.
555
+
556
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-asset-contexts-includes-mark-price-current-funding-open-interest-etc
557
+ """
558
+ payload = {"type": "metaAndAssetCtxs"}
559
+
560
+ return await self._post_request("/info", data=payload)
561
+
562
+ async def perp_account_summary(self, user: str, dex: str | None = None) -> dict:
563
+ """Получение сводной информации по аккаунту пользователя в перпетуалах.
564
+
565
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-perpetuals-account-summary
566
+ """
567
+ payload = {
568
+ "type": "clearinghouseState",
569
+ "user": user,
570
+ "dex": dex,
571
+ }
572
+
573
+ return await self._post_request("/info", data=payload)
574
+
575
+ async def perp_user_funding_history(
576
+ self,
577
+ user: str,
578
+ start_time: int,
579
+ end_time: int | None = None,
580
+ ) -> list[dict]:
581
+ """Получение истории фондирования пользователя.
582
+
583
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-a-users-funding-history-or-non-funding-ledger-updates
584
+ """
585
+ payload = {
586
+ "type": "userFunding",
587
+ "user": user,
588
+ "startTime": start_time,
589
+ "endTime": end_time,
590
+ }
591
+
592
+ return await self._post_request("/info", data=payload)
593
+
594
+ async def perp_user_non_funding_ledger_updates(
595
+ self,
596
+ user: str,
597
+ start_time: int,
598
+ end_time: int | None = None,
599
+ ) -> list[dict]:
600
+ """Получение нефондировочных обновлений леджера пользователя.
601
+
602
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-a-users-funding-history-or-non-funding-ledger-updates
603
+ """
604
+ payload = {
605
+ "type": "userNonFundingLedgerUpdates",
606
+ "user": user,
607
+ "startTime": start_time,
608
+ "endTime": end_time,
609
+ }
610
+
611
+ return await self._post_request("/info", data=payload)
612
+
613
+ async def perp_funding_history(
614
+ self,
615
+ coin: str,
616
+ start_time: int,
617
+ end_time: int | None = None,
618
+ ) -> list[dict]:
619
+ """Получение исторических ставок фондирования по монете.
620
+
621
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-historical-funding-rates
622
+ """
623
+ payload = {
624
+ "type": "fundingHistory",
625
+ "coin": coin,
626
+ "startTime": start_time,
627
+ "endTime": end_time,
628
+ }
629
+
630
+ return await self._post_request("/info", data=payload)
631
+
632
+ async def perp_predicted_fundings(self) -> list[list[Any]]:
633
+ """Получение предсказанных ставок фондирования по площадкам.
634
+
635
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-predicted-funding-rates-for-different-venues
636
+ """
637
+ payload = {"type": "predictedFundings"}
638
+
639
+ return await self._post_request("/info", data=payload)
640
+
641
+ async def perps_at_open_interest_cap(self) -> list[str]:
642
+ """Получение списка перпетуалов с достигнутым лимитом открытого интереса.
643
+
644
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#query-perps-at-open-interest-caps
645
+ """
646
+ payload = {"type": "perpsAtOpenInterestCap"}
647
+
648
+ return await self._post_request("/info", data=payload)
649
+
650
+ async def perp_deploy_auction_status(self) -> dict:
651
+ """Получение статуса аукциона развёртывания перпетуалов.
652
+
653
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-information-about-the-perp-deploy-auction
654
+ """
655
+ payload = {"type": "perpDeployAuctionStatus"}
656
+
657
+ return await self._post_request("/info", data=payload)
658
+
659
+ async def perp_active_asset_data(self, user: str, coin: str) -> dict:
660
+ """Получение активных параметров актива пользователя в перпетуалах.
661
+
662
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-active-asset-data
663
+ """
664
+ payload = {
665
+ "type": "activeAssetData",
666
+ "user": user,
667
+ "coin": coin,
668
+ }
669
+
670
+ return await self._post_request("/info", data=payload)
671
+
672
+ async def perp_dex_limits(self, dex: str) -> dict:
673
+ """Получение лимитов для перпетуального DEX, развёрнутого билдерами.
674
+
675
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-builder-deployed-perp-market-limits
676
+ """
677
+ payload = {
678
+ "type": "perpDexLimits",
679
+ "dex": dex,
680
+ }
681
+
682
+ return await self._post_request("/info", data=payload)
683
+
684
+ async def open_orders(self, user: str, dex: str | None = None) -> list[dict]:
685
+ """Получение списка открытых ордеров пользователя.
686
+
687
+ https://docs.chainstack.com/reference/hyperliquid-info-openorders
688
+ """
689
+ payload = {
690
+ "type": "openOrders",
691
+ "user": user,
692
+ "dex": dex,
693
+ }
694
+
695
+ return await self._post_request("/info", data=payload)
696
+
697
+ async def exchange_status(self) -> dict:
698
+ """Получение текущего статуса биржи Hyperliquid.
699
+
700
+ https://docs.chainstack.com/reference/hyperliquid-info-exchangestatus
701
+ """
702
+ payload = {"type": "exchangeStatus"}
703
+
704
+ return await self._post_request("/info", data=payload)
705
+
706
+ async def frontend_open_orders(self, user: str, dex: str | None = None) -> list[dict]:
707
+ """Получение открытых ордеров в формате фронтенда.
708
+
709
+ https://docs.chainstack.com/reference/hyperliquid-info-frontendopenorders
710
+ """
711
+ payload = {
712
+ "type": "frontendOpenOrders",
713
+ "user": user,
714
+ "dex": dex,
715
+ }
716
+
717
+ return await self._post_request("/info", data=payload)
718
+
719
+ async def liquidatable(self, user: str) -> dict:
720
+ """Проверка, подлежит ли аккаунт пользователя ликвидации.
721
+
722
+ https://docs.chainstack.com/reference/hyperliquid-info-liquidatable
723
+ """
724
+ payload = {
725
+ "type": "liquidatable",
726
+ "user": user,
727
+ }
728
+
729
+ return await self._post_request("/info", data=payload)
730
+
731
+ async def max_market_order_ntls(self) -> list[dict]:
732
+ """Получение максимальных объёмов рыночных ордеров по активам.
733
+
734
+ https://docs.chainstack.com/reference/hyperliquid-info-maxmarketorderntls
735
+ """
736
+ payload = {"type": "maxMarketOrderNtls"}
737
+
738
+ return await self._post_request("/info", data=payload)
739
+
740
+ async def vault_summaries(self) -> list[dict]:
741
+ """Получение сводки по всем доступным вултам (vaults).
742
+
743
+ https://docs.chainstack.com/reference/hyperliquid-info-vaultsummaries
744
+ """
745
+ payload = {"type": "vaultSummaries"}
746
+
747
+ return await self._post_request("/info", data=payload)
748
+
749
+ async def user_vault_equities(self, user: str) -> list[dict]:
750
+ """Получение данных об инвестициях пользователя в вулты (vaults).
751
+
752
+ https://docs.chainstack.com/reference/hyperliquid-info-uservaultequities
753
+ """
754
+ payload = {
755
+ "type": "userVaultEquities",
756
+ "user": user,
757
+ }
758
+
759
+ return await self._post_request("/info", data=payload)
760
+
761
+ async def leading_vaults(self, user: str) -> list[dict]:
762
+ """Получение списка вултов (vaults), которыми управляет пользователь.
763
+
764
+ https://docs.chainstack.com/reference/hyperliquid-info-leadingvaults
765
+ """
766
+ payload = {
767
+ "type": "leadingVaults",
768
+ "user": user,
769
+ }
770
+
771
+ return await self._post_request("/info", data=payload)
772
+
773
+ async def extra_agents(self, user: str) -> list[dict]:
774
+ """Получение списка дополнительных агентов пользователя.
775
+
776
+ https://docs.chainstack.com/reference/hyperliquid-info-extraagents
777
+ """
778
+ payload = {
779
+ "type": "extraAgents",
780
+ "user": user,
781
+ }
782
+
783
+ return await self._post_request("/info", data=payload)
784
+
785
+ async def sub_accounts(self, user: str) -> list[dict]:
786
+ """Получение списка саб-аккаунтов пользователя.
787
+
788
+ https://docs.chainstack.com/reference/hyperliquid-info-subaccounts
789
+ """
790
+ payload = {
791
+ "type": "subAccounts",
792
+ "user": user,
793
+ }
794
+
795
+ return await self._post_request("/info", data=payload)
796
+
797
+ async def user_fees(self, user: str) -> dict:
798
+ """Получение информации о торговых комиссиях пользователя.
799
+
800
+ https://docs.chainstack.com/reference/hyperliquid-info-userfees
801
+ """
802
+ payload = {
803
+ "type": "userFees",
804
+ "user": user,
805
+ }
806
+
807
+ return await self._post_request("/info", data=payload)
808
+
809
+ async def user_rate_limit(self, user: str) -> dict:
810
+ """Получение сведений о лимитах запросов пользователя.
811
+
812
+ https://docs.chainstack.com/reference/hyperliquid-info-userratelimit
813
+ """
814
+ payload = {
815
+ "type": "userRateLimit",
816
+ "user": user,
817
+ }
818
+
819
+ return await self._post_request("/info", data=payload)
820
+
821
+ async def delegations(self, user: str) -> list[dict]:
822
+ """Получение списка делегаций пользователя.
823
+
824
+ https://docs.chainstack.com/reference/hyperliquid-info-delegations
825
+ """
826
+ payload = {
827
+ "type": "delegations",
828
+ "user": user,
829
+ }
830
+
831
+ return await self._post_request("/info", data=payload)
832
+
833
+ async def delegator_summary(self, user: str) -> dict:
834
+ """Получение сводки по делегациям пользователя.
835
+
836
+ https://docs.chainstack.com/reference/hyperliquid-info-delegator-summary
837
+ """
838
+ payload = {
839
+ "type": "delegatorSummary",
840
+ "user": user,
841
+ }
842
+
843
+ return await self._post_request("/info", data=payload)
844
+
845
+ async def max_builder_fee(self, user: str, builder: str) -> int:
846
+ """Получение максимальной комиссии билдера, одобренной пользователем.
847
+
848
+ https://docs.chainstack.com/reference/hyperliquid-info-max-builder-fee
849
+ """
850
+ payload = {
851
+ "type": "maxBuilderFee",
852
+ "user": user,
853
+ "builder": builder,
854
+ }
855
+
856
+ return await self._post_request("/info", data=payload)
857
+
858
+ async def user_to_multi_sig_signers(self, user: str) -> list[str]:
859
+ """Получение списка подписантов мультисиг-кошелька пользователя.
860
+
861
+ https://docs.chainstack.com/reference/hyperliquid-info-user-to-multi-sig-signers
862
+ """
863
+ payload = {
864
+ "type": "userToMultiSigSigners",
865
+ "user": user,
866
+ }
867
+
868
+ return await self._post_request("/info", data=payload)
869
+
870
+ async def user_role(self, user: str) -> dict:
871
+ """Получение информации о роли пользователя в системе.
872
+
873
+ https://docs.chainstack.com/reference/hyperliquid-info-user-role
874
+ """
875
+ payload = {
876
+ "type": "userRole",
877
+ "user": user,
878
+ }
879
+
880
+ return await self._post_request("/info", data=payload)
881
+
882
+ async def validator_l1_votes(self) -> list[dict]:
883
+ """Получение сведений о голосах валидаторов на L1.
884
+
885
+ https://docs.chainstack.com/reference/hyperliquid-info-validator-l1-votes
886
+ """
887
+ payload = {"type": "validatorL1Votes"}
888
+
889
+ return await self._post_request("/info", data=payload)
890
+
891
+ async def web_data2(self) -> dict:
892
+ """Получение агрегированных данных для веб-интерфейса.
893
+
894
+ https://docs.chainstack.com/reference/hyperliquid-info-web-data2
895
+ """
896
+ payload = {"type": "webData2"}
897
+
898
+ return await self._post_request("/info", data=payload)
899
+
900
+ async def all_mids(self, dex: str | None = None) -> dict:
901
+ """Получение текущих средних цен по всем активам.
902
+
903
+ https://docs.chainstack.com/reference/hyperliquid-info-allmids
904
+ """
905
+ payload = {
906
+ "type": "allMids",
907
+ "dex": dex,
908
+ }
909
+
910
+ return await self._post_request("/info", data=payload)
911
+
912
+ async def user_fills(
913
+ self,
914
+ user: str,
915
+ aggregate_by_time: bool | None = None,
916
+ ) -> list[dict]:
917
+ """Получение последних исполнений ордеров пользователя.
918
+
919
+ https://docs.chainstack.com/reference/hyperliquid-info-user-fills
920
+ """
921
+ payload = {
922
+ "type": "userFills",
923
+ "user": user,
924
+ "aggregateByTime": aggregate_by_time,
925
+ }
926
+
927
+ return await self._post_request("/info", data=payload)
928
+
929
+ async def user_fills_by_time(
930
+ self,
931
+ user: str,
932
+ start_time: int,
933
+ end_time: int | None = None,
934
+ aggregate_by_time: bool | None = None,
935
+ ) -> list[dict]:
936
+ """Получение исполнений ордеров пользователя за период.
937
+
938
+ https://docs.chainstack.com/reference/hyperliquid-info-user-fills-by-time
939
+ """
940
+ payload = {
941
+ "type": "userFillsByTime",
942
+ "user": user,
943
+ "startTime": start_time,
944
+ "endTime": end_time,
945
+ "aggregateByTime": aggregate_by_time,
946
+ }
947
+
948
+ return await self._post_request("/info", data=payload)
949
+
950
+ async def order_status(self, user: str, oid: int | str) -> dict:
951
+ """Получение статуса ордера по идентификатору.
952
+
953
+ https://docs.chainstack.com/reference/hyperliquid-info-order-status
954
+ """
955
+ payload = {
956
+ "type": "orderStatus",
957
+ "user": user,
958
+ "oid": oid,
959
+ }
960
+
961
+ return await self._post_request("/info", data=payload)
962
+
963
+ async def l2_book(
964
+ self,
965
+ coin: str,
966
+ n_sig_figs: Literal[2, 3, 4, 5] | None = None,
967
+ mantissa: Literal[1, 2, 5] | None = None,
968
+ ) -> list[list[dict]]:
969
+ """Получение снапшота стакана уровня L2 для актива.
970
+
971
+ https://docs.chainstack.com/reference/hyperliquid-info-l2-book
972
+ """
973
+ payload = {
974
+ "type": "l2Book",
975
+ "coin": coin,
976
+ "nSigFigs": n_sig_figs,
977
+ "mantissa": mantissa,
978
+ }
979
+
980
+ return await self._post_request("/info", data=payload)
981
+
982
+ async def batch_clearinghouse_states(
983
+ self,
984
+ users: list[str],
985
+ dex: str | None = None,
986
+ ) -> list[dict | None]:
987
+ """Получение сводок фьючерсных аккаунтов группы пользователей.
988
+
989
+ https://docs.chainstack.com/reference/hyperliquid-info-batch-clearinghouse-states
990
+ """
991
+ payload = {
992
+ "type": "batchClearinghouseStates",
993
+ "users": users,
994
+ "dex": dex,
995
+ }
996
+
997
+ return await self._post_request("/info", data=payload)
998
+
999
+ async def candle_snapshot(
1000
+ self,
1001
+ coin: str,
1002
+ interval: str,
1003
+ start_time: int,
1004
+ end_time: int,
1005
+ ) -> list[dict]:
1006
+ """Получение датасета свечей за указанный период.
1007
+
1008
+ https://docs.chainstack.com/reference/hyperliquid-info-candle-snapshot
1009
+ """
1010
+ payload = {
1011
+ "type": "candleSnapshot",
1012
+ "req": {
1013
+ "coin": coin,
1014
+ "interval": interval,
1015
+ "startTime": start_time,
1016
+ "endTime": end_time,
1017
+ },
1018
+ }
1019
+
1020
+ return await self._post_request("/info", data=payload)
1021
+
1022
+ async def historical_orders(self, user: str) -> list[dict]:
1023
+ """Получение истории ордеров пользователя.
1024
+
1025
+ https://docs.chainstack.com/reference/hyperliquid-info-historical-orders
1026
+ """
1027
+ payload = {
1028
+ "type": "historicalOrders",
1029
+ "user": user,
1030
+ }
1031
+
1032
+ return await self._post_request("/info", data=payload)
1033
+
1034
+ async def user_twap_slice_fills(self, user: str) -> list[dict]:
1035
+ """Получение последних TWAP-исполнений пользователя.
1036
+
1037
+ https://docs.chainstack.com/reference/hyperliquid-info-user-twap-slice-fills
1038
+ """
1039
+ payload = {
1040
+ "type": "userTwapSliceFills",
1041
+ "user": user,
1042
+ }
1043
+
1044
+ return await self._post_request("/info", data=payload)
1045
+
1046
+ async def recent_trades(self, coin: str) -> list[dict]:
1047
+ """Получение последних публичных сделок по активу.
1048
+
1049
+ https://docs.chainstack.com/reference/hyperliquid-info-recent-trades
1050
+ """
1051
+ payload = {
1052
+ "type": "recentTrades",
1053
+ "coin": coin,
1054
+ }
1055
+
1056
+ return await self._post_request("/info", data=payload)
1057
+
1058
+ async def vault_details(self, vault_address: str, user: str | None = None) -> dict:
1059
+ """Получение подробной информации о выбранном вулте.
1060
+
1061
+ https://docs.chainstack.com/reference/hyperliquid-info-vault-details
1062
+ """
1063
+ payload = {
1064
+ "type": "vaultDetails",
1065
+ "vaultAddress": vault_address,
1066
+ "user": user,
1067
+ }
1068
+
1069
+ return await self._post_request("/info", data=payload)
1070
+
1071
+ async def portfolio(self, user: str) -> list[list[Any]]:
1072
+ """Получение данных о производительности портфеля пользователя.
1073
+
1074
+ https://docs.chainstack.com/reference/hyperliquid-info-portfolio
1075
+ """
1076
+ payload = {
1077
+ "type": "portfolio",
1078
+ "user": user,
1079
+ }
1080
+
1081
+ return await self._post_request("/info", data=payload)
1082
+
1083
+ async def referral(self, user: str) -> dict:
1084
+ """Получение реферальной информации пользователя.
1085
+
1086
+ https://docs.chainstack.com/reference/hyperliquid-info-referral
1087
+ """
1088
+ payload = {
1089
+ "type": "referral",
1090
+ "user": user,
1091
+ }
1092
+
1093
+ return await self._post_request("/info", data=payload)
1094
+
1095
+ async def delegator_rewards(self, user: str) -> list[dict]:
1096
+ """Получение истории стейкинг-наград пользователя.
1097
+
1098
+ https://docs.chainstack.com/reference/hyperliquid-info-delegator-rewards
1099
+ """
1100
+ payload = {
1101
+ "type": "delegatorRewards",
1102
+ "user": user,
1103
+ }
1104
+
1105
+ return await self._post_request("/info", data=payload)
1106
+
1107
+ async def gossip_root_ips(self) -> list[str]:
1108
+ """Получение списка узлов для P2P-госсипа.
1109
+
1110
+ https://docs.chainstack.com/reference/hyperliquid-info-gossip-root-ips
1111
+ """
1112
+ payload = {"type": "gossipRootIps"}
1113
+
1114
+ return await self._post_request("/info", data=payload)
1115
+
1116
+ # topic: Spot
1117
+
1118
+ async def spot_metadata(self) -> dict:
1119
+ """Получение спотовой метаинформации о бирже.
1120
+
1121
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-metadata
1122
+ """
1123
+ payload = {"type": "spotMeta"}
1124
+
1125
+ return await self._post_request("/info", data=payload)
1126
+
1127
+ async def spot_meta_and_asset_contexts(self) -> list[Any]:
1128
+ """Получение метаданных и контекстов спотовых активов.
1129
+
1130
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-asset-contexts
1131
+ """
1132
+ payload = {"type": "spotMetaAndAssetCtxs"}
1133
+
1134
+ return await self._post_request("/info", data=payload)
1135
+
1136
+ async def spot_token_balances(self, user: str) -> dict:
1137
+ """Получение балансов токенов пользователя на спотовом рынке.
1138
+
1139
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-a-users-token-balances
1140
+ """
1141
+ payload = {
1142
+ "type": "spotClearinghouseState",
1143
+ "user": user,
1144
+ }
1145
+
1146
+ return await self._post_request("/info", data=payload)
1147
+
1148
+ async def spot_deploy_state(self, user: str) -> dict:
1149
+ """Получение информации об аукционе развёртывания спотового токена.
1150
+
1151
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-information-about-the-spot-deploy-auction
1152
+ """
1153
+ payload = {
1154
+ "type": "spotDeployState",
1155
+ "user": user,
1156
+ }
1157
+
1158
+ return await self._post_request("/info", data=payload)
1159
+
1160
+ async def spot_pair_deploy_auction_status(self) -> dict:
1161
+ """Получение статуса аукциона развёртывания спотовых пар.
1162
+
1163
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-information-about-the-spot-pair-deploy-auction
1164
+ """
1165
+ payload = {"type": "spotPairDeployAuctionStatus"}
1166
+
1167
+ return await self._post_request("/info", data=payload)
1168
+
1169
+ async def spot_token_details(self, token_id: str) -> dict:
1170
+ """Получение подробной информации о спотовом токене.
1171
+
1172
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-information-about-a-token
1173
+ """
1174
+ payload = {
1175
+ "type": "tokenDetails",
1176
+ "tokenId": token_id,
1177
+ }
1178
+
1179
+ return await self._post_request("/info", data=payload)
1180
+
1181
+ # topic: Exchange endpoint
1182
+
1183
+ async def place_order(
1184
+ self,
1185
+ asset: int,
1186
+ is_buy: bool,
1187
+ size: str,
1188
+ reduce_only: bool,
1189
+ order_type: Literal["limit", "trigger"],
1190
+ order_body: dict,
1191
+ price: str | None = None,
1192
+ client_order_id: str | None = None,
1193
+ grouping: Literal["na", "normalTpsl", "positionTpsl"] = "na",
1194
+ builder_address: str | None = None,
1195
+ builder_fee: int | None = None,
1196
+ nonce: int | None = None,
1197
+ expires_after: int | None = None,
1198
+ vault_address: str | None = None,
1199
+ ) -> dict:
1200
+ """Выставление ордера на бирже.
1201
+
1202
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#place-an-order
1203
+ """
1204
+ order_payload = {
1205
+ "a": asset,
1206
+ "b": is_buy,
1207
+ "p": price,
1208
+ "s": size,
1209
+ "r": reduce_only,
1210
+ "t": {order_type: order_body},
1211
+ }
1212
+ if client_order_id is not None:
1213
+ order_payload["c"] = client_order_id
1214
+
1215
+ return await self.batch_place_orders(
1216
+ [order_payload],
1217
+ grouping=grouping,
1218
+ builder_address=builder_address,
1219
+ builder_fee=builder_fee,
1220
+ nonce=nonce,
1221
+ expires_after=expires_after,
1222
+ vault_address=vault_address,
1223
+ )
1224
+
1225
+ async def batch_place_orders(
1226
+ self,
1227
+ orders: list[dict[str, Any]],
1228
+ grouping: Literal["na", "normalTpsl", "positionTpsl"] = "na",
1229
+ builder_address: str | None = None,
1230
+ builder_fee: int | None = None,
1231
+ nonce: int | None = None,
1232
+ expires_after: int | None = None,
1233
+ vault_address: str | None = None,
1234
+ ) -> dict:
1235
+ """Пакетное выставление ордеров на бирже.
1236
+
1237
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#place-an-order
1238
+ """
1239
+ if not orders:
1240
+ raise ValueError("orders must not be empty")
1241
+ if self._wallet is None:
1242
+ raise NotAuthorized("Private key is required for private endpoints.")
1243
+ if builder_address is not None and builder_fee is None:
1244
+ raise TypeError("builder_fee is required when builder_address is provided")
1245
+ if builder_address is None and builder_fee is not None:
1246
+ raise TypeError("builder_address is required when builder_fee is provided")
1247
+
1248
+ required_keys = {"a", "b", "p", "s", "r", "t"}
1249
+ normalized_orders = []
1250
+ for order in orders:
1251
+ missing_keys = required_keys - order.keys()
1252
+ if missing_keys:
1253
+ missing = ", ".join(sorted(missing_keys))
1254
+ raise ValueError(f"order is missing required fields: {missing}")
1255
+ normalized = dict(order)
1256
+ normalized["p"] = str(normalized["p"])
1257
+ normalized["s"] = str(normalized["s"])
1258
+ if normalized.get("c") is None:
1259
+ normalized.pop("c", None)
1260
+ normalized_orders.append(normalized)
1261
+
1262
+ action = {
1263
+ "type": "order",
1264
+ "orders": normalized_orders,
1265
+ "grouping": grouping,
1266
+ }
1267
+ if builder_address is not None:
1268
+ action["builder"] = {"b": builder_address, "f": builder_fee}
1269
+
1270
+ effective_vault = vault_address or self._vault_address
1271
+ action_nonce = nonce if nonce is not None else int(time.time() * 1000)
1272
+ signature = _sign_l1_action(
1273
+ self._wallet,
1274
+ action,
1275
+ effective_vault,
1276
+ action_nonce,
1277
+ expires_after,
1278
+ )
1279
+
1280
+ payload = {
1281
+ "action": action,
1282
+ "nonce": action_nonce,
1283
+ "signature": signature,
1284
+ }
1285
+ if effective_vault is not None:
1286
+ payload["vaultAddress"] = effective_vault
1287
+ if expires_after is not None:
1288
+ payload["expiresAfter"] = expires_after
1289
+
1290
+ return await self._post_request("/exchange", data=payload)
1291
+
1292
+ async def cancel_order(
1293
+ self,
1294
+ asset: int,
1295
+ order_id: int,
1296
+ nonce: int | None = None,
1297
+ expires_after: int | None = None,
1298
+ vault_address: str | None = None,
1299
+ ) -> dict:
1300
+ """Отмена ордера по идентификатору.
1301
+
1302
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s
1303
+ """
1304
+ return await self.cancel_orders(
1305
+ cancels=[{"a": asset, "o": order_id}],
1306
+ nonce=nonce,
1307
+ expires_after=expires_after,
1308
+ vault_address=vault_address,
1309
+ )
1310
+
1311
+ async def cancel_orders(
1312
+ self,
1313
+ cancels: list[dict[str, int | str]],
1314
+ nonce: int | None = None,
1315
+ expires_after: int | None = None,
1316
+ vault_address: str | None = None,
1317
+ ) -> dict:
1318
+ """Отмена ордеров по идентификатору ордера.
1319
+
1320
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s
1321
+ """
1322
+ if not cancels:
1323
+ raise ValueError("cancels must not be empty")
1324
+ if self._wallet is None:
1325
+ raise NotAuthorized("Private key is required for private endpoints.")
1326
+
1327
+ normalized: list[dict[str, int]] = []
1328
+ for cancel in cancels:
1329
+ missing_keys = {"a", "o"} - cancel.keys()
1330
+ if missing_keys:
1331
+ missing = ", ".join(sorted(missing_keys))
1332
+ raise ValueError(f"cancel entry is missing required fields: {missing}")
1333
+ normalized.append(
1334
+ {
1335
+ "a": int(cancel["a"]),
1336
+ "o": int(cancel["o"]),
1337
+ }
1338
+ )
1339
+
1340
+ action = {"type": "cancel", "cancels": normalized}
1341
+
1342
+ effective_vault = vault_address or self._vault_address
1343
+ action_nonce = nonce if nonce is not None else int(time.time() * 1000)
1344
+ signature = _sign_l1_action(
1345
+ self._wallet,
1346
+ action,
1347
+ effective_vault,
1348
+ action_nonce,
1349
+ expires_after,
1350
+ )
1351
+
1352
+ payload: dict[str, Any] = {
1353
+ "action": action,
1354
+ "nonce": action_nonce,
1355
+ "signature": signature,
1356
+ }
1357
+ if effective_vault is not None:
1358
+ payload["vaultAddress"] = effective_vault
1359
+ if expires_after is not None:
1360
+ payload["expiresAfter"] = expires_after
1361
+
1362
+ return await self._post_request("/exchange", data=payload)
1363
+
1364
+ async def cancel_order_by_cloid(
1365
+ self,
1366
+ asset: int,
1367
+ client_order_id: str,
1368
+ nonce: int | None = None,
1369
+ expires_after: int | None = None,
1370
+ vault_address: str | None = None,
1371
+ ) -> dict:
1372
+ """Отмена ордера по клиентскому идентификатору.
1373
+
1374
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid
1375
+ """
1376
+ return await self.cancel_orders_by_cloid(
1377
+ cancels=[{"asset": asset, "cloid": client_order_id}],
1378
+ nonce=nonce,
1379
+ expires_after=expires_after,
1380
+ vault_address=vault_address,
1381
+ )
1382
+
1383
+ async def cancel_orders_by_cloid(
1384
+ self,
1385
+ cancels: list[dict[str, Any]],
1386
+ nonce: int | None = None,
1387
+ expires_after: int | None = None,
1388
+ vault_address: str | None = None,
1389
+ ) -> dict:
1390
+ """Отмена ордеров по клиентскому идентификатору.
1391
+
1392
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-order-s-by-cloid
1393
+ """
1394
+ if not cancels:
1395
+ raise ValueError("cancels must not be empty")
1396
+ if self._wallet is None:
1397
+ raise NotAuthorized("Private key is required for private endpoints.")
1398
+
1399
+ normalized: list[dict[str, Any]] = []
1400
+ for cancel in cancels:
1401
+ missing_keys = {"asset", "cloid"} - cancel.keys()
1402
+ if missing_keys:
1403
+ missing = ", ".join(sorted(missing_keys))
1404
+ raise ValueError(f"cancel entry is missing required fields: {missing}")
1405
+ normalized.append(
1406
+ {
1407
+ "asset": int(cancel["asset"]),
1408
+ "cloid": str(cancel["cloid"]),
1409
+ }
1410
+ )
1411
+
1412
+ action = {"type": "cancelByCloid", "cancels": normalized}
1413
+
1414
+ effective_vault = vault_address or self._vault_address
1415
+ action_nonce = nonce if nonce is not None else int(time.time() * 1000)
1416
+ signature = _sign_l1_action(
1417
+ self._wallet,
1418
+ action,
1419
+ effective_vault,
1420
+ action_nonce,
1421
+ expires_after,
1422
+ )
1423
+
1424
+ payload: dict[str, Any] = {
1425
+ "action": action,
1426
+ "nonce": action_nonce,
1427
+ "signature": signature,
1428
+ }
1429
+ if effective_vault is not None:
1430
+ payload["vaultAddress"] = effective_vault
1431
+ if expires_after is not None:
1432
+ payload["expiresAfter"] = expires_after
1433
+
1434
+ return await self._post_request("/exchange", data=payload)
1435
+
1436
+ async def schedule_cancel(
1437
+ self,
1438
+ time_ms: int | None = None,
1439
+ nonce: int | None = None,
1440
+ expires_after: int | None = None,
1441
+ vault_address: str | None = None,
1442
+ ) -> dict:
1443
+ """Планирование массовой отмены ордеров.
1444
+
1445
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#schedule-cancel-dead-mans-switch
1446
+ """
1447
+ if self._wallet is None:
1448
+ raise NotAuthorized("Private key is required for private endpoints.")
1449
+
1450
+ action: dict[str, Any] = {"type": "scheduleCancel"}
1451
+ if time_ms is not None:
1452
+ action["time"] = time_ms
1453
+
1454
+ effective_vault = vault_address or self._vault_address
1455
+ action_nonce = nonce if nonce is not None else int(time.time() * 1000)
1456
+ signature = _sign_l1_action(
1457
+ self._wallet,
1458
+ action,
1459
+ effective_vault,
1460
+ action_nonce,
1461
+ expires_after,
1462
+ )
1463
+
1464
+ payload: dict[str, Any] = {
1465
+ "action": action,
1466
+ "nonce": action_nonce,
1467
+ "signature": signature,
1468
+ }
1469
+ if effective_vault is not None:
1470
+ payload["vaultAddress"] = effective_vault
1471
+ if expires_after is not None:
1472
+ payload["expiresAfter"] = expires_after
1473
+
1474
+ return await self._post_request("/exchange", data=payload)
1475
+
1476
+ async def modify_order(
1477
+ self,
1478
+ order_id: int | str,
1479
+ asset: int,
1480
+ is_buy: bool,
1481
+ price: str | float,
1482
+ size: str | float,
1483
+ reduce_only: bool,
1484
+ order_type: Literal["limit", "trigger"],
1485
+ order_body: dict[str, Any],
1486
+ client_order_id: str | None = None,
1487
+ nonce: int | None = None,
1488
+ expires_after: int | None = None,
1489
+ vault_address: str | None = None,
1490
+ ) -> dict:
1491
+ """Модификация существующего ордера.
1492
+
1493
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#modify-an-order
1494
+ """
1495
+ order_payload: dict[str, Any] = {
1496
+ "a": asset,
1497
+ "b": is_buy,
1498
+ "p": str(price),
1499
+ "s": str(size),
1500
+ "r": reduce_only,
1501
+ "t": {order_type: order_body},
1502
+ }
1503
+ if client_order_id is not None:
1504
+ order_payload["c"] = client_order_id
1505
+
1506
+ return await self.batch_modify_orders(
1507
+ modifies=[{"oid": order_id, "order": order_payload}],
1508
+ nonce=nonce,
1509
+ expires_after=expires_after,
1510
+ vault_address=vault_address,
1511
+ )
1512
+
1513
+ async def batch_modify_orders(
1514
+ self,
1515
+ modifies: list[dict[str, Any]],
1516
+ nonce: int | None = None,
1517
+ expires_after: int | None = None,
1518
+ vault_address: str | None = None,
1519
+ ) -> dict:
1520
+ """Пакетная модификация ордеров.
1521
+
1522
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#modify-multiple-orders
1523
+ """
1524
+ if not modifies:
1525
+ raise ValueError("modifies must not be empty")
1526
+ if self._wallet is None:
1527
+ raise NotAuthorized("Private key is required for private endpoints.")
1528
+
1529
+ normalized: list[dict[str, Any]] = []
1530
+ for modify in modifies:
1531
+ missing_keys = {"oid", "order"} - modify.keys()
1532
+ if missing_keys:
1533
+ missing = ", ".join(sorted(missing_keys))
1534
+ raise ValueError(f"modify entry is missing required fields: {missing}")
1535
+ order = dict(modify["order"])
1536
+ required_order_keys = {"a", "b", "p", "s", "r", "t"}
1537
+ missing_order_keys = required_order_keys - order.keys()
1538
+ if missing_order_keys:
1539
+ missing = ", ".join(sorted(missing_order_keys))
1540
+ raise ValueError(f"order payload is missing required fields: {missing}")
1541
+ order["p"] = str(order["p"])
1542
+ order["s"] = str(order["s"])
1543
+ if order.get("c") is None:
1544
+ order.pop("c", None)
1545
+ normalized.append(
1546
+ {
1547
+ "oid": modify["oid"],
1548
+ "order": order,
1549
+ }
1550
+ )
1551
+
1552
+ action = {"type": "batchModify", "modifies": normalized}
1553
+
1554
+ effective_vault = vault_address or self._vault_address
1555
+ action_nonce = nonce if nonce is not None else int(time.time() * 1000)
1556
+ signature = _sign_l1_action(
1557
+ self._wallet,
1558
+ action,
1559
+ effective_vault,
1560
+ action_nonce,
1561
+ expires_after,
1562
+ )
1563
+
1564
+ payload: dict[str, Any] = {
1565
+ "action": action,
1566
+ "nonce": action_nonce,
1567
+ "signature": signature,
1568
+ }
1569
+ if effective_vault is not None:
1570
+ payload["vaultAddress"] = effective_vault
1571
+ if expires_after is not None:
1572
+ payload["expiresAfter"] = expires_after
1573
+
1574
+ return await self._post_request("/exchange", data=payload)
1575
+
1576
+ async def update_leverage(
1577
+ self,
1578
+ asset: int,
1579
+ is_cross: bool,
1580
+ leverage: int,
1581
+ nonce: int | None = None,
1582
+ expires_after: int | None = None,
1583
+ vault_address: str | None = None,
1584
+ ) -> dict:
1585
+ """Обновление кредитного плеча по активу.
1586
+
1587
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#update-leverage
1588
+ """
1589
+ if self._wallet is None:
1590
+ raise NotAuthorized("Private key is required for private endpoints.")
1591
+
1592
+ action = {
1593
+ "type": "updateLeverage",
1594
+ "asset": asset,
1595
+ "isCross": is_cross,
1596
+ "leverage": leverage,
1597
+ }
1598
+
1599
+ effective_vault = vault_address or self._vault_address
1600
+ action_nonce = nonce if nonce is not None else int(time.time() * 1000)
1601
+ signature = _sign_l1_action(
1602
+ self._wallet,
1603
+ action,
1604
+ effective_vault,
1605
+ action_nonce,
1606
+ expires_after,
1607
+ )
1608
+
1609
+ payload: dict[str, Any] = {
1610
+ "action": action,
1611
+ "nonce": action_nonce,
1612
+ "signature": signature,
1613
+ }
1614
+ if effective_vault is not None:
1615
+ payload["vaultAddress"] = effective_vault
1616
+ if expires_after is not None:
1617
+ payload["expiresAfter"] = expires_after
1618
+
1619
+ return await self._post_request("/exchange", data=payload)
1620
+
1621
+ async def update_isolated_margin(
1622
+ self,
1623
+ asset: int,
1624
+ is_buy: bool,
1625
+ notional_change: int,
1626
+ nonce: int | None = None,
1627
+ expires_after: int | None = None,
1628
+ vault_address: str | None = None,
1629
+ ) -> dict:
1630
+ """Обновление маржи изолированной позиции.
1631
+
1632
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#update-isolated-margin
1633
+ """
1634
+ if self._wallet is None:
1635
+ raise NotAuthorized("Private key is required for private endpoints.")
1636
+
1637
+ action = {
1638
+ "type": "updateIsolatedMargin",
1639
+ "asset": asset,
1640
+ "isBuy": is_buy,
1641
+ "ntli": notional_change,
1642
+ }
1643
+
1644
+ effective_vault = vault_address or self._vault_address
1645
+ action_nonce = nonce if nonce is not None else int(time.time() * 1000)
1646
+ signature = _sign_l1_action(
1647
+ self._wallet,
1648
+ action,
1649
+ effective_vault,
1650
+ action_nonce,
1651
+ expires_after,
1652
+ )
1653
+
1654
+ payload: dict[str, Any] = {
1655
+ "action": action,
1656
+ "nonce": action_nonce,
1657
+ "signature": signature,
1658
+ }
1659
+ if effective_vault is not None:
1660
+ payload["vaultAddress"] = effective_vault
1661
+ if expires_after is not None:
1662
+ payload["expiresAfter"] = expires_after
1663
+
1664
+ return await self._post_request("/exchange", data=payload)
1665
+
1666
+ async def usd_send(
1667
+ self,
1668
+ hyperliquid_chain: Literal["Mainnet", "Testnet"],
1669
+ signature_chain_id: str,
1670
+ destination: str,
1671
+ amount: str,
1672
+ time_ms: int,
1673
+ nonce: int | None = None,
1674
+ ) -> dict:
1675
+ """Перевод USDC между пользователями Hyperliquid.
1676
+
1677
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#core-usdc-transfer
1678
+ """
1679
+ if self._wallet is None:
1680
+ raise NotAuthorized("Private key is required for private endpoints.")
1681
+
1682
+ action = {
1683
+ "type": "usdSend",
1684
+ "hyperliquidChain": hyperliquid_chain,
1685
+ "signatureChainId": signature_chain_id,
1686
+ "destination": destination,
1687
+ "amount": amount,
1688
+ "time": time_ms,
1689
+ }
1690
+ action_nonce = nonce if nonce is not None else time_ms
1691
+ is_mainnet = hyperliquid_chain == "Mainnet"
1692
+ signature = _sign_user_signed_action(
1693
+ self._wallet,
1694
+ action,
1695
+ USD_SEND_SIGN_TYPES,
1696
+ "HyperliquidTransaction:UsdSend",
1697
+ is_mainnet,
1698
+ )
1699
+
1700
+ payload = {
1701
+ "action": action,
1702
+ "nonce": action_nonce,
1703
+ "signature": signature,
1704
+ }
1705
+
1706
+ return await self._post_request("/exchange", data=payload)
1707
+
1708
+ async def spot_send(
1709
+ self,
1710
+ hyperliquid_chain: Literal["Mainnet", "Testnet"],
1711
+ signature_chain_id: str,
1712
+ destination: str,
1713
+ token: str,
1714
+ amount: str,
1715
+ time_ms: int,
1716
+ nonce: int | None = None,
1717
+ ) -> dict:
1718
+ """Перевод спотового актива между пользователями Hyperliquid.
1719
+
1720
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#core-spot-transfer
1721
+ """
1722
+ if self._wallet is None:
1723
+ raise NotAuthorized("Private key is required for private endpoints.")
1724
+
1725
+ action = {
1726
+ "type": "spotSend",
1727
+ "hyperliquidChain": hyperliquid_chain,
1728
+ "signatureChainId": signature_chain_id,
1729
+ "destination": destination,
1730
+ "token": token,
1731
+ "amount": amount,
1732
+ "time": time_ms,
1733
+ }
1734
+ action_nonce = nonce if nonce is not None else time_ms
1735
+ is_mainnet = hyperliquid_chain == "Mainnet"
1736
+ signature = _sign_user_signed_action(
1737
+ self._wallet,
1738
+ action,
1739
+ SPOT_TRANSFER_SIGN_TYPES,
1740
+ "HyperliquidTransaction:SpotSend",
1741
+ is_mainnet,
1742
+ )
1743
+
1744
+ payload = {
1745
+ "action": action,
1746
+ "nonce": action_nonce,
1747
+ "signature": signature,
1748
+ }
1749
+
1750
+ return await self._post_request("/exchange", data=payload)
1751
+
1752
+ async def initiate_withdrawal(
1753
+ self,
1754
+ hyperliquid_chain: Literal["Mainnet", "Testnet"],
1755
+ signature_chain_id: str,
1756
+ amount: str,
1757
+ time_ms: int,
1758
+ destination: str,
1759
+ nonce: int | None = None,
1760
+ ) -> dict:
1761
+ """Инициация вывода средств на L1.
1762
+
1763
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#initiate-a-withdrawal-request
1764
+ """
1765
+ if self._wallet is None:
1766
+ raise NotAuthorized("Private key is required for private endpoints.")
1767
+
1768
+ action = {
1769
+ "type": "withdraw3",
1770
+ "hyperliquidChain": hyperliquid_chain,
1771
+ "signatureChainId": signature_chain_id,
1772
+ "amount": amount,
1773
+ "time": time_ms,
1774
+ "destination": destination,
1775
+ }
1776
+ action_nonce = nonce if nonce is not None else time_ms
1777
+ is_mainnet = hyperliquid_chain == "Mainnet"
1778
+ signature = _sign_user_signed_action(
1779
+ self._wallet,
1780
+ action,
1781
+ WITHDRAW_SIGN_TYPES,
1782
+ "HyperliquidTransaction:Withdraw",
1783
+ is_mainnet,
1784
+ )
1785
+
1786
+ payload = {
1787
+ "action": action,
1788
+ "nonce": action_nonce,
1789
+ "signature": signature,
1790
+ }
1791
+
1792
+ return await self._post_request("/exchange", data=payload)
1793
+
1794
+ async def usd_class_transfer(
1795
+ self,
1796
+ hyperliquid_chain: Literal["Mainnet", "Testnet"],
1797
+ signature_chain_id: str,
1798
+ amount: str,
1799
+ to_perp: bool,
1800
+ subaccount: str | None = None,
1801
+ ) -> dict:
1802
+ """Перевод USDC между спотовым и перпетуальным аккаунтами.
1803
+
1804
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#transfer-from-spot-account-to-perp-account-and-vice-versa
1805
+ """
1806
+ if self._wallet is None:
1807
+ raise NotAuthorized("Private key is required for private endpoints.")
1808
+
1809
+ amount_field = amount
1810
+ if subaccount is not None:
1811
+ amount_field = f"{amount} subaccount:{subaccount}"
1812
+
1813
+ nonce = int(time.time() * 1000)
1814
+
1815
+ action = {
1816
+ "type": "usdClassTransfer",
1817
+ "hyperliquidChain": hyperliquid_chain,
1818
+ "signatureChainId": signature_chain_id,
1819
+ "amount": amount_field,
1820
+ "toPerp": to_perp,
1821
+ "nonce": nonce,
1822
+ }
1823
+ is_mainnet = hyperliquid_chain == "Mainnet"
1824
+ signature = _sign_user_signed_action(
1825
+ self._wallet,
1826
+ action,
1827
+ USD_CLASS_TRANSFER_SIGN_TYPES,
1828
+ "HyperliquidTransaction:UsdClassTransfer",
1829
+ is_mainnet,
1830
+ )
1831
+
1832
+ payload = {
1833
+ "action": action,
1834
+ "nonce": nonce,
1835
+ "signature": signature,
1836
+ }
1837
+
1838
+ return await self._post_request("/exchange", data=payload)
1839
+
1840
+ async def send_asset(
1841
+ self,
1842
+ hyperliquid_chain: Literal["Mainnet", "Testnet"],
1843
+ signature_chain_id: str,
1844
+ destination: str,
1845
+ source_dex: str,
1846
+ destination_dex: str,
1847
+ token: str,
1848
+ amount: str,
1849
+ from_subaccount: str,
1850
+ nonce_value: int,
1851
+ nonce: int | None = None,
1852
+ ) -> dict:
1853
+ """Перевод токена между балансами и субаккаунтами (тестнет).
1854
+
1855
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#send-asset-testnet-only
1856
+ """
1857
+ if self._wallet is None:
1858
+ raise NotAuthorized("Private key is required for private endpoints.")
1859
+
1860
+ action = {
1861
+ "type": "sendAsset",
1862
+ "hyperliquidChain": hyperliquid_chain,
1863
+ "signatureChainId": signature_chain_id,
1864
+ "destination": destination,
1865
+ "sourceDex": source_dex,
1866
+ "destinationDex": destination_dex,
1867
+ "token": token,
1868
+ "amount": amount,
1869
+ "fromSubAccount": from_subaccount,
1870
+ "nonce": nonce_value,
1871
+ }
1872
+ action_nonce = nonce if nonce is not None else nonce_value
1873
+ is_mainnet = hyperliquid_chain == "Mainnet"
1874
+ signature = _sign_user_signed_action(
1875
+ self._wallet,
1876
+ action,
1877
+ SEND_ASSET_SIGN_TYPES,
1878
+ "HyperliquidTransaction:SendAsset",
1879
+ is_mainnet,
1880
+ )
1881
+
1882
+ payload = {
1883
+ "action": action,
1884
+ "nonce": action_nonce,
1885
+ "signature": signature,
1886
+ }
1887
+
1888
+ return await self._post_request("/exchange", data=payload)
1889
+
1890
+ async def staking_deposit(
1891
+ self,
1892
+ hyperliquid_chain: Literal["Mainnet", "Testnet"],
1893
+ signature_chain_id: str,
1894
+ wei_amount: int,
1895
+ nonce_value: int,
1896
+ nonce: int | None = None,
1897
+ ) -> dict:
1898
+ """Депозит нативного токена в стейкинг.
1899
+
1900
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#deposit-into-staking
1901
+ """
1902
+ if self._wallet is None:
1903
+ raise NotAuthorized("Private key is required for private endpoints.")
1904
+
1905
+ action = {
1906
+ "type": "cDeposit",
1907
+ "hyperliquidChain": hyperliquid_chain,
1908
+ "signatureChainId": signature_chain_id,
1909
+ "wei": wei_amount,
1910
+ "nonce": nonce_value,
1911
+ }
1912
+ action_nonce = nonce if nonce is not None else nonce_value
1913
+ is_mainnet = hyperliquid_chain == "Mainnet"
1914
+ signature = _sign_user_signed_action(
1915
+ self._wallet,
1916
+ action,
1917
+ STAKING_SIGN_TYPES,
1918
+ "HyperliquidTransaction:CDeposit",
1919
+ is_mainnet,
1920
+ )
1921
+
1922
+ payload = {
1923
+ "action": action,
1924
+ "nonce": action_nonce,
1925
+ "signature": signature,
1926
+ }
1927
+
1928
+ return await self._post_request("/exchange", data=payload)
1929
+
1930
+ async def staking_withdraw(
1931
+ self,
1932
+ hyperliquid_chain: Literal["Mainnet", "Testnet"],
1933
+ signature_chain_id: str,
1934
+ wei_amount: int,
1935
+ nonce_value: int,
1936
+ nonce: int | None = None,
1937
+ ) -> dict:
1938
+ """Вывод нативного токена из стейкинга.
1939
+
1940
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#withdraw-from-staking
1941
+ """
1942
+ if self._wallet is None:
1943
+ raise NotAuthorized("Private key is required for private endpoints.")
1944
+
1945
+ action = {
1946
+ "type": "cWithdraw",
1947
+ "hyperliquidChain": hyperliquid_chain,
1948
+ "signatureChainId": signature_chain_id,
1949
+ "wei": wei_amount,
1950
+ "nonce": nonce_value,
1951
+ }
1952
+ action_nonce = nonce if nonce is not None else nonce_value
1953
+ is_mainnet = hyperliquid_chain == "Mainnet"
1954
+ signature = _sign_user_signed_action(
1955
+ self._wallet,
1956
+ action,
1957
+ STAKING_SIGN_TYPES,
1958
+ "HyperliquidTransaction:CWithdraw",
1959
+ is_mainnet,
1960
+ )
1961
+
1962
+ payload = {
1963
+ "action": action,
1964
+ "nonce": action_nonce,
1965
+ "signature": signature,
1966
+ }
1967
+
1968
+ return await self._post_request("/exchange", data=payload)
1969
+
1970
+ async def token_delegate(
1971
+ self,
1972
+ hyperliquid_chain: Literal["Mainnet", "Testnet"],
1973
+ signature_chain_id: str,
1974
+ validator: str,
1975
+ is_undelegate: bool,
1976
+ wei_amount: int,
1977
+ nonce_value: int,
1978
+ nonce: int | None = None,
1979
+ ) -> dict:
1980
+ """Делегирование или отзыв делегирования нативного токена валидатору.
1981
+
1982
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#delegate-or-undelegate-stake-from-validator
1983
+ """
1984
+ if self._wallet is None:
1985
+ raise NotAuthorized("Private key is required for private endpoints.")
1986
+
1987
+ action = {
1988
+ "type": "tokenDelegate",
1989
+ "hyperliquidChain": hyperliquid_chain,
1990
+ "signatureChainId": signature_chain_id,
1991
+ "validator": validator,
1992
+ "isUndelegate": is_undelegate,
1993
+ "wei": wei_amount,
1994
+ "nonce": nonce_value,
1995
+ }
1996
+ action_nonce = nonce if nonce is not None else nonce_value
1997
+ is_mainnet = hyperliquid_chain == "Mainnet"
1998
+ signature = _sign_user_signed_action(
1999
+ self._wallet,
2000
+ action,
2001
+ TOKEN_DELEGATE_TYPES,
2002
+ "HyperliquidTransaction:TokenDelegate",
2003
+ is_mainnet,
2004
+ )
2005
+
2006
+ payload = {
2007
+ "action": action,
2008
+ "nonce": action_nonce,
2009
+ "signature": signature,
2010
+ }
2011
+
2012
+ return await self._post_request("/exchange", data=payload)
2013
+
2014
+ async def vault_transfer(
2015
+ self,
2016
+ vault_address: str,
2017
+ is_deposit: bool,
2018
+ usd: int,
2019
+ nonce: int | None = None,
2020
+ expires_after: int | None = None,
2021
+ signing_vault_address: str | None = None,
2022
+ ) -> dict:
2023
+ """Перевод средств в или из хранилища.
2024
+
2025
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#deposit-or-withdraw-from-a-vault
2026
+ """
2027
+ if self._wallet is None:
2028
+ raise NotAuthorized("Private key is required for private endpoints.")
2029
+
2030
+ action = {
2031
+ "type": "vaultTransfer",
2032
+ "vaultAddress": vault_address,
2033
+ "isDeposit": is_deposit,
2034
+ "usd": usd,
2035
+ }
2036
+
2037
+ effective_vault = signing_vault_address or self._vault_address
2038
+ action_nonce = nonce if nonce is not None else int(time.time() * 1000)
2039
+ signature = _sign_l1_action(
2040
+ self._wallet,
2041
+ action,
2042
+ effective_vault,
2043
+ action_nonce,
2044
+ expires_after,
2045
+ )
2046
+
2047
+ payload: dict[str, Any] = {
2048
+ "action": action,
2049
+ "nonce": action_nonce,
2050
+ "signature": signature,
2051
+ }
2052
+ if effective_vault is not None:
2053
+ payload["vaultAddress"] = effective_vault
2054
+ if expires_after is not None:
2055
+ payload["expiresAfter"] = expires_after
2056
+
2057
+ return await self._post_request("/exchange", data=payload)
2058
+
2059
+ async def approve_agent(
2060
+ self,
2061
+ hyperliquid_chain: Literal["Mainnet", "Testnet"],
2062
+ signature_chain_id: str,
2063
+ agent_address: str,
2064
+ nonce_value: int,
2065
+ agent_name: str | None = None,
2066
+ nonce: int | None = None,
2067
+ ) -> dict:
2068
+ """Одобрение API-кошелька.
2069
+
2070
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#approve-an-api-wallet
2071
+ """
2072
+ if self._wallet is None:
2073
+ raise NotAuthorized("Private key is required for private endpoints.")
2074
+
2075
+ action: dict[str, Any] = {
2076
+ "type": "approveAgent",
2077
+ "hyperliquidChain": hyperliquid_chain,
2078
+ "signatureChainId": signature_chain_id,
2079
+ "agentAddress": agent_address,
2080
+ "nonce": nonce_value,
2081
+ }
2082
+ if agent_name is not None:
2083
+ action["agentName"] = agent_name
2084
+ else:
2085
+ action["agentName"] = ""
2086
+ action_nonce = nonce if nonce is not None else nonce_value
2087
+ is_mainnet = hyperliquid_chain == "Mainnet"
2088
+ signature = _sign_user_signed_action(
2089
+ self._wallet,
2090
+ action,
2091
+ APPROVE_AGENT_SIGN_TYPES,
2092
+ "HyperliquidTransaction:ApproveAgent",
2093
+ is_mainnet,
2094
+ )
2095
+
2096
+ payload = {
2097
+ "action": action,
2098
+ "nonce": action_nonce,
2099
+ "signature": signature,
2100
+ }
2101
+
2102
+ return await self._post_request("/exchange", data=payload)
2103
+
2104
+ async def approve_builder_fee(
2105
+ self,
2106
+ hyperliquid_chain: Literal["Mainnet", "Testnet"],
2107
+ signature_chain_id: str,
2108
+ max_fee_rate: str,
2109
+ builder_address: str,
2110
+ nonce_value: int,
2111
+ nonce: int | None = None,
2112
+ ) -> dict:
2113
+ """Одобрение максимальной комиссии билдера.
2114
+
2115
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#approve-a-builder-fee
2116
+ """
2117
+ if self._wallet is None:
2118
+ raise NotAuthorized("Private key is required for private endpoints.")
2119
+
2120
+ action = {
2121
+ "type": "approveBuilderFee",
2122
+ "hyperliquidChain": hyperliquid_chain,
2123
+ "signatureChainId": signature_chain_id,
2124
+ "maxFeeRate": max_fee_rate,
2125
+ "builder": builder_address,
2126
+ "nonce": nonce_value,
2127
+ }
2128
+ action_nonce = nonce if nonce is not None else nonce_value
2129
+ is_mainnet = hyperliquid_chain == "Mainnet"
2130
+ signature = _sign_user_signed_action(
2131
+ self._wallet,
2132
+ action,
2133
+ APPROVE_BUILDER_FEE_SIGN_TYPES,
2134
+ "HyperliquidTransaction:ApproveBuilderFee",
2135
+ is_mainnet,
2136
+ )
2137
+
2138
+ payload = {
2139
+ "action": action,
2140
+ "nonce": action_nonce,
2141
+ "signature": signature,
2142
+ }
2143
+
2144
+ return await self._post_request("/exchange", data=payload)
2145
+
2146
+ async def place_twap_order(
2147
+ self,
2148
+ asset: int,
2149
+ is_buy: bool,
2150
+ size: str | float,
2151
+ reduce_only: bool,
2152
+ minutes: int,
2153
+ randomize: bool,
2154
+ nonce: int | None = None,
2155
+ expires_after: int | None = None,
2156
+ vault_address: str | None = None,
2157
+ ) -> dict:
2158
+ """Создание TWAP-ордера.
2159
+
2160
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#place-a-twap-order
2161
+ """
2162
+ if self._wallet is None:
2163
+ raise NotAuthorized("Private key is required for private endpoints.")
2164
+
2165
+ action = {
2166
+ "type": "twapOrder",
2167
+ "twap": {
2168
+ "a": asset,
2169
+ "b": is_buy,
2170
+ "s": str(size),
2171
+ "r": reduce_only,
2172
+ "m": minutes,
2173
+ "t": randomize,
2174
+ },
2175
+ }
2176
+
2177
+ effective_vault = vault_address or self._vault_address
2178
+ action_nonce = nonce if nonce is not None else int(time.time() * 1000)
2179
+ signature = _sign_l1_action(
2180
+ self._wallet,
2181
+ action,
2182
+ effective_vault,
2183
+ action_nonce,
2184
+ expires_after,
2185
+ )
2186
+
2187
+ payload: dict[str, Any] = {
2188
+ "action": action,
2189
+ "nonce": action_nonce,
2190
+ "signature": signature,
2191
+ }
2192
+ if effective_vault is not None:
2193
+ payload["vaultAddress"] = effective_vault
2194
+ if expires_after is not None:
2195
+ payload["expiresAfter"] = expires_after
2196
+
2197
+ return await self._post_request("/exchange", data=payload)
2198
+
2199
+ async def cancel_twap_order(
2200
+ self,
2201
+ asset: int,
2202
+ twap_id: int,
2203
+ nonce: int | None = None,
2204
+ expires_after: int | None = None,
2205
+ vault_address: str | None = None,
2206
+ ) -> dict:
2207
+ """Отмена TWAP-ордера.
2208
+
2209
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#cancel-a-twap-order
2210
+ """
2211
+ if self._wallet is None:
2212
+ raise NotAuthorized("Private key is required for private endpoints.")
2213
+
2214
+ action = {
2215
+ "type": "twapCancel",
2216
+ "a": asset,
2217
+ "t": twap_id,
2218
+ }
2219
+
2220
+ effective_vault = vault_address or self._vault_address
2221
+ action_nonce = nonce if nonce is not None else int(time.time() * 1000)
2222
+ signature = _sign_l1_action(
2223
+ self._wallet,
2224
+ action,
2225
+ effective_vault,
2226
+ action_nonce,
2227
+ expires_after,
2228
+ )
2229
+
2230
+ payload: dict[str, Any] = {
2231
+ "action": action,
2232
+ "nonce": action_nonce,
2233
+ "signature": signature,
2234
+ }
2235
+ if effective_vault is not None:
2236
+ payload["vaultAddress"] = effective_vault
2237
+ if expires_after is not None:
2238
+ payload["expiresAfter"] = expires_after
2239
+
2240
+ return await self._post_request("/exchange", data=payload)
2241
+
2242
+ async def reserve_request_weight(
2243
+ self,
2244
+ weight: int,
2245
+ nonce: int | None = None,
2246
+ expires_after: int | None = None,
2247
+ vault_address: str | None = None,
2248
+ ) -> dict:
2249
+ """Резервирование дополнительного лимита действий.
2250
+
2251
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#reserve-additional-actions
2252
+ """
2253
+ if self._wallet is None:
2254
+ raise NotAuthorized("Private key is required for private endpoints.")
2255
+
2256
+ action = {"type": "reserveRequestWeight", "weight": weight}
2257
+
2258
+ effective_vault = vault_address or self._vault_address
2259
+ action_nonce = nonce if nonce is not None else int(time.time() * 1000)
2260
+ signature = _sign_l1_action(
2261
+ self._wallet,
2262
+ action,
2263
+ effective_vault,
2264
+ action_nonce,
2265
+ expires_after,
2266
+ )
2267
+
2268
+ payload: dict[str, Any] = {
2269
+ "action": action,
2270
+ "nonce": action_nonce,
2271
+ "signature": signature,
2272
+ }
2273
+ if effective_vault is not None:
2274
+ payload["vaultAddress"] = effective_vault
2275
+ if expires_after is not None:
2276
+ payload["expiresAfter"] = expires_after
2277
+
2278
+ return await self._post_request("/exchange", data=payload)
2279
+
2280
+ async def invalidate_pending_nonce(
2281
+ self,
2282
+ nonce: int | None = None,
2283
+ expires_after: int | None = None,
2284
+ vault_address: str | None = None,
2285
+ ) -> dict:
2286
+ """Инвалидация ожидающего nonce без выполнения действия.
2287
+
2288
+ https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/exchange-endpoint#invalidate-pending-nonce-noop
2289
+ """
2290
+ if self._wallet is None:
2291
+ raise NotAuthorized("Private key is required for private endpoints.")
2292
+
2293
+ action = {"type": "noop"}
2294
+
2295
+ effective_vault = vault_address or self._vault_address
2296
+ action_nonce = nonce if nonce is not None else int(time.time() * 1000)
2297
+ signature = _sign_l1_action(
2298
+ self._wallet,
2299
+ action,
2300
+ effective_vault,
2301
+ action_nonce,
2302
+ expires_after,
2303
+ )
2304
+
2305
+ payload: dict[str, Any] = {
2306
+ "action": action,
2307
+ "nonce": action_nonce,
2308
+ "signature": signature,
2309
+ }
2310
+ if effective_vault is not None:
2311
+ payload["vaultAddress"] = effective_vault
2312
+ if expires_after is not None:
2313
+ payload["expiresAfter"] = expires_after
2314
+
2315
+ return await self._post_request("/exchange", data=payload)