Signing

Every exchange action on Hyperliquid requires an EIP-712arrow-up-right signature. ExchangeClient handles this automatically. The following functions are for custom integrations or actions not yet supported by ExchangeClient.

How signing works

Hyperliquid has two signing flows depending on the action type:

L1 actions
User-signed actions

Examples

Trading and position management

Fund movements and account security

EIP-712 domain

Exchange, chain ID 1337

HyperliquidSignTransaction, user's chain ID

What gets signed

Action hash as connectionId

Action fields directly

L1 action

The action is never signed directly. Instead, a phantom agent is constructed:

  1. Msgpackarrow-up-right-encode the action object (field order matters β€” the expected order varies by action type)

  2. Append the noncearrow-up-right as uint64 big-endian (8 bytes)

  3. Append a vaultarrow-up-right marker: 0x01 + 20-byte vault address, or 0x00 if none

  4. If expiresAfterarrow-up-right is set, append 0x00 + the timestamp as uint64 big-endian

  5. Keccak-256arrow-up-right hash the concatenated bytes. This is the connectionId

  6. Sign an EIP-712arrow-up-right message with:

    • Domain: { name: "Exchange", version: "1", chainId: 1337, verifyingContract: 0x0...0 }

    • Type: Agent { source: string, connectionId: bytes32 }

    • Message: { source: "a" (mainnet) or "b" (testnet), connectionId } where connectionId is the hash from step 5

  7. Send { action, signature: { r, s, v }, nonce } to the exchange endpointarrow-up-right

Chain ID 1337 is hardcoded and doesn't depend on the wallet's network. The phantom agent construct means the validator recovers the signer from the Agent message, then verifies that the connectionId matches the action hash.

User-signed action

The action fields are placed directly into the EIP-712 message, with no hashing or phantom agent:

  1. Each action type defines its own typed dataarrow-up-right structure (for example, HyperliquidTransaction:ApproveAgent)

  2. Sign an EIP-712 message with:

    • Domain: { name: "HyperliquidSignTransaction", version: "1", chainId: <signatureChainId>, verifyingContract: 0x0...0 }

    • Type and message: defined per action

  3. Send { action, signature: { r, s, v }, nonce } to the exchange endpoint

The signatureChainId field in the action (hex, such as "0x66eee") sets the EIP-712 domain chain ID.

Common rules

Both flows share:

  • Nonce: current timestamp in milliseconds. Hyperliquid stores the 100 highest nonces per signer and rejects duplicates. See Noncesarrow-up-right.

  • Signature: ECDSA { r, s, v } where v is 27 or 28

  • Hex strings: lowercase all hex values before signing

The following functions implement these flows. Each accepts any compatible wallet.

L1 actions

signL1Action signs an L1 action and returns an ECDSA signature.

Use isTestnet: true when signing for the testnet β€” this changes the EIP-712 source from "a" to "b".

User-signed actions

signUserSignedAction signs a user-signed action and returns an ECDSA signature.

Each action type has its own EIP-712 types, exported from @nktkas/hyperliquid/api/exchange using the convention PascalCase(actionType) + "Types" (for example, Withdraw3Types for withdraw3, ApproveAgentTypes for approveAgent).

Action hashing

createL1ActionHash produces the keccak256 hash used as connectionId in L1 signing. Use it to verify that your action serialization matches what the SDK produces:

circle-exclamation

Optional parameters vaultAddress and expiresAfter are included in the hash when present.

Multi-sig actions

signMultiSigAction signs the multi-sigarrow-up-right wrapper after all signers have signed the inner action. The inner signing differs for L1 and user-signed actions. Use isTestnet: true when signing for the testnet, as with signL1Action.

L1 actions

Each signer signs the [multiSigUser, outerSigner, action] tuple via signL1Action. The leader (first signer) then signs the wrapper via signMultiSigAction:

User-signed

Each signer signs the action with embedded payloadMultiSigUser and outerSigner fields via signUserSignedAction. The leader then signs the wrapper via signMultiSigAction:

Wallet compatibility

All signing functions accept AbstractWallet β€” a union of supported wallet interfaces:

signTypedData

Address

Chain ID

1 param (object)

address property

fallback 0x1

1 param (object)

getAddresses()

getChainId()

3 params

getAddress()

provider.getNetwork()

Any object matching one of these interfaces works. See the Custom tab in signing examples.

Helpers

These functions work with any supported wallet type:

  • getWalletAddress β€” returns the wallet address, always lowercase

  • getWalletChainId β€” returns the wallet chain ID as hex, falls back to "0x1" for local wallets without a provider

Last updated