Hyperliquid Testnet: Faucet, API URLs and Mainnet Gotchas
How to test a Hyperliquid app on testnet: test USDC and HYPE, the API, WebSocket and HyperEVM addresses, and what breaks when you move to mainnet.

Hyperliquid's testnet is a separate copy of the exchange where you trade with mock USDC. Use it to test order logic, signing and error handling before real money is involved.
Setup is quick. The harder part is moving your code to mainnet, where several things work differently. This guide covers both. For endpoints, API keys and limits in general, see our Hyperliquid API guide. If you don't write code, skip the code blocks and read the results.
Hyperliquid testnet addresses
| Part | Testnet | Mainnet |
|---|---|---|
| App | app.hyperliquid-testnet.xyz | app.hyperliquid.xyz |
| API (info and exchange) | https://api.hyperliquid-testnet.xyz | https://api.hyperliquid.xyz |
| WebSocket | wss://api.hyperliquid-testnet.xyz/ws | wss://api.hyperliquid.xyz/ws |
| HyperEVM RPC | https://rpc.hyperliquid-testnet.xyz/evm | https://rpc.hyperliquid.xyz/evm |
| HyperEVM chain ID | 998 | 999 |
| Block explorer | app.hyperliquid-testnet.xyz/explorer | app.hyperliquid.xyz/explorer |
The Python SDK has both API addresses built in as constants.TESTNET_API_URL and constants.MAINNET_API_URL.
Hyperliquid testnet faucet: how to get test USDC
Hyperliquid's own faucet is the easiest, but you can claim only once, and only after a mainnet deposit. Circle's faucet works without a deposit. Your options:
| Source | What you get | The catch |
|---|---|---|
| Hyperliquid faucet | 1,000 mock USDC, straight into your testnet trading account | The same address must have deposited on mainnet. One claim per address |
| Circle faucet, network "HyperEVM Testnet" | 20 USDC per request, no account or deposit needed | One request every 2 hours per network. It arrives on HyperEVM, so you move it into your trading account yourself (see below) |
| Circle faucet on Arbitrum Sepolia, then Circle's bridge (CCTP) | 20 USDC per request, less a 0.20 USDC forwarding fee, sent to your trading account | Needs Arbitrum Sepolia ETH for gas. Mostly for teams testing deposit flows |
| Another tester | Any amount | A teammate who already has test USDC can send it to your address |
If you sign in to the Hyperliquid app with email, it creates a different wallet address on mainnet and testnet. Export the mainnet wallet and import it into a browser wallet so you use the same address on both, or the faucet won't see your mainnet deposit.
Moving Circle's USDC into your trading account. Testnet USDC from Circle's faucet lands on HyperEVM, at 0x2B3370eE501B4a559b57D449569354196457D8Ab. To trade with it, approve and deposit it into Circle's deposit contract, 0x0B80659a4076E9E93C7DbE0f10675A16a3e5C206, which credits your Hyperliquid testnet account. Testnet spotMeta lists this contract as USDC's link between HyperEVM and the trading layer. We saw other testers' deposits going through it, but didn't send one ourselves.
How to get testnet HYPE for HyperEVM
Mock USDC is for trading on HyperCore, Hyperliquid's trading layer. To send transactions or deploy contracts on HyperEVM testnet, you need testnet HYPE to pay gas. You can make your own with test USDC:
- Get test USDC into your trading account, with Hyperliquid's faucet or Circle's Arbitrum Sepolia route above. Neither needs HyperEVM gas.
- Sell a few dollars of it for HYPE on the testnet HYPE/USDC spot market (
@1035). - Send the HYPE to
0x2222222222222222222222222222222222222222, which moves it to your address on HyperEVM.
From then on, you can claim 20 USDC from Circle's HyperEVM faucet, move it into your trading account and sell it for more HYPE whenever you need to.
Step 3 comes from Hyperliquid's docs on moving HYPE between HyperCore and HyperEVM. We checked that the testnet HYPE/USDC market was listed and trading when we tested, but didn't run the transfer.
Create an API wallet on testnet
An API wallet is a separate key that signs trades for your account, so your main wallet's key stays out of your code. Approve one in the testnet app. Approvals don't carry across networks: the API wallet we approved on testnet isn't approved on mainnet.
Place and cancel a test order
This places a small buy far below the market, which should rest on the book rather than fill. It then cancels the order and lists your open orders. Set HL_SECRET_KEY to your testnet API wallet's key and HL_ACCOUNT_ADDRESS to your testnet main wallet.
import os
import eth_account
from hyperliquid.exchange import Exchange
from hyperliquid.info import Info
from hyperliquid.utils import constants
URL = constants.TESTNET_API_URL
wallet = eth_account.Account.from_key(os.environ["HL_SECRET_KEY"]) # an API wallet made on testnet
account = os.environ["HL_ACCOUNT_ADDRESS"] # your testnet main wallet
info = Info(URL, skip_ws=True)
exchange = Exchange(wallet, URL, account_address=account)
price = round(float(info.all_mids()["BTC"]) * 0.5) # far below the market, so it won't fill
result = exchange.order("BTC", True, 0.0003, price, {"limit": {"tif": "Gtc"}})
if result["status"] != "ok":
raise SystemExit(result)
status = result["response"]["data"]["statuses"][0]
print("Order:", status) # can be {"error": ...} even when status is "ok"
if "resting" in status:
print("Cancel:", exchange.cancel("BTC", status["resting"]["oid"])["response"]["data"]["statuses"])
print("Open orders now:", info.open_orders(account))It printed this (your order ID will differ):
Order: {'resting': {'oid': 63428174589}}
Cancel: ['success']
Open orders now: []Notice the order status check. A rejected order can still come back with a top-level "status": "ok", with the reason inside the order's status. Testnet is the safe place to learn that.
Stream testnet trades
The WebSocket works the same way as on mainnet, at the testnet address. This prints the subscription confirmation, then the first trade from each of the next two messages (a message can hold several trades):
import json
import websocket # installed with the SDK (websocket-client)
ws = websocket.create_connection("wss://api.hyperliquid-testnet.xyz/ws", timeout=30)
ws.send(json.dumps({"method": "subscribe", "subscription": {"type": "trades", "coin": "BTC"}}))
print(ws.recv()) # the subscription confirmation
for _ in range(2):
print(json.loads(ws.recv())["data"][0])
ws.close()On mainnet, a connection with no messages for 60 seconds is dropped, so send a ping on quiet subscriptions. Our WebSocket guide covers reconnecting.
What's different on testnet
Hardcoded testnet values can break your code, or send the wrong order, on mainnet. These are the differences we found:
Asset IDs don't match
Orders refer to markets by number, and the numbers differ between networks:
from hyperliquid.info import Info
from hyperliquid.utils import constants
def asset_ids(url):
universe = Info(url, skip_ws=True).meta()["universe"]
return {asset["name"]: i for i, asset in enumerate(universe)}
test, main = asset_ids(constants.TESTNET_API_URL), asset_ids(constants.MAINNET_API_URL)
for coin in ["BTC", "ETH", "SOL", "HYPE"]:
print(f"{coin}: mainnet asset {main[coin]}, testnet asset {test[coin]}")BTC: mainnet asset 0, testnet asset 3
ETH: mainnet asset 1, testnet asset 4
SOL: mainnet asset 5, testnet asset 0
HYPE: mainnet asset 159, testnet asset 135Of the 207 perps on both networks, only one had the same ID. Spot pairs differ too: HYPE/USDC is named @107 on mainnet and @1035 on testnet (asset IDs 10107 and 11035 in orders). Look IDs up from meta and spotMeta on the network you're using, every time. For perps, using names through the SDK, as the examples here do, avoids the problem; spot pair names like @107 change between networks too, so look those up as well.
Prices aren't mainnet prices
Testnet has its own order books and traders, so prices drift from mainnet. Here are both, read at the same moment:
from hyperliquid.info import Info
from hyperliquid.utils import constants
testnet = Info(constants.TESTNET_API_URL, skip_ws=True)
mainnet = Info(constants.MAINNET_API_URL, skip_ws=True)
for coin in ["BTC", "ETH", "HYPE"]:
print(coin, "testnet:", testnet.all_mids()[coin], "| mainnet:", mainnet.all_mids()[coin])BTC testnet: 81679.5 | mainnet: 81332.5
ETH testnet: 2379.2 | mainnet: 2383.95
HYPE testnet: 86.4825 | mainnet: 77.3055Liquidity differs too. Don't judge slippage, fill rates or a strategy's results from testnet. Testnet is for checking that your code works. For backtests or data pipelines that need real market history, use mainnet data, such as the free Hyperliquid historical data in Reservoir (you only pay AWS transfer costs).
Market settings differ
Maximum leverage was different on 101 of the 207 shared perps, and size decimals (szDecimals, how finely you can size an order) on 7. Testnet also had 272 HIP-3 perp DEXs (markets deployed by third parties) against 10 on mainnet. Read limits from meta on the network you trade on.
Signatures are network-specific
Signatures are tied to the network they're made for, so a testnet signature won't work on mainnet. The SDK handles this from the URL you give it. If you sign requests yourself, check you've switched the network setting too.
The HyperEVM is a different chain
HyperEVM testnet is chain 998, and mainnet is 999. Check it before you deploy a contract or send a transaction:
import requests
for name, url in [("testnet", "https://rpc.hyperliquid-testnet.xyz/evm"),
("mainnet", "https://rpc.hyperliquid.xyz/evm")]:
chain = requests.post(url, json={"jsonrpc": "2.0", "id": 1, "method": "eth_chainId"}).json()["result"]
print(name, "chain ID:", int(chain, 16))Common Hyperliquid testnet errors and how to fix them
We triggered each of these on testnet with a real API wallet. The order rejections (minimum value, price, margin, Alo and Ioc) came back with a top-level "status": "ok", with the error inside the order's own status, so check each order's status, not just the response. The wallet and scheduled-cancel errors came back as "status": "err", and the SDK error never reaches the API.
| Error you see | What it means | Fix |
|---|---|---|
Order must have minimum value of $10. | Size times price is under $10 | Raise the size |
Order has invalid price. or Price must be divisible by tick size. | The price breaks the tick rules (see below the table) | Round the price (see the helper below) |
ValueError: float_to_wire causes rounding (from the Python SDK, before anything is sent) | A price or size has more than 8 decimal places, which the SDK won't send | Round the price and size to the market's rules first (see the helper below) |
Insufficient margin to place order. | Not enough test USDC in the account for that size | Claim more test USDC, or cut the size |
Post only order would have immediately matched, bbo was … | An add-liquidity-only ( Alo) order was priced through the best bid or ask | Price it behind the best bid or ask |
Order could not immediately match against any resting orders. | An immediate-or-cancel ( Ioc) order found nothing to fill at that price | Use a price that crosses the book, or a resting order |
User or API Wallet 0x… does not exist. | The signing key isn't approved for this account on this network, its approval expired, or the request was signed for the other network | Approve the API wallet in the testnet app (More → API) and sign for the network you send to |
Cannot set scheduled cancel time until enough volume traded. Required: $1000000. | Scheduled cancel (the dead man's switch) needs $1,000,000 of traded volume. Our testnet account had $66,609 | Test it on mainnet, with care |
KeyError in the SDK, or the body null from the raw API | The coin name doesn't exist on this network (see below the table) | Read names from meta on thenetwork you're using |
An account value of 0.0 when you query your account | You queried the API wallet's address, which holds nothing | Query your main account's address |
Tick rules. Prices can have at most 5 significant figures, and on perps at most 6 minus the market's szDecimals decimal places (8 minus on spot). Whole-number prices are always allowed. In our tests, too many decimals gave Order has invalid price., and 6 significant figures (40867.5 on BTC) gave Price must be divisible by tick size.
Unknown coins. Testnet's market list differs from mainnet's. For l2Book, testnet answered an unknown coin with HTTP 500 and mainnet with HTTP 200, both with the body null, so check the body, not just the status.
One more we didn't trigger ourselves: per Hyperliquid's docs, its faucet won't pay out to an address with no mainnet deposit. Use Circle's faucet instead.
Three of these, the minimum value, invalid price and SDK rounding errors, can be caught before you send anything. This helper rounds a perp order to the market's rules and checks the $10 minimum, using testnet's market data:
from hyperliquid.info import Info
from hyperliquid.utils import constants
info = Info(constants.TESTNET_API_URL, skip_ws=True)
sz_decimals = {asset["name"]: asset["szDecimals"] for asset in info.meta()["universe"]}
def prepare_order(coin, size, price):
"""Round a perp order to Hyperliquid's rules and check the $10 minimum."""
decimals = sz_decimals[coin] # KeyError here means the coin isn't on this network
size = round(size, decimals)
price = float(f"{price:.5g}") # at most 5 significant figures
if price != int(price):
price = round(price, 6 - decimals) # and at most 6 - szDecimals decimals on perps
if size * price < 10:
raise ValueError(f"Order value ${size * price:.2f} is under the $10 minimum")
return size, price
print(prepare_order("BTC", 0.0012345678, 40887.123456))
print(prepare_order("ETH", 0.0123456, 1234.56789))
try:
prepare_order("BTC", 0.0001, 40887)
except ValueError as e:
print("Rejected before sending:", e)(0.00123, 40887.0)
(0.0123, 1234.6)
Rejected before sending: Order value $4.09 is under the $10 minimumFor more API errors, including nonce errors and duplicate orders after a timeout, see the common mistakes section of the Hyperliquid API guide linked at the top. Testnet is the right place to hit all of them first.
Before you move to mainnet
- Swap the API URL and WebSocket URL, and for HyperEVM, the RPC URL and chain ID.
- Approve an API wallet on mainnet; testnet approvals don't carry over.
- Remove any hardcoded asset IDs, prices, leverage or size rules.
- Keep the per-order status checks you built on testnet.
- Re-check rate limits under real traffic.
FAQ
How do I get testnet HYPE?
Buy it with test USDC on the testnet HYPE/USDC spot market, then send it to 0x2222222222222222222222222222222222222222 to move it to HyperEVM. Circle's faucet can top up your test USDC every 2 hours.
Is Hyperliquid testnet free?
Trading there costs nothing real, as testnet USDC is mock money. Hyperliquid's faucet needs a past mainnet deposit from the same address; Circle's faucet doesn't.
Why can't I get testnet USDC?
For Hyperliquid's faucet, check two things: that the address has deposited on mainnet, and, if you signed in with email, that you're using the same address on both networks. If you have no mainnet deposit, use Circle's faucet instead.
Do API wallets carry over from mainnet?
No. Approve an API wallet on each network. Using a separate key for testnet keeps the two apart.
What is the Hyperliquid testnet chain ID?
998 for HyperEVM testnet. Mainnet is 999.
Sources
[1] Hyperliquid docs: Testnet faucet
[2] Hyperliquid docs: API
[3] Hyperliquid docs: Nonces and API wallets
[4] Hyperliquid docs: Signing
[5] Hyperliquid docs: HyperEVM
[6] Hyperliquid Python SDK
[7] Hyperliquid docs: HyperCore and HyperEVM transfers
[8] Circle: Testnet faucet
Read next

Hyperliquid Fees and Revenue: A Guide for Builders
Hyperliquid fees for builders: maker/taker tiers, builder codes, HIP-3 growth mode, where fees go, and how to pull fee data with the API.
Read ↗
How to Read Hydromancer's Live Stats Page
A plain guide to Hydromancer's live stats page: what each row, number and colour means, and what the dashboard doesn't claim.
Read ↗
Hyperliquid Builder Codes: How They Work and How to Track Fees
How Hyperliquid builder codes work: approval, fee limits, adding one to an order, and four ways to track the fills and fees your code earns.
Read ↗