diff --git a/hyperliquid/utils/signing.py b/hyperliquid/utils/signing.py index 56041471..f34bc5d8 100644 --- a/hyperliquid/utils/signing.py +++ b/hyperliquid/utils/signing.py @@ -465,6 +465,7 @@ def recover_agent_or_user_from_l1_action(action, signature, active_pool, nonce, def recover_user_from_user_signed_action(action, signature, payload_types, primary_type, is_mainnet): + action["signatureChainId"] = "0x66eee" 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) diff --git a/tests/signing_test.py b/tests/signing_test.py index b6e89d9f..9c6ff842 100644 --- a/tests/signing_test.py +++ b/tests/signing_test.py @@ -4,6 +4,7 @@ from hyperliquid.exchange import _multi_sig_payload_action from hyperliquid.utils.signing import ( + USD_SEND_SIGN_TYPES, OrderRequest, ScheduleCancelAction, action_hash, @@ -11,6 +12,7 @@ float_to_int_for_hashing, order_request_to_order_wire, order_wires_to_order_action, + recover_user_from_user_signed_action, sign_l1_action, sign_usd_transfer_action, sign_withdraw_from_bridge_action, @@ -288,3 +290,25 @@ def test_schedule_cancel_action(): assert signature_testnet["r"] == "0x4e4f2dbd4107c69783e251b7e1057d9f2b9d11cee213441ccfa2be63516dc5bc" assert signature_testnet["s"] == "0x706c656b23428c8ba356d68db207e11139ede1670481a9e01ae2dfcdb0e1a678" assert signature_testnet["v"] == 27 + + +def test_user_signed_action_recover_roundtrip(): + # Regression test: recover_user_from_user_signed_action must set signatureChainId + # symmetrically with sign_user_signed_action. A verifier reconstructs the action + # without signatureChainId, so recover has to add it -- otherwise user_signed_payload + # raises KeyError: 'signatureChainId'. + wallet = eth_account.Account.from_key("0x0123456789012345678901234567890123456789012345678901234567890123") + + def fresh_action(): + return { + "destination": "0x0000000000000000000000000000000000000001", + "amount": "1", + "time": 1677777606040, + } + + for is_mainnet in (True, False): + signature = sign_usd_transfer_action(wallet, fresh_action(), is_mainnet) + recovered = recover_user_from_user_signed_action( + fresh_action(), signature, USD_SEND_SIGN_TYPES, "HyperliquidTransaction:UsdSend", is_mainnet + ) + assert recovered == wallet.address