Skip to main content
The facade at https://api.gains.trade/hl/{chain} (https://api-testnet.gains.trade/hl/arbitrum-sepolia on testnet, wss://api.gains.trade/hl/{chain}/ws for the stream) speaks Hyperliquid’s POST /info, POST /exchange and WebSocket protocol, verifies Hyperliquid’s own signing scheme, and forwards to the native Gains API. It never renames a Hyperliquid field. Gains-only information appears as extra fields that a Hyperliquid client ignores.
Only the subset that integrators actually use is implemented. Anything else returns a Hyperliquid-shaped empty result or error, never a different schema.

Before the first order

  1. approveAgent (user-signed, HyperliquidTransaction:ApproveAgent) registers the agent with full trading scope. The response is Hyperliquid’s {"status":"ok","response":{"type":"default"}} plus one extra field, delegationTx.
  2. The master wallet sends delegationTx (setTradingDelegate(relayer) on the Gains Diamond, once per chain). Until it is mined, L1 actions answer {"status":"err","response":"L1 error: API Wallet 0x... is not yet the on-chain delegate of its user. ..."} with delegationTx repeated.
  3. The master wallet approves the Diamond for USDC once.
This on-chain step does not exist on Hyperliquid and cannot be hidden: Gains is non-custodial and the delegation is the trader’s own transaction. The facade is the one place Gains relays and pays gas, because Hyperliquid SDKs only sign typed data; native integrations broadcast their own transactions. hyperliquidChain may be Mainnet or Testnet: an SDK pointed at a custom base URL signs as Testnet, and the facade accepts it.

Conventions

POST /info

Unknown type answers 422 with the text Failed to deserialize the JSON body into the target type, like Hyperliquid. An unknown coin answers 200 with null.

POST /exchange

Envelope {action, nonce, signature, vaultAddress?, expiresAfter?}. L1 actions are hashed exactly as the Hyperliquid Python SDK does (msgpack of the action in key order, nonce, vault marker, optional expiresAfter) and verified as the phantom agent Agent{source, connectionId} on chain id 1337, source a on mainnet chains and b on testnet. User-signed actions use the HyperliquidSignTransaction domain with the client’s signatureChainId. The verification is tested against the official SDK’s signing vectors.

Responses

Order statuses use Hyperliquid’s shapes with one addition for the oracle step:
filled appears on orderStatus and orderUpdates once the oracle answers, never synchronously: a market order on Gains is pending for a few seconds.

WebSocket

{"method":"subscribe","subscription":{...}}, unsubscribe, ping/pong ({"channel":"pong"}) and post (info and action) as on Hyperliquid. Subscriptions: allMids, l2Book, bbo, trades, candle, activeAssetCtx, orderUpdates, userEvents (delivered on channel user), userFills (with isSnapshot), userFundings (empty snapshot), notification, clearinghouseState, openOrders. webData2/webData3, twapStates, userNonFundingLedgerUpdates, activeAssetData, spot and vault channels are not available. orderUpdates carries status: "pending" then filled or timedOut.

Differences at a glance