| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490 |
- import time
- from decimal import Decimal
- import msgpack
- from eth_account import Account
- from eth_account.messages import encode_typed_data
- from eth_utils import keccak, to_hex
- from hyperliquid.utils.types import Cloid, Literal, NotRequired, Optional, TypedDict, Union
- Tif = Union[Literal["Alo"], Literal["Ioc"], Literal["Gtc"]]
- Tpsl = Union[Literal["tp"], Literal["sl"]]
- LimitOrderType = TypedDict("LimitOrderType", {"tif": Tif})
- TriggerOrderType = TypedDict("TriggerOrderType", {"triggerPx": float, "isMarket": bool, "tpsl": Tpsl})
- TriggerOrderTypeWire = TypedDict("TriggerOrderTypeWire", {"triggerPx": str, "isMarket": bool, "tpsl": Tpsl})
- OrderType = TypedDict("OrderType", {"limit": LimitOrderType, "trigger": TriggerOrderType}, total=False)
- OrderTypeWire = TypedDict("OrderTypeWire", {"limit": LimitOrderType, "trigger": TriggerOrderTypeWire}, total=False)
- OrderRequest = TypedDict(
- "OrderRequest",
- {
- "coin": str,
- "is_buy": bool,
- "sz": float,
- "limit_px": float,
- "order_type": OrderType,
- "reduce_only": bool,
- "cloid": NotRequired[Optional[Cloid]],
- },
- total=False,
- )
- OidOrCloid = Union[int, Cloid]
- ModifyRequest = TypedDict(
- "ModifyRequest",
- {
- "oid": OidOrCloid,
- "order": OrderRequest,
- },
- total=False,
- )
- CancelRequest = TypedDict("CancelRequest", {"coin": str, "oid": int})
- CancelByCloidRequest = TypedDict("CancelByCloidRequest", {"coin": str, "cloid": Cloid})
- Grouping = Union[Literal["na"], Literal["normalTpsl"], Literal["positionTpsl"]]
- Order = TypedDict(
- "Order", {"asset": int, "isBuy": bool, "limitPx": float, "sz": float, "reduceOnly": bool, "cloid": Optional[Cloid]}
- )
- OrderWire = TypedDict(
- "OrderWire",
- {
- "a": int,
- "b": bool,
- "p": str,
- "s": str,
- "r": bool,
- "t": OrderTypeWire,
- "c": NotRequired[Optional[str]],
- },
- )
- ModifyWire = TypedDict(
- "ModifyWire",
- {
- "oid": int,
- "order": OrderWire,
- },
- )
- ScheduleCancelAction = TypedDict(
- "ScheduleCancelAction",
- {
- "type": Literal["scheduleCancel"],
- "time": NotRequired[Optional[int]],
- },
- )
- USD_SEND_SIGN_TYPES = [
- {"name": "hyperliquidChain", "type": "string"},
- {"name": "destination", "type": "string"},
- {"name": "amount", "type": "string"},
- {"name": "time", "type": "uint64"},
- ]
- SPOT_TRANSFER_SIGN_TYPES = [
- {"name": "hyperliquidChain", "type": "string"},
- {"name": "destination", "type": "string"},
- {"name": "token", "type": "string"},
- {"name": "amount", "type": "string"},
- {"name": "time", "type": "uint64"},
- ]
- WITHDRAW_SIGN_TYPES = [
- {"name": "hyperliquidChain", "type": "string"},
- {"name": "destination", "type": "string"},
- {"name": "amount", "type": "string"},
- {"name": "time", "type": "uint64"},
- ]
- USD_CLASS_TRANSFER_SIGN_TYPES = [
- {"name": "hyperliquidChain", "type": "string"},
- {"name": "amount", "type": "string"},
- {"name": "toPerp", "type": "bool"},
- {"name": "nonce", "type": "uint64"},
- ]
- SEND_ASSET_SIGN_TYPES = [
- {"name": "hyperliquidChain", "type": "string"},
- {"name": "destination", "type": "string"},
- {"name": "sourceDex", "type": "string"},
- {"name": "destinationDex", "type": "string"},
- {"name": "token", "type": "string"},
- {"name": "amount", "type": "string"},
- {"name": "fromSubAccount", "type": "string"},
- {"name": "nonce", "type": "uint64"},
- ]
- TOKEN_DELEGATE_TYPES = [
- {"name": "hyperliquidChain", "type": "string"},
- {"name": "validator", "type": "address"},
- {"name": "wei", "type": "uint64"},
- {"name": "isUndelegate", "type": "bool"},
- {"name": "nonce", "type": "uint64"},
- ]
- CONVERT_TO_MULTI_SIG_USER_SIGN_TYPES = [
- {"name": "hyperliquidChain", "type": "string"},
- {"name": "signers", "type": "string"},
- {"name": "nonce", "type": "uint64"},
- ]
- MULTI_SIG_ENVELOPE_SIGN_TYPES = [
- {"name": "hyperliquidChain", "type": "string"},
- {"name": "multiSigActionHash", "type": "bytes32"},
- {"name": "nonce", "type": "uint64"},
- ]
- def order_type_to_wire(order_type: OrderType) -> OrderTypeWire:
- if "limit" in order_type:
- return {"limit": order_type["limit"]}
- elif "trigger" in order_type:
- return {
- "trigger": {
- "isMarket": order_type["trigger"]["isMarket"],
- "triggerPx": float_to_wire(order_type["trigger"]["triggerPx"]),
- "tpsl": order_type["trigger"]["tpsl"],
- }
- }
- raise ValueError("Invalid order type", order_type)
- def address_to_bytes(address):
- return bytes.fromhex(address[2:] if address.startswith("0x") else address)
- def action_hash(action, vault_address, nonce, expires_after):
- data = msgpack.packb(action)
- data += nonce.to_bytes(8, "big")
- if vault_address is None:
- data += b"\x00"
- else:
- data += b"\x01"
- data += address_to_bytes(vault_address)
- if expires_after is not None:
- data += b"\x00"
- data += expires_after.to_bytes(8, "big")
- return keccak(data)
- def construct_phantom_agent(hash, is_mainnet):
- return {"source": "a" if is_mainnet else "b", "connectionId": hash}
- def l1_payload(phantom_agent):
- return {
- "domain": {
- "chainId": 1337,
- "name": "Exchange",
- "verifyingContract": "0x0000000000000000000000000000000000000000",
- "version": "1",
- },
- "types": {
- "Agent": [
- {"name": "source", "type": "string"},
- {"name": "connectionId", "type": "bytes32"},
- ],
- "EIP712Domain": [
- {"name": "name", "type": "string"},
- {"name": "version", "type": "string"},
- {"name": "chainId", "type": "uint256"},
- {"name": "verifyingContract", "type": "address"},
- ],
- },
- "primaryType": "Agent",
- "message": phantom_agent,
- }
- def user_signed_payload(primary_type, payload_types, action):
- chain_id = int(action["signatureChainId"], 16)
- return {
- "domain": {
- "name": "HyperliquidSignTransaction",
- "version": "1",
- "chainId": chain_id,
- "verifyingContract": "0x0000000000000000000000000000000000000000",
- },
- "types": {
- primary_type: payload_types,
- "EIP712Domain": [
- {"name": "name", "type": "string"},
- {"name": "version", "type": "string"},
- {"name": "chainId", "type": "uint256"},
- {"name": "verifyingContract", "type": "address"},
- ],
- },
- "primaryType": primary_type,
- "message": action,
- }
- def sign_l1_action(wallet, action, active_pool, nonce, expires_after, is_mainnet):
- hash = action_hash(action, active_pool, nonce, expires_after)
- phantom_agent = construct_phantom_agent(hash, is_mainnet)
- data = l1_payload(phantom_agent)
- return sign_inner(wallet, data)
- def sign_user_signed_action(wallet, action, payload_types, primary_type, is_mainnet):
- # signatureChainId is the chain used by the wallet to sign and can be any chain.
- # hyperliquidChain determines the environment and prevents replaying an action on a different chain.
- action["signatureChainId"] = "0x66eee"
- action["hyperliquidChain"] = "Mainnet" if is_mainnet else "Testnet"
- data = user_signed_payload(primary_type, payload_types, action)
- return sign_inner(wallet, data)
- def add_multi_sig_types(sign_types):
- enriched_sign_types = []
- enriched = False
- for sign_type in sign_types:
- enriched_sign_types.append(sign_type)
- if sign_type["name"] == "hyperliquidChain":
- enriched = True
- enriched_sign_types.append(
- {
- "name": "payloadMultiSigUser",
- "type": "address",
- }
- )
- enriched_sign_types.append(
- {
- "name": "outerSigner",
- "type": "address",
- }
- )
- if not enriched:
- print('"hyperliquidChain" missing from sign_types. sign_types was not enriched with multi-sig signing types')
- return enriched_sign_types
- def add_multi_sig_fields(action, payload_multi_sig_user, outer_signer):
- action = action.copy()
- action["payloadMultiSigUser"] = payload_multi_sig_user.lower()
- action["outerSigner"] = outer_signer.lower()
- return action
- def sign_multi_sig_user_signed_action_payload(
- wallet, action, is_mainnet, sign_types, tx_type, payload_multi_sig_user, outer_signer
- ):
- envelope = add_multi_sig_fields(action, payload_multi_sig_user, outer_signer)
- sign_types = add_multi_sig_types(sign_types)
- return sign_user_signed_action(
- wallet,
- envelope,
- sign_types,
- tx_type,
- is_mainnet,
- )
- def sign_multi_sig_l1_action_payload(
- wallet, action, is_mainnet, vault_address, timestamp, expires_after, payload_multi_sig_user, outer_signer
- ):
- envelope = [payload_multi_sig_user.lower(), outer_signer.lower(), action]
- return sign_l1_action(
- wallet,
- envelope,
- vault_address,
- timestamp,
- expires_after,
- is_mainnet,
- )
- def sign_multi_sig_action(wallet, action, is_mainnet, vault_address, nonce, expires_after):
- action_without_tag = action.copy()
- del action_without_tag["type"]
- multi_sig_action_hash = action_hash(action_without_tag, vault_address, nonce, expires_after)
- envelope = {
- "multiSigActionHash": multi_sig_action_hash,
- "nonce": nonce,
- }
- return sign_user_signed_action(
- wallet,
- envelope,
- MULTI_SIG_ENVELOPE_SIGN_TYPES,
- "HyperliquidTransaction:SendMultiSig",
- is_mainnet,
- )
- def sign_usd_transfer_action(wallet, action, is_mainnet):
- return sign_user_signed_action(
- wallet,
- action,
- USD_SEND_SIGN_TYPES,
- "HyperliquidTransaction:UsdSend",
- is_mainnet,
- )
- def sign_spot_transfer_action(wallet, action, is_mainnet):
- return sign_user_signed_action(
- wallet,
- action,
- SPOT_TRANSFER_SIGN_TYPES,
- "HyperliquidTransaction:SpotSend",
- is_mainnet,
- )
- def sign_withdraw_from_bridge_action(wallet, action, is_mainnet):
- return sign_user_signed_action(
- wallet,
- action,
- WITHDRAW_SIGN_TYPES,
- "HyperliquidTransaction:Withdraw",
- is_mainnet,
- )
- def sign_usd_class_transfer_action(wallet, action, is_mainnet):
- return sign_user_signed_action(
- wallet,
- action,
- USD_CLASS_TRANSFER_SIGN_TYPES,
- "HyperliquidTransaction:UsdClassTransfer",
- is_mainnet,
- )
- def sign_send_asset_action(wallet, action, is_mainnet):
- return sign_user_signed_action(
- wallet,
- action,
- SEND_ASSET_SIGN_TYPES,
- "HyperliquidTransaction:SendAsset",
- is_mainnet,
- )
- def sign_convert_to_multi_sig_user_action(wallet, action, is_mainnet):
- return sign_user_signed_action(
- wallet,
- action,
- CONVERT_TO_MULTI_SIG_USER_SIGN_TYPES,
- "HyperliquidTransaction:ConvertToMultiSigUser",
- is_mainnet,
- )
- def sign_agent(wallet, action, is_mainnet):
- return sign_user_signed_action(
- wallet,
- action,
- [
- {"name": "hyperliquidChain", "type": "string"},
- {"name": "agentAddress", "type": "address"},
- {"name": "agentName", "type": "string"},
- {"name": "nonce", "type": "uint64"},
- ],
- "HyperliquidTransaction:ApproveAgent",
- is_mainnet,
- )
- def sign_approve_builder_fee(wallet, action, is_mainnet):
- return sign_user_signed_action(
- wallet,
- action,
- [
- {"name": "hyperliquidChain", "type": "string"},
- {"name": "maxFeeRate", "type": "string"},
- {"name": "builder", "type": "address"},
- {"name": "nonce", "type": "uint64"},
- ],
- "HyperliquidTransaction:ApproveBuilderFee",
- is_mainnet,
- )
- def sign_token_delegate_action(wallet, action, is_mainnet):
- return sign_user_signed_action(
- wallet,
- action,
- TOKEN_DELEGATE_TYPES,
- "HyperliquidTransaction:TokenDelegate",
- is_mainnet,
- )
- def sign_inner(wallet, data):
- structured_data = encode_typed_data(full_message=data)
- signed = wallet.sign_message(structured_data)
- return {"r": to_hex(signed["r"]), "s": to_hex(signed["s"]), "v": signed["v"]}
- def recover_agent_or_user_from_l1_action(action, signature, active_pool, nonce, expires_after, is_mainnet):
- hash = action_hash(action, active_pool, nonce, expires_after)
- phantom_agent = construct_phantom_agent(hash, is_mainnet)
- data = l1_payload(phantom_agent)
- structured_data = encode_typed_data(full_message=data)
- address = Account.recover_message(structured_data, vrs=[signature["v"], signature["r"], signature["s"]])
- return address
- def recover_user_from_user_signed_action(action, signature, payload_types, primary_type, is_mainnet):
- action["hyperliquidChain"] = "Mainnet" if is_mainnet else "Testnet"
- data = user_signed_payload(primary_type, payload_types, action)
- structured_data = encode_typed_data(full_message=data)
- address = Account.recover_message(structured_data, vrs=[signature["v"], signature["r"], signature["s"]])
- return address
- def float_to_wire(x: float) -> str:
- rounded = f"{x:.8f}"
- if abs(float(rounded) - x) >= 1e-12:
- raise ValueError("float_to_wire causes rounding", x)
- if rounded == "-0":
- rounded = "0"
- normalized = Decimal(rounded).normalize()
- return f"{normalized:f}"
- def float_to_int_for_hashing(x: float) -> int:
- return float_to_int(x, 8)
- def float_to_usd_int(x: float) -> int:
- return float_to_int(x, 6)
- def float_to_int(x: float, power: int) -> int:
- with_decimals = x * 10**power
- if abs(round(with_decimals) - with_decimals) >= 1e-3:
- raise ValueError("float_to_int causes rounding", x)
- res: int = round(with_decimals)
- return res
- def get_timestamp_ms() -> int:
- return int(time.time() * 1000)
- def order_request_to_order_wire(order: OrderRequest, asset: int) -> OrderWire:
- order_wire: OrderWire = {
- "a": asset,
- "b": order["is_buy"],
- "p": float_to_wire(order["limit_px"]),
- "s": float_to_wire(order["sz"]),
- "r": order["reduce_only"],
- "t": order_type_to_wire(order["order_type"]),
- }
- if "cloid" in order and order["cloid"] is not None:
- order_wire["c"] = order["cloid"].to_raw()
- return order_wire
- def order_wires_to_order_action(order_wires, builder=None):
- action = {
- "type": "order",
- "orders": order_wires,
- "grouping": "na",
- }
- if builder:
- action["builder"] = builder
- return action
|