Skip to content

Commit 93b692d

Browse files
authored
Update hyperliquid docs with protocol v2 flow (#161)
* Update hyperliquid docs * fix
1 parent d04c88a commit 93b692d

File tree

1 file changed

+242
-96
lines changed

1 file changed

+242
-96
lines changed
Lines changed: 242 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
---
22
title: "Hyperliquid Support"
3-
description: "How to deposit and withdraw from Hyperliquid to any Relay Chain"
3+
description: "How to deposit and withdraw from Hyperliquid to any Relay chain"
44
---
55

6-
Relay supports depositing & withdrawing to Hyperliquid (Hypercore) perpsUSDC from any supported chain. To try it today, use the [Relay App](https://relay.link/bridge/hyperliquid). 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.
6+
Relay supports depositing and withdrawing Hyperliquid (Hypercore) perpsUSDC from any supported chain. To try it today, use the [Relay App](https://relay.link/bridge/hyperliquid). Relay also provides complete support for HyperEVM, which can be accessed using the standard quote flow with `chainId=999`. This document details how to integrate Hyperliquid deposits and withdrawals into your application.
77

88
# API Access
99

10-
Hyperliquid can be accessed using the standard Relay API flow, with the following properties. To get started, check out the [**execution steps**](https://docs.relay.link/references/api/step-execution) of our API. Then when youre ready to swap, head over to the [**Get Quote**](https://docs.relay.link/references/api/get-quote) API endpoint.
10+
Hyperliquid can be accessed using the standard Relay API flow. To get started, review the [**execution steps**](https://docs.relay.link/references/api/step-execution) documentation. When you're ready to execute swaps, refer to the [**Get Quote**](https://docs.relay.link/references/api/get-quote) API endpoint.
1111

12-
## Hyperliquid Specific API Parameters
12+
## Hyperliquid-Specific API Parameters
1313

14-
| **Action** | **Parameter** | **Input** | **Description** |
15-
| ---------------------------- | --------------------- | --------- | -------------------- |
16-
| Deposit to Hyperliquid | toChainId | 1337 | Hyperliquid Chain Id |
17-
| | recipient | | Hyperliquid Address |
18-
| Withdrawals from Hyperliquid | protocolVersion\*\*\* | v2 | |
19-
| | fromChainId | 1337 | Hyperliquid Chain Id |
14+
| **Action** | **Parameter** | **Input** | **Description** |
15+
| ---------------------------- | --------------- | --------- | ----------------------------------------------------- |
16+
| Deposit to Hyperliquid | toChainId | 1337 | Hyperliquid Chain ID |
17+
| | recipient | | Hyperliquid Address |
18+
| Withdraw from Hyperliquid | protocolVersion | v2 | Required for withdrawals |
19+
| | fromChainId | 1337 | Hyperliquid Chain ID |
2020

21-
These params define the necessary parameters for Hyperliquid interactions. All other required API parameters are still necessary. _`*** for withdrawals protocolVersion:v2 is a required param.`_
21+
These parameters are specific to Hyperliquid interactions. All other standard API parameters remain required. Note that `protocolVersion: v2` is mandatory for withdrawals.
2222

2323
## Currency Addresses
2424

@@ -29,118 +29,264 @@ These params define the necessary parameters for Hyperliquid interactions. All o
2929
curl -X POST https://api.hyperliquid.xyz/info -H 'Content-Type: application/json' -d '{"type": "spotMeta"}' | jq '.tokens[]'
3030
```
3131

32-
- The address of non-Spot and non-Perps currencies (eg. currencies on HIP-3 DEXs) is determined by appending the hex-encoded DEX name at the end of the corresponding Spot currency address used by the DEX. For example, `USDC` on the `xyz` DEX is represented as `0x6d1e7cde53ba9467b783cb7c530ce05478797a`, where `0x6d1e7cde53ba9467b783cb7c530ce054` is the address of Spot USDC and `78797a` is the hex representation of xyz.
32+
- For currencies on HIP-3 DEXs (non-Spot and non-Perps), append the hex-encoded DEX name to the corresponding Spot currency address. For example, `USDC` on the `xyz` DEX is represented as `0x6d1e7cde53ba9467b783cb7c530ce05478797a`, where `0x6d1e7cde53ba9467b783cb7c530ce054` is the Spot USDC address and `78797a` is the hex representation of "xyz".
3333

34-
## Interacting with Hyperliquid to Initiate Withdrawals
34+
## Withdrawals from Hyperliquid
3535

36-
In order to facilitate withdrawals from hyperliquid in your application, you need to query users balances, and submit hyperliquid transactions.
36+
Hyperliquid withdrawals use a two-step signature flow. The v2 implementation leverages the unique nonce associated with every Hyperliquid transfer to map deposits to request IDs. This requires two signatures:
3737

38-
### Getting Hyperliquid Balances
38+
1. **Authorize**: Sign a nonce-mapping message that associates a specific nonce with a request ID
39+
2. **Deposit**: Sign and submit the Hyperliquid transfer transaction using the same nonce
3940

40-
You can use the [Hyperliquid info API](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint) 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.
41+
<Warning>
42+
The deposit transaction must use the exact same nonce from the authorization signature. If the nonces don't match, Relay cannot associate the transfer with the request ID.
43+
</Warning>
4144

42-
### Submitting Hyperliquid Tx
45+
### Getting Hyperliquid Balances
4346

44-
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.
47+
Use the [Hyperliquid info API](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint) to query balances. Pass `clearinghouseState` as the `type` parameter, then read the `withdrawable` property, which returns a USD value in human-readable format.
4548

46-
1. You’ll need to take the quote (which returns a transaction step) and convert this into signature data:
49+
### Example Quote Request
4750

51+
```bash
52+
curl 'https://api.relay.link/quote' \
53+
-H 'content-type: application/json' \
54+
-d '{
55+
"user": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e",
56+
"originChainId": 1337,
57+
"destinationChainId": 10,
58+
"originCurrency": "0x00000000000000000000000000000000",
59+
"destinationCurrency": "0x0000000000000000000000000000000000000000",
60+
"recipient": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e",
61+
"tradeType": "EXACT_INPUT",
62+
"amount": "1000000000",
63+
"refundTo": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e",
64+
"protocolVersion": "v2"
65+
}'
4866
```
67+
68+
The response includes two steps that must be executed in order.
69+
70+
### Step 1: Authorize (Nonce-Mapping Signature)
71+
72+
The first step (`id: authorize`) requires signing an EIP-712 message that maps the nonce to the request ID. This signature can be executed on any EVM chain.
73+
74+
<Note>
75+
When signing on a different chain than the default, update both `item.data.sign.domain.chainId` and `item.data.post.body.signatureChainId` to match the chain ID where the user signs the message.
76+
</Note>
77+
78+
Example authorize step response:
79+
80+
```json
4981
{
50-
domain: {
51-
name: 'HyperliquidSignTransaction',
52-
version: '1',
53-
chainId: chainId, //This will be the active chain id for the wallet
54-
verifyingContract: '0x0000000000000000000000000000000000000000'
55-
},
56-
types: {
57-
'HyperliquidTransaction:UsdSend': [
58-
{ name: 'hyperliquidChain', type: 'string' },
59-
{ name: 'destination', type: 'string' },
60-
{ name: 'amount', type: 'string' },
61-
{ name: 'time', type: 'uint64' }
62-
],
63-
EIP712Domain: [
64-
{ name: 'name', type: 'string' },
65-
{ name: 'version', type: 'string' },
66-
{ name: 'chainId', type: 'uint256' },
67-
{ name: 'verifyingContract', type: 'address' }
68-
]
69-
},
70-
primaryType: 'HyperliquidTransaction:UsdSend',
71-
value: {
72-
type: 'usdSend',
73-
signatureChainId: `0x${chainId.toString(16)}`,
74-
hyperliquidChain: 'Mainnet',
75-
destination: destination?.toLowerCase(),
76-
amount,
77-
time: new Date().getTime()
78-
}
82+
"id": "authorize",
83+
"action": "Sign nonce-mapping for the deposit",
84+
"description": "Sign the message that maps the deposit to the order",
85+
"kind": "signature",
86+
"items": [
87+
{
88+
"status": "incomplete",
89+
"data": {
90+
"sign": {
91+
"signatureKind": "eip712",
92+
"domain": {
93+
"name": "RelayNonceMapping",
94+
"version": "1",
95+
"chainId": 1,
96+
"verifyingContract": "0x0000000000000000000000000000000000000000"
97+
},
98+
"types": {
99+
"NonceMapping": [
100+
{ "name": "chainId", "type": "string" },
101+
{ "name": "wallet", "type": "address" },
102+
{ "name": "id", "type": "bytes32" },
103+
{ "name": "nonce", "type": "uint256" }
104+
]
105+
},
106+
"value": {
107+
"chainId": "hyperliquid",
108+
"wallet": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e",
109+
"nonce": 1765463670254,
110+
"id": "0x7b3e352d6bb600f7d937250968dc9b9c7deaf34b3787fb2312fc7374603f1667"
111+
},
112+
"primaryType": "NonceMapping"
113+
},
114+
"post": {
115+
"endpoint": "/authorize",
116+
"method": "POST",
117+
"body": {
118+
"type": "nonce-mapping",
119+
"walletChainId": 1337,
120+
"wallet": "0xf3d63166f0ca56c3c1a3508fce03ff0cf3fb691e",
121+
"nonce": 1765463670254,
122+
"id": "0x7b3e352d6bb600f7d937250968dc9b9c7deaf34b3787fb2312fc7374603f1667",
123+
"signatureChainId": 1
124+
}
125+
}
126+
}
127+
}
128+
],
129+
"requestId": "0x33d530353522088c2a4016a7eeb2185b8f5926ab230eff44d4495746a43a6f87"
79130
}
80131
```
81132

82-
Let’s break down the object above:
133+
After signing, post the signature to the `/authorize` endpoint as specified in `item.data.post`.
83134

84-
- `domain` - static except for the `chainId`.
85-
- `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.
86-
- `primaryType` should match the name of the first type.
87-
- `value`dynamic execution data
88-
- `signatureChainId` is the hex representation of the aforementioned active chain id.
135+
### Step 2: Deposit (Hyperliquid Transaction)
89136

90-
```jsx
91-
const items = steps[0]?.items;
92-
const destination = items[0]?.data?.action?.parameters?.destination;
93-
```
137+
The second step (`id: deposit`) requires signing and submitting the actual Hyperliquid transfer. The API response includes `eip712Types` and `eip712PrimaryType` to simplify constructing the signature data.
94138

95-
The amount can also be retrieved in a similar fashion:
139+
Example deposit step response:
96140

97-
```jsx
98-
const items = steps[0]?.items;
99-
const amount = items[0]?.data?.action?.parameters?.amount;
141+
```json
142+
{
143+
"id": "deposit",
144+
"action": "Confirm transaction in your wallet",
145+
"description": "Depositing funds to the relayer to execute the swap for ETH",
146+
"kind": "transaction",
147+
"items": [
148+
{
149+
"status": "incomplete",
150+
"data": {
151+
"action": {
152+
"type": "sendAsset",
153+
"parameters": {
154+
"hyperliquidChain": "Mainnet",
155+
"destination": "0x865eb9baa5492cef598adf7afb1038654fcb7081",
156+
"sourceDex": "",
157+
"destinationDex": "",
158+
"token": "USDC:0x6d1e7cde53ba9467b783cb7c530ce054",
159+
"amount": "10.000000",
160+
"fromSubAccount": "",
161+
"nonce": 1765463670254
162+
}
163+
},
164+
"nonce": 1765463670254,
165+
"eip712Types": {
166+
"HyperliquidTransaction:SendAsset": [
167+
{ "name": "hyperliquidChain", "type": "string" },
168+
{ "name": "destination", "type": "string" },
169+
{ "name": "sourceDex", "type": "string" },
170+
{ "name": "destinationDex", "type": "string" },
171+
{ "name": "token", "type": "string" },
172+
{ "name": "amount", "type": "string" },
173+
{ "name": "fromSubAccount", "type": "string" },
174+
{ "name": "nonce", "type": "uint64" }
175+
]
176+
},
177+
"eip712PrimaryType": "HyperliquidTransaction:SendAsset"
178+
},
179+
"check": {
180+
"endpoint": "/intents/status?requestId=0x33d530353522088c2a4016a7eeb2185b8f5926ab230eff44d4495746a43a6f87",
181+
"method": "GET"
182+
}
183+
}
184+
],
185+
"requestId": "0x33d530353522088c2a4016a7eeb2185b8f5926ab230eff44d4495746a43a6f87"
186+
}
100187
```
101188

102-
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:
189+
### Constructing the Signature Data
103190

104-
```jsx
191+
To sign the deposit step, you need to convert it into EIP-712 signature data. Extract the necessary fields from the step response and construct the signature object:
105192

106-
import { useWalletClient } from 'wagmi'
107-
...
108-
const walletClient = useWalletClient()
109-
//All of these fields were already populated above
193+
```typescript
194+
// Extract data from the deposit step
195+
const stepItem = step.items[0]
196+
const action = stepItem.data.action
197+
const eip712Types = stepItem.data.eip712Types
198+
const eip712PrimaryType = stepItem.data.eip712PrimaryType
199+
200+
// The chain ID of the connected wallet
201+
const chainId = 1 // Example: Ethereum mainnet
202+
203+
// Construct the EIP-712 signature data
110204
const signatureData = {
111-
account: wallet.account as Account,
112-
domain: domain,
113-
types: types,
114-
primaryType: primaryType,
115-
message: value
205+
domain: {
206+
name: 'HyperliquidSignTransaction',
207+
version: '1',
208+
chainId: chainId,
209+
verifyingContract: '0x0000000000000000000000000000000000000000'
210+
},
211+
types: {
212+
...eip712Types,
213+
EIP712Domain: [
214+
{ name: 'name', type: 'string' },
215+
{ name: 'version', type: 'string' },
216+
{ name: 'chainId', type: 'uint256' },
217+
{ name: 'verifyingContract', type: 'address' }
218+
]
219+
},
220+
primaryType: eip712PrimaryType,
221+
value: {
222+
...action.parameters,
223+
type: action.type,
224+
signatureChainId: `0x${chainId.toString(16)}`
225+
}
116226
}
117-
signature = await wallet.signTypedData(signatureData)
118227
```
119228

120-
Now the transaction must be submitted:
229+
Key fields explained:
230+
231+
- `domain`: Static except for `chainId`, which should match the connected wallet's active chain
232+
- `types`: Combines the API-provided `eip712Types` with the standard `EIP712Domain`
233+
- `primaryType`: Use the `eip712PrimaryType` from the API response
234+
- `message`: Spread the action parameters and add `type` and `signatureChainId`
235+
- `signatureChainId`: Hex representation of the active chain ID
121236

122-
```jsx
237+
### Signing the Message
238+
239+
Use your wallet client to sign the EIP-712 typed data:
240+
241+
```typescript
242+
import { useWalletClient } from 'wagmi'
243+
244+
const { data: walletClient } = useWalletClient()
245+
246+
const signature = await walletClient.signTypedData({
247+
account: walletClient.account,
248+
domain: signatureData.domain,
249+
types: signatureData.types,
250+
primaryType: signatureData.primaryType,
251+
message: signatureData.value
252+
})
253+
```
254+
255+
### Submitting to Hyperliquid
256+
257+
After signing, submit the transaction to Hyperliquid's exchange API:
258+
259+
```typescript
123260
import { parseSignature } from 'viem'
124-
...
125-
const { r, s, v } = parseSignature(signature as `0x${string}`)
126-
const res = await axios.post('https://api.hyperliquid.xyz/exchange', {
127-
signature: {
128-
r,
129-
s,
130-
v: Number(v ?? 0n)
131-
},
132-
nonce: time,
133-
action: {
134-
type: type,
135-
signatureChainId: `0x${chainId.toString(16)}`,
136-
hyperliquidChain: 'Mainnet',
137-
destination: destination?.toLowerCase(),
138-
amount: amount,
139-
time: time
140-
}
141-
})
261+
import axios from 'axios'
262+
263+
const { r, s, v } = parseSignature(signature)
264+
265+
266+
const action = signatureData.value
267+
const nonce = stepItem.data.nonce
268+
269+
// Submit to Hyperliquid
270+
const response = await axios.post('https://api.hyperliquid.xyz/exchange', {
271+
signature: {
272+
r,
273+
s,
274+
v: Number(v ?? 0n)
275+
},
276+
nonce,
277+
action
278+
})
279+
280+
if (response.status !== 200 || response.data?.status !== 'ok') {
281+
throw new Error('Failed to submit transaction to Hyperliquid')
282+
}
142283
```
143284

144-
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”`.
285+
Important considerations:
286+
287+
- Parse the signature into its component parts (`r`, `s`, `v`)
288+
- Ensure `v` is a `Number`, not a `BigInt`
289+
- The action data must exactly match what was signed
290+
- A successful response returns status `200` with `data.status: "ok"`
145291

146-
To see an [example](https://github.com/reservoirprotocol/relay-kit/blob/main/packages/sdk/src/utils/hyperliquid.ts) you can have a look at our publicly available SDK.
292+
For a complete implementation example, refer to our [publicly available SDK](https://github.com/reservoirprotocol/relay-kit/blob/main/packages/sdk/src/utils/hyperliquid.ts).

0 commit comments

Comments
 (0)