Skip to content
Join the waitlistWaitlist

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.

  • 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.

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},
})
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, 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 }).

KeyValueWhy
walletAddressyour Hyperliquid account addressccxt 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
privateKeythe Guard client keyHyperliquid does not accept it; only your Guard does
urls.api.public, urls.api.privateGuard’s URLevery request goes to Guard
urls.test.public, urls.test.privateGuard’s URLccxt’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.builderFeeFalsesee below
options.refSetTruesee below
options.approvedBuilderFeeleave it unsetnever True: see below

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

  • handle_builder_fee_approval() sends approveBuilderFee for ccxt’s own builder address (0x6530…27a6) at feeRate (default 0.01%; 0% with builderFee: False), unless options.approvedBuilderFee is already True. Guard refuses it (funds_or_permissions: only your main wallet can approve a builder fee). ccxt catches the error and sets builderFee to False; approvedBuilderFee stays False.
  • set_ref() sends setReferrer with code CCXT1. Guard refuses it (funds_or_permissions). ccxt ignores the error. refSet: True skips 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.

  • 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 cross set_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 ExchangeError whose message carries Guard’s reason and veto code.
  • fetch_balance, fetch_positions and market data pass through to Hyperliquid unchanged.
  • 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.

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