<!-- https://zunderlabs.com/docs/integrations/ccxt · Markdown version of the page -->

# ccxt

Put Guard in front of a ccxt Hyperliquid client by overriding its API URLs. The exact config keys, and two ccxt defaults to switch off.

:::note[Planned]
Guard 1.0 integration, not verified yet. Written from ccxt's source, `python/ccxt/hyperliquid.py` and `python/ccxt/base/exchange.py` on the `master` branch, read 6 Oct 2026.
:::

## The change

ccxt builds every Hyperliquid URL from `urls['api']['public']` and `urls['api']['private']` (`hyperliquid.sign()`). Anything you pass to the constructor is deep-merged over ccxt's defaults (`Exchange.__init__`: `settings = self.deep_extend(self.describe(), config)`). So the URL override goes into the constructor.

```diff
 exchange = ccxt.hyperliquid({
     "walletAddress": "0xYourAccountAddress",
-    "privateKey": "0x…API wallet key…",
+    "privateKey": "0x…client key from Guard…",
+    "urls": {"api":  {"public": "http://127.0.0.1:8547", "private": "http://127.0.0.1:8547"},
+             "test": {"public": "http://127.0.0.1:8547", "private": "http://127.0.0.1:8547"}},
+    "options": {"builderFee": False, "refSet": True},
 })
```

## Full example (Python)

```python
import ccxt

GUARD = "http://127.0.0.1:8547"

exchange = ccxt.hyperliquid({
    "walletAddress": "0xYourAccountAddress",  # your main account, not the API wallet
    "privateKey": "0x...",                    # the client key Guard printed
    "urls": {
        "api":  {"public": GUARD, "private": GUARD},
        "test": {"public": GUARD, "private": GUARD},
    },
    "options": {"builderFee": False, "refSet": True},
})

# The entry and its stop in one request (Hyperliquid grouping "normalTpsl").
order = exchange.create_order(
    "BTC/USDC:USDC", "limit", "buy", 0.5, 60000,
    params={"stopLoss": {"triggerPrice": 58800, "type": "market"}},
)
```

The `stopLoss` parameter makes ccxt send the entry and a reduce-only stop together, with grouping `normalTpsl` (`create_orders_request()`). Guard sizes the entry from that stop. Without a stop the entry is refused under the default rules ([why](https://zunderlabs.com/docs/integrations#send-the-stop-with-the-entry)).

JavaScript takes the same keys: `new ccxt.hyperliquid({ walletAddress, privateKey, urls, options })`.

## The config keys

| Key | Value | Why |
|---|---|---|
| `walletAddress` | your Hyperliquid account address | ccxt requires it (`requiredCredentials`); Guard checks it is the account it guards |
| `privateKey` | the Guard client key | Hyperliquid does not accept it; only your Guard does |
| `urls.api.public`, `urls.api.private` | Guard's URL | every request goes to Guard |
| `urls.test.public`, `urls.test.private` | Guard's URL | ccxt's `set_sandbox_mode(True)` replaces `urls['api']` with `urls['test']`. Without this override, turning on sandbox mode would send orders straight to Hyperliquid testnet, around Guard |
| `options.builderFee` | `False` | see below |
| `options.refSet` | `True` | see below |

## Two ccxt defaults behind Guard

On its first private call ccxt runs `initialize_client()`, which does two things you do not want behind Guard:

- **`handle_builder_fee_approval()`** approves ccxt's own builder address (`0x6530…27a6`) at `feeRate` (default `0.01%`). With `builderFee: False` it still approves, at 0%. The approval is a user-signed action, which Guard refuses (only your main wallet can approve a builder fee). ccxt catches the error and sets `builderFee` to `False`; `approvedBuilderFee` stays `False`, so ccxt attaches no builder to orders.
- **`set_ref()`** sends `setReferrer` with code `CCXT1`. Guard refuses it as a non-trading action. ccxt ignores the error. `refSet: True` skips the request.

Both refusals are harmless; the options only save two refused requests at start-up.

## What to expect

- An oversized order comes back filled at a smaller size. Read the filled amount; do not assume the size you sent.
- A refused order raises a ccxt `ExchangeError` whose message carries Guard's reason and [veto code](https://zunderlabs.com/docs/reference/veto-codes).
- `fetch_balance`, `fetch_positions` and market data pass through to Hyperliquid unchanged.

## Sources

- ccxt, `python/ccxt/hyperliquid.py`: `describe()` (`urls`, `requiredCredentials`, `options`), `sign()`, `initialize_client()`, `handle_builder_fee_approval()`, `set_ref()`, `create_orders_request()`.
- ccxt, `python/ccxt/base/exchange.py`: `Exchange.__init__`, `set_sandbox_mode()`.
- [ccxt manual, Hyperliquid](https://docs.ccxt.com/#/exchanges/hyperliquid).
