bybit_client/client.rs
1//! Main Bybit client.
2
3use crate::api::{AccountService, MarketService, PositionService, TradeService};
4use crate::config::ClientConfig;
5use crate::error::BybitError;
6use crate::http::HttpClient;
7
8/// Main client for interacting with the Bybit API.
9///
10/// # Example
11///
12/// ```no_run
13/// use bybit_client::{BybitClient, ClientConfig};
14///
15/// #[tokio::main]
16/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
17/// // Create a public-only client.
18/// let client = BybitClient::public_only()?;
19///
20/// // Or create an authenticated client.
21/// let client = BybitClient::new("api_key", "api_secret")?;
22///
23/// // Use testnet.
24/// let client = BybitClient::with_config(
25/// ClientConfig::new("api_key", "api_secret").testnet()
26/// )?;
27///
28/// Ok(())
29/// }
30/// ```
31#[derive(Debug, Clone)]
32pub struct BybitClient {
33 http: HttpClient,
34}
35
36impl BybitClient {
37 /// Create a new client with API credentials.
38 pub fn new(
39 api_key: impl Into<String>,
40 api_secret: impl Into<String>,
41 ) -> Result<Self, BybitError> {
42 let config = ClientConfig::new(api_key, api_secret);
43 Self::with_config(config)
44 }
45
46 /// Create a client for public endpoints only (no authentication).
47 pub fn public_only() -> Result<Self, BybitError> {
48 let config = ClientConfig::public_only();
49 Self::with_config(config)
50 }
51
52 /// Create a client with custom configuration.
53 pub fn with_config(config: ClientConfig) -> Result<Self, BybitError> {
54 let http = HttpClient::new(config)?;
55 Ok(Self { http })
56 }
57
58 /// Get the underlying HTTP client.
59 pub fn http(&self) -> &HttpClient {
60 &self.http
61 }
62
63 /// Get the client configuration.
64 pub fn config(&self) -> &ClientConfig {
65 self.http.config()
66 }
67
68 /// Synchronize time with the server.
69 ///
70 /// This is useful when your system clock is not accurate.
71 /// The client will adjust timestamps in authenticated requests
72 /// based on the calculated offset.
73 pub async fn sync_time(&self) -> Result<i64, BybitError> {
74 self.http.sync_time().await
75 }
76
77 /// Check if the client has authentication credentials.
78 pub fn has_credentials(&self) -> bool {
79 self.config().has_credentials()
80 }
81
82 /// Get the market data service for public endpoints.
83 ///
84 /// # Example
85 ///
86 /// ```no_run
87 /// # use bybit_client::{BybitClient, Category};
88 /// # use bybit_client::api::market::GetTickersParams;
89 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
90 /// let client = BybitClient::public_only()?;
91 /// let params = GetTickersParams::new(Category::Linear).symbol("BTCUSDT");
92 /// let tickers = client.market().get_tickers(¶ms).await?;
93 /// # Ok(())
94 /// # }
95 /// ```
96 pub fn market(&self) -> MarketService {
97 MarketService::new(self.http.clone())
98 }
99
100 /// Get the trade service for order management endpoints.
101 ///
102 /// Note: Trading endpoints require authentication.
103 ///
104 /// # Example
105 ///
106 /// ```no_run
107 /// # use bybit_client::{BybitClient, Category, Side, OrderType};
108 /// # use bybit_client::types::trade::OrderParams;
109 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
110 /// let client = BybitClient::new("api_key", "api_secret")?;
111 ///
112 /// // Place a market order.
113 /// let params = OrderParams::market(Category::Linear, "BTCUSDT", Side::Buy, "0.001");
114 /// let result = client.trade().submit_order(¶ms).await?;
115 /// println!("Order ID: {}", result.order_id);
116 /// # Ok(())
117 /// # }
118 /// ```
119 pub fn trade(&self) -> TradeService {
120 TradeService::new(self.http.clone())
121 }
122
123 /// Get the position service for position management endpoints.
124 ///
125 /// Note: Position endpoints require authentication.
126 ///
127 /// # Example
128 ///
129 /// ```no_run
130 /// # use bybit_client::{BybitClient, Category};
131 /// # use bybit_client::types::position::GetPositionInfoParams;
132 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
133 /// let client = BybitClient::new("api_key", "api_secret")?;
134 ///
135 /// let params = GetPositionInfoParams::new(Category::Linear).symbol("BTCUSDT");
136 /// let result = client.position().get_position_info(¶ms).await?;
137 /// for pos in &result.list {
138 /// println!("{}: {} @ {}", pos.symbol, pos.size, pos.avg_price);
139 /// }
140 /// # Ok(())
141 /// # }
142 /// ```
143 pub fn position(&self) -> PositionService {
144 PositionService::new(self.http.clone())
145 }
146
147 /// Get the account service for wallet and account management endpoints.
148 ///
149 /// Note: Account endpoints require authentication.
150 ///
151 /// # Example
152 ///
153 /// ```no_run
154 /// # use bybit_client::{BybitClient, AccountType};
155 /// # use bybit_client::types::account::GetWalletBalanceParams;
156 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
157 /// let client = BybitClient::new("api_key", "api_secret")?;
158 ///
159 /// let params = GetWalletBalanceParams::new(AccountType::Unified);
160 /// let result = client.account().get_wallet_balance(¶ms).await?;
161 /// for wallet in &result.list {
162 /// println!("Total equity: {}", wallet.total_equity);
163 /// }
164 /// # Ok(())
165 /// # }
166 /// ```
167 pub fn account(&self) -> AccountService {
168 AccountService::new(self.http.clone())
169 }
170
171 /// Make a public GET request.
172 pub async fn get<T, P>(&self, endpoint: &str, params: Option<&P>) -> Result<T, BybitError>
173 where
174 T: serde::de::DeserializeOwned,
175 P: serde::Serialize + ?Sized,
176 {
177 self.http.get(endpoint, params).await
178 }
179
180 /// Make an authenticated GET request.
181 pub async fn get_signed<T, P>(
182 &self,
183 endpoint: &str,
184 params: Option<&P>,
185 ) -> Result<T, BybitError>
186 where
187 T: serde::de::DeserializeOwned,
188 P: serde::Serialize + ?Sized,
189 {
190 self.http.get_signed(endpoint, params).await
191 }
192
193 /// Make an authenticated POST request.
194 pub async fn post_signed<T, B>(&self, endpoint: &str, body: Option<&B>) -> Result<T, BybitError>
195 where
196 T: serde::de::DeserializeOwned,
197 B: serde::Serialize + ?Sized,
198 {
199 self.http.post_signed(endpoint, body).await
200 }
201}
202
203/// Builder for creating a BybitClient with custom configuration.
204#[derive(Debug, Default)]
205pub struct BybitClientBuilder {
206 config: ClientConfig,
207}
208
209impl BybitClientBuilder {
210 /// Create a new builder.
211 pub fn new() -> Self {
212 Self::default()
213 }
214
215 /// Set API credentials.
216 pub fn credentials(
217 mut self,
218 api_key: impl Into<String>,
219 api_secret: impl Into<String>,
220 ) -> Self {
221 self.config = ClientConfig::new(api_key, api_secret);
222 self
223 }
224
225 /// Use testnet environment.
226 pub fn testnet(mut self) -> Self {
227 self.config = self.config.testnet();
228 self
229 }
230
231 /// Use demo trading environment.
232 pub fn demo(mut self) -> Self {
233 self.config = self.config.demo();
234 self
235 }
236
237 /// Set recv_window.
238 pub fn recv_window(mut self, ms: u32) -> Self {
239 self.config = self.config.recv_window(ms);
240 self
241 }
242
243 /// Enable debug mode.
244 pub fn debug(mut self, enabled: bool) -> Self {
245 self.config = self.config.debug(enabled);
246 self
247 }
248
249 /// Set request timeout.
250 pub fn timeout_ms(mut self, ms: u64) -> Self {
251 self.config = self.config.timeout_ms(ms);
252 self
253 }
254
255 /// Build the client.
256 pub fn build(self) -> Result<BybitClient, BybitError> {
257 BybitClient::with_config(self.config)
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_client_builder() {
267 let client = match BybitClientBuilder::new()
268 .credentials("test_key", "test_secret")
269 .testnet()
270 .debug(true)
271 .build()
272 {
273 Ok(client) => client,
274 Err(err) => panic!("Failed to build client: {}", err),
275 };
276
277 assert!(client.has_credentials());
278 assert!(client.config().debug);
279 }
280
281 #[test]
282 fn test_public_only_client() {
283 let client = match BybitClient::public_only() {
284 Ok(client) => client,
285 Err(err) => panic!("Failed to build public client: {}", err),
286 };
287 assert!(!client.has_credentials());
288 }
289}