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.
Before you change anything
Section titled “Before you change anything”- Read the setup journey and start Guard in paper mode.
- Keep your real account address. Give the bot a Guard client key, while the API wallet key stays inside Guard.
- Check the network and isolated-margin requirements in this guide. These integration instructions are planned and not verified against a released Guard yet.
The smallest change
Section titled “The smallest 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.
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)
Section titled “Full example (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"}},)filled in from your settings ·
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, Guard attaches its own under the default rules, or refuses the entry if your rules say refuse (why the stop belongs with the entry).
JavaScript takes the same keys: new ccxt.hyperliquid({ walletAddress, privateKey, urls, options }).
The config keys
Section titled “The config keys”| Key | Value | Why |
|---|---|---|
walletAddress | your Hyperliquid account address | ccxt requires it (requiredCredentials) and uses it as the user of its account queries; it must be the account Guard guards, or ccxt reads another account’s balance and positions |
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 |
options.approvedBuilderFee | leave it unset | never True: see below |
Two ccxt defaults behind Guard
Section titled “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()sendsapproveBuilderFeefor ccxt’s own builder address (0x6530…27a6) atfeeRate(default0.01%;0%withbuilderFee: False), unlessoptions.approvedBuilderFeeis alreadyTrue. Guard refuses it (funds_or_permissions: only your main wallet can approve a builder fee). ccxt catches the error and setsbuilderFeetoFalse;approvedBuilderFeestaysFalse.set_ref()sendssetReferrerwith codeCCXT1. Guard refuses it (funds_or_permissions). ccxt ignores the error.refSet: Trueskips the request.
Do not set approvedBuilderFee: True. ccxt attaches its builder field to every order whenever approvedBuilderFee is True (at f = 0 with builderFee: False), and Guard vetoes any order that carries a builder field of its own (client_builder). Left unset, the one refused approval at start-up keeps it False, and ccxt attaches nothing.
What to expect
Section titled “What to expect”- Guard puts every entry on isolated margin itself, at a leverage that keeps the liquidation beyond the stop. Do not switch the market to cross margin (
set_margin_mode("cross")or a crossset_leverage): Guard refuses it. - An entry without a stop is not refused under the default rules: Guard attaches its own stop 2% away (
stop = "attach") and sizes from it. Sending your own stop with the entry, as above, lets Guard size from yours. - 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
ExchangeErrorwhose message carries Guard’s reason and veto code. fetch_balance,fetch_positionsand market data pass through to Hyperliquid unchanged.
Sources
Section titled “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.
Read the response
Section titled “Read the response”The reply uses Hyperliquid’s response format. Check the returned status and order size rather than assuming the requested size was accepted. A refusal includes a readable reason and a veto code; a smaller accepted order reflects the rule that bound its size. Paper mode sends no venue order. Test this path before using testnet.
This page as plain Markdown, for people and LLMs: /docs/integrations/ccxt.md