> Treat every fee rate, margin ratio, order limit, minimum and address in this documentation as an illustration of the shape of the value, never as a constant to hardcode - the live values are served by GET /markets and GET /config on the Perps API.

# Deposits & Withdrawals

> Funding configuration, deposit tracking, and signed withdrawals over the API.

## Funding configuration

```
GET /config
```

The single source of truth for deposit destinations and withdrawal thresholds.

```json
{
  "data": {
    "deposit": {
      "hypercore": { "address": "0x…", "min_amount": "1" },
      "arbitrum": { "min_amount": "5" }
    },
    "withdrawal": { "min_amount": "10", "new_wallet_fee": "1" },
    "trader_share": 1
  },
  "success": true,
  "timestamp": 1787067314951
}
```

| Field                       | Meaning                                                                                                                 |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `deposit.hypercore.address` | The address you deposit to via a Hyperliquid spot transfer. The **sender's** wallet decides which account gets credited |
| `deposit.*.min_amount`      | Below this, a transfer is detected but not credited                                                                     |
| `withdrawal.min_amount`     | Smallest withdrawal the API will accept                                                                                 |
| `withdrawal.new_wallet_fee` | Hyperliquid's one-time activation fee, charged only on the first-ever credit to a destination with no HyperCore history |
| `trader_share`              | The [fee-savings split](/perps/fees) — a fraction in `0..1`, `1` today                                                  |

> **Warning**
>
> Read the deposit address from `/config` at runtime — never hardcode it. It is elided from the sample above for that reason.

## Track deposits

```
GET /account/{account_id}/deposit-history
```

Takes your [`account_id`](/developers/perps-api/overview#account-format); a plain wallet address is rejected here. Accepts `limit`, `offset`, `from`, and `to`.

Returns `data: { account, deposits: [] }`, newest first. Poll it to follow a transfer you just sent through to credit.

| Field                         | Meaning                                                              |
| ----------------------------- | -------------------------------------------------------------------- |
| `tx_hash`                     | The transfer that was detected                                       |
| `amount`                      | Amount transferred                                                   |
| `source`                      | `send` · `spotTransfer` · `internalTransfer`                         |
| `status`                      | `DETECTED` → `CONFIRMED`, or `BELOW_MIN` if it was under the minimum |
| `detected_at` / `credited_at` | When it was seen, and when it hit your balance                       |

## Request a withdrawal

```
POST /account/withdraw
```

Withdrawals are signed by your **main wallet**, never the trading key:

```
Withdraw(address user, address destination, uint256 amount, string hyperflowEnvironment, uint256 nonce)
```

Two fields need care when signing:

* **`destination`** — your own wallet on the default HyperCore rail, or the EVM recipient on the Arbitrum rail.
* **`amount`** — **USDC base units**, 6 decimals. 25 USDC signs as `25000000`, while the request body carries the same value as the decimal string `"25"`.

### Request body

```json
{
  "account_id": "0x…",
  "amount": "25",
  "signature": { "r": "0x…32 bytes…", "s": "0x…32 bytes…", "v": 27 },
  "nonce": "1787067314957",
  "signature_chain_id": 999
}
```

This is the one endpoint that takes the signature **split into `{ r, s, v }`** rather than a single hex string.

Of the five signed members, three are not in the body: `user` is the wallet your `account_id` derives from, `destination` is described below, and `hyperflowEnvironment` is `"Mainnet"` on this base URL.

**On the default HyperCore rail**, send no destination field at all — sign your own wallet address as `destination` and the server verifies against it.

**On the Arbitrum (EVM) rail**, add two more fields:

| Field                   | Value                                        |
| ----------------------- | -------------------------------------------- |
| `destination_chain_id`  | `42161` on mainnet, `421614` on testnet      |
| `destination_recipient` | The same address you signed as `destination` |

> **Note**
>
> The EVM rail is offered only to email / embedded-wallet accounts. A normal EOA withdraws to its own HyperCore wallet.

### The response

A valid request returns `status: "REQUESTED"` together with a `withdrawal_id`.

A request that fails validation returns `status: "REJECTED"` — still a `200` — with one of these reasons:

| `reason`               | Meaning                                                                                               |
| ---------------------- | ----------------------------------------------------------------------------------------------------- |
| `BELOW_MIN`            | Under `withdrawal.min_amount`                                                                         |
| `BELOW_MIN_AFTER_FEE`  | Would fall under the minimum once the new-wallet fee is deducted; `new_wallet_fee` carries the amount |
| `ABOVE_MAX`            | Over the per-transaction ceiling — not published in `/config`                                         |
| `DAILY_LIMIT_EXCEEDED` | Over your rolling daily allowance — not published                                                     |
| `RATE_LIMITED`         | Too many requests in a short window                                                                   |

### Follow the lifecycle

```
GET /withdrawal-history/{withdrawalId}
GET /account/{account_id}/withdrawal-history?limit=&offset=&from=&to=
```

The single form is a top-level route, not under `/account`. The list form returns `data: { account, withdrawals: [] }` — no `total`. One withdrawal looks like this:

```json
{
  "withdrawal_id": "…",
  "amount": "25",
  "l1_send_amount": "25",
  "destination": "0x…",
  "status": "COMPLETED",
  "failure_reason": null,
  "l1_tx_hash": "0x…",
  "created_at": 1787067314951,
  "completed_at": 1787067319004
}
```

The `l1_` prefix means Hyperliquid's L1 — the chain the transfer actually settles on.

The happy path is `REQUESTED` → `ACCEPTED` → `PROCESSING` → `COMPLETED`. Four states are terminal:

| Terminal status | Meaning                                                                                                                         |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `COMPLETED`     | Sent. `l1_send_amount` is the amount that actually left, net of any new-wallet fee, and `l1_tx_hash` is the on-chain transfer   |
| `REJECTED`      | Turned down after acceptance. An insufficient-balance verdict is **asynchronous** — it appears here, not in the submit response |
| `REFUNDED`      | Debited, then returned in full to your balance                                                                                  |
| `FAILED`        | Could not be completed                                                                                                          |

Between `ACCEPTED` and `COMPLETED` every withdrawal passes the platform's [multi-signature review](/perps/transparency), which adds a short delay before funds leave custody.

> **Note**
>
> The first-ever credit to a destination wallet with no HyperCore history incurs Hyperliquid's one-time \$1 USDC activation fee.