pub struct HyperliquidClient { /* private fields */ }Expand description
Hyperliquid perpetual futures execution client.
Wraps the official hyperliquid_rust_sdk to implement the ExecutionClient trait.
Supports perpetual futures trading on Hyperliquid DEX.
Implementations§
Source§impl HyperliquidClient
impl HyperliquidClient
Sourcepub async fn connect(
config: HyperliquidConfig,
) -> Result<Self, HyperliquidConnectError>
pub async fn connect( config: HyperliquidConfig, ) -> Result<Self, HyperliquidConnectError>
Create a new client asynchronously.
Use this when calling from an async context (e.g., tokio tests).
For sync contexts, use ExecutionClient::new().
Reads the markets of each DEX in HyperliquidConfig::dexes; see HIP-3
perpetuals.
§Errors
HyperliquidConnectError::Connectivitywhen the SDK clients cannot be created, or a request for the DEXs’ markets fails in transit.HyperliquidConnectError::UnknownDexwhen Hyperliquid lists no DEX by a configured name on the configured network.HyperliquidConnectError::Metadatawhen a DEX’s markets cannot be read.
§Cost
With any DEX configured: perpDexs, spotMeta and each DEX’s meta, weight 20 each, so
40 + 20 × dexes, besides what the SDK reads to build its own clients.
Sourcepub fn wallet_address(&self) -> String
pub fn wallet_address(&self) -> String
Returns the wallet address as a hex string (for logging/debugging).
Sourcepub fn order_precision(
&self,
instrument: &InstrumentNameExchange,
) -> Option<OrderPrecision>
pub fn order_precision( &self, instrument: &InstrumentNameExchange, ) -> Option<OrderPrecision>
The precision rules for orders on the perpetual instrument, from its szDecimals.
open_order refuses an order that breaks them with
OrderError::InvalidPrecision and sends nothing. Round with these rules first. See
OrderPrecision.
None when instrument names no perpetual this client trades, or one listed after the
client connected: the client reads each DEX’s perpetuals once, at
connect, so a perpetual listed since needs a new client.
Trait Implementations§
Source§impl Clone for HyperliquidClient
impl Clone for HyperliquidClient
Source§impl Debug for HyperliquidClient
impl Debug for HyperliquidClient
Source§impl ExecutionClient for HyperliquidClient
impl ExecutionClient for HyperliquidClient
Source§fn new(config: Self::Config) -> Self
fn new(config: Self::Config) -> Self
Creates a new Hyperliquid client synchronously.
§Panics
- If no Tokio runtime is available on the current thread
- If called from within an async context (e.g., inside
async fn,spawn, orblock_on) - Whenever
HyperliquidClient::connectwould fail: SDK initialization fails (network error, invalid credentials), or a configured DEX is unknown or unreadable
§Recommended Usage
Use HyperliquidClient::connect instead — it’s async-safe and returns Result.
This method exists only for trait compliance; prefer connect() in all new code.
Source§async fn account_snapshot(
&self,
_assets: &[AssetNameExchange],
instruments: &[InstrumentNameExchange],
) -> Result<UnindexedAccountSnapshot, UnindexedClientError>
async fn account_snapshot( &self, _assets: &[AssetNameExchange], instruments: &[InstrumentNameExchange], ) -> Result<UnindexedAccountSnapshot, UnindexedClientError>
The collateral balances, and each perpetual’s open orders and position, across the default DEX and every configured HIP-3 DEX.
§Balances
Read where the account’s abstraction mode holds them (see HIP-3 perpetuals):
- Unified account or portfolio margin: one balance per collateral token of the DEXs
traded (
USDC,USDH), from the spot clearinghouse, free of what is on hold. A token not held is reported at zero. Other tokens, which portfolio margin may also count as collateral, are left tospot::HyperliquidSpotClient. - Standard (the default for an account that never chose a mode): one balance per DEX,
from its margin summary, as each margins separately: the default DEX’s as
USDC, each HIP-3 DEX’s as{dex}:{collateral}(xyz:USDC,flx:USDH). The free balance is what margin does not use, never below zero.
A mode this version does not know fails the snapshot rather than report balances read from the wrong place.
Positions on a HIP-3 DEX the client is not configured with are not read, and so not
reported: name the DEX in HyperliquidConfig::dexes to see them.
§Positions
Every requested perpetual is listed. Its position is PositionReport::Open when
Hyperliquid reports a non-zero size for it, and PositionReport::Flat otherwise, since
each DEX’s state holds every open position on it. A size that does not parse is
PositionReport::Unreported, with a warning.
§Errors
ApiError::InstrumentInvalid for a requested
instrument that names no perpetual this client trades, such as one on a HIP-3 DEX it is not
configured with: it was not read, so nothing can be said about it.
§Cost
At once, userAbstraction (weight 20) and, for each DEX traded, the default one included,
a clearinghouseState (weight 2) and an openOrders (weight 20); then, under a unified
account or portfolio margin, spotClearinghouseState (weight 2). With n HIP-3 DEXs
that is 42 + 22n, or 44 + 22n, against Hyperliquid’s limit of 1,200 a minute per IP
address.
Source§async fn account_stream(
&self,
_assets: &[AssetNameExchange],
_instruments: &[InstrumentNameExchange],
) -> Result<Self::AccountStream, UnindexedClientError>
async fn account_stream( &self, _assets: &[AssetNameExchange], _instruments: &[InstrumentNameExchange], ) -> Result<Self::AccountStream, UnindexedClientError>
Returns a live stream of account events (fills, order updates, funding payments).
Funding payments arrive as AccountEventKind::CashFlow; see the module
docs.
§Instrument filtering
The instruments parameter is ignored — Hyperliquid’s WebSocket API does not
support per-instrument subscriptions for user events. All fills, order updates and
funding payments across all instruments are delivered. Consumers requiring instrument
filtering must filter client-side.
§Task lifecycle
Spawns four background tasks (fills, orders, funding payments, and the reconnect’s order
check below) that are automatically cancelled when the returned stream is dropped. The
ws_client is held by the orders task; when cancelled, the tasks exit and the WebSocket
connection closes.
§Orders that ended while disconnected
The SDK reconnects the socket underneath this stream and resubscribes, but orderUpdates
opens with no snapshot, so an order that ended while the socket was down is never reported
on it. After each reconnect the stream therefore reports how each order the client holds as
live ended, where it did, as an AccountEventKind::OrderSnapshot of its inactive state,
read as OrderStatusClient::fetch_ended_orders reads it: filled (without an average
price), cancelled (with what filled before) or rejected. Each carries
StrategyId::unknown, since Hyperliquid records no strategy; the engine matches it to the
order it tracks by client order id.
- When. The SDK tells each subscription that the socket dropped, and the venue opens
the resubscribed
userFillswith a snapshot of recent fills. The check starts once that snapshot’s fills have been sent on, so the fills of an order that it carries arrive before how the order ended. The snapshot is all the fill recovery there is (see the module docs), and Hyperliquid does not document how far back it reaches, so after a long outage an order can be reported filled before, or without, fills older than that. The SDK resubscribes in no fixed order and only logs a resubscription that fails: an order that ends after the check lists the open orders but beforeorderUpdatesis subscribed again is missed, and ifuserFillsis not subscribed again no check runs until a later reconnect. - Which orders. The client holds an order as live from the response to placing it,
from a listing of open orders (
account_snapshot,fetch_open_orders), and from its live reports on any of its account streams, until it sees the order end, which a successful cancel does. It holds up to 4,096 orders and forgets the oldest past that, logged atwarn. An order placed outside this client and never listed or reported to it is not covered. Every instrument is checked, as the stream ignoresinstruments. - Cost. One
openOrdersrequest (weight 20) per DEX traded, together listing every open order, then oneorderStatusrequest (weight 2) for each held order the listing no longer shows, 8 at a time. Both count against Hyperliquid’s REST limit of 1,200 a minute per IP address. The cost is per stream: a perpetuals and a spot stream on one wallet each make their own. - Failures. Each order’s lookup is settled as it ends. An instrument whose listing or
lookup fails, or whose check is still running after 30 s, is retried while the stream is
open 1, 2, 4, 8 and 16 minutes later, asking only about the orders still held, then given
up, logged at
error; its orders are asked about again at the next reconnect. A listing that fails charges every instrument in it. An order Hyperliquid does not know stops being held, logged atwarn; one still live, or in a state this version cannot read, stays held.
§Errors
ConnectivityError::Socket if the WebSocket cannot be opened, or if any of the
userFills, orderUpdates and userFundings subscriptions fails: the stream is not
opened with only some of them.
Source§async fn fetch_balances(
&self,
_assets: &[AssetNameExchange],
) -> Result<Vec<AssetBalance<AssetNameExchange>>, UnindexedClientError>
async fn fetch_balances( &self, _assets: &[AssetNameExchange], ) -> Result<Vec<AssetBalance<AssetNameExchange>>, UnindexedClientError>
The collateral balances, read as account_snapshot
reads them: userAbstraction (weight 20), then either spotClearinghouseState or each
DEX’s clearinghouseState (weight 2 each).
Source§async fn fetch_open_orders(
&self,
instruments: &[InstrumentNameExchange],
) -> Result<Vec<Order<ExchangeId, InstrumentNameExchange, Open>>, UnindexedClientError>
async fn fetch_open_orders( &self, instruments: &[InstrumentNameExchange], ) -> Result<Vec<Order<ExchangeId, InstrumentNameExchange, Open>>, UnindexedClientError>
Every open order on each DEX traded, with one openOrders (weight 20) per DEX.
§Errors
ApiError::InstrumentInvalid for an instrument
in instruments that names no perpetual this client trades.
Source§async fn fetch_trades(
&self,
start: DateTime<Utc>,
end: DateTime<Utc>,
instruments: &[InstrumentNameExchange],
) -> Result<TradesRead<AssetNameExchange, InstrumentNameExchange>, UnindexedClientError>
async fn fetch_trades( &self, start: DateTime<Utc>, end: DateTime<Utc>, instruments: &[InstrumentNameExchange], ) -> Result<TradesRead<AssetNameExchange, InstrumentNameExchange>, UnindexedClientError>
Reads the span with userFillsByTime, to its end within the call, so the read is always
complete (resume: None). Hyperliquid keeps only each wallet’s 10,000 most recent fills,
so a span reaching further back is read only as far as those go, and
held_since says where that is. One read covers every
DEX; fills on a HIP-3 DEX this client is not configured with are left out.
§Errors
ApiError::InstrumentInvalid for an instrument
in instruments that names no perpetual this client trades.
const EXCHANGE: ExchangeId = ExchangeId::HyperliquidPerp
Source§const SUPPORTED_KINDS: &'static [InstrumentKindDiscriminant]
const SUPPORTED_KINDS: &'static [InstrumentKindDiscriminant]
InstrumentKindDiscriminants this client can trade. Read moretype Config = HyperliquidConfig
type AccountStream = Pin<Box<dyn Stream<Item = AccountEvent<ExchangeId, AssetNameExchange, InstrumentNameExchange>> + Send>>
Source§async fn cancel_order(
&self,
request: OrderRequestCancel<ExchangeId, &InstrumentNameExchange>,
) -> UnindexedOrderResponseCancel
async fn cancel_order( &self, request: OrderRequestCancel<ExchangeId, &InstrumentNameExchange>, ) -> UnindexedOrderResponseCancel
Source§async fn open_order(
&self,
request: OrderRequestOpen<ExchangeId, &InstrumentNameExchange>,
) -> Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>
async fn open_order( &self, request: OrderRequestOpen<ExchangeId, &InstrumentNameExchange>, ) -> Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>
Source§fn validate_config(
config: &Self::Config,
instruments: &[ClientInstrument<'_>],
) -> Result<(), String>
fn validate_config( config: &Self::Config, instruments: &[ClientInstrument<'_>], ) -> Result<(), String>
fn cancel_orders<'a>( &self, requests: impl IntoIterator<Item = OrderRequestCancel<ExchangeId, &'a InstrumentNameExchange>>, ) -> impl Stream<Item = UnindexedOrderResponseCancel> + Send
fn open_orders<'a>( &self, requests: impl IntoIterator<Item = OrderRequestOpen<ExchangeId, &'a InstrumentNameExchange>>, ) -> impl Stream<Item = Order<ExchangeId, InstrumentNameExchange, UnindexedOrderState>> + Send
Source§impl OrderStatusClient for HyperliquidClient
Looks each order up with orderStatus (weight 2), by the cloid it was placed with, or by its
oid for an order placed without one, which this client reports under its oid. Up to 8 run
at once.
impl OrderStatusClient for HyperliquidClient
Looks each order up with orderStatus (weight 2), by the cloid it was placed with, or by its
oid for an order placed without one, which this client reports under its oid. Up to 8 run
at once.
filled is reported as fully filled, without an average price, which the record does not
carry; every status ending in Canceled, and scheduledCancel, as cancelled with what filled
before it; and every status ending in Rejected as
OpenFailed. Hyperliquid has no expiry.
An order the venue does not know, one on another instrument than the key’s, and an id that is
neither a canonical UUID nor an oid are unknown. An oid whose order carries a cloid is
unknown too, since this client reports that order under its cloid.
The account stream runs the same lookup itself after a reconnect; see
account_stream.
Source§async fn fetch_order_status(
&self,
key: &UnindexedOrderKey,
) -> Result<VenueOrderStatus, UnindexedClientError>
async fn fetch_order_status( &self, key: &UnindexedOrderKey, ) -> Result<VenueOrderStatus, UnindexedClientError>
One orderStatus (weight 2), which reports a live order as well as an ended one, read
as fetch_ended_orders reads it.
§Errors
When the lookup fails, or Hyperliquid’s record of the order does not convert.
Source§async fn fetch_ended_orders(
&self,
orders: &[UnindexedOrderKey],
) -> Result<Vec<UnindexedInactiveOrder>, UnindexedClientError>
async fn fetch_ended_orders( &self, orders: &[UnindexedOrderKey], ) -> Result<Vec<UnindexedInactiveOrder>, UnindexedClientError>
orders ended, for those that have. Read moreAuto Trait Implementations§
impl !RefUnwindSafe for HyperliquidClient
impl !UnwindSafe for HyperliquidClient
impl Freeze for HyperliquidClient
impl Send for HyperliquidClient
impl Sync for HyperliquidClient
impl Unpin for HyperliquidClient
impl UnsafeUnpin for HyperliquidClient
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.Source§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.Source§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.Source§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.Source§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.Source§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.Source§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.Source§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreimpl<T> JsonSchemaMaybe for T
Source§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.