Skip to main content

ccxt_exchanges/binance/
signing_strategy.rs

1//! Binance signing strategy for the generic SignedRequestBuilder.
2//!
3//! Implements the SigningStrategy trait for Binance-specific authentication.
4
5use crate::binance::Binance;
6use async_trait::async_trait;
7use ccxt_core::Result;
8use ccxt_core::signed_request::{SigningContext, SigningStrategy};
9use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
10use std::sync::Arc;
11
12/// Binance signing strategy implementing the generic SigningStrategy trait.
13///
14/// Handles Binance-specific signing requirements:
15/// - Millisecond timestamp with server time offset
16/// - HMAC-SHA256 signature (hex encoded)
17/// - X-MBX-APIKEY header for authentication
18pub struct BinanceSigningStrategy {
19    /// Reference to the Binance exchange for accessing auth and time sync
20    binance: Arc<Binance>,
21}
22
23impl BinanceSigningStrategy {
24    /// Create a new Binance signing strategy.
25    pub fn new(binance: Arc<Binance>) -> Self {
26        Self { binance }
27    }
28
29    /// Create from a reference (clones the Arc internally).
30    pub fn from_ref(binance: &Arc<Binance>) -> Self {
31        Self {
32            binance: binance.clone(),
33        }
34    }
35}
36
37#[async_trait]
38impl SigningStrategy for BinanceSigningStrategy {
39    async fn prepare_request(&self, ctx: &mut SigningContext) -> Result<()> {
40        // Step 1: Validate credentials
41        self.binance.check_required_credentials()?;
42
43        // Step 2: Get signing timestamp (with server time offset)
44        let timestamp = self.binance.get_signing_timestamp().await?;
45        ctx.timestamp = timestamp.to_string();
46
47        // Step 3: Add timestamp and recvWindow to params
48        ctx.params
49            .insert("timestamp".to_string(), ctx.timestamp.clone());
50        ctx.params.insert(
51            "recvWindow".to_string(),
52            self.binance.options().recv_window.to_string(),
53        );
54
55        // Step 4: Get auth and compute signature
56        let auth = self.binance.get_auth()?;
57        let query_string = build_signing_string(&ctx.params);
58        let signature = auth.sign(&query_string)?;
59
60        // Step 5: Add signature to params
61        ctx.signature = Some(signature.clone());
62        ctx.params.insert("signature".to_string(), signature);
63
64        Ok(())
65    }
66
67    fn add_auth_headers(&self, headers: &mut HeaderMap, _ctx: &SigningContext) {
68        // Add X-MBX-APIKEY header
69        if let Ok(auth) = self.binance.get_auth() {
70            if let Ok(header_name) = HeaderName::from_bytes(b"X-MBX-APIKEY") {
71                if let Ok(header_value) = HeaderValue::from_str(auth.api_key()) {
72                    headers.insert(header_name, header_value);
73                }
74            }
75        }
76    }
77}
78
79/// Build query string for signing (without URL encoding).
80fn build_signing_string(params: &std::collections::BTreeMap<String, String>) -> String {
81    params
82        .iter()
83        .map(|(k, v)| format!("{}={}", k, v))
84        .collect::<Vec<_>>()
85        .join("&")
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use ccxt_core::ExchangeConfig;
92
93    #[test]
94    fn test_signing_strategy_creation() {
95        let config = ExchangeConfig::default();
96        let binance = Arc::new(Binance::new(config).unwrap());
97        let _strategy = BinanceSigningStrategy::new(binance);
98        // Strategy created successfully
99    }
100
101    #[test]
102    fn test_build_signing_string() {
103        let mut params = std::collections::BTreeMap::new();
104        params.insert("symbol".to_string(), "BTCUSDT".to_string());
105        params.insert("side".to_string(), "BUY".to_string());
106        params.insert("timestamp".to_string(), "1234567890".to_string());
107
108        let query = build_signing_string(&params);
109        // BTreeMap maintains alphabetical order
110        assert_eq!(query, "side=BUY&symbol=BTCUSDT&timestamp=1234567890");
111    }
112}