Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,11 @@ Legend: ✅ implemented — ⬜ not yet implemented.
| General | `legalCheck` | ⬜ | |
| General | `preTransferCheck` | ⬜ | |
| General | `subAccounts2` | ⬜ | |
| General | `twapHistory` | ⬜ | |
| General | `twapHistory` | ✅ | `RestApi::twapHistory` |
| General | `usdcRouting` | ⬜ | |
| General | `userBorrowLendInterest` | ⬜ | |
| General | `userToMultiSigSigners` | ⬜ | |
| General | `userTwapSliceFillsByTime` | ⬜ | |
| General | `userTwapSliceFillsByTime` | ✅ | `RestApi::userTwapSliceFillsByTime` |
| General | `validatorL1Votes` | ⬜ | |
| General | `validatorSummaries` | ⬜ | |
| General | `vaultSummaries` | ⬜ | |
Expand All @@ -139,7 +139,7 @@ Legend: ✅ implemented — ⬜ not yet implemented.
| Perpetuals | `predictedFundings` | ✅ | `RestApi::predictedFundings` |
| Perpetuals | `perpsAtOpenInterestCap` | ✅ | `RestApi::perpsAtOpenInterestCap` |
| Perpetuals | `perpDeployAuctionStatus` | ✅ | `RestApi::perpDeployAuctionStatus` |
| Perpetuals | `activeAssetData` | ⬜ | |
| Perpetuals | `activeAssetData` | ✅ | `RestApi::activeAssetData` |
| Perpetuals | `perpDexLimits` | ✅ | `RestApi::perpDexLimits` |
| Perpetuals | `perpDexStatus` | ✅ | `RestApi::perpDexStatus` |
| Perpetuals | `allPerpMetas` | ✅ | `RestApi::allPerpMetas` |
Expand All @@ -161,7 +161,7 @@ Legend: ✅ implemented — ⬜ not yet implemented.
| Spot / Outcomes | `outcomeDeployerLimits` | ⬜ | |
| Spot / Outcomes | `outcomeTemplates` | ⬜ | |

53 of 78 documented info endpoints implemented. One (`tokenDetails`) has a `RestEndpointType` enum value reserved but no request builder or method yet.
56 of 78 documented info endpoints implemented. One (`tokenDetails`) has a `RestEndpointType` enum value reserved but no request builder or method yet.

### Exchange actions (`/exchange`)

Expand Down
8 changes: 8 additions & 0 deletions examples/rest_leverage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ int main()
const std::string asset = "ETH";
const double size = 0.01;

spdlog::info("=== activeAssetData ({}) ===", asset);
auto activeAssetData = api.activeAssetData(wallet.accountAddress, asset);
spdlog::info("activeAssetData: leverage={}x ({}) maxTradeSz=[{}, {}] availableToTrade=[{}, {}] markPx={}",
activeAssetData.leverageValue, hyperliquid::toString(activeAssetData.leverageType),
activeAssetData.maxTradeSzLong, activeAssetData.maxTradeSzShort,
activeAssetData.availableToTradeLong, activeAssetData.availableToTradeShort,
activeAssetData.markPx);

spdlog::info("=== Switch {} to isolated leverage ===", asset);

hyperliquid::UpdateLeverageRequest leverageReq;
Expand Down
31 changes: 31 additions & 0 deletions examples/rest_twap_slices.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,22 @@
#include <chrono>
#include <thread>

namespace
{
uint64_t nowMs()
{
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count());
}
}

int main()
{
auto wallet = loadWalletFromConfig();
std::string userAddress = wallet.accountAddress;
hyperliquid::setLogLevel(hyperliquid::LogLevel::Info);
uint64_t startTime = nowMs();

hyperliquid::ApiConfig config;
config.env = hyperliquid::Environment::Testnet;
Expand Down Expand Up @@ -66,5 +77,25 @@ int main()
if (cancelResp.error)
spdlog::info(" Error: {}", *cancelResp.error);

spdlog::info("=== twapHistory ===");
auto history = api.twapHistory(userAddress);
spdlog::info("twapHistory: {} entries", history.history.size());
for (const auto& entry : history.history)
{
if (entry.twapId != *twapResp.twapId) continue;
spdlog::info(" MATCH twapId={} status={} time={}",
entry.twapId, hyperliquid::toString(entry.status), entry.time);
}

spdlog::info("=== userTwapSliceFillsByTime (since twapOrder was placed) ===");
auto slicesByTime = api.userTwapSliceFillsByTime(userAddress, startTime);
spdlog::info("userTwapSliceFillsByTime: {} fills since {}", slicesByTime.fills.size(), startTime);
for (const auto& slice : slicesByTime.fills)
{
if (slice.twapId != *twapResp.twapId) continue;
spdlog::info(" MATCH twapId={} coin={} px={} sz={} time={}",
slice.twapId, slice.fill.coin, slice.fill.px, slice.fill.sz, slice.fill.time);
}

return 0;
}
12 changes: 12 additions & 0 deletions include/hyperliquid/rest/RestApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ class RestApi {
const std::optional<std::string>& dex = std::nullopt);
HistoricalOrdersResponse historicalOrders(const std::string& user);
UserTwapSliceFillsResponse userTwapSliceFills(const std::string& user);
UserTwapSliceFillsResponse userTwapSliceFillsByTime(const std::string& user,
uint64_t startTime,
const std::optional<uint64_t>& endTime = std::nullopt,
const std::optional<bool>& aggregateByTime = std::nullopt);
TwapHistoryResponse twapHistory(const std::string& user);
ActiveAssetData activeAssetData(const std::string& user, const std::string& coin);
SubAccountsResponse subAccounts(const std::string& user);
UserFeesResponse userFees(const std::string& user);
MaxBuilderFeeResponse maxBuilderFee(const std::string& user, const std::string& builder);
Expand Down Expand Up @@ -226,6 +232,12 @@ class RestApi {
const std::optional<std::string>& dex = std::nullopt);
void historicalOrdersAsync(const std::string& user);
void userTwapSliceFillsAsync(const std::string& user);
void userTwapSliceFillsByTimeAsync(const std::string& user,
uint64_t startTime,
const std::optional<uint64_t>& endTime = std::nullopt,
const std::optional<bool>& aggregateByTime = std::nullopt);
void twapHistoryAsync(const std::string& user);
void activeAssetDataAsync(const std::string& user, const std::string& coin);
void subAccountsAsync(const std::string& user);
void userFeesAsync(const std::string& user);
void maxBuilderFeeAsync(const std::string& user, const std::string& builder);
Expand Down
3 changes: 3 additions & 0 deletions include/hyperliquid/rest/RestApiMessageParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ namespace hyperliquid
FrontendOpenOrdersResponse parseFrontendOpenOrders(const std::string& message);
HistoricalOrdersResponse parseHistoricalOrders(const std::string& message);
UserTwapSliceFillsResponse parseUserTwapSliceFills(const std::string& message);
UserTwapSliceFillsResponse parseUserTwapSliceFillsByTime(const std::string& message);
TwapHistoryResponse parseTwapHistory(const std::string& message);
ActiveAssetData parseActiveAssetData(const std::string& message);
SubAccountsResponse parseSubAccounts(const std::string& message);
UserFeesResponse parseUserFees(const std::string& message);
MaxBuilderFeeResponse parseMaxBuilderFee(const std::string& message);
Expand Down
3 changes: 3 additions & 0 deletions include/hyperliquid/rest/RestEndpointListener.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class RestEndpointListener {
virtual void onFrontendOpenOrders(const FrontendOpenOrdersResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onHistoricalOrders(const HistoricalOrdersResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onUserTwapSliceFills(const UserTwapSliceFillsResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onUserTwapSliceFillsByTime(const UserTwapSliceFillsResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onTwapHistory(const TwapHistoryResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onActiveAssetData(const ActiveAssetData&, std::optional<uint64_t> = std::nullopt) {}
virtual void onSubAccounts(const SubAccountsResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onUserFees(const UserFeesResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onMaxBuilderFee(const MaxBuilderFeeResponse&, std::optional<uint64_t> = std::nullopt) {}
Expand Down
9 changes: 9 additions & 0 deletions include/hyperliquid/types/RequestTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ namespace hyperliquid
FrontendOpenOrders,
HistoricalOrders,
UserTwapSliceFills,
UserTwapSliceFillsByTime,
TwapHistory,
ActiveAssetData,
SubAccounts,
UserFees,
MaxBuilderFee,
Expand Down Expand Up @@ -267,6 +270,9 @@ namespace hyperliquid
case RestEndpointType::FrontendOpenOrders: return "frontendOpenOrders";
case RestEndpointType::HistoricalOrders: return "historicalOrders";
case RestEndpointType::UserTwapSliceFills: return "userTwapSliceFills";
case RestEndpointType::UserTwapSliceFillsByTime: return "userTwapSliceFillsByTime";
case RestEndpointType::TwapHistory: return "twapHistory";
case RestEndpointType::ActiveAssetData: return "activeAssetData";
case RestEndpointType::SubAccounts: return "subAccounts";
case RestEndpointType::UserFees: return "userFees";
case RestEndpointType::MaxBuilderFee: return "maxBuilderFee";
Expand Down Expand Up @@ -370,6 +376,9 @@ namespace hyperliquid
case RestEndpointType::FrontendOpenOrders: return false;
case RestEndpointType::HistoricalOrders: return false;
case RestEndpointType::UserTwapSliceFills: return false;
case RestEndpointType::UserTwapSliceFillsByTime: return false;
case RestEndpointType::TwapHistory: return false;
case RestEndpointType::ActiveAssetData: return false;
case RestEndpointType::SubAccounts: return false;
case RestEndpointType::UserFees: return false;
case RestEndpointType::MaxBuilderFee: return false;
Expand Down
18 changes: 17 additions & 1 deletion include/hyperliquid/types/ResponseTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -344,12 +344,14 @@ namespace hyperliquid
std::vector<TwapState> states;
};

enum class TwapHistoryStatus { Activated, Terminated, Finished, Error, Unknown };
enum class TwapHistoryStatus { Activated, Terminated, WaitingForTrigger, Stopped, Finished, Error, Unknown };

inline TwapHistoryStatus stringToTwapHistoryStatus(std::string_view s)
{
if (s == "activated") return TwapHistoryStatus::Activated;
if (s == "terminated") return TwapHistoryStatus::Terminated;
if (s == "waitingForTrigger") return TwapHistoryStatus::WaitingForTrigger;
if (s == "stopped") return TwapHistoryStatus::Stopped;
if (s == "finished") return TwapHistoryStatus::Finished;
if (s == "error") return TwapHistoryStatus::Error;
return TwapHistoryStatus::Unknown;
Expand All @@ -361,6 +363,8 @@ namespace hyperliquid
{
case TwapHistoryStatus::Activated: return "activated";
case TwapHistoryStatus::Terminated: return "terminated";
case TwapHistoryStatus::WaitingForTrigger: return "waitingForTrigger";
case TwapHistoryStatus::Stopped: return "stopped";
case TwapHistoryStatus::Finished: return "finished";
case TwapHistoryStatus::Error: return "error";
default: return "unknown";
Expand All @@ -373,6 +377,7 @@ namespace hyperliquid
TwapHistoryStatus status;
std::string description;
uint64_t time;
uint64_t twapId = 0; // only populated by the REST twapHistory endpoint, not the userTwapHistory websocket channel
bool isSnapshot = false;
};

Expand Down Expand Up @@ -493,10 +498,16 @@ namespace hyperliquid
std::string user;
std::string coin;
LeverageType leverageType;
double leverageValue = 0.0;
// isolated-margin HIP-3 dex only ("rawUsd" alongside "type"/"value" in the leverage object).
std::optional<double> leverageRawUsd;
double maxTradeSzLong;
double maxTradeSzShort;
double availableToTradeLong;
double availableToTradeShort;
// Only present on the REST activeAssetData response, confirmed live - not observed on the
// activeAssetData websocket channel payload, so left unset (0.0) there.
double markPx = 0.0;
};

struct Notification
Expand Down Expand Up @@ -1151,6 +1162,11 @@ namespace hyperliquid
std::vector<TwapSliceFill> fills;
};

struct TwapHistoryResponse
{
std::vector<TwapHistoryEntry> history;
};

struct SubAccount
{
std::string name;
Expand Down
8 changes: 8 additions & 0 deletions include/hyperliquid/websocket/WebsocketApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ namespace hyperliquid
std::optional<uint64_t> correlationId = std::nullopt);
void historicalOrders(const std::string& user, std::optional<uint64_t> correlationId = std::nullopt);
void userTwapSliceFills(const std::string& user, std::optional<uint64_t> correlationId = std::nullopt);
void userTwapSliceFillsByTime(const std::string& user,
uint64_t startTime,
const std::optional<uint64_t>& endTime = std::nullopt,
const std::optional<bool>& aggregateByTime = std::nullopt,
std::optional<uint64_t> correlationId = std::nullopt);
void twapHistory(const std::string& user, std::optional<uint64_t> correlationId = std::nullopt);
void activeAssetData(const std::string& user, const std::string& coin,
std::optional<uint64_t> correlationId = std::nullopt);
void subAccounts(const std::string& user, std::optional<uint64_t> correlationId = std::nullopt);
void userFees(const std::string& user, std::optional<uint64_t> correlationId = std::nullopt);
void maxBuilderFee(const std::string& user, const std::string& builder,
Expand Down
2 changes: 2 additions & 0 deletions include/hyperliquid/websocket/WebsocketApiListener.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class WebsocketApiListener {
virtual void onPostResponse(const FrontendOpenOrdersResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onPostResponse(const HistoricalOrdersResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onPostResponse(const UserTwapSliceFillsResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onPostResponse(const TwapHistoryResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onPostResponse(const ActiveAssetData&, std::optional<uint64_t> = std::nullopt) {}
virtual void onPostResponse(const SubAccountsResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onPostResponse(const UserFeesResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onPostResponse(const MaxBuilderFeeResponse&, std::optional<uint64_t> = std::nullopt) {}
Expand Down
31 changes: 31 additions & 0 deletions src/messages/InfoRequestBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,37 @@ nlohmann::ordered_json InfoRequestBuilder::userTwapSliceFills(const std::string&
return body;
}

nlohmann::ordered_json InfoRequestBuilder::userTwapSliceFillsByTime(const std::string& user,
uint64_t startTime,
const std::optional<uint64_t>& endTime,
const std::optional<bool>& aggregateByTime)
{
nlohmann::ordered_json body;
body["type"] = toString(RestEndpointType::UserTwapSliceFillsByTime);
body["user"] = user;
body["startTime"] = startTime;
if (endTime) body["endTime"] = *endTime;
if (aggregateByTime) body["aggregateByTime"] = *aggregateByTime;
return body;
}

nlohmann::ordered_json InfoRequestBuilder::twapHistory(const std::string& user)
{
nlohmann::ordered_json body;
body["type"] = toString(RestEndpointType::TwapHistory);
body["user"] = user;
return body;
}

nlohmann::ordered_json InfoRequestBuilder::activeAssetData(const std::string& user, const std::string& coin)
{
nlohmann::ordered_json body;
body["type"] = toString(RestEndpointType::ActiveAssetData);
body["user"] = user;
body["coin"] = coin;
return body;
}

nlohmann::ordered_json InfoRequestBuilder::subAccounts(const std::string& user)
{
nlohmann::ordered_json body;
Expand Down
6 changes: 6 additions & 0 deletions src/messages/InfoRequestBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ class InfoRequestBuilder {
const std::optional<std::string>& dex = std::nullopt);
static nlohmann::ordered_json historicalOrders(const std::string& user);
static nlohmann::ordered_json userTwapSliceFills(const std::string& user);
static nlohmann::ordered_json userTwapSliceFillsByTime(const std::string& user,
uint64_t startTime,
const std::optional<uint64_t>& endTime = std::nullopt,
const std::optional<bool>& aggregateByTime = std::nullopt);
static nlohmann::ordered_json twapHistory(const std::string& user);
static nlohmann::ordered_json activeAssetData(const std::string& user, const std::string& coin);
static nlohmann::ordered_json subAccounts(const std::string& user);
static nlohmann::ordered_json userFees(const std::string& user);
static nlohmann::ordered_json maxBuilderFee(const std::string& user, const std::string& builder);
Expand Down
41 changes: 41 additions & 0 deletions src/rest/RestApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,28 @@ UserTwapSliceFillsResponse RestApi::userTwapSliceFills(const std::string& user)
impl_->signAndSendSync(RestEndpointType::UserTwapSliceFills, InfoRequestBuilder::userTwapSliceFills(user)));
}

UserTwapSliceFillsResponse RestApi::userTwapSliceFillsByTime(const std::string& user,
uint64_t startTime,
const std::optional<uint64_t>& endTime,
const std::optional<bool>& aggregateByTime)
{
return RestApiMessageParser().parseUserTwapSliceFillsByTime(
impl_->signAndSendSync(RestEndpointType::UserTwapSliceFillsByTime,
InfoRequestBuilder::userTwapSliceFillsByTime(user, startTime, endTime, aggregateByTime)));
}

TwapHistoryResponse RestApi::twapHistory(const std::string& user)
{
return RestApiMessageParser().parseTwapHistory(
impl_->signAndSendSync(RestEndpointType::TwapHistory, InfoRequestBuilder::twapHistory(user)));
}

ActiveAssetData RestApi::activeAssetData(const std::string& user, const std::string& coin)
{
return RestApiMessageParser().parseActiveAssetData(
impl_->signAndSendSync(RestEndpointType::ActiveAssetData, InfoRequestBuilder::activeAssetData(user, coin)));
}

SubAccountsResponse RestApi::subAccounts(const std::string& user)
{
return RestApiMessageParser().parseSubAccounts(
Expand Down Expand Up @@ -971,6 +993,25 @@ void RestApi::userTwapSliceFillsAsync(const std::string& user)
impl_->signAndSend(RestEndpointType::UserTwapSliceFills, InfoRequestBuilder::userTwapSliceFills(user));
}

void RestApi::userTwapSliceFillsByTimeAsync(const std::string& user,
uint64_t startTime,
const std::optional<uint64_t>& endTime,
const std::optional<bool>& aggregateByTime)
{
impl_->signAndSend(RestEndpointType::UserTwapSliceFillsByTime,
InfoRequestBuilder::userTwapSliceFillsByTime(user, startTime, endTime, aggregateByTime));
}

void RestApi::twapHistoryAsync(const std::string& user)
{
impl_->signAndSend(RestEndpointType::TwapHistory, InfoRequestBuilder::twapHistory(user));
}

void RestApi::activeAssetDataAsync(const std::string& user, const std::string& coin)
{
impl_->signAndSend(RestEndpointType::ActiveAssetData, InfoRequestBuilder::activeAssetData(user, coin));
}

void RestApi::subAccountsAsync(const std::string& user)
{
impl_->signAndSend(RestEndpointType::SubAccounts, InfoRequestBuilder::subAccounts(user));
Expand Down
Loading
Loading