Overview
This guide shows how to use Magic’s Express Server Wallet to trade perpetual futures on Hyperliquid. The@nktkas/hyperliquid SDK accepts any viem-compatible account, so your server creates a custom account that delegates signTypedData to the TEE — the private key never leaves the enclave.
Prerequisites
Before starting, ensure you have:- A Magic Secret Key — from your Magic Dashboard
- An OIDC Provider ID — configured for your auth provider (setup guide)
- A user JWT from your authentication provider
- USDC on Arbitrum One and ETH for gas (to activate the wallet on Hyperliquid mainnet — required even for testnet)
How It Works
- Your server authenticates the user and obtains a JWT
- JWT is sent to Magic’s TEE to get (or create) the user’s EOA
- Your app deposits at least $5 USDC from Arbitrum to activate the wallet on Hyperliquid
- For testnet: claim 1,000 mock USDC via the faucet API
- A custom viem account delegates
signTypedDatacalls to the TEE - The
@nktkas/hyperliquidExchangeClient uses this account for order signing - Orders, cancellations, and leverage updates are signed and submitted to Hyperliquid
Hyperliquid uses EIP-712 typed data signatures for all exchange actions. The
@nktkas/hyperliquid SDK handles nonces and signature construction automatically — your TEE account only needs to implement signTypedData.TEE Request Helper
All TEE calls use the same authentication headers. Create a reusable helper for your server-side code.TypeScript
Get or Create a Wallet
Fetch the user’s wallet address. If one doesn’t exist, it will be created automatically.TypeScript
Creating a TEE-Backed Account
The Hyperliquid SDK expects a wallet with asignTypedData method. Create a custom viem account that delegates signing to the TEE.
TypeScript
Setting Up Hyperliquid Clients
Install dependencies and initialize the SDK with the TEE-backed account.TypeScript
Depositing USDC
Hyperliquid runs on its own L1 chain — you can’t trade directly from an Arbitrum wallet. To fund your Hyperliquid account, send USDC to the bridge contract on Arbitrum. This is a standard ERC20transfer — your Hyperliquid account is credited within ~1 minute.
First, set up a public client and transaction helper for Arbitrum One:
TypeScript
TypeScript
Testnet Faucet
Once a wallet has been activated with a mainnet deposit of at least $5 USDC, you can claim 1,000 mock USDC on Hyperliquid testnet. This is a one-time claim per address.TypeScript
Withdrawing USDC
To withdraw USDC from Hyperliquid back to Arbitrum, use the SDK’swithdraw3 method. The withdrawal is signed on Hyperliquid and processed by validators — no Arbitrum transaction is needed from the user.
TypeScript
Reading Market Data
Use theInfoClient to fetch prices, order books, and account state. These calls are read-only and don’t require signing.
TypeScript
Placing Orders
The ExchangeClient handles nonce generation and EIP-712 signing automatically.Limit Order
Place a Good-Til-Canceled limit order at a specific price.TypeScript
Market Order
Hyperliquid doesn’t have a native market order type. Use an Immediate-or-Cancel (IOC) limit order with a slippage price.TypeScript
For market buys, set the slippage price above the current mid. For market sells, set it below. The unfilled portion of an IOC order is automatically canceled.
Canceling Orders
TypeScript
Updating Leverage
TypeScript
Switching to Production
To move from testnet to mainnet, remove theisTestnet flag from the transport. The deposit and withdrawal code already uses mainnet addresses.
TypeScript
Asset indices can change between testnet and mainnet. In production, use
info.meta() to look up the correct index for each asset dynamically rather than hardcoding values.Key Dependencies
| Package | Purpose |
|---|---|
@nktkas/hyperliquid | TypeScript SDK for Hyperliquid exchange API |
viem | Ethereum client, account utilities, and typed data hashing |
TEE Endpoints Used
| Endpoint | Purpose |
|---|---|
POST /v1/wallet | Get or create an EOA wallet |
POST /v1/wallet/sign/data | Sign a raw data hash (EIP-712 typed data hash) |
Troubleshooting
TEE returns 401 or 403
TEE returns 401 or 403
Symptoms: Authentication errors when calling TEE endpoints.Solutions:
- Verify the JWT token is valid and not expired
- Check that
X-Magic-Secret-Keymatches your dashboard credentials - Ensure the OIDC Provider ID is correct
- Confirm your domain is allowlisted in the Magic Dashboard
Order rejected with invalid signature
Order rejected with invalid signature
Symptoms: Hyperliquid rejects the order with a signature error.Solutions:
- Verify the
signHashViaTeefunction correctly reconstructs the signature from r, s, v components - Ensure the TEE account address matches the address that deposited USDC on Hyperliquid
- Check that
serializeSignatureis imported fromviem
Insufficient margin
Insufficient margin
Symptoms: Order rejected due to insufficient margin or balance.Solutions:
- Deposit USDC to Hyperliquid using the bridge contract (see Depositing USDC above)
- Check available margin with
info.clearinghouseState({ user: address }) - Ensure at least 5 USDC was deposited (minimum)
- Reduce order size or increase leverage
Asset index not found
Asset index not found
Symptoms: Order rejected because the asset index is invalid.Solutions:
- Use
info.meta()to look up the correct asset index for each coin - Asset indices may differ between testnet and mainnet
- Common indices: 0 = BTC, 4 = ETH (but always verify with
meta())
Resources
Express API Docs
Learn about Magic’s Express Server Wallet API
Hyperliquid Documentation
Official Hyperliquid protocol documentation
@nktkas/hyperliquid SDK
TypeScript SDK with full API coverage
Hyperliquid Testnet
Testnet UI for deposits and testing