Skip to main content

df_hyperliquid_core/account/settings/
limits.rs

1//! What an address may still send (`userRateLimit`), and the venue's formulas for what it earns:
2//! requests by volume traded, cancels past the cap, open orders by volume. Action limits are per
3//! address, a subaccount having its own; reads are the IP's ([`crate::client::http::limit`]).
4
5use crate::client::{ApiError, Transport, info, user_request};
6use crate::utils::{Address, Dec, Round};
7use serde::Deserialize;
8
9/// What every address starts with; it earns one more request for every USDC it trades.
10pub const INITIAL_REQUESTS: u64 = 10_000;
11
12/// How often a throttled address may send one request.
13pub const THROTTLED_EVERY_MS: u64 = 10_000;
14
15/// The open orders every address has.
16pub const OPEN_ORDERS: u64 = 1_000;
17/// The most open orders an address can earn.
18pub const MOST_OPEN_ORDERS: u64 = 5_000;
19
20/// The volume that earns one more open order, in USDC.
21pub const VOLUME_PER_OPEN_ORDER: i64 = 5_000_000;
22
23/// `{"type":"userRateLimit","user":..}`.
24#[must_use]
25pub fn rate_limit_request(user: Address) -> String {
26    user_request("userRateLimit", user)
27}
28
29/// One address's limit. Weighs 20 against the IP.
30pub async fn rate_limit(transport: &impl Transport, user: Address) -> Result<RateLimit, ApiError> {
31    info(transport, rate_limit_request(user)).await
32}
33
34/// `userRateLimit`.
35#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
36pub struct RateLimit {
37    /// USDC traded since the address began: what buys its cap.
38    #[serde(rename = "cumVlm")]
39    pub volume: Dec,
40    #[serde(rename = "nRequestsUsed")]
41    pub used: u64,
42    #[serde(rename = "nRequestsCap")]
43    pub cap: u64,
44    /// Bought with `reserveRequestWeight`.
45    #[serde(rename = "nRequestsSurplus", default)]
46    pub surplus: u64,
47}
48
49impl RateLimit {
50    /// What is left before the address is throttled; never a wrapped total,
51    /// since throttled requests still count.
52    #[must_use]
53    pub const fn remaining(&self) -> u64 {
54        self.cap.saturating_sub(self.used)
55    }
56
57    #[must_use]
58    pub const fn is_throttled(&self) -> bool {
59        self.remaining() == 0
60    }
61
62    /// What cancels may use: `min(cap + 100000, cap * 2)`, so a throttled address can still cancel.
63    #[must_use]
64    pub const fn cancel_cap(&self) -> u64 {
65        let wider = self.cap.saturating_add(100_000);
66        let doubled = self.cap.saturating_mul(2);
67        if wider < doubled { wider } else { doubled }
68    }
69
70    /// The cap `volume` earns: 10,000 and one for every whole USDC. `None`
71    /// for a negative volume.
72    #[must_use]
73    pub fn earned_cap(volume: Dec) -> Option<u64> {
74        let whole = u64::try_from(volume.units_at(0, Round::Down)?).ok()?;
75        INITIAL_REQUESTS.checked_add(whole)
76    }
77}
78
79/// The USDC to trade to earn `requests` more.
80#[must_use]
81pub fn volume_for(requests: u64) -> Dec {
82    Dec::lit::<0>(i128::from(requests))
83}
84
85/// What `reserveRequestWeight` charges for `requests` more, in USDC: 0.0005 a request, paid from
86/// the perps balance.
87#[must_use]
88pub fn surplus_cost(requests: u64) -> Option<Dec> {
89    Dec::lit::<0>(i128::from(requests)).checked_mul(Dec::lit::<4>(5))
90}
91
92/// What an action costs the address: one, or one for every order or cancel it batches. The IP
93/// counts a batch as one.
94#[must_use]
95pub fn address_requests(batched: usize) -> u64 {
96    u64::try_from(batched.max(1)).unwrap_or(u64::MAX)
97}
98
99/// The open orders `volume` allows: one more for every 5M USDC, at most 5000.
100#[must_use]
101pub fn open_order_cap(volume: Dec) -> u64 {
102    let earned = volume
103        .checked_div(Dec::from_int(VOLUME_PER_OPEN_ORDER), 0, Round::Down)
104        .and_then(|earned| earned.units_at(0, Round::Down))
105        .and_then(|earned| u64::try_from(earned).ok())
106        .unwrap_or(0);
107    OPEN_ORDERS.saturating_add(earned).min(MOST_OPEN_ORDERS)
108}
109
110#[cfg(test)]
111#[path = "tests/limits.rs"]
112mod tests;