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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ if(HYPERLIQUID_BUILD_TESTS)
hyperliquid_add_test(transfers_test)
hyperliquid_add_test(misc_info_test)
hyperliquid_add_test(user_dex_abstraction_test)
hyperliquid_add_test(user_portfolio_margin_test)
hyperliquid_add_test(websocket_api_exchange_test)
hyperliquid_add_test(websocket_api_dispatch_test)
hyperliquid_add_test(rest_abstraction_test)
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ Legend: ✅ implemented — ⬜ not yet implemented.
| General | `borrowLendReserveState` | ✅ | `RestApi::borrowLendReserveState` |
| General | `allBorrowLendReserveStates` | ✅ | `RestApi::allBorrowLendReserveStates` |
| General | `approvedBuilders` | ✅ | `RestApi::approvedBuilders` |
| General | `exchangeStatus` | ⬜ | |
| General | `exchangeStatus` | ✅ | `RestApi::exchangeStatus` |
| General | `extraAgents` | ⬜ | |
| General | `gossipPriorityAuctionStatus` | ⬜ | |
| General | `gossipRootIps` | ⬜ | |
Expand Down Expand Up @@ -161,7 +161,7 @@ Legend: ✅ implemented — ⬜ not yet implemented.
| Spot / Outcomes | `outcomeDeployerLimits` | ⬜ | |
| Spot / Outcomes | `outcomeTemplates` | ⬜ | |

58 of 78 documented info endpoints implemented. One (`tokenDetails`) has a `RestEndpointType` enum value reserved but no request builder or method yet.
59 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 Expand Up @@ -231,12 +231,12 @@ Legend: ✅ implemented — ⬜ not yet implemented.
| `subAccountTransfer` | ✅ | `RestApi::subAccountTransfer` |
| `topUpIsolatedOnlyMargin` | ⬜ | |
| `userOutcome` | ⬜ | |
| `userPortfolioMargin` | ⬜ | |
| `userPortfolioMargin` | ✅ | `RestApi::userPortfolioMargin` |
| `validatorL1Stream` | ⬜ | |
| `vaultDistribute` | ⬜ | |
| `vaultModify` | ⬜ | |

36 of 68 documented exchange actions implemented on REST (`RestApi`). `WebsocketApi` covers a smaller subset — `placeOrder`, `cancelOrder`, `cancelOrderByCloid`, `scheduleCancel`, `modifyOrder`, `batchModifyOrder` — plus posting `meta`/`spotMeta`/`outcomeMeta`/`perpDexs` info reads over the socket; the newer transfer/staking/TWAP actions are REST-only so far.
39 of 68 documented exchange actions implemented on REST (`RestApi`). `WebsocketApi` covers a smaller subset — `placeOrder`, `cancelOrder`, `cancelOrderByCloid`, `scheduleCancel`, `modifyOrder`, `batchModifyOrder` — plus posting `meta`/`spotMeta`/`outcomeMeta`/`perpDexs` info reads over the socket; the newer transfer/staking/TWAP actions are REST-only so far.

`perpDeploy` is a large multi-variant action (16 sub-actions sharing `"type": "perpDeploy"`); only `registerAsset2` (deploying a new HIP-3 perp asset, optionally creating a new dex) is implemented. The other 15 variants (`registerAsset`, `setOracle`, `setFundingMultipliers`, `setFundingInterestRates`, `haltTrading`, `setMarginTableIds`, `insertMarginTable`, `setFeeRecipient`, `setOpenInterestCaps`, `setSubDeployers`, `setMarginModes`, `setFeeScale`, `setGrowthModes`, `setPerpAnnotation`, `disableDex`) are post-deployment admin/config actions for an already-deployed dex and are not yet implemented.

Expand Down
9 changes: 9 additions & 0 deletions examples/rest_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ int main()
spdlog::info(" user={} isolatedAsset={} marginAvailable=[{}, {}]",
p.user, p.isolatedAsset, p.marginAvailable[0], p.marginAvailable[1]);

// Cheap, no-wallet operational status check. Confirmed live against both testnet and
// mainnet: {"specialStatuses":null,"time":<ms>} - specialStatuses is null absent any active
// special status.
spdlog::info("=== exchangeStatus ===");
auto status = api.exchangeStatus();
spdlog::info("time={} specialStatuses={}", status.time, status.specialStatuses.size());
for (const auto& s : status.specialStatuses)
spdlog::info(" {}", s);

uint64_t now = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count());
Expand Down
11 changes: 11 additions & 0 deletions examples/rest_misc_actions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,16 @@ int main()
evmReq.data = "0x";
logSimpleResponse("sendToEvmWithData", api.sendToEvmWithData(evmReq));

// Enables/disables portfolio margin mode for the calling account. EIP-712 user-signed action
// (see CONTRIBUTING.md) - request shape (user, enabled) cross-checked against
// nktkas/hyperliquid's userPortfolioMargin.ts, not independently confirmed against a live
// response in this environment (no funded testnet wallet available here); the generic
// {status,response} shape it's parsed with is already exercised by other exchange actions
// above (e.g. sendToEvmWithData).
hyperliquid::UserPortfolioMarginRequest portfolioMarginReq;
portfolioMarginReq.user = wallet.accountAddress;
portfolioMarginReq.enabled = true;
logSimpleResponse("userPortfolioMargin", api.userPortfolioMargin(portfolioMarginReq));

return 0;
}
4 changes: 4 additions & 0 deletions include/hyperliquid/rest/RestApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class RestApi {
LiquidatableResponse liquidatable();
UserDexAbstractionResponse userDexAbstractionState(const std::string& user);
UserAbstractionResponse userAbstraction(const std::string& user);
ExchangeStatusResponse exchangeStatus();
PlaceOrderResponse placeOrder(const std::vector<OrderRequest>& orders,
Grouping grouping,
const std::optional<Builder>& builder = std::nullopt,
Expand Down Expand Up @@ -178,6 +179,7 @@ class RestApi {
DelegatorRewardsResponse delegatorRewards(const std::string& user);
SimpleResponse sendToEvmWithData(const SendToEvmWithDataRequest& request);
SimpleResponse userDexAbstraction(const UserDexAbstractionRequest& request);
SimpleResponse userPortfolioMargin(const UserPortfolioMarginRequest& request);
SimpleResponse agentSendAsset(const AgentSendAssetRequest& request,
const std::optional<std::string>& vaultAddress = std::nullopt);
SimpleResponse reserveRequestWeight(const ReserveRequestWeightRequest& request,
Expand Down Expand Up @@ -265,6 +267,7 @@ class RestApi {
void liquidatableAsync();
void userDexAbstractionStateAsync(const std::string& user);
void userAbstractionAsync(const std::string& user);
void exchangeStatusAsync();
void placeOrderAsync(const std::vector<OrderRequest>& orders,
Grouping grouping,
const std::optional<Builder>& builder = std::nullopt,
Expand Down Expand Up @@ -317,6 +320,7 @@ class RestApi {
void delegatorRewardsAsync(const std::string& user);
void sendToEvmWithDataAsync(const SendToEvmWithDataRequest& request);
void userDexAbstractionAsync(const UserDexAbstractionRequest& request);
void userPortfolioMarginAsync(const UserPortfolioMarginRequest& request);
void agentSendAssetAsync(const AgentSendAssetRequest& request,
const std::optional<std::string>& vaultAddress = std::nullopt);
void reserveRequestWeightAsync(const ReserveRequestWeightRequest& request,
Expand Down
1 change: 1 addition & 0 deletions include/hyperliquid/rest/RestApiMessageParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ namespace hyperliquid
DelegatorRewardsResponse parseDelegatorRewards(const std::string& message);
UserDexAbstractionResponse parseUserDexAbstractionState(const std::string& message);
UserAbstractionResponse parseUserAbstraction(const std::string& message);
ExchangeStatusResponse parseExchangeStatus(const std::string& message);

private:
struct Impl;
Expand Down
1 change: 1 addition & 0 deletions include/hyperliquid/rest/RestEndpointListener.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class RestEndpointListener {
virtual void onLiquidatable(const LiquidatableResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onUserDexAbstractionState(const UserDexAbstractionResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onUserAbstraction(const UserAbstractionResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onExchangeStatus(const ExchangeStatusResponse&, std::optional<uint64_t> = std::nullopt) {}
};

}
17 changes: 15 additions & 2 deletions include/hyperliquid/types/RequestTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ namespace hyperliquid
UserFees,
MaxBuilderFee,
ApprovedBuilders,
ExchangeStatus,
VaultDetails,
UserVaultEquities,
Portfolio,
Expand Down Expand Up @@ -249,6 +250,7 @@ namespace hyperliquid
TokenDelegate,
SendToEvmWithData,
UserDexAbstraction,
UserPortfolioMargin,
AgentSendAsset,
ReserveRequestWeight,
Noop,
Expand Down Expand Up @@ -281,6 +283,7 @@ namespace hyperliquid
case RestEndpointType::UserFees: return "userFees";
case RestEndpointType::MaxBuilderFee: return "maxBuilderFee";
case RestEndpointType::ApprovedBuilders: return "approvedBuilders";
case RestEndpointType::ExchangeStatus: return "exchangeStatus";
case RestEndpointType::VaultDetails: return "vaultDetails";
case RestEndpointType::UserVaultEquities: return "userVaultEquities";
case RestEndpointType::Portfolio: return "portfolio";
Expand Down Expand Up @@ -357,6 +360,7 @@ namespace hyperliquid
case RestEndpointType::TokenDelegate: return "tokenDelegate";
case RestEndpointType::SendToEvmWithData: return "sendToEvmWithData";
case RestEndpointType::UserDexAbstraction: return "userDexAbstraction";
case RestEndpointType::UserPortfolioMargin: return "userPortfolioMargin";
case RestEndpointType::AgentSendAsset: return "agentSendAsset";
case RestEndpointType::ReserveRequestWeight: return "reserveRequestWeight";
case RestEndpointType::Noop: return "noop";
Expand Down Expand Up @@ -391,6 +395,7 @@ namespace hyperliquid
case RestEndpointType::UserFees: return false;
case RestEndpointType::MaxBuilderFee: return false;
case RestEndpointType::ApprovedBuilders: return false;
case RestEndpointType::ExchangeStatus: return false;
case RestEndpointType::VaultDetails: return false;
case RestEndpointType::UserVaultEquities: return false;
case RestEndpointType::Portfolio: return false;
Expand Down Expand Up @@ -467,6 +472,7 @@ namespace hyperliquid
case RestEndpointType::TokenDelegate: return true;
case RestEndpointType::SendToEvmWithData: return true;
case RestEndpointType::UserDexAbstraction: return true;
case RestEndpointType::UserPortfolioMargin: return true;
case RestEndpointType::AgentSendAsset: return true;
case RestEndpointType::ReserveRequestWeight: return true;
case RestEndpointType::Noop: return true;
Expand All @@ -480,8 +486,8 @@ namespace hyperliquid
}

// usdClassTransfer/sendAsset/usdSend/spotSend/withdraw3/approveBuilderFee/userSetAbstraction,
// the staking actions (cDeposit/cWithdraw/tokenDelegate), sendToEvmWithData, and
// userDexAbstraction are EIP-712 user-signed actions (see
// the staking actions (cDeposit/cWithdraw/tokenDelegate), sendToEvmWithData,
// userDexAbstraction, and userPortfolioMargin are EIP-712 user-signed actions (see
// Signing::prepareUserSignedActionBody), not L1 actions. All other authenticated actions here
// (including agentSendAsset/reserveRequestWeight/noop) are L1 actions signed with the
// agent/master key directly.
Expand All @@ -501,6 +507,7 @@ namespace hyperliquid
case RestEndpointType::TokenDelegate:
case RestEndpointType::SendToEvmWithData:
case RestEndpointType::UserDexAbstraction:
case RestEndpointType::UserPortfolioMargin:
return true;
default:
return false;
Expand Down Expand Up @@ -912,6 +919,12 @@ namespace hyperliquid
bool enabled;
};

struct UserPortfolioMarginRequest
{
std::string user;
bool enabled;
};

struct AgentSendAssetRequest
{
std::string destination;
Expand Down
7 changes: 7 additions & 0 deletions include/hyperliquid/types/ResponseTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,13 @@ namespace hyperliquid
std::vector<std::string> coins;
};

struct ExchangeStatusResponse
{
// Empty when the wire value is null (the common case - no active special statuses).
std::vector<std::string> specialStatuses;
uint64_t time = 0;
};

struct PredictedFundingVenue
{
std::string venue;
Expand Down
3 changes: 3 additions & 0 deletions include/hyperliquid/websocket/WebsocketApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ namespace hyperliquid

void userDexAbstractionState(const std::string& user, std::optional<uint64_t> correlationId = std::nullopt);
void userAbstraction(const std::string& user, std::optional<uint64_t> correlationId = std::nullopt);
void exchangeStatus(std::optional<uint64_t> correlationId = std::nullopt);

void delegations(const std::string& user, std::optional<uint64_t> correlationId = std::nullopt);
void delegatorSummary(const std::string& user, std::optional<uint64_t> correlationId = std::nullopt);
Expand Down Expand Up @@ -264,6 +265,8 @@ namespace hyperliquid

void userDexAbstraction(const UserDexAbstractionRequest& request,
std::optional<uint64_t> correlationId = std::nullopt);
void userPortfolioMargin(const UserPortfolioMarginRequest& request,
std::optional<uint64_t> correlationId = std::nullopt);

void start();
void stop();
Expand Down
1 change: 1 addition & 0 deletions include/hyperliquid/websocket/WebsocketApiListener.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class WebsocketApiListener {
virtual void onPostResponse(const DelegatorSummaryResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onPostResponse(const DelegatorHistoryResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onPostResponse(const DelegatorRewardsResponse&, std::optional<uint64_t> = std::nullopt) {}
virtual void onPostResponse(const ExchangeStatusResponse&, std::optional<uint64_t> = std::nullopt) {}

// Every exchange action shares this response shape, so RestEndpointType disambiguates.
virtual void onPostResponse(RestEndpointType, const SimpleResponse&,
Expand Down
12 changes: 12 additions & 0 deletions src/messages/ExchangeRequestBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,18 @@ namespace hyperliquid
return body;
}

nlohmann::ordered_json ExchangeRequestBuilder::userPortfolioMargin(const UserPortfolioMarginRequest& request) const
{
nlohmann::ordered_json action;
action["type"] = "userPortfolioMargin";
action["user"] = request.user;
action["enabled"] = request.enabled;

nlohmann::ordered_json body;
body["action"] = action;
return body;
}

nlohmann::ordered_json ExchangeRequestBuilder::agentSendAsset(const AgentSendAssetRequest& request) const
{
nlohmann::ordered_json action;
Expand Down
2 changes: 2 additions & 0 deletions src/messages/ExchangeRequestBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ class ExchangeRequestBuilder {

nlohmann::ordered_json userDexAbstraction(const UserDexAbstractionRequest& request) const;

nlohmann::ordered_json userPortfolioMargin(const UserPortfolioMarginRequest& request) const;

nlohmann::ordered_json agentSendAsset(const AgentSendAssetRequest& request) const;

nlohmann::ordered_json reserveRequestWeight(const ReserveRequestWeightRequest& request) const;
Expand Down
7 changes: 7 additions & 0 deletions src/messages/InfoRequestBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -515,4 +515,11 @@ nlohmann::ordered_json InfoRequestBuilder::userAbstraction(const std::string& us
return body;
}

nlohmann::ordered_json InfoRequestBuilder::exchangeStatus()
{
nlohmann::ordered_json body;
body["type"] = toString(RestEndpointType::ExchangeStatus);
return body;
}

}
2 changes: 2 additions & 0 deletions src/messages/InfoRequestBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ class InfoRequestBuilder {

static nlohmann::ordered_json userDexAbstractionState(const std::string& user);
static nlohmann::ordered_json userAbstraction(const std::string& user);

static nlohmann::ordered_json exchangeStatus();
};

} // namespace hyperliquid
24 changes: 24 additions & 0 deletions src/rest/RestApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,12 @@ UserAbstractionResponse RestApi::userAbstraction(const std::string& user)
impl_->signAndSendSync(RestEndpointType::UserAbstraction, InfoRequestBuilder::userAbstraction(user)));
}

ExchangeStatusResponse RestApi::exchangeStatus()
{
return RestApiMessageParser().parseExchangeStatus(
impl_->signAndSendSync(RestEndpointType::ExchangeStatus, InfoRequestBuilder::exchangeStatus()));
}

PlaceOrderResponse RestApi::placeOrder(const std::vector<OrderRequest>& orders,
Grouping grouping,
const std::optional<Builder>& builder,
Expand Down Expand Up @@ -800,6 +806,13 @@ SimpleResponse RestApi::userDexAbstraction(const UserDexAbstractionRequest& requ
impl_->exchangeRequestBuilder.userDexAbstraction(request)));
}

SimpleResponse RestApi::userPortfolioMargin(const UserPortfolioMarginRequest& request)
{
return RestApiMessageParser().parseSimpleResponse(
impl_->signAndSendSync(RestEndpointType::UserPortfolioMargin,
impl_->exchangeRequestBuilder.userPortfolioMargin(request)));
}

SimpleResponse RestApi::agentSendAsset(const AgentSendAssetRequest& request,
const std::optional<std::string>& vaultAddress)
{
Expand Down Expand Up @@ -1124,6 +1137,11 @@ void RestApi::userAbstractionAsync(const std::string& user)
impl_->signAndSend(RestEndpointType::UserAbstraction, InfoRequestBuilder::userAbstraction(user));
}

void RestApi::exchangeStatusAsync()
{
impl_->signAndSend(RestEndpointType::ExchangeStatus, InfoRequestBuilder::exchangeStatus());
}

void RestApi::placeOrderAsync(const std::vector<OrderRequest>& orders,
Grouping grouping,
const std::optional<Builder>& builder,
Expand Down Expand Up @@ -1375,6 +1393,12 @@ void RestApi::userDexAbstractionAsync(const UserDexAbstractionRequest& request)
impl_->exchangeRequestBuilder.userDexAbstraction(request));
}

void RestApi::userPortfolioMarginAsync(const UserPortfolioMarginRequest& request)
{
impl_->signAndSend(RestEndpointType::UserPortfolioMargin,
impl_->exchangeRequestBuilder.userPortfolioMargin(request));
}

void RestApi::agentSendAssetAsync(const AgentSendAssetRequest& request,
const std::optional<std::string>& vaultAddress)
{
Expand Down
Loading
Loading