Relay supports depositing & withdrawing to Hyperliquid (Hypercore) perpsUSDC from any supported chain. To try it today, use the Relay App. Relay also has complete support for HyperEVM, which can be accessed using our main quote flow with chainId=999. This document details how to deposit and withdraw from Hyperliquid within your app.

API Access

Hyperliquid can be accessed using the standard Relay API flow, with the following properties. To get started, check out the execution steps of our API. Then when you’re ready to swap, head over to the Get Quote API endpoint.

Hyperliquid Specific API Parameters

ActionParameterInputDescription
Deposit to HyperliquidtoChainId1337Hyperliquid Chain Id
recipientHyperliquid Address
Withdrawals from HyperliquiduseDepositAddress***true
fromChainId1337Hyperliquid Chain Id
These params define the necessary parameters for Hyperliquid interactions. All other required API parameters are still necessary. *** for withdrawals useDepositAddress:true is a required param do to the current structure of hypercore.

Interacting with Hyperliquid to Initiate Withdrawals

In order to facilitate withdrawals from hyperliquid in your application, you need to query users balances, and submit hyperliquid transactions.

Getting Hyperliquid Balances

You can use the Hyperliquid info API to get balances. Make sure to pass in clearinghouseState as the type. Then you can read the withdrawable property which should be a USD value in human readable format.

Submitting Hyperliquid Tx

Once you verify that your address has a usdc perps balance you must sign a message proving ownership over the account and verifying an amount of USDC perps that can be withdrawn. The Relay SDK already handles this for you but here’s a step by step guide on how to do this yourself.
  1. You’ll need to take the quote (which returns a transaction step) and convert this into signature data:
{
            domain: {
              name: 'HyperliquidSignTransaction',
              version: '1',
              chainId: chainId, //This will be the active chain id for the wallet
              verifyingContract: '0x0000000000000000000000000000000000000000'
            },
            types: {
              'HyperliquidTransaction:UsdSend': [
                { name: 'hyperliquidChain', type: 'string' },
                { name: 'destination', type: 'string' },
                { name: 'amount', type: 'string' },
                { name: 'time', type: 'uint64' }
              ],
              EIP712Domain: [
                { name: 'name', type: 'string' },
                { name: 'version', type: 'string' },
                { name: 'chainId', type: 'uint256' },
                { name: 'verifyingContract', type: 'address' }
              ]
            },
            primaryType: 'HyperliquidTransaction:UsdSend',
            value: {
              type: 'usdSend',
              signatureChainId: `0x${chainId.toString(16)}`,
              hyperliquidChain: 'Mainnet',
              destination: destination?.toLowerCase(),
              amount,
              time: new Date().getTime()
            }
}
Let’s break down the object above:
  • domain - static except for the chainId.
  • chainId is the active chain id in the connected wallet. This is the chain id that will sign the message. Next up is the types. These are also static and can be hardcoded.
  • primaryType should match the name of the first type.
  • valuedynamic execution data
  • signatureChainId is the hex representation of the aforementioned active chain id.
const items = steps[0]?.items;
const destination = items[0]?.data?.action?.parameters?.destination;
The amount can also be retrieved in a similar fashion:
const items = steps[0]?.items;
const amount = items[0]?.data?.action?.parameters?.amount;
Finally we generate a time, this is used to avoid collisions in Hyperliquid, which recommends using the current UTC millisecond time. The data must then be signed and submitted. The signature should be compliant with eip712:

import { useWalletClient } from 'wagmi'
...
const walletClient = useWalletClient()
//All of these fields were already populated above
const signatureData = {
	account: wallet.account as Account,
	domain: domain,
	types: types,
	primaryType: primaryType,
	message: value
}
signature = await wallet.signTypedData(signatureData)
Now the transaction must be submitted:
import { parseSignature } from 'viem'
...
const { r, s, v } = parseSignature(signature as `0x${string}`)
const res = await axios.post('https://api.hyperliquid.xyz/exchange', {
    signature: {
      r,
      s,
      v: Number(v ?? 0n)
    },
    nonce: time,
    action: {
      type: type,
      signatureChainId: `0x${chainId.toString(16)}`,
      hyperliquidChain: 'Mainnet',
      destination: destination?.toLowerCase(),
      amount: amount,
      time: time
    }
 })
You must parse the signature into parts (r, s, v). Also make sure that the v is a number and not a BigInt/BigNumber. Use the same data you used when signing, it should match exactly the data you signed. The successful response should be a 200 and the data.status should be an “ok”. To see how an example you can have a look at our publicly available SDK.