df_hyperliquid_core/account/settings/
limits.rs1use crate::client::{ApiError, Transport, info, user_request};
6use crate::utils::{Address, Dec, Round};
7use serde::Deserialize;
8
9pub const INITIAL_REQUESTS: u64 = 10_000;
11
12pub const THROTTLED_EVERY_MS: u64 = 10_000;
14
15pub const OPEN_ORDERS: u64 = 1_000;
17pub const MOST_OPEN_ORDERS: u64 = 5_000;
19
20pub const VOLUME_PER_OPEN_ORDER: i64 = 5_000_000;
22
23#[must_use]
25pub fn rate_limit_request(user: Address) -> String {
26 user_request("userRateLimit", user)
27}
28
29pub async fn rate_limit(transport: &impl Transport, user: Address) -> Result<RateLimit, ApiError> {
31 info(transport, rate_limit_request(user)).await
32}
33
34#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
36pub struct RateLimit {
37 #[serde(rename = "cumVlm")]
39 pub volume: Dec,
40 #[serde(rename = "nRequestsUsed")]
41 pub used: u64,
42 #[serde(rename = "nRequestsCap")]
43 pub cap: u64,
44 #[serde(rename = "nRequestsSurplus", default)]
46 pub surplus: u64,
47}
48
49impl RateLimit {
50 #[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 #[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 #[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#[must_use]
81pub fn volume_for(requests: u64) -> Dec {
82 Dec::lit::<0>(i128::from(requests))
83}
84
85#[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#[must_use]
95pub fn address_requests(batched: usize) -> u64 {
96 u64::try_from(batched.max(1)).unwrap_or(u64::MAX)
97}
98
99#[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;