Releases: SlickQuant/hyperliquid-cpp
Releases · SlickQuant/hyperliquid-cpp
Release v0.1.2
Changes
Added
Exchange: API-wallet account selection support — added a constructor overload with optionalaccount_addressso authenticated flows can query the effective trading account separately from the signing key.Info: canonical asset lookup helpers — addedname_to_coin,name_to_asset,asset_to_sz_decimals, andcanonical_coin()to resolve human-readable market names to Hyperliquid wire identifiers and asset ids.WebsocketManager::message_to_identifier()— explicit inbound message routing helper for channel-to-handler lookup.- Offline exchange parity tests — added
tests/test_exchange.cppcovering spot/perp mapping, payload shapes, price rounding, and effective-account selection.
Fixed
WebsocketManager: use-after-free / same-dispatch unsubscribe race — callbacks now dispatch from a lock-free snapshot of shared handler state instead of copyingstd::functions under a mutex. Each callback entry carries anactiveflag andin_flightcounter:unsubscribe()removes the callback from future snapshots, marks that specific callback inactive, and waits on its per-callback in-flight count with C++20 atomic wait/notify. This prevents a later callback from firing after being removed earlier in the same snapshot while still allowing self-unsubscribe without deadlocking.updateIsolatedMarginpayload shape — exchange requests now send the documentedntlifield withisBuy: trueinstead of the previous mismatched parameters.usdClassTransferformatting — the signed action now uses the SDK-compatible"amount"string and appends" subaccount:<address>"when trading through a vault.- Effective account selection for
market_close()— account state is now queried againstvault_address, thenaccount_address, then the signer wallet, matching Hyperliquid API-wallet semantics. - Vault handling for user-signed transfers — user-signed exchange payloads now include or omit
vaultAddressconsistently with the current Python SDK behavior. basic_order.cppdefault credentials — the example now uses the Hardhat private key instead of the Hardhat address.
Changed
WebsocketManager: removedpending_subs_pre-connection queue — the underlyingslick::net::Websocketalready buffers outbound frames until the connection is established, making the manual pending-subscription queue redundant.subscribe()andunsubscribe()now callws_->send()unconditionally;flush_pending(),writer_mutex_, and thepending_subs_vector are all removed.- Metadata loading now matches the Hyperliquid SDK/docs — perp assets use
metaindices, spot assets use10000 + spotMeta.index, and spot aliases such asPURR/USDCand@<index>resolve to canonical wire coins. - REST and WebSocket market-data requests now canonicalize coin names before sending requests, so spot and perp subscriptions/queries use documented wire names instead of assuming perpetual-only symbols.
- Exchange precision handling was tightened to SDK behavior —
float_to_wire()andfloat_to_usd_int()now reject silent rounding, and market-order slippage pricing now applies 5 significant figures with max decimals derived fromszDecimals. - WebSocket routing was rebuilt around explicit message identifiers —
trades,candle,activeAssetCtx,activeSpotAssetCtx,userEvents, andorderUpdatesnow route using the actual inbound payload shape, with a 50-second heartbeat under the documented idle timeout. - Internal identifier normalization is narrower — coin-bearing websocket identifiers now preserve canonical coin casing, while address-bearing identifiers still lowercase the user/address portion for stable routing.
- Docs and examples were refreshed —
README.mdnow documents the expanded asset lookup maps,account_address, and the updatedupdate_isolated_margin(coin, amount)signature.
Tests
UnsubscribeBlocksUntilCallbackCompletes(integration) — regression test for the above fix; subscribes with a callback that blocks until explicitly released, callsunsubscribe()concurrently from a second thread, and asserts thatunsubscribe()does not return before the in-flight callback has finished.CallbackCanRemoveLaterCallbackFromSameDispatch(integration) — regression test for the snapshot hazard where callback A unsubscribes callback B on the same channel before the dispatcher reaches B in the already-loaded snapshot.- Signing regressions — added tests asserting that
float_to_wire()andfloat_to_usd_int()throw when serialization would require rounding. - WebSocket routing regressions — extended channel-identifier tests to cover inbound routing, snapshot-safe channel naming, spot/perp active-asset messages, address normalization, and preserved coin casing.
Release v0.1.1
Changes
Added
install()rules and generated CMake package-config files (hyperliquid-config.cmake,hyperliquid-config-version.cmake,hyperliquid-targets.cmake) so the library can be consumed viafind_package(hyperliquid CONFIG REQUIRED)aftercmake --install/vcpkg installHYPERLIQUID_BUILD_TESTSandHYPERLIQUID_BUILD_EXAMPLESCMake options — defaultONfor top-level builds, automaticallyOFFwhen the project is consumed viaadd_subdirectory/FetchContent
Changed
target_compile_features(hyperliquid PUBLIC cxx_std_20)— the C++20 requirement now propagates to consumers through the exported target- Raised minimum CMake version from 3.20 to 3.21 (required for
PROJECT_IS_TOP_LEVEL)
Release v0.1.0
Added
Core library (hyperliquid)
Info — read-only REST + WebSocket client
meta()— perpetuals universe (names,szDecimals, …)spot_meta()— spot token list and spot universeall_mids()— all mid prices as{coin: priceString}l2_snapshot(coin)— full L2 order-book snapshotbbo(coin)— best bid/offer (WebSocket-only; REST returns 422 on testnet)candle_snapshot(coin, interval, start_ms[, end_ms])— OHLCV candles; intervals:1m 5m 15m 30m 1h 4h 8h 12h 1d 3d 1wperp_asset_ctxs()— funding rate, open interest, and mark price for all perpsspot_asset_ctxs()— spot market contextfunding_history(coin, start_ms[, end_ms])— historical funding ratesuser_state(address)— margin summary and open positionsopen_orders(address)— open ordersuser_fills(address)— complete fill historyuser_fills_by_time(address, start_ms[, end_ms])— fills within a time windowquery_order_by_oid(address, oid)— single order lookup by order IDquery_order_by_cloid(address, cloid)— single order lookup by client order IDsub_accounts(address)— sub-account listload_meta()— populatescoin_to_assetmap (called automatically byExchange)subscribe(subscription, callback)— WebSocket subscription; returns integer ID; multiple callbacks per channel supportedunsubscribe(subscription, id)— remove a subscription; double-unsubscribe is harmlessskip_wsconstructor flag (Info(url, true)) — disables WebSocket for REST-only use
Exchange — authenticated trading client
- Order management
order(coin, is_buy, sz, limit_px, order_type[, reduce_only, cloid, builder, grouping])— single limit or trigger orderbulk_orders(orders[, builder, grouping])— multiple orders in one round tripmarket_open(coin, is_buy, sz[, slippage, cloid])— IoC limit at mid ± slippage (default 5 %)market_close(coin[, sz, slippage, cloid])— close position at mid ± slippage; queriesuser_statefor size
- Cancel
cancel(coin, oid)/cancel_by_cloid(coin, cloid)bulk_cancel(cancels)/bulk_cancel_by_cloid(cancels)schedule_cancel([time_ms])— dead-man's switch
- Modify
modify_order(oid, new_order)/bulk_modify_orders(mods)
- Leverage and margin
update_leverage(coin, is_cross, leverage)update_isolated_margin(coin, is_buy, ntl)
- Transfers (user-signed EIP-712)
usd_class_transfer(amount, to_perp)— spot ↔ perp vaultusd_transfer(amount, destination)— send USDC to an addressspot_transfer(amount, destination, token)— send spot tokenswithdraw_from_bridge(amount, destination)— L1 withdrawal
- Agent and builder
approve_agent(agent_address[, agent_name])approve_builder_fee(builder, max_fee_rate)
- Sub-accounts
create_sub_account(name)sub_account_transfer(usd, to_sub, sub_account_user)
wallet_address()— returns the Ethereum address derived from the private keyvault_addressconstructor parameter — sign on behalf of a sub-account / vault
Cryptographic internals
- Embedded Keccak-256 implementation (Ethereum's pre-FIPS variant — distinct from OpenSSL's
SHA3-256) - EIP-712 signing for L1 actions (Exchange domain,
chainId1337) - EIP-712 signing for user-signed actions (HyperliquidSignTransaction domain,
chainId0x66eee) - msgpack-based action hashing using
nlohmann::ordered_jsonfor byte-compatible key ordering with the Python SDK - Private key accepted with or without
"0x"prefix
Types (hyperliquid::utils::types)
Tifenum —Gtc,Ioc,AloLimitOrderType{tif}— standard limit orderTriggerOrderType{trigger_px, is_market, tpsl}— stop / TP-SL;tpslis"tp"or"sl"Cloid— 16-byte client order ID; construct viaCloid::from_int(uint64_t)orCloid::from_str("0x...")BuilderInfo{builder_address, fee_tbps}—fee_tbpsis in tenths of basis points (e.g.10= 1 bps)OrderRequest,CancelRequest,CancelByCloidRequest,ModifyRequest— bulk operation helpers
Constants
MAINNET_API_URL—https://api.hyperliquid.xyzTESTNET_API_URL—https://api.hyperliquid-testnet.xyz
Build system
- CMake ≥ 3.20 build with C++20
- vcpkg integration (
nlohmann-json,openssl,gtest,slick-net) - Targets:
hyperliquid(static library),basic_order(example),hyperliquid_tests(unit tests),hyperliquid_integration_tests(integration tests)
Examples
examples/basic_order.cpp— places a resting GTC limit buy on testnet then cancels it; accepts private key as an optional CLI argument, falls back to the hardhat test key
Tests
- Unit tests (
hyperliquid_tests, ~40 tests, no network):- Keccak-256 hash vectors
- EIP-712 signing correctness
- Type serialisation (
Tif,Cloid,BuilderInfo, order/cancel request encoding) - WebSocket URL construction and channel-identifier utilities
- Integration tests (
hyperliquid_integration_tests, ~54 tests, require testnet access):- REST: all
Infoendpoints againstapi.hyperliquid-testnet.xyz - WebSocket: live subscription tests for
allMids,l2Book,trades,candle; multi-callback fan-out; partial unsubscribe; unsubscribe-stops-delivery
- REST: all