hyperliquid-python-sdk-async 0.24.6__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.
@@ -0,0 +1,527 @@
1
+ from typing import Any
2
+
3
+ import time
4
+ from decimal import Decimal
5
+
6
+ import msgpack
7
+ from eth_account import Account
8
+ from eth_account.messages import encode_typed_data
9
+ from eth_utils import keccak, to_hex
10
+
11
+ from hyperliquid.utils.types import Cloid, Literal, NotRequired, Optional, TypedDict, Union
12
+
13
+ Tif = Union[Literal["Alo"], Literal["Ioc"], Literal["Gtc"]]
14
+ Tpsl = Union[Literal["tp"], Literal["sl"]]
15
+ LimitOrderType = TypedDict("LimitOrderType", {"tif": Tif})
16
+ TriggerOrderType = TypedDict("TriggerOrderType", {"triggerPx": float, "isMarket": bool, "tpsl": Tpsl})
17
+ TriggerOrderTypeWire = TypedDict("TriggerOrderTypeWire", {"triggerPx": str, "isMarket": bool, "tpsl": Tpsl})
18
+ OrderType = TypedDict("OrderType", {"limit": LimitOrderType, "trigger": TriggerOrderType}, total=False)
19
+ OrderTypeWire = TypedDict("OrderTypeWire", {"limit": LimitOrderType, "trigger": TriggerOrderTypeWire}, total=False)
20
+ OrderRequest = TypedDict(
21
+ "OrderRequest",
22
+ {
23
+ "coin": str,
24
+ "is_buy": bool,
25
+ "sz": float,
26
+ "limit_px": float,
27
+ "order_type": OrderType,
28
+ "reduce_only": bool,
29
+ "cloid": NotRequired[Optional[Cloid]],
30
+ },
31
+ total=False,
32
+ )
33
+ OidOrCloid = Union[int, Cloid]
34
+ ModifyRequest = TypedDict(
35
+ "ModifyRequest",
36
+ {
37
+ "oid": OidOrCloid,
38
+ "order": OrderRequest,
39
+ },
40
+ total=False,
41
+ )
42
+ CancelRequest = TypedDict("CancelRequest", {"coin": str, "oid": int})
43
+ CancelByCloidRequest = TypedDict("CancelByCloidRequest", {"coin": str, "cloid": Cloid})
44
+
45
+ PriorityGrouping = TypedDict("PriorityGrouping", {"p": int})
46
+ Grouping = Union[Literal["na"], Literal["normalTpsl"], Literal["positionTpsl"], PriorityGrouping]
47
+ Order = TypedDict(
48
+ "Order", {"asset": int, "isBuy": bool, "limitPx": float, "sz": float, "reduceOnly": bool, "cloid": Optional[Cloid]}
49
+ )
50
+
51
+
52
+ OrderWire = TypedDict(
53
+ "OrderWire",
54
+ {
55
+ "a": int,
56
+ "b": bool,
57
+ "p": str,
58
+ "s": str,
59
+ "r": bool,
60
+ "t": OrderTypeWire,
61
+ "c": NotRequired[Optional[str]],
62
+ },
63
+ )
64
+
65
+ ModifyWire = TypedDict(
66
+ "ModifyWire",
67
+ {
68
+ "oid": int,
69
+ "order": OrderWire,
70
+ },
71
+ )
72
+
73
+ ScheduleCancelAction = TypedDict(
74
+ "ScheduleCancelAction",
75
+ {
76
+ "type": Literal["scheduleCancel"],
77
+ "time": NotRequired[Optional[int]],
78
+ },
79
+ )
80
+
81
+ USD_SEND_SIGN_TYPES = [
82
+ {"name": "hyperliquidChain", "type": "string"},
83
+ {"name": "destination", "type": "string"},
84
+ {"name": "amount", "type": "string"},
85
+ {"name": "time", "type": "uint64"},
86
+ ]
87
+
88
+ SPOT_TRANSFER_SIGN_TYPES = [
89
+ {"name": "hyperliquidChain", "type": "string"},
90
+ {"name": "destination", "type": "string"},
91
+ {"name": "token", "type": "string"},
92
+ {"name": "amount", "type": "string"},
93
+ {"name": "time", "type": "uint64"},
94
+ ]
95
+
96
+ WITHDRAW_SIGN_TYPES = [
97
+ {"name": "hyperliquidChain", "type": "string"},
98
+ {"name": "destination", "type": "string"},
99
+ {"name": "amount", "type": "string"},
100
+ {"name": "time", "type": "uint64"},
101
+ ]
102
+
103
+ USD_CLASS_TRANSFER_SIGN_TYPES = [
104
+ {"name": "hyperliquidChain", "type": "string"},
105
+ {"name": "amount", "type": "string"},
106
+ {"name": "toPerp", "type": "bool"},
107
+ {"name": "nonce", "type": "uint64"},
108
+ ]
109
+
110
+ SEND_ASSET_SIGN_TYPES = [
111
+ {"name": "hyperliquidChain", "type": "string"},
112
+ {"name": "destination", "type": "string"},
113
+ {"name": "sourceDex", "type": "string"},
114
+ {"name": "destinationDex", "type": "string"},
115
+ {"name": "token", "type": "string"},
116
+ {"name": "amount", "type": "string"},
117
+ {"name": "fromSubAccount", "type": "string"},
118
+ {"name": "nonce", "type": "uint64"},
119
+ ]
120
+
121
+ USER_DEX_ABSTRACTION_SIGN_TYPES = [
122
+ {"name": "hyperliquidChain", "type": "string"},
123
+ {"name": "user", "type": "address"},
124
+ {"name": "enabled", "type": "bool"},
125
+ {"name": "nonce", "type": "uint64"},
126
+ ]
127
+
128
+ USER_SET_ABSTRACTION_SIGN_TYPES = [
129
+ {"name": "hyperliquidChain", "type": "string"},
130
+ {"name": "user", "type": "address"},
131
+ {"name": "abstraction", "type": "string"},
132
+ {"name": "nonce", "type": "uint64"},
133
+ ]
134
+
135
+ TOKEN_DELEGATE_TYPES = [
136
+ {"name": "hyperliquidChain", "type": "string"},
137
+ {"name": "validator", "type": "address"},
138
+ {"name": "wei", "type": "uint64"},
139
+ {"name": "isUndelegate", "type": "bool"},
140
+ {"name": "nonce", "type": "uint64"},
141
+ ]
142
+
143
+ CONVERT_TO_MULTI_SIG_USER_SIGN_TYPES = [
144
+ {"name": "hyperliquidChain", "type": "string"},
145
+ {"name": "signers", "type": "string"},
146
+ {"name": "nonce", "type": "uint64"},
147
+ ]
148
+
149
+ MULTI_SIG_ENVELOPE_SIGN_TYPES = [
150
+ {"name": "hyperliquidChain", "type": "string"},
151
+ {"name": "multiSigActionHash", "type": "bytes32"},
152
+ {"name": "nonce", "type": "uint64"},
153
+ ]
154
+
155
+
156
+ def order_type_to_wire(order_type: OrderType) -> OrderTypeWire:
157
+ if "limit" in order_type:
158
+ return {"limit": order_type["limit"]}
159
+ elif "trigger" in order_type:
160
+ return {
161
+ "trigger": {
162
+ "isMarket": order_type["trigger"]["isMarket"],
163
+ "triggerPx": float_to_wire(order_type["trigger"]["triggerPx"]),
164
+ "tpsl": order_type["trigger"]["tpsl"],
165
+ }
166
+ }
167
+ raise ValueError("Invalid order type", order_type)
168
+
169
+
170
+ def address_to_bytes(address):
171
+ return bytes.fromhex(address[2:] if address.startswith("0x") else address)
172
+
173
+
174
+ def action_hash(action, vault_address, nonce, expires_after):
175
+ data = msgpack.packb(action)
176
+ data += nonce.to_bytes(8, "big")
177
+ if vault_address is None:
178
+ data += b"\x00"
179
+ else:
180
+ data += b"\x01"
181
+ data += address_to_bytes(vault_address)
182
+ if expires_after is not None:
183
+ data += b"\x00"
184
+ data += expires_after.to_bytes(8, "big")
185
+ return keccak(data)
186
+
187
+
188
+ def construct_phantom_agent(hash, is_mainnet):
189
+ return {"source": "a" if is_mainnet else "b", "connectionId": hash}
190
+
191
+
192
+ def l1_payload(phantom_agent):
193
+ return {
194
+ "domain": {
195
+ "chainId": 1337,
196
+ "name": "Exchange",
197
+ "verifyingContract": "0x0000000000000000000000000000000000000000",
198
+ "version": "1",
199
+ },
200
+ "types": {
201
+ "Agent": [
202
+ {"name": "source", "type": "string"},
203
+ {"name": "connectionId", "type": "bytes32"},
204
+ ],
205
+ "EIP712Domain": [
206
+ {"name": "name", "type": "string"},
207
+ {"name": "version", "type": "string"},
208
+ {"name": "chainId", "type": "uint256"},
209
+ {"name": "verifyingContract", "type": "address"},
210
+ ],
211
+ },
212
+ "primaryType": "Agent",
213
+ "message": phantom_agent,
214
+ }
215
+
216
+
217
+ def user_signed_payload(primary_type, payload_types, action):
218
+ chain_id = int(action["signatureChainId"], 16)
219
+ return {
220
+ "domain": {
221
+ "name": "HyperliquidSignTransaction",
222
+ "version": "1",
223
+ "chainId": chain_id,
224
+ "verifyingContract": "0x0000000000000000000000000000000000000000",
225
+ },
226
+ "types": {
227
+ primary_type: payload_types,
228
+ "EIP712Domain": [
229
+ {"name": "name", "type": "string"},
230
+ {"name": "version", "type": "string"},
231
+ {"name": "chainId", "type": "uint256"},
232
+ {"name": "verifyingContract", "type": "address"},
233
+ ],
234
+ },
235
+ "primaryType": primary_type,
236
+ "message": action,
237
+ }
238
+
239
+
240
+ def sign_l1_action(wallet, action, active_pool, nonce, expires_after, is_mainnet):
241
+ hash = action_hash(action, active_pool, nonce, expires_after)
242
+ phantom_agent = construct_phantom_agent(hash, is_mainnet)
243
+ data = l1_payload(phantom_agent)
244
+ return sign_inner(wallet, data)
245
+
246
+
247
+ def sign_user_signed_action(wallet, action, payload_types, primary_type, is_mainnet):
248
+ # signatureChainId is the chain used by the wallet to sign and can be any chain.
249
+ # hyperliquidChain determines the environment and prevents replaying an action on a different chain.
250
+ action["signatureChainId"] = "0x66eee"
251
+ action["hyperliquidChain"] = "Mainnet" if is_mainnet else "Testnet"
252
+ data = user_signed_payload(primary_type, payload_types, action)
253
+ return sign_inner(wallet, data)
254
+
255
+
256
+ def add_multi_sig_types(sign_types):
257
+ enriched_sign_types = []
258
+ enriched = False
259
+ for sign_type in sign_types:
260
+ enriched_sign_types.append(sign_type)
261
+ if sign_type["name"] == "hyperliquidChain":
262
+ enriched = True
263
+ enriched_sign_types.append(
264
+ {
265
+ "name": "payloadMultiSigUser",
266
+ "type": "address",
267
+ }
268
+ )
269
+ enriched_sign_types.append(
270
+ {
271
+ "name": "outerSigner",
272
+ "type": "address",
273
+ }
274
+ )
275
+ if not enriched:
276
+ print('"hyperliquidChain" missing from sign_types. sign_types was not enriched with multi-sig signing types')
277
+ return enriched_sign_types
278
+
279
+
280
+ def add_multi_sig_fields(action, payload_multi_sig_user, outer_signer):
281
+ action = action.copy()
282
+ action["payloadMultiSigUser"] = payload_multi_sig_user.lower()
283
+ action["outerSigner"] = outer_signer.lower()
284
+ return action
285
+
286
+
287
+ def sign_multi_sig_user_signed_action_payload(
288
+ wallet, action, is_mainnet, sign_types, tx_type, payload_multi_sig_user, outer_signer
289
+ ):
290
+ envelope = add_multi_sig_fields(action, payload_multi_sig_user, outer_signer)
291
+ sign_types = add_multi_sig_types(sign_types)
292
+ return sign_user_signed_action(
293
+ wallet,
294
+ envelope,
295
+ sign_types,
296
+ tx_type,
297
+ is_mainnet,
298
+ )
299
+
300
+
301
+ def sign_multi_sig_l1_action_payload(
302
+ wallet, action, is_mainnet, vault_address, timestamp, expires_after, payload_multi_sig_user, outer_signer
303
+ ):
304
+ envelope = [payload_multi_sig_user.lower(), outer_signer.lower(), action]
305
+ return sign_l1_action(
306
+ wallet,
307
+ envelope,
308
+ vault_address,
309
+ timestamp,
310
+ expires_after,
311
+ is_mainnet,
312
+ )
313
+
314
+
315
+ def sign_multi_sig_action(wallet, action, is_mainnet, vault_address, nonce, expires_after):
316
+ action_without_tag = action.copy()
317
+ del action_without_tag["type"]
318
+ multi_sig_action_hash = action_hash(action_without_tag, vault_address, nonce, expires_after)
319
+ envelope = {
320
+ "multiSigActionHash": multi_sig_action_hash,
321
+ "nonce": nonce,
322
+ }
323
+ return sign_user_signed_action(
324
+ wallet,
325
+ envelope,
326
+ MULTI_SIG_ENVELOPE_SIGN_TYPES,
327
+ "HyperliquidTransaction:SendMultiSig",
328
+ is_mainnet,
329
+ )
330
+
331
+
332
+ def sign_usd_transfer_action(wallet, action, is_mainnet):
333
+ return sign_user_signed_action(
334
+ wallet,
335
+ action,
336
+ USD_SEND_SIGN_TYPES,
337
+ "HyperliquidTransaction:UsdSend",
338
+ is_mainnet,
339
+ )
340
+
341
+
342
+ def sign_spot_transfer_action(wallet, action, is_mainnet):
343
+ return sign_user_signed_action(
344
+ wallet,
345
+ action,
346
+ SPOT_TRANSFER_SIGN_TYPES,
347
+ "HyperliquidTransaction:SpotSend",
348
+ is_mainnet,
349
+ )
350
+
351
+
352
+ def sign_withdraw_from_bridge_action(wallet, action, is_mainnet):
353
+ return sign_user_signed_action(
354
+ wallet,
355
+ action,
356
+ WITHDRAW_SIGN_TYPES,
357
+ "HyperliquidTransaction:Withdraw",
358
+ is_mainnet,
359
+ )
360
+
361
+
362
+ def sign_usd_class_transfer_action(wallet, action, is_mainnet):
363
+ return sign_user_signed_action(
364
+ wallet,
365
+ action,
366
+ USD_CLASS_TRANSFER_SIGN_TYPES,
367
+ "HyperliquidTransaction:UsdClassTransfer",
368
+ is_mainnet,
369
+ )
370
+
371
+
372
+ def sign_send_asset_action(wallet, action, is_mainnet):
373
+ return sign_user_signed_action(
374
+ wallet,
375
+ action,
376
+ SEND_ASSET_SIGN_TYPES,
377
+ "HyperliquidTransaction:SendAsset",
378
+ is_mainnet,
379
+ )
380
+
381
+
382
+ def sign_user_dex_abstraction_action(wallet, action, is_mainnet):
383
+ return sign_user_signed_action(
384
+ wallet,
385
+ action,
386
+ USER_DEX_ABSTRACTION_SIGN_TYPES,
387
+ "HyperliquidTransaction:UserDexAbstraction",
388
+ is_mainnet,
389
+ )
390
+
391
+
392
+ def sign_user_set_abstraction_action(wallet, action, is_mainnet):
393
+ return sign_user_signed_action(
394
+ wallet,
395
+ action,
396
+ USER_SET_ABSTRACTION_SIGN_TYPES,
397
+ "HyperliquidTransaction:UserSetAbstraction",
398
+ is_mainnet,
399
+ )
400
+
401
+
402
+ def sign_convert_to_multi_sig_user_action(wallet, action, is_mainnet):
403
+ return sign_user_signed_action(
404
+ wallet,
405
+ action,
406
+ CONVERT_TO_MULTI_SIG_USER_SIGN_TYPES,
407
+ "HyperliquidTransaction:ConvertToMultiSigUser",
408
+ is_mainnet,
409
+ )
410
+
411
+
412
+ def sign_agent(wallet, action, is_mainnet):
413
+ return sign_user_signed_action(
414
+ wallet,
415
+ action,
416
+ [
417
+ {"name": "hyperliquidChain", "type": "string"},
418
+ {"name": "agentAddress", "type": "address"},
419
+ {"name": "agentName", "type": "string"},
420
+ {"name": "nonce", "type": "uint64"},
421
+ ],
422
+ "HyperliquidTransaction:ApproveAgent",
423
+ is_mainnet,
424
+ )
425
+
426
+
427
+ def sign_approve_builder_fee(wallet, action, is_mainnet):
428
+ return sign_user_signed_action(
429
+ wallet,
430
+ action,
431
+ [
432
+ {"name": "hyperliquidChain", "type": "string"},
433
+ {"name": "maxFeeRate", "type": "string"},
434
+ {"name": "builder", "type": "address"},
435
+ {"name": "nonce", "type": "uint64"},
436
+ ],
437
+ "HyperliquidTransaction:ApproveBuilderFee",
438
+ is_mainnet,
439
+ )
440
+
441
+
442
+ def sign_token_delegate_action(wallet, action, is_mainnet):
443
+ return sign_user_signed_action(
444
+ wallet,
445
+ action,
446
+ TOKEN_DELEGATE_TYPES,
447
+ "HyperliquidTransaction:TokenDelegate",
448
+ is_mainnet,
449
+ )
450
+
451
+
452
+ def sign_inner(wallet, data):
453
+ structured_data = encode_typed_data(full_message=data)
454
+ signed = wallet.sign_message(structured_data)
455
+ return {"r": to_hex(signed["r"]), "s": to_hex(signed["s"]), "v": signed["v"]}
456
+
457
+
458
+ def recover_agent_or_user_from_l1_action(action, signature, active_pool, nonce, expires_after, is_mainnet):
459
+ hash = action_hash(action, active_pool, nonce, expires_after)
460
+ phantom_agent = construct_phantom_agent(hash, is_mainnet)
461
+ data = l1_payload(phantom_agent)
462
+ structured_data = encode_typed_data(full_message=data)
463
+ address = Account.recover_message(structured_data, vrs=[signature["v"], signature["r"], signature["s"]])
464
+ return address
465
+
466
+
467
+ def recover_user_from_user_signed_action(action, signature, payload_types, primary_type, is_mainnet):
468
+ action["hyperliquidChain"] = "Mainnet" if is_mainnet else "Testnet"
469
+ data = user_signed_payload(primary_type, payload_types, action)
470
+ structured_data = encode_typed_data(full_message=data)
471
+ address = Account.recover_message(structured_data, vrs=[signature["v"], signature["r"], signature["s"]])
472
+ return address
473
+
474
+
475
+ def float_to_wire(x: float) -> str:
476
+ rounded = f"{x:.8f}"
477
+ if abs(float(rounded) - x) >= 1e-12:
478
+ raise ValueError("float_to_wire causes rounding", x)
479
+ if rounded == "-0":
480
+ rounded = "0"
481
+ normalized = Decimal(rounded).normalize()
482
+ return f"{normalized:f}"
483
+
484
+
485
+ def float_to_int_for_hashing(x: float) -> int:
486
+ return float_to_int(x, 8)
487
+
488
+
489
+ def float_to_usd_int(x: float) -> int:
490
+ return float_to_int(x, 6)
491
+
492
+
493
+ def float_to_int(x: float, power: int) -> int:
494
+ with_decimals = x * 10**power
495
+ if abs(round(with_decimals) - with_decimals) >= 1e-3:
496
+ raise ValueError("float_to_int causes rounding", x)
497
+ res: int = round(with_decimals)
498
+ return res
499
+
500
+
501
+ def get_timestamp_ms() -> int:
502
+ return int(time.time() * 1000)
503
+
504
+
505
+ def order_request_to_order_wire(order: OrderRequest, asset: int) -> OrderWire:
506
+ order_wire: OrderWire = {
507
+ "a": asset,
508
+ "b": order["is_buy"],
509
+ "p": float_to_wire(order["limit_px"]),
510
+ "s": float_to_wire(order["sz"]),
511
+ "r": order["reduce_only"],
512
+ "t": order_type_to_wire(order["order_type"]),
513
+ }
514
+ if "cloid" in order and order["cloid"] is not None:
515
+ order_wire["c"] = order["cloid"].to_raw()
516
+ return order_wire
517
+
518
+
519
+ def order_wires_to_order_action(order_wires: list[OrderWire], builder: Any = None, grouping: Grouping = "na") -> Any:
520
+ action = {
521
+ "type": "order",
522
+ "orders": order_wires,
523
+ "grouping": grouping,
524
+ }
525
+ if builder:
526
+ action["builder"] = builder
527
+ return action