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.

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 the entry is refused under the default rules (why).

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

KeyValueWhy
walletAddressyour Hyperliquid account addressccxt requires it (requiredCredentials); Guard checks it is the account it guards
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

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.

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

This page as plain Markdown, for people and LLMs: /docs/integrations/ccxt.md