birdie/lib.rs
1//! # Birdie - Binance Rust Development Kit
2//!
3//! Birdie is a third party Binance API client, allowing you to easily interact
4//! with the Binance API using Rust.
5//!
6//! ## Disclaimer
7//!
8//! - This is a third party client and is not affiliated with Binance. Use at
9//! your own risk.
10//! - While we will try to keep this client aligned with the official Binance
11//! API specification, please refer to the official Binance API documentation
12//! for the most up-to-date information.
13//! - Always test your code.
14//!
15//! ## Components
16//!
17//! Birdie is divided into several components, each representing a different
18//! part of the Binance API:
19//!
20//! - [`mod@fix_api`] - FIX API client (stub).
21//! - [`mod@rest_api`] - REST API client.
22//! - [`mod@spot`] - Spot API.
23//! - [`mod@margin`] - Margin API.
24//! - [`mod@usd_futures`] - USD Futures API (WIP).
25//! - [`mod@web_socket_api`] - Web Socket API client.
26//! - [`mod@web_socket_stream`] - Web Socket stream client.
27//!
28//! ## REST API Client
29//!
30//! See the [`mod@rest_api`] module for more information.
31//!
32//! To interact with the Binance REST API, create a REST API client first.
33//!
34//! ```no_run
35//! let endpoint = "https://api.binance.com";
36//! let api_key = "your_api_key";
37//! let api_secret = "your_api_secret";
38//! let client = birdie::rest_api(endpoint, api_key, api_secret).unwrap();
39//! ```
40//!
41//! Once you have the client, you can access the different categories of the
42//! API and the different endpoints.
43//!
44//! ### Example 1: retrieve account information
45//!
46//! Also see the `account_balances.rs` example in the `examples/` directory.
47//!
48//! ```no_run
49//! use birdie::{rest_api::Endpoint, spot::account::AccountInformationParams};
50//!
51//! let params = AccountInformationParams::new().omit_zero_balances(true);
52//! let resp = client.spot().account().account_information().request(params).await;
53//! assert!(resp.is_ok());
54//! ```
55//!
56//! ### Example 2: retrieve klines of a symbol
57//!
58//! Also see the `klines.rs` example in the `examples/` directory.
59//!
60//! ```no_run
61//! use birdie::{enums::KlineInterval, rest_api::Endpoint, spot::market::KlinesParams};
62//!
63//! let params = KlinesParams::new("BTCUSDT", KlineInterval::OneMinute).limit(5);
64//! let klines = client.spot().market().klines().request(params).await;
65//! assert!(klines.is_ok());
66//! ```
67//!
68//! ### Example 3: place a limit order
69//!
70//! ```no_run
71//! use birdie::{
72//! enums::{OrderSide, OrderType},
73//! rest_api::Endpoint,
74//! spot::trade::NewOrderParams,
75//! };
76//!
77//! let params = NewOrderParams::new("SOLUSDT", OrderSide::Buy, OrderType::Limit)
78//! .time_in_force(birdie::enums::TimeInForce::Gtc)
79//! .new_order_resp_type(birdie::enums::ResponseType::Result)
80//! .quantity(1.0)
81//! .price(100.0);
82//! let resp = client.trade().new_order().request(params).await;
83//! assert!(resp.is_ok());
84//! ````
85//!
86//! ## Web Socket API Client
87//!
88//! ## Web Socket Streams
89
90pub mod enums;
91pub mod errors;
92pub mod filters;
93pub mod fix_api;
94pub mod rest_api;
95pub mod web_socket;
96pub mod web_socket_api;
97pub mod web_socket_stream;
98
99pub mod margin;
100pub mod spot;
101pub mod usd_futures;
102
103use base64::{engine::general_purpose::STANDARD as b64, Engine};
104use ed25519_dalek::{ed25519::signature::SignerMut, pkcs8::DecodePrivateKey, SigningKey};
105use hmac::{Hmac, Mac};
106use rest_api::{RestApiClient, RestApiError};
107use serde::{Deserialize, Serialize};
108use sha2::Sha256;
109
110pub fn rest_api(
111 base_url: &str,
112 api_key: &str,
113 secret_key: &str,
114) -> Result<RestApiClient, RestApiError> {
115 RestApiClient::new(base_url, api_key, secret_key)
116}
117
118pub fn web_socket_api(
119 endpoint: &str,
120 api_key: &str,
121 secret_key: &str,
122) -> web_socket_api::WebSocketApiClient {
123 web_socket_api::WebSocketApiClient::new(endpoint, api_key, secret_key)
124}
125
126pub trait Params: Sized + Send + Serialize {
127 fn as_query(&self) -> Result<String, RestApiError> {
128 Ok(serde_qs::to_string(self)?)
129 }
130}
131
132pub trait Response: Sized + for<'de> Deserialize<'de> {}
133
134pub(crate) fn hmac_signature(key: &str, data: &str) -> Result<String, hmac::digest::InvalidLength> {
135 let mut mac = Hmac::<Sha256>::new_from_slice(key.as_bytes())?;
136 mac.update(data.as_bytes());
137 Ok(hex::encode(mac.finalize().into_bytes()))
138}
139
140pub(crate) fn ed25519_signature(
141 key: &str,
142 data: &str,
143) -> Result<String, ed25519_dalek::pkcs8::Error> {
144 let mut key = SigningKey::from_pkcs8_pem(key)?;
145 let signature = key.sign(data.as_bytes());
146 let encoded = b64.encode(signature.to_bytes());
147 Ok(encoded)
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
155 fn hmac() {
156 let key = "NhqPtmdSJYdKjVHjA7PZj4Mge3R5YNiP1e3UZjInClVN65XAbvqqM6A7H5fATj0j";
157 let data = "symbol=LTCBTC&side=BUY&type=LIMIT&timeInForce=GTC&quantity=1&price=0.1&recvWindow=5000×tamp=1499827319559";
158 let sig = hmac_signature(key, data).unwrap();
159 assert_eq!(
160 sig,
161 "c8db56825ae71d6d79447849e617115f4a920fa2acdcab2b053c4b2838bd6b71"
162 );
163 }
164
165 #[tokio::test]
166 async fn ed25519() {
167 let pem = r#"\
168-----BEGIN PRIVATE KEY-----
169MC4CAQAwBQYDK2VwBCIEIHWPzTvl8pIHsAbZtJv+/0kaXO611fs90IewpT1PEwFT
170-----END PRIVATE KEY-----
171"#;
172 let data = "hello world";
173 let signature = ed25519_signature(pem, data).unwrap();
174 assert_eq!(signature, "e2jzxQfzCeKvlScapTXk1Jt7e1i6rIXLZ4UVWcJ7kykMSARX/rUV7a3za+LlFizFUORuUs/38zlVbxFnrcCLBw==");
175 }
176}