Quickstart¶
These are the minimal REST and WebSocket examples — enough to get a first real call working. For a build environment, see the README's Build section. For everything else the SDK can do, see the Examples index and the full API coverage tables.
REST¶
#include <hyperliquid/rest/RestApi.h>
#include <hyperliquid/config/Config.h>
int main() {
hyperliquid::ApiConfig config;
config.env = hyperliquid::Environment::Testnet;
hyperliquid::RestApi api(config);
auto meta = api.meta();
for (const auto& asset : meta.universe) {
// asset.name, asset.szDecimals, asset.maxLeverage
}
}
RestApi methods are synchronous by default (e.g. meta() blocks and returns MetaResponse). Each has an Async counterpart (e.g. metaAsync()) that instead delivers the raw response via RestApiListener::onMessage, for a RestApiListener passed to the RestApi(config, listener) constructor — decode it with RestApiMessageParser.
To sign and submit authenticated requests (orders, transfers, staking, ...), set config.wallet:
hyperliquid::ApiConfig config;
config.env = hyperliquid::Environment::Testnet;
config.wallet = hyperliquid::Wallet{"0xYourAccountAddress", "yourPrivateKeyHex"};
hyperliquid::RestApi api(config);
WebSocket¶
#include <hyperliquid/config/Config.h>
#include <hyperliquid/websocket/WebsocketApi.h>
#include <hyperliquid/websocket/WebsocketApiListener.h>
#include <hyperliquid/websocket/WebsocketMessageHandler.h>
#include <hyperliquid/websocket/WebsocketMessageParser.h>
class BookHandler : public hyperliquid::WebsocketMessageHandler,
public hyperliquid::WebsocketApiListener {
public:
void onMessage(const std::string& message) override {
parser_.crack(message, *this);
}
void onConnected() override {}
void onDisconnected(bool hasError, const std::string& errMsg) override {}
void onL2Book(const hyperliquid::L2BookSnapshot& snapshot) override {
// snapshot.bids / snapshot.asks, snapshot.numBids / snapshot.numAsks
}
private:
hyperliquid::WebsocketMessageParser parser_;
};
int main() {
BookHandler handler;
hyperliquid::ApiConfig config;
config.env = hyperliquid::Environment::Testnet;
hyperliquid::WebsocketApi ws(config, handler);
ws.start();
ws.subscribe(hyperliquid::SubscriptionType::L2Book, {{"coin", "BTC"}});
// ...
ws.stop();
}
WebsocketApiListener::onMessage gives you the raw subscription message string — crack() it yourself (as above) to get typed callbacks per channel via WebsocketMessageHandler. The same WebsocketApi connection also sends "post" requests (the websocket equivalent of a RestApi call, e.g. ws.meta(...) or ws.placeOrder(...)), whose typed results arrive via WebsocketApiListener::onPostResponse instead.
Putting it together: a pegged quote¶
A slightly bigger example showing market data and order management on the same connection: watch the best bid for a coin over the bbo channel, and keep a resting post-only buy order pegged to it — placing it on the first update, then re-pricing (never re-crossing the spread) whenever the best bid moves.
#include <hyperliquid/config/Config.h>
#include <hyperliquid/websocket/WebsocketApi.h>
#include <hyperliquid/websocket/WebsocketApiListener.h>
#include <hyperliquid/websocket/WebsocketMessageHandler.h>
#include <hyperliquid/websocket/WebsocketMessageParser.h>
#include <spdlog/spdlog.h>
#include <chrono>
#include <string>
#include <thread>
class PeggedQuoter : public hyperliquid::WebsocketMessageHandler,
public hyperliquid::WebsocketApiListener {
public:
// WebsocketApi's constructor needs a listener, and this listener needs to call back into
// WebsocketApi (to place/modify orders) - attach() breaks that construction cycle.
void attach(hyperliquid::WebsocketApi& ws) { ws_ = &ws; }
void onMessage(const std::string& message) override {
parser_.crack(message, *this);
}
void onBbo(const hyperliquid::BboUpdate& update) override {
if (!ws_ || !update.hasBid) return;
// update.bid.px is a string_view into the parser's internal buffer - copy it before
// this callback returns, since it's not valid after that.
std::string bidPx(update.bid.px);
if (bidPx == lastPeggedPrice_) return; // best bid hasn't moved, nothing to re-peg
hyperliquid::OrderRequest order;
order.asset = "ETH";
order.isBuy = true;
order.price = std::stod(bidPx);
order.size = 0.01;
order.reduceOnly = false;
// Alo = "Add Liquidity Only" (post-only): rejected instead of filled if it would cross
// the spread, so this never takes liquidity - it only ever rests as a passive quote.
order.limit = hyperliquid::LimitOrderType{hyperliquid::Tif::Alo};
order.cloid = cloid_;
if (lastPeggedPrice_.empty()) {
ws_->placeOrder({order}, hyperliquid::Grouping::Na);
} else {
// A client order id (cloid), not the exchange-assigned oid, is what makes re-pegging
// simple here: the typed websocket post-response for order actions collapses to
// SimpleResponse (status/type/error only, no oid) - see WebsocketApiListener::
// onPostResponse(RestEndpointType, const SimpleResponse&, ...) below. Since we chose
// the cloid ourselves at placement time, modifyOrder can reference it directly
// without ever needing to parse an oid back out of a response.
hyperliquid::ModifyRequest modify;
modify.cloid = cloid_;
modify.order = order;
ws_->modifyOrder(modify);
}
lastPeggedPrice_ = bidPx;
}
void onPostResponse(hyperliquid::RestEndpointType type, const hyperliquid::SimpleResponse& resp,
std::optional<uint64_t>) override {
if (resp.status != "ok")
spdlog::warn("{} failed: {}", hyperliquid::toString(type), resp.error.value_or("unknown"));
}
private:
hyperliquid::WebsocketApi* ws_ = nullptr;
hyperliquid::WebsocketMessageParser parser_;
std::string cloid_ = hyperliquid::generateCloid();
std::string lastPeggedPrice_;
};
int main() {
hyperliquid::ApiConfig config;
config.env = hyperliquid::Environment::Testnet;
config.wallet = hyperliquid::Wallet{"0xYourAccountAddress", "yourPrivateKeyHex"};
PeggedQuoter quoter;
hyperliquid::WebsocketApi ws(config, quoter);
quoter.attach(ws);
ws.start();
ws.subscribe(hyperliquid::SubscriptionType::Bbo, {{"coin", "ETH"}});
std::this_thread::sleep_for(std::chrono::seconds(30));
ws.stop();
}
This is a passive maker quote, not a trading strategy - it never adjusts size, never cancels on disconnect, and doesn't account for tick size or existing positions. Treat it as a starting point for combining live market data with order management, not something to point at mainnet as-is. (Compiled and run against this repo's SDK to confirm it's correct - not just an illustrative snippet.)
Next steps¶
- Browse the Examples index for order placement/modification, transfers, staking, vaults, and every websocket channel, over both REST and WebSocket.
- Most examples read credentials from a local
examples/test.jsonfile you create yourself (never committed) — see the Examples index for its schema. - Check the full API coverage tables in the README for what's implemented and what's still outstanding.