Skip to content

Raise instead of warn when multi-sig sign types cannot be enriched - #315

Open
BhariGowda wants to merge 1 commit into
hyperliquid-dex:masterfrom
BhariGowda:fix/multi-sig-types-silent-failure
Open

Raise instead of warn when multi-sig sign types cannot be enriched#315
BhariGowda wants to merge 1 commit into
hyperliquid-dex:masterfrom
BhariGowda:fix/multi-sig-types-silent-failure

Conversation

@BhariGowda

@BhariGowda BhariGowda commented Aug 22, 2026

Copy link
Copy Markdown

add_multi_sig_types inserts payloadMultiSigUser and outerSigner after the hyperliquidChain entry. When sign_types has no hyperliquidChain entry it printed a warning and returned the list unchanged, and signing then carried on with the un-enriched types.

That does not fail loudly. eth_account builds the EIP-712 hash from the type list and ignores message keys that have no matching type entry, so add_multi_sig_fields still puts payloadMultiSigUser and outerSigner into the message and they are silently dropped from the hash. The signature that comes out is byte-identical to a plain single-signer one for the same action:

BAD = [t for t in USD_SEND_SIGN_TYPES if t["name"] != "hyperliquidChain"]

sign_multi_sig_user_signed_action_payload(w, action, False, BAD, "HyperliquidTransaction:UsdSend", msu, outer)
    == sign_user_signed_action(w, action, BAD, "HyperliquidTransaction:UsdSend", False)
# True

Both produce r=0x31d2170f…, s=0x1f3c5743…, v=28.

Since it carries no binding to the multi-sig user or the outer signer, it is not usable as an inner multi-sig signature. Every sign-types list exported from this module has hyperliquidChain first, so this only affects callers passing their own type list, which is the case for any action the SDK does not have a helper for yet. A warning on stdout is easy to miss there, and the failure surfaces later as a rejected action with nothing pointing back at the cause.

Two things I checked before changing this to raise.

It is not a quirk of one eth_account release. The two signatures come out byte-identical, same r and same s, on 0.10.0, 0.11.3, 0.12.3 and 0.13.7, which covers the whole range this package allows (eth-account = ">=0.10.0,<0.14.0").

There is no valid user-signed action whose types legitimately omit hyperliquidChain, so raising here cannot break a configuration that works today. All 10 sign-types constants exported from hyperliquid/utils/signing.py have it as their first field. I also went through the third-party TypeScript SDK nktkas/hyperliquid, which models 17 user-signed actions including 6 this SDK has no helper for yet (cDeposit, cWithdraw, linkStakingUser, sendToEvmWithData, stakingLinkDisableTradingUser, userPortfolioMargin), and all 17 have hyperliquidChain first as well. Reaching the un-enriched branch is therefore always a caller mistake rather than a legitimate case the SDK needs to keep supporting.

Raising ValueError instead. No signature that the chain accepts today changes, because the un-enriched path could not produce one.

The test asserts that both add_multi_sig_types and sign_multi_sig_user_signed_action_payload raise on a type list with no hyperliquidChain, and pins the enriched field order for the normal case.

Ran pytest tests/signing_test.py (14 passed) plus black, isort and flake8 with the repo's configured settings.

add_multi_sig_types inserts payloadMultiSigUser and outerSigner after the
hyperliquidChain entry. When sign_types has no hyperliquidChain entry it
printed a warning and returned the list unchanged, then signing carried
on with the un-enriched types.

eth_account builds the EIP-712 hash from the type list and ignores
message keys with no matching type entry, so add_multi_sig_fields still
puts payloadMultiSigUser and outerSigner into the message and they are
silently dropped from the hash. The result is a signature identical to a
plain single-signer one for the same action:

    BAD = [t for t in USD_SEND_SIGN_TYPES if t["name"] != "hyperliquidChain"]
    sign_multi_sig_user_signed_action_payload(w, a, False, BAD, ..., msu, outer)
      == sign_user_signed_action(w, a, BAD, ..., False)   # True

It carries no binding to the multi-sig user or the outer signer, so it is
not usable as an inner multi-sig signature. All of the sign types
exported from this module have hyperliquidChain first, so this only
affects callers passing their own type list, which is the case for any
action the SDK does not have a helper for yet. A stdout warning is easy
to miss there.

Raising ValueError instead. No signature that the chain accepts today
changes, since the un-enriched path could not produce one.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant