Skip to content

exchange.multi_sig() always returns 'Invalid multi-sig outer signer' for User-Signed Actions (sendAsset) #263

@allenxing4071

Description

@allenxing4071

Description

exchange.multi_sig() always returns {"status": "err", "response": "Invalid multi-sig outer signer"} when used with User-Signed Actions like sendAsset. The inner signatures are generated using sign_multi_sig_user_signed_action_payload(), and the outer signature is generated by sign_multi_sig_action() inside exchange.multi_sig().

Environment

  • SDK version: 0.22.0 (also tested with 0.20.0)
  • Python: 3.11 and 3.13
  • Network: Mainnet (https://api.hyperliquid.xyz)

Steps to Reproduce

  1. Create a 2/2 multi-sig account with two authorized users (A and B)
  2. Sign a sendAsset inner action with both A and B using sign_multi_sig_user_signed_action_payload()
  3. Submit via exchange.multi_sig() where the Exchange is initialized with wallet A (an authorized user)
  4. Response: {"status": "err", "response": "Invalid multi-sig outer signer"}

Code

```python
from eth_account import Account
from hyperliquid.exchange import Exchange
from hyperliquid.utils.signing import SEND_ASSET_SIGN_TYPES, sign_multi_sig_user_signed_action_payload

MULTISIG_USER = "0x5c99..." # multi-sig account
KEY_A = "0x..." # authorized user A
KEY_B = "0x..." # authorized user B

outer_wallet = Account.from_key(KEY_A)
outer_signer = outer_wallet.address.lower()
nonce = int(time.time() * 1000)

action = {
"type": "sendAsset", "nonce": nonce - 1, "token": "USDC", "amount": "5.0",
"sourceDex": "", "destination": "0x...", "destinationDex": "", "fromSubAccount": "",
}

sig1 = sign_multi_sig_user_signed_action_payload(
wallet=Account.from_key(KEY_A), action=action, is_mainnet=True,
sign_types=SEND_ASSET_SIGN_TYPES, tx_type="HyperliquidTransaction:SendAsset",
payload_multi_sig_user=MULTISIG_USER, outer_signer=outer_signer,
)
sig2 = sign_multi_sig_user_signed_action_payload(
wallet=Account.from_key(KEY_B), action=action, is_mainnet=True,
sign_types=SEND_ASSET_SIGN_TYPES, tx_type="HyperliquidTransaction:SendAsset",
payload_multi_sig_user=MULTISIG_USER, outer_signer=outer_signer,
)

action["hyperliquidChain"] = "Mainnet"
action["signatureChainId"] = "0x66eee"

exchange = Exchange(outer_wallet, "https://api.hyperliquid.xyz")
result = exchange.multi_sig(MULTISIG_USER, action, [sig1, sig2], nonce)
print(result) # {"status": "err", "response": "Invalid multi-sig outer signer"}
```

Key observations

  • The same wallet (A) that is verified as an authorized user on-chain (confirmed via userToMultiSigSigners API) produces Invalid multi-sig outer signer
  • Normal (non-multi-sig) sendAsset with the same wallet works fine
  • A freshly created multi-sig account with brand new wallets also fails the same way
  • The TypeScript SDK (viem) with identical parameters succeeds, suggesting the issue is specific to the Python SDK's sign_multi_sig_action() or action_hash() implementation

Possibly related

Could there be a msgpack serialization mismatch between the Python SDK and the Hyperliquid backend when computing the action hash for the outer SendMultiSig signature?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions