unicex 0.5.0__py3-none-any.whl → 0.8.0__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 (100) hide show
  1. unicex/__init__.py +56 -124
  2. unicex/_abc/__init__.py +2 -0
  3. unicex/_abc/exchange_info.py +176 -0
  4. unicex/_abc/uni_client.py +2 -2
  5. unicex/_base/client.py +2 -2
  6. unicex/binance/__init__.py +12 -0
  7. unicex/binance/adapter.py +2 -2
  8. unicex/binance/exchange_info.py +12 -0
  9. unicex/bitget/__init__.py +14 -4
  10. unicex/bitget/adapter.py +1 -1
  11. unicex/bitget/exchange_info.py +12 -0
  12. unicex/bitget/uni_websocket_manager.py +1 -1
  13. unicex/bybit/__init__.py +12 -0
  14. unicex/bybit/adapter.py +2 -2
  15. unicex/bybit/exchange_info.py +12 -0
  16. unicex/bybit/uni_client.py +2 -2
  17. unicex/enums.py +16 -5
  18. unicex/extra.py +84 -6
  19. unicex/gateio/__init__.py +12 -0
  20. unicex/gateio/adapter.py +4 -4
  21. unicex/gateio/exchange_info.py +12 -0
  22. unicex/hyperliquid/__init__.py +12 -0
  23. unicex/hyperliquid/adapter.py +151 -30
  24. unicex/hyperliquid/client.py +2208 -125
  25. unicex/hyperliquid/exchange_info.py +100 -0
  26. unicex/hyperliquid/uni_client.py +176 -22
  27. unicex/mapper.py +35 -33
  28. unicex/mexc/__init__.py +12 -0
  29. unicex/mexc/adapter.py +30 -13
  30. unicex/mexc/exchange_info.py +32 -0
  31. unicex/mexc/uni_client.py +6 -0
  32. unicex/okx/__init__.py +12 -0
  33. unicex/okx/adapter.py +25 -9
  34. unicex/okx/client.py +2599 -26
  35. unicex/okx/exchange_info.py +50 -0
  36. unicex/okx/uni_client.py +10 -10
  37. unicex/types.py +31 -0
  38. unicex-0.8.0.dist-info/METADATA +192 -0
  39. unicex-0.8.0.dist-info/RECORD +75 -0
  40. unicex/bitrue/__init__.py +0 -15
  41. unicex/bitrue/adapter.py +0 -8
  42. unicex/bitrue/client.py +0 -128
  43. unicex/bitrue/uni_client.py +0 -151
  44. unicex/bitrue/uni_websocket_manager.py +0 -269
  45. unicex/bitrue/user_websocket.py +0 -7
  46. unicex/bitrue/websocket_manager.py +0 -11
  47. unicex/bitunix/__init__.py +0 -15
  48. unicex/bitunix/adapter.py +0 -8
  49. unicex/bitunix/client.py +0 -8
  50. unicex/bitunix/uni_client.py +0 -151
  51. unicex/bitunix/uni_websocket_manager.py +0 -269
  52. unicex/bitunix/user_websocket.py +0 -7
  53. unicex/bitunix/websocket_manager.py +0 -11
  54. unicex/btse/__init__.py +0 -15
  55. unicex/btse/adapter.py +0 -8
  56. unicex/btse/client.py +0 -123
  57. unicex/btse/uni_client.py +0 -151
  58. unicex/btse/uni_websocket_manager.py +0 -269
  59. unicex/btse/user_websocket.py +0 -7
  60. unicex/btse/websocket_manager.py +0 -11
  61. unicex/kcex/__init__.py +0 -15
  62. unicex/kcex/adapter.py +0 -8
  63. unicex/kcex/client.py +0 -8
  64. unicex/kcex/uni_client.py +0 -151
  65. unicex/kcex/uni_websocket_manager.py +0 -269
  66. unicex/kcex/user_websocket.py +0 -7
  67. unicex/kcex/websocket_manager.py +0 -11
  68. unicex/kraken/__init__.py +0 -15
  69. unicex/kraken/adapter.py +0 -8
  70. unicex/kraken/client.py +0 -165
  71. unicex/kraken/uni_client.py +0 -151
  72. unicex/kraken/uni_websocket_manager.py +0 -269
  73. unicex/kraken/user_websocket.py +0 -7
  74. unicex/kraken/websocket_manager.py +0 -11
  75. unicex/kucoin/__init__.py +0 -15
  76. unicex/kucoin/adapter.py +0 -8
  77. unicex/kucoin/client.py +0 -120
  78. unicex/kucoin/uni_client.py +0 -151
  79. unicex/kucoin/uni_websocket_manager.py +0 -269
  80. unicex/kucoin/user_websocket.py +0 -7
  81. unicex/kucoin/websocket_manager.py +0 -11
  82. unicex/weex/__init__.py +0 -15
  83. unicex/weex/adapter.py +0 -8
  84. unicex/weex/client.py +0 -8
  85. unicex/weex/uni_client.py +0 -151
  86. unicex/weex/uni_websocket_manager.py +0 -269
  87. unicex/weex/user_websocket.py +0 -7
  88. unicex/weex/websocket_manager.py +0 -11
  89. unicex/xt/__init__.py +0 -15
  90. unicex/xt/adapter.py +0 -8
  91. unicex/xt/client.py +0 -8
  92. unicex/xt/uni_client.py +0 -151
  93. unicex/xt/uni_websocket_manager.py +0 -269
  94. unicex/xt/user_websocket.py +0 -7
  95. unicex/xt/websocket_manager.py +0 -11
  96. unicex-0.5.0.dist-info/METADATA +0 -170
  97. unicex-0.5.0.dist-info/RECORD +0 -123
  98. {unicex-0.5.0.dist-info → unicex-0.8.0.dist-info}/WHEEL +0 -0
  99. {unicex-0.5.0.dist-info → unicex-0.8.0.dist-info}/licenses/LICENSE +0 -0
  100. {unicex-0.5.0.dist-info → unicex-0.8.0.dist-info}/top_level.txt +0 -0
@@ -1,232 +1,2315 @@
1
1
  __all__ = ["Client"]
2
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
3
13
 
4
14
  from unicex._base import BaseClient
15
+ from unicex.exceptions import NotAuthorized
16
+ from unicex.types import LoggerLike
5
17
  from unicex.utils import filter_params
6
18
 
19
+ # Authentication
7
20
 
8
- class Client(BaseClient):
9
- """Клиент для работы с Hyperliquid API."""
10
21
 
11
- _BASE_URL = "https://api.hyperliquid.xyz"
12
- """Базовый URL для REST API Hyperliquid."""
22
+ def _l1_payload(phantom_agent: dict[str, Any]) -> dict[str, Any]:
23
+ """Формирует EIP-712 payload для подписи "агента".
13
24
 
14
- # topic: Info endpoint: Spot
25
+ Простыми словами:
26
+ Это упаковка данных в формат, который кошелёк сможет подписать.
27
+ В Ethereum есть стандарт EIP-712 — "structured data signing".
28
+ Он позволяет подписывать не просто строку, а структуру (объект),
29
+ чтобы потом её можно было проверить.
15
30
 
16
- async def spot_metadata(self) -> dict:
17
- """Получение метаданных спотового рынка.
31
+ Пример:
32
+ >>> phantom = {"source": "a", "connectionId": b"1234...."}
33
+ >>> _l1_payload(phantom)
34
+ {...сложный словарь...}
18
35
 
19
- https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-metadata
20
- """
21
- url = self._BASE_URL + "/info"
22
- data = filter_params({"type": "spotMeta"})
36
+ Параметры:
37
+ phantom_agent (dict): объект с полями:
38
+ - source (str): откуда пришёл агент ("a" для mainnet, "b" для testnet)
39
+ - connectionId (bytes32): уникальный ID (обычно хэш)
23
40
 
24
- return await self._make_request("POST", url, data=data)
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
+ }
25
66
 
26
- async def spot_asset_contexts(self) -> list:
27
- """Получение контекстов спотовых активов.
28
67
 
29
- https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-spot-asset-contexts
30
- """
31
- url = self._BASE_URL + "/info"
32
- data = filter_params({"type": "spotMetaAndAssetCtxs"})
68
+ def _address_to_bytes(address: str) -> bytes:
69
+ r"""Переводит Ethereum-адрес в байты.
33
70
 
34
- return await self._make_request("POST", url, data=data)
71
+ Простыми словами:
72
+ Берём строку вида "0xABC123..." и превращаем её в бинарные данные.
73
+ Это нужно, потому что внутри подписи адрес должен храниться как массив байтов.
35
74
 
36
- async def spot_token_balances(self, user: str) -> dict:
37
- """Получение балансов пользователя на спотовом рынке.
75
+ Пример:
76
+ >>> _address_to_bytes("0x0000000000000000000000000000000000000001")
77
+ b'\\x00...\\x01'
38
78
 
39
- https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-a-users-token-balances
40
- """
41
- url = self._BASE_URL + "/info"
42
- data = filter_params({"type": "spotClearinghouseState", "user": user})
79
+ Параметры:
80
+ address (str): строковый Ethereum-адрес, с "0x" или без.
43
81
 
44
- return await self._make_request("POST", url, data=data)
82
+ Возвращает:
83
+ bytes: бинарное представление адреса.
84
+ """
85
+ return bytes.fromhex(address[2:] if address.startswith("0x") else address)
45
86
 
46
- async def spot_deploy_state(self, user: str) -> dict:
47
- """Получение сведений об аукционе развертывания спотовых токенов.
48
87
 
49
- https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-information-about-the-spot-deploy-auction
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`): Максимальное время ожидания ответа от сервера, сек.
50
422
  """
51
- url = self._BASE_URL + "/info"
52
- data = filter_params({"type": "spotDeployState", "user": user})
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)
53
440
 
54
- return await self._make_request("POST", url, data=data)
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
+ """Создаёт инстанцию клиента.
55
455
 
56
- async def spot_pair_deploy_auction_status(self) -> dict:
57
- """Получение статуса аукциона развертывания спотовых пар.
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`): Таймаут ответа сервера, сек.
58
466
 
59
- https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-information-about-the-spot-pair-deploy-auction
467
+ Возвращает:
468
+ `Self`: Созданный экземпляр клиента.
60
469
  """
61
- url = self._BASE_URL + "/info"
62
- data = filter_params({"type": "spotPairDeployAuctionStatus"})
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
+ )
63
481
 
64
- return await self._make_request("POST", url, data=data)
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.
65
490
 
66
- async def token_details(self, token_id: str) -> dict:
67
- """Получение сведений о спотовом токене.
491
+ Параметры:
492
+ method (`Literal["GET", "POST"]`): HTTP-метод запроса.
493
+ endpoint (`str`): Относительный путь эндпоинта.
494
+ data (`dict[str, Any] | None`): Тело запроса для POST-запросов.
68
495
 
69
- https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/spot#retrieve-information-about-a-token
496
+ Возвращает:
497
+ `Any`: Ответ Hyperliquid API.
70
498
  """
71
- url = self._BASE_URL + "/info"
72
- data = filter_params({"type": "tokenDetails", "tokenId": token_id})
499
+ filtered_data = filter_params(data) if data is not None else None
73
500
 
74
- return await self._make_request("POST", url, data=data)
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
+ )
75
507
 
76
- # topic: Info endpoint: Perpetuals
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)
77
511
 
78
- async def perp_dexs(self) -> list:
79
- """Получение списка всех perpetual DEX.
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)
80
528
 
81
- https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-all-perpetual-dexs
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
82
536
  """
83
- url = self._BASE_URL + "/info"
84
- data = filter_params({"type": "perpDexs"})
537
+ payload = {"type": "perpDexs"}
85
538
 
86
- return await self._make_request("POST", url, data=data)
539
+ return await self._post_request("/info", data=payload)
87
540
 
88
541
  async def perp_metadata(self, dex: str | None = None) -> dict:
89
- """Получение метаданных perpetual-рынка.
542
+ """Получение метаданных по перпетуальным контрактам.
90
543
 
91
544
  https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-perpetuals-metadata-universe-and-margin-tables
92
545
  """
93
- url = self._BASE_URL + "/info"
94
- data = filter_params({"type": "meta", "dex": dex})
546
+ payload = {
547
+ "type": "meta",
548
+ "dex": dex,
549
+ }
95
550
 
96
- return await self._make_request("POST", url, data=data)
551
+ return await self._post_request("/info", data=payload)
97
552
 
98
- async def perp_asset_contexts(self) -> list:
99
- """Получение контекстов perpetual-активов.
553
+ async def perp_meta_and_asset_contexts(self) -> list[Any]:
554
+ """Получение метаданных и контекстов активов перпетуальных контрактов.
100
555
 
101
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
102
557
  """
103
- url = self._BASE_URL + "/info"
104
- data = filter_params({"type": "metaAndAssetCtxs"})
558
+ payload = {"type": "metaAndAssetCtxs"}
105
559
 
106
- return await self._make_request("POST", url, data=data)
560
+ return await self._post_request("/info", data=payload)
107
561
 
108
562
  async def perp_account_summary(self, user: str, dex: str | None = None) -> dict:
109
- """Получение сводки по perpetual-счету пользователя.
563
+ """Получение сводной информации по аккаунту пользователя в перпетуалах.
110
564
 
111
565
  https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-perpetuals-account-summary
112
566
  """
113
- url = self._BASE_URL + "/info"
114
- data = filter_params({"type": "clearinghouseState", "user": user, "dex": dex})
567
+ payload = {
568
+ "type": "clearinghouseState",
569
+ "user": user,
570
+ "dex": dex,
571
+ }
115
572
 
116
- return await self._make_request("POST", url, data=data)
573
+ return await self._post_request("/info", data=payload)
117
574
 
118
- async def user_funding_history(
575
+ async def perp_user_funding_history(
119
576
  self,
120
577
  user: str,
121
578
  start_time: int,
122
579
  end_time: int | None = None,
123
- ) -> list:
580
+ ) -> list[dict]:
124
581
  """Получение истории фондирования пользователя.
125
582
 
126
583
  https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-a-users-funding-history-or-non-funding-ledger-updates
127
584
  """
128
- url = self._BASE_URL + "/info"
129
- data = filter_params(
130
- {
131
- "type": "userFunding",
132
- "user": user,
133
- "startTime": start_time,
134
- "endTime": end_time,
135
- }
136
- )
585
+ payload = {
586
+ "type": "userFunding",
587
+ "user": user,
588
+ "startTime": start_time,
589
+ "endTime": end_time,
590
+ }
137
591
 
138
- return await self._make_request("POST", url, data=data)
592
+ return await self._post_request("/info", data=payload)
139
593
 
140
- async def user_non_funding_ledger_updates(
594
+ async def perp_user_non_funding_ledger_updates(
141
595
  self,
142
596
  user: str,
143
597
  start_time: int,
144
598
  end_time: int | None = None,
145
- ) -> list:
146
- """Получение нефондовых операций пользователя.
599
+ ) -> list[dict]:
600
+ """Получение нефондировочных обновлений леджера пользователя.
147
601
 
148
602
  https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-a-users-funding-history-or-non-funding-ledger-updates
149
603
  """
150
- url = self._BASE_URL + "/info"
151
- data = filter_params(
152
- {
153
- "type": "userNonFundingLedgerUpdates",
154
- "user": user,
155
- "startTime": start_time,
156
- "endTime": end_time,
157
- }
158
- )
604
+ payload = {
605
+ "type": "userNonFundingLedgerUpdates",
606
+ "user": user,
607
+ "startTime": start_time,
608
+ "endTime": end_time,
609
+ }
159
610
 
160
- return await self._make_request("POST", url, data=data)
611
+ return await self._post_request("/info", data=payload)
161
612
 
162
- async def funding_history(
613
+ async def perp_funding_history(
163
614
  self,
164
615
  coin: str,
165
616
  start_time: int,
166
617
  end_time: int | None = None,
167
- ) -> list:
168
- """Получение исторических ставок фондирования.
618
+ ) -> list[dict]:
619
+ """Получение исторических ставок фондирования по монете.
169
620
 
170
621
  https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-historical-funding-rates
171
622
  """
172
- url = self._BASE_URL + "/info"
173
- data = filter_params(
174
- {
175
- "type": "fundingHistory",
176
- "coin": coin,
177
- "startTime": start_time,
178
- "endTime": end_time,
179
- }
180
- )
623
+ payload = {
624
+ "type": "fundingHistory",
625
+ "coin": coin,
626
+ "startTime": start_time,
627
+ "endTime": end_time,
628
+ }
181
629
 
182
- return await self._make_request("POST", url, data=data)
630
+ return await self._post_request("/info", data=payload)
183
631
 
184
- async def predicted_fundings(self) -> list:
185
- """Получение прогнозируемых ставок фондирования по площадкам.
632
+ async def perp_predicted_fundings(self) -> list[list[Any]]:
633
+ """Получение предсказанных ставок фондирования по площадкам.
186
634
 
187
635
  https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-predicted-funding-rates-for-different-venues
188
636
  """
189
- url = self._BASE_URL + "/info"
190
- data = filter_params({"type": "predictedFundings"})
637
+ payload = {"type": "predictedFundings"}
191
638
 
192
- return await self._make_request("POST", url, data=data)
639
+ return await self._post_request("/info", data=payload)
193
640
 
194
- async def perps_at_open_interest_cap(self) -> list:
195
- """Получение списка инструментов на пределе открытого интереса.
641
+ async def perps_at_open_interest_cap(self) -> list[str]:
642
+ """Получение списка перпетуалов с достигнутым лимитом открытого интереса.
196
643
 
197
644
  https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#query-perps-at-open-interest-caps
198
645
  """
199
- url = self._BASE_URL + "/info"
200
- data = filter_params({"type": "perpsAtOpenInterestCap"})
646
+ payload = {"type": "perpsAtOpenInterestCap"}
201
647
 
202
- return await self._make_request("POST", url, data=data)
648
+ return await self._post_request("/info", data=payload)
203
649
 
204
650
  async def perp_deploy_auction_status(self) -> dict:
205
- """Получение статуса аукциона развертывания perpetual-рынка.
651
+ """Получение статуса аукциона развёртывания перпетуалов.
206
652
 
207
653
  https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-information-about-the-perp-deploy-auction
208
654
  """
209
- url = self._BASE_URL + "/info"
210
- data = filter_params({"type": "perpDeployAuctionStatus"})
655
+ payload = {"type": "perpDeployAuctionStatus"}
211
656
 
212
- return await self._make_request("POST", url, data=data)
657
+ return await self._post_request("/info", data=payload)
213
658
 
214
- async def active_asset_data(self, user: str, coin: str) -> dict:
215
- """Получение актуальных данных по активу пользователя.
659
+ async def perp_active_asset_data(self, user: str, coin: str) -> dict:
660
+ """Получение активных параметров актива пользователя в перпетуалах.
216
661
 
217
662
  https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-users-active-asset-data
218
663
  """
219
- url = self._BASE_URL + "/info"
220
- data = filter_params({"type": "activeAssetData", "user": user, "coin": coin})
664
+ payload = {
665
+ "type": "activeAssetData",
666
+ "user": user,
667
+ "coin": coin,
668
+ }
221
669
 
222
- return await self._make_request("POST", url, data=data)
670
+ return await self._post_request("/info", data=payload)
223
671
 
224
672
  async def perp_dex_limits(self, dex: str) -> dict:
225
- """Получение лимитов для perpetual DEX, созданного билдерами.
673
+ """Получение лимитов для перпетуального DEX, развёрнутого билдерами.
226
674
 
227
675
  https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint/perpetuals#retrieve-builder-deployed-perp-market-limits
228
676
  """
229
- url = self._BASE_URL + "/info"
230
- data = filter_params({"type": "perpDexLimits", "dex": dex})
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
231
2314
 
232
- return await self._make_request("POST", url, data=data)
2315
+ return await self._post_request("/exchange", data=payload)