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&timestamp=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}