> ## Documentation Index
> Fetch the complete documentation index at: https://perps-api.vooi.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Hyperliquid account

> Move assets within Hyperliquid, switch the spot/perp collateral mode, and approve a builder fee

Some Hyperliquid actions require your **main** Hyperliquid wallet to sign on the venue side, even after the VOOI-managed agent is registered: moving assets between perp DEXes, sub-accounts, and spot; changing the account abstraction mode that controls how spot and perp share collateral; and approving the cap on a broker's builder fee (only when the broker your user signed up through monetizes through Hyperliquid builder fees). These three — send-asset, set-abstraction, and builder-fee approval — follow the same prepare → sign → execute pattern as registration and transfers, and all sign against a fixed EIP-712 domain on Arbitrum (chain id `42161`): make sure the user's wallet is on Arbitrum before signing, or the venue rejects the signature. The stablecoin swap is the exception — a single step with no prepare/execute and no signature.

<Info>
  **Prerequisites:** an active VOOI API token, Hyperliquid connected via [Register on Hyperliquid](/docs/register-hyperliquid) or [Use your own keys](/docs/exchanges), and access to the private key of the main Hyperliquid wallet that holds your funds. The VOOI-managed agent cannot sign these actions.
</Info>

## Move assets within Hyperliquid

[`POST /exchange/hyperliquid/send-asset/prepare`](/docs/api-reference/exchange-hyperliquid/build-eip-712-data-the-main-hyperliquid-wallet-must-sign-to-transfer-assets-between-perp-dexes-spot-users-and-or-sub-accounts) builds an EIP-712 message your main wallet signs to move tokens **within Hyperliquid** — between the default USDC perp DEX and a [HIP-3 DEX](/docs/trading-markets#hyperliquid-hip-3-dexes), between your main account and a sub-account, between your spot and perp accounts, or to another user.

For cross-exchange moves (Arbitrum ↔ Hyperliquid ↔ Lighter), use [cross-exchange transfers](/docs/transfers) instead.

### 1. Prepare

```bash theme={null}
POST https://perps-api.vooi.io/exchange/hyperliquid/send-asset/prepare
Authorization: Bearer <token>
Content-Type: application/json

{
  "sourceDex": "",
  "destinationDex": "xyz",
  "fromSubAccount": "",
  "destination": "0xYourMainWallet",
  "token": "USDC:0x...",
  "amount": "100"
}
```

| Field | Description |
| - | - |
| `sourceDex` | Source perp DEX. The accepted values are a closed set: `""` (the default USDC perp), `"xyz"` (trade.xyz), `"mkts"`, and `"vntl"`. Any other slug is rejected. See [Hyperliquid HIP-3 DEXes](/docs/trading-markets#hyperliquid-hip-3-dexes). |
| `destinationDex` | Destination perp DEX, same encoding. Pass the same value on both sides for a sub-account or spot move within one DEX. |
| `fromSubAccount` | Source sub-account address, or `""` for the main account. |
| `destination` | Destination wallet address — a sub-account, your spot account, or another user. |
| `token` | Token identifier in the form Hyperliquid expects (e.g. `USDC:0x...`). |
| `amount` | Decimal string, not in wei. |

The response carries:

* `signData` — EIP-712 typed data (`domain`, `types`, `primaryType`, `message`) for the `sendAsset` action.
* `pendingData` — opaque payload to pass back unchanged in step 2.
* `fee` — Hyperliquid-side fee, always `0` for `sendAsset`.

### 2. Sign and execute

Sign `signData` as EIP-712 typed data with the main wallet's private key, with the wallet on Arbitrum (chain id `42161`) so the signature is accepted. The `message.nonce` is a `uint64` — convert numeric values to `BigInt` if your signer requires it (same pattern as [Register on Hyperliquid](/docs/register-hyperliquid#3-sign-the-approveagent-message)). Then post `pendingData` and the signature to the execute endpoint:

```typescript theme={null}
const signature = await account.signTypedData({
  domain: signData.domain,
  types: signData.types,
  primaryType: signData.primaryType,
  message: Object.fromEntries(
    Object.entries(signData.message).map(([k, v]) =>
      typeof v === 'number' ? [k, BigInt(v)] : [k, v],
    ),
  ),
});

await fetch('https://perps-api.vooi.io/exchange/hyperliquid/send-asset/execute', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiToken}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ pendingData, signature }),
});
```

Execute returns no body on success. VOOI submits the signed action to Hyperliquid on your behalf — you do not broadcast anything on-chain.

## Account abstraction mode

The abstraction mode controls how Hyperliquid shares collateral between your spot and perp accounts, and which assets count as collateral for perps. Read it with [`GET /exchange/account-info?exchange=hyperliquid`](/docs/api-reference/exchange/get-account-settings-aster-returns-hedge-mode-margin-type-multi-assets-margin-hyperliquid-returns-the-abstraction-mode-extended-returns-the-applied-referral-code-bybit-returns-the-margin-mode-unified-account-balances-and-the-account-upgrade-status-lighter-and-robinhood-return-the-account-fee-tier-and-the-fees-it-charges) and change it through the prepare → execute pair below.

VOOI accepts four settable values on `set-abstraction`. The `default` value is read-only — it means the user has never selected a mode on Hyperliquid and cannot be passed back. For the venue-side margining math, see Hyperliquid's docs on [account abstraction modes](https://hyperliquid.gitbook.io/hyperliquid-docs/trading/account-abstraction-modes) and [portfolio margin](https://hyperliquid.gitbook.io/hyperliquid-docs/trading/portfolio-margin) — the summary below covers the part a VOOI client needs to reason about.

### `disabled` — standard mode

Spot and perp balances are kept separate, and each perp DEX (the default USDC perp DEX and any [HIP-3 DEX](/docs/trading-markets#hyperliquid-hip-3-dexes) the user has touched) maintains its own cross-margin pool. Moving funds between them requires an explicit [send-asset](#move-assets-within-hyperliquid) action.

This is the right mode for market makers, high-frequency strategies, and builder-fee deployers — it has no daily action cap. Hyperliquid only accrues builder fees while the account is in standard mode.

### `unifiedAccount`

Hyperliquid keeps a single per-asset balance that funds spot trading, every perp DEX, and validator operations at once. A user with USDC in spot can open a perp position on the default DEX without first moving funds; a user with USDH in spot can collateralize a position on a USDH-quoted DEX the same way (none of the DEXes VOOI currently surfaces is USDH-quoted, but individual [HIP-3 DEXes](/docs/trading-markets#hyperliquid-hip-3-dexes) pick their own quote currency).

This is the recommended mode for most end users. One caveat to surface in a UI: Hyperliquid caps the account at 50,000 user actions per day.

In this mode [`GET /exchange/accounts`](/docs/api-reference/exchange/get-account-balances) returns only the two `"spot"` rows (USDC and USDH). The spot balances back perp positions on every DEX directly, so no separate perp rows are returned. See [Account balances](/docs/trading-positions#account-balances).

### `portfolioMargin`

Builds on `unifiedAccount` by margining all eligible spot and perp positions together as one portfolio, with cross-asset offsets. Eligible collateral assets at time of writing are USDC, USDH, BTC, and HYPE.

This mode is **pre-alpha on Hyperliquid** and currently restricted: master accounts need >\$5M weighted volume to enable it, and per-asset borrow/supply caps apply. When a cap is exceeded, the venue temporarily reverts margining for that asset to non-portfolio behavior. Treat it as opt-in for sophisticated users and link out to Hyperliquid's [portfolio margin](https://hyperliquid.gitbook.io/hyperliquid-docs/trading/portfolio-margin) page for the current eligibility rules.

### `dexAbstraction`

Legacy mode. USDC sits in the perp balance, every other collateral asset sits in the spot balance, and cross-margin on HIP-3 DEXes behaves unintuitively. Hyperliquid recommends new clients **not** offer this mode; VOOI still returns it so existing accounts can read their current state and migrate off.

### Read the current mode

[`GET /exchange/account-info?exchange=hyperliquid`](/docs/api-reference/exchange/get-account-settings-aster-returns-hedge-mode-margin-type-multi-assets-margin-hyperliquid-returns-the-abstraction-mode-extended-returns-the-applied-referral-code-bybit-returns-the-margin-mode-unified-account-balances-and-the-account-upgrade-status-lighter-and-robinhood-return-the-account-fee-tier-and-the-fees-it-charges) returns `{ abstractionMode: "disabled" | "unifiedAccount" | "portfolioMargin" | "dexAbstraction" | "default" }`. `default` means the user has never picked a mode on Hyperliquid; treat it the same as `disabled` for UX purposes (separated balances, no daily-action cap).

A typical client polls this on a low cadence (every 30 s is enough — the value only changes when the user explicitly switches it) and caches it next to the user's HL connection state.

### Decide whether to prompt a switch

Most clients want to keep users in `unifiedAccount` so a single funded balance covers every perp DEX without [send-asset](#move-assets-within-hyperliquid) hops. `portfolioMargin` extends `unifiedAccount` with cross-asset margining, so for routing decisions it counts as "unified" too. The minimal predicate:

```typescript theme={null}
function needsUnifiedSwitch(mode: AbstractionMode): boolean {
  return mode !== 'unifiedAccount' && mode !== 'portfolioMargin';
}
```

If `needsUnifiedSwitch(mode)` returns `true` and the user is about to deposit, transfer in, or open a cross-DEX position, surface a "Switch to unified" call-to-action before the action so they don't end up with funds stranded on the wrong DEX. If the user prefers to stay in standard mode (e.g. they're a market maker collecting builder fees), respect that — don't auto-switch silently.

### Change the mode

[`POST /exchange/hyperliquid/set-abstraction/prepare`](/docs/api-reference/exchange-hyperliquid/build-eip-712-data-the-main-hyperliquid-wallet-must-sign-to-change-the-abstraction-mode) builds the EIP-712 envelope your **main** Hyperliquid wallet has to sign — the VOOI-managed agent cannot sign this action. [`POST /exchange/hyperliquid/set-abstraction/execute`](/docs/api-reference/exchange-hyperliquid/submit-a-signed-hyperliquid-abstraction-mode-change) submits it.

```bash theme={null}
POST https://perps-api.vooi.io/exchange/hyperliquid/set-abstraction/prepare
Authorization: Bearer <token>
Content-Type: application/json

{ "abstractionMode": "unifiedAccount" }
```

Response: `signData` (EIP-712 typed data for the `userSetAbstraction` action) and `pendingData` (opaque, pass back unchanged).

Make sure the user's wallet is on Arbitrum (chain id `42161`) before signing — `signData.domain.chainId` is fixed and a signature produced on a different chain won't be accepted. Then post `pendingData` and the signature to the execute endpoint:

```typescript theme={null}
// 1. Make sure the wallet is on Arbitrum.
if (currentChainId !== 42161) {
  await wallet.switchChain(42161);
}

// 2. Sign — the message has a uint64 nonce, so cast numeric values to BigInt
//    if your signer requires it.
const signature = await account.signTypedData({
  domain: signData.domain,
  types: signData.types,
  primaryType: signData.primaryType,
  message: Object.fromEntries(
    Object.entries(signData.message).map(([k, v]) =>
      typeof v === 'number' ? [k, BigInt(v)] : [k, v],
    ),
  ),
});

// 3. Execute echoes back the new mode.
const result = await fetch('https://perps-api.vooi.io/exchange/hyperliquid/set-abstraction/execute', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiToken}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ pendingData, signature }),
}).then((r) => r.json());
// → { "abstractionMode": "unifiedAccount" }
```

The new mode takes effect immediately. After execute returns, invalidate any cached `account-info` and `/exchange/accounts` data — the row set changes when the user enters or leaves `unifiedAccount` (perp DEX rows drop on entry and reappear on exit, see [Account balances](/docs/trading-positions#account-balances)).

If the user rejects the wallet prompt or signs on the wrong chain, `execute` returns `400` and the venue-side mode is unchanged — safe to re-run the prepare/sign/execute flow without cleanup.

## Approve a builder fee

**Skip this flow unless the user's broker uses Hyperliquid builder fees.** Hyperliquid only requires the main wallet to sign a one-time `approveBuilderFee` action when an order carries a non-zero builder fee — without that approval, the venue rejects fee-bearing orders. A broker that monetizes this way will have set defaults via [`POST /broker/exchanges`](/docs/api-reference/broker/add-new-broker-key-and-fee-settings-for-a-specific-exchange) for `hyperliquid`. (Aster bakes the equivalent into the agent registration step — see [Customize the builder approval](/docs/register-aster#customize-the-builder-approval).)

The three endpoints below are unified across venues — the same `/exchange/broker/*` paths handle Hyperliquid, Lighter, Robinhood, and Aster. Pass `exchange: "hyperliquid"` for the Hyperliquid main-wallet flow described here. The other venues use the same shape with a different `exchange` value and signer.

[`GET /exchange/broker/approved`](/docs/api-reference/exchange-broker/get-the-configured-and-on-chain-approved-builder-fees-for-the-user-s-broker) reports whether the user has already approved a given builder address on the venue — call it before showing an approval prompt.

```bash theme={null}
GET https://perps-api.vooi.io/exchange/broker/approved?exchange=hyperliquid&identifier=0xYourBroker
Authorization: Bearer <token>
# → { "approved": true }
```

### 1. Prepare

[`POST /exchange/broker/prepare`](/docs/api-reference/exchange-broker/build-the-payload-the-user-must-sign-to-approve-a-broker-on-the-given-exchange) builds the EIP-712 envelope for the `approveBuilderFee` action.

```bash theme={null}
POST https://perps-api.vooi.io/exchange/broker/prepare
Authorization: Bearer <token>
Content-Type: application/json

{ "exchange": "hyperliquid" }
```

`identifier` (builder address) and `maxFeeBps` (cap) are optional. The expected call shape depends on how the broker is set up:

* **Broker default configured for this user (the common case).** You may pass either field, both, or neither — each is validated independently. Any value you do pass must equal the broker's configured value exactly, or the request returns `400`; any field you omit falls back to the broker's configured value. (The body's `maxFeeBps` corresponds to the cap, so it matches the broker record's `maxFeeBps`, not its per-order `feeBps`.)
* **No broker default configured.** Pass both `identifier` and `maxFeeBps` explicitly, otherwise the call returns `400`.

The response shape matches the [send-asset](#move-assets-within-hyperliquid) prepare step — `signData` (EIP-712 typed data for the `approveBuilderFee` action) and `pendingData` (opaque, pass back unchanged in step 2).

### 2. Sign and execute

Sign `signData` as EIP-712 typed data with the main wallet's private key (the agent cannot sign this action), with the wallet on Arbitrum (chain id `42161`) so the signature is accepted. `message.nonce` is a `uint64` — cast numeric values to `BigInt` if your signer requires it, same pattern as [Move assets within Hyperliquid](#move-assets-within-hyperliquid).

```typescript theme={null}
const signature = await account.signTypedData({
  domain: signData.domain,
  types: signData.types,
  primaryType: signData.primaryType,
  message: Object.fromEntries(
    Object.entries(signData.message).map(([k, v]) =>
      typeof v === 'number' ? [k, BigInt(v)] : [k, v],
    ),
  ),
});

await fetch('https://perps-api.vooi.io/exchange/broker/execute', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiToken}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ exchange: 'hyperliquid', pendingData, signature }),
});
```

Execute returns `{ "status": "ok" }` on success. The approval is one-shot — re-run the flow only if the broker raises the cap or rotates to a different builder.

## Swap USDC ↔ USDH

[`POST /exchange/hyperliquid/stablecoins-swap`](/docs/api-reference/exchange-hyperliquid/swap-between-stablecoins-on-the-hyperliquid-spot-market) converts between USDC and USDH on the Hyperliquid spot market. The endpoint is single-step — no main-wallet signature required, just the standard bearer token.

```bash theme={null}
POST https://perps-api.vooi.io/exchange/hyperliquid/stablecoins-swap
Authorization: Bearer <token>
Content-Type: application/json

{ "tokenIn": "USDC", "tokenOut": "USDH", "amount": "100" }
```

`amount` is the human-readable size of `tokenIn` to spend. The response returns `amountOut` — the size of `tokenOut` you received after the swap fills.

The swap routes through Hyperliquid's USDH/USDC spot market with a price guard (no worse than `1.005` when buying USDH, no better than `0.995` when selling). If the book can't fill within that band, the order is rejected.

One supported way to fund a USDH-quoted DEX when the user holds USDC, and to convert back when closing positions there.

## Next steps

<CardGroup cols={2}>
  <Card title="Cross-exchange transfers" icon="arrows-left-right" href="/docs/transfers">
    Move USDC between Hyperliquid, Lighter, and Arbitrum
  </Card>

  <Card title="Positions and accounts" icon="chart-line" href="/docs/trading-positions">
    Read positions, balances, and per-market settings
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.