Skip to main content

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(&params).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(&params).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(&params).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(&params).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}