Skip to main content

bybit/
environment.rs

1//! Bybit deployments and their REST and WebSocket base URLs.
2
3use crate::{Category, Path};
4
5/// A Bybit deployment: where to send REST requests and open WebSocket
6/// streams.
7///
8/// Region-specific sites have their own domains; use the variant of the site
9/// your account is registered on. Sites whose WebSocket domain Bybit does not
10/// document (UAE, EEA) or that did not resolve when this list was made
11/// (Japan, Hong Kong) are not listed: use [`Environment::Custom`].
12///
13/// See <https://bybit-exchange.github.io/docs/v5/guide> and
14/// <https://bybit-exchange.github.io/docs/v5/ws/connect>.
15///
16/// ```
17/// use bybit::{Category, Environment};
18///
19/// let env = Environment::Demo;
20/// assert_eq!(env.api_url(), "https://api-demo.bybit.com");
21/// // Demo trading has private streams only; public data comes from mainnet.
22/// assert_eq!(
23///     env.public_stream_url(Category::Linear),
24///     "wss://stream.bybit.com/v5/public/linear"
25/// );
26/// assert_eq!(env.private_stream_url(), "wss://stream-demo.bybit.com/v5/private");
27/// ```
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29#[non_exhaustive]
30pub enum Environment {
31    /// `api.bybit.com`, `stream.bybit.com`.
32    Mainnet,
33    /// Alternative mainnet REST domain `api.bytick.com` (streams on
34    /// `stream.bybit.com`).
35    MainnetBytick,
36    /// `api-testnet.bybit.com`, `stream-testnet.bybit.com`.
37    Testnet,
38    /// Demo trading: `api-demo.bybit.com`; private streams on
39    /// `stream-demo.bybit.com`, public data from mainnet `stream.bybit.com`.
40    Demo,
41    /// Accounts registered on bybit.tr.
42    Turkey,
43    /// Accounts registered on bybit.kz.
44    Kazakhstan,
45    /// Accounts registered on bybitgeorgia.ge.
46    Georgia,
47    /// Accounts registered on bybit.id.
48    Indonesia,
49    /// Any other deployment, e.g. `api: "https://api.bybit.ae"`.
50    /// Both values are base URLs without a trailing slash.
51    Custom {
52        /// REST base URL, e.g. `https://api.bybit.com`.
53        api: &'static str,
54        /// WebSocket base URL, e.g. `wss://stream.bybit.com`.
55        stream: &'static str,
56    },
57}
58
59impl Environment {
60    /// REST base URL, e.g. `https://api.bybit.com`.
61    pub fn api_url(&self) -> &'static str {
62        match self {
63            Self::Mainnet => "https://api.bybit.com",
64            Self::MainnetBytick => "https://api.bytick.com",
65            Self::Testnet => "https://api-testnet.bybit.com",
66            Self::Demo => "https://api-demo.bybit.com",
67            Self::Turkey => "https://api.bybit.tr",
68            Self::Kazakhstan => "https://api.bybit.kz",
69            Self::Georgia => "https://api.bybitgeorgia.ge",
70            Self::Indonesia => "https://api.bybit.id",
71            Self::Custom { api, .. } => api,
72        }
73    }
74
75    /// URL of the public stream of `category`, e.g.
76    /// `wss://stream.bybit.com/v5/public/linear`.
77    pub fn public_stream_url(&self, category: Category) -> String {
78        let path = match category {
79            Category::Spot => Path::PublicSpot,
80            Category::Linear => Path::PublicLinear,
81            Category::Inverse => Path::PublicInverse,
82            Category::Option => Path::PublicOption,
83        };
84        format!("{}{path}", self.public_stream_base())
85    }
86
87    /// URL of the private stream (orders, positions, executions, wallet),
88    /// e.g. `wss://stream.bybit.com/v5/private`.
89    pub fn private_stream_url(&self) -> String {
90        format!("{}{}", self.private_stream_base(), Path::Private)
91    }
92
93    /// URL of the order entry stream (`/v5/trade`), or `None` where Bybit
94    /// does not offer it: demo trading, and sites without a documented
95    /// order entry stream (Georgia, Indonesia).
96    pub fn trade_stream_url(&self) -> Option<String> {
97        match self {
98            Self::Demo | Self::Georgia | Self::Indonesia => None,
99            _ => Some(format!("{}{}", self.private_stream_base(), Path::Trade)),
100        }
101    }
102
103    fn public_stream_base(&self) -> &'static str {
104        match self {
105            // Demo trading serves private streams only.
106            Self::Demo => Self::Mainnet.private_stream_base(),
107            _ => self.private_stream_base(),
108        }
109    }
110
111    fn private_stream_base(&self) -> &'static str {
112        match self {
113            Self::Mainnet | Self::MainnetBytick => "wss://stream.bybit.com",
114            Self::Testnet => "wss://stream-testnet.bybit.com",
115            Self::Demo => "wss://stream-demo.bybit.com",
116            Self::Turkey => "wss://stream.bybit.tr",
117            Self::Kazakhstan => "wss://stream.bybit.kz",
118            Self::Georgia => "wss://stream.bybitgeorgia.ge",
119            Self::Indonesia => "wss://stream.bybit.id",
120            Self::Custom { stream, .. } => stream,
121        }
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn urls_of_every_environment() {
131        let cases = [
132            (
133                Environment::Mainnet,
134                "https://api.bybit.com",
135                "wss://stream.bybit.com/v5/public/spot",
136                "wss://stream.bybit.com/v5/private",
137            ),
138            (
139                Environment::MainnetBytick,
140                "https://api.bytick.com",
141                "wss://stream.bybit.com/v5/public/spot",
142                "wss://stream.bybit.com/v5/private",
143            ),
144            (
145                Environment::Testnet,
146                "https://api-testnet.bybit.com",
147                "wss://stream-testnet.bybit.com/v5/public/spot",
148                "wss://stream-testnet.bybit.com/v5/private",
149            ),
150            (
151                Environment::Demo,
152                "https://api-demo.bybit.com",
153                "wss://stream.bybit.com/v5/public/spot",
154                "wss://stream-demo.bybit.com/v5/private",
155            ),
156            (
157                Environment::Turkey,
158                "https://api.bybit.tr",
159                "wss://stream.bybit.tr/v5/public/spot",
160                "wss://stream.bybit.tr/v5/private",
161            ),
162            (
163                Environment::Kazakhstan,
164                "https://api.bybit.kz",
165                "wss://stream.bybit.kz/v5/public/spot",
166                "wss://stream.bybit.kz/v5/private",
167            ),
168            (
169                Environment::Georgia,
170                "https://api.bybitgeorgia.ge",
171                "wss://stream.bybitgeorgia.ge/v5/public/spot",
172                "wss://stream.bybitgeorgia.ge/v5/private",
173            ),
174            (
175                Environment::Indonesia,
176                "https://api.bybit.id",
177                "wss://stream.bybit.id/v5/public/spot",
178                "wss://stream.bybit.id/v5/private",
179            ),
180            (
181                Environment::Custom {
182                    api: "https://api.example.com",
183                    stream: "wss://stream.example.com",
184                },
185                "https://api.example.com",
186                "wss://stream.example.com/v5/public/spot",
187                "wss://stream.example.com/v5/private",
188            ),
189        ];
190
191        assert_eq!(
192            Environment::Mainnet.trade_stream_url().as_deref(),
193            Some("wss://stream.bybit.com/v5/trade")
194        );
195        assert_eq!(
196            Environment::Testnet.trade_stream_url().as_deref(),
197            Some("wss://stream-testnet.bybit.com/v5/trade")
198        );
199        assert_eq!(Environment::Demo.trade_stream_url(), None);
200
201        for (env, api, public, private) in cases {
202            assert_eq!(env.api_url(), api, "{env:?}");
203            assert_eq!(env.public_stream_url(Category::Spot), public, "{env:?}");
204            assert_eq!(env.private_stream_url(), private, "{env:?}");
205        }
206    }
207
208    #[test]
209    fn public_stream_path_follows_the_category() {
210        let env = Environment::Mainnet;
211
212        assert!(
213            env.public_stream_url(Category::Linear)
214                .ends_with("/v5/public/linear")
215        );
216        assert!(
217            env.public_stream_url(Category::Inverse)
218                .ends_with("/v5/public/inverse")
219        );
220        assert!(
221            env.public_stream_url(Category::Option)
222                .ends_with("/v5/public/option")
223        );
224    }
225}