Skip to main content

binance_sdk/staking/rest_api/apis/
soft_staking_api.rs

1/*
2 * Staking REST API
3 *
4 * Subscribe to staking products, track positions, and query rewards via the Binance Staking API.
5 *
6 * The version of the OpenAPI document: 1.0.0
7 *
8 *
9 * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
10 * https://openapi-generator.tech
11 * Do not edit the class manually.
12 */
13
14#![allow(unused_imports)]
15use async_trait::async_trait;
16use derive_builder::Builder;
17use reqwest;
18use rust_decimal::prelude::*;
19use serde::{Deserialize, Serialize};
20use serde_json::{Value, json};
21use std::collections::BTreeMap;
22
23use crate::common::{
24    config::ConfigurationRestApi,
25    models::{ParamBuildError, RestApiResponse},
26    utils::send_request,
27};
28use crate::staking::rest_api::models;
29
30const HAS_TIME_UNIT: bool = false;
31
32#[async_trait]
33pub trait SoftStakingApi: Send + Sync {
34    async fn get_soft_staking_product_list(
35        &self,
36        params: GetSoftStakingProductListParams,
37    ) -> anyhow::Result<RestApiResponse<models::GetSoftStakingProductListResponse>>;
38    async fn get_soft_staking_rewards_history(
39        &self,
40        params: GetSoftStakingRewardsHistoryParams,
41    ) -> anyhow::Result<RestApiResponse<models::GetSoftStakingRewardsHistoryResponse>>;
42    async fn set_soft_staking(
43        &self,
44        params: SetSoftStakingParams,
45    ) -> anyhow::Result<RestApiResponse<models::SetSoftStakingResponse>>;
46}
47
48#[derive(Debug, Clone)]
49pub struct SoftStakingApiClient {
50    configuration: ConfigurationRestApi,
51}
52
53impl SoftStakingApiClient {
54    pub fn new(configuration: ConfigurationRestApi) -> Self {
55        Self { configuration }
56    }
57}
58
59/// Request parameters for the [`get_soft_staking_product_list`] operation.
60///
61/// This struct holds all of the inputs you can pass when calling
62/// [`get_soft_staking_product_list`](#method.get_soft_staking_product_list).
63#[derive(Clone, Debug, Builder, Deserialize, Default)]
64#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
65pub struct GetSoftStakingProductListParams {
66    ///
67    /// The `asset` parameter.
68    ///
69    /// This field is **optional.
70    #[builder(setter(into), default)]
71    #[serde(rename = "asset", default)]
72    pub asset: Option<String>,
73    /// Currently querying page
74    ///
75    /// This field is **optional.
76    #[builder(setter(into), default)]
77    #[serde(rename = "current", default)]
78    pub current: Option<i64>,
79    ///
80    /// The `size` parameter.
81    ///
82    /// This field is **optional.
83    #[builder(setter(into), default)]
84    #[serde(rename = "size", default)]
85    pub size: Option<i64>,
86    /// Request validity window in milliseconds.
87    ///
88    /// This field is **optional.
89    #[builder(setter(into), default)]
90    #[serde(rename = "recvWindow", default)]
91    pub recv_window: Option<i64>,
92}
93
94impl GetSoftStakingProductListParams {
95    /// Create a builder for [`get_soft_staking_product_list`].
96    ///
97    #[must_use]
98    pub fn builder() -> GetSoftStakingProductListParamsBuilder {
99        GetSoftStakingProductListParamsBuilder::default()
100    }
101}
102/// Request parameters for the [`get_soft_staking_rewards_history`] operation.
103///
104/// This struct holds all of the inputs you can pass when calling
105/// [`get_soft_staking_rewards_history`](#method.get_soft_staking_rewards_history).
106#[derive(Clone, Debug, Builder, Deserialize, Default)]
107#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
108pub struct GetSoftStakingRewardsHistoryParams {
109    ///
110    /// The `asset` parameter.
111    ///
112    /// This field is **optional.
113    #[builder(setter(into), default)]
114    #[serde(rename = "asset", default)]
115    pub asset: Option<String>,
116    ///
117    /// The `start_time` parameter.
118    ///
119    /// This field is **optional.
120    #[builder(setter(into), default)]
121    #[serde(rename = "startTime", default)]
122    pub start_time: Option<i64>,
123    ///
124    /// The `end_time` parameter.
125    ///
126    /// This field is **optional.
127    #[builder(setter(into), default)]
128    #[serde(rename = "endTime", default)]
129    pub end_time: Option<i64>,
130    /// Currently querying page
131    ///
132    /// This field is **optional.
133    #[builder(setter(into), default)]
134    #[serde(rename = "current", default)]
135    pub current: Option<i64>,
136    ///
137    /// The `size` parameter.
138    ///
139    /// This field is **optional.
140    #[builder(setter(into), default)]
141    #[serde(rename = "size", default)]
142    pub size: Option<i64>,
143    /// Request validity window in milliseconds.
144    ///
145    /// This field is **optional.
146    #[builder(setter(into), default)]
147    #[serde(rename = "recvWindow", default)]
148    pub recv_window: Option<i64>,
149}
150
151impl GetSoftStakingRewardsHistoryParams {
152    /// Create a builder for [`get_soft_staking_rewards_history`].
153    ///
154    #[must_use]
155    pub fn builder() -> GetSoftStakingRewardsHistoryParamsBuilder {
156        GetSoftStakingRewardsHistoryParamsBuilder::default()
157    }
158}
159/// Request parameters for the [`set_soft_staking`] operation.
160///
161/// This struct holds all of the inputs you can pass when calling
162/// [`set_soft_staking`](#method.set_soft_staking).
163#[derive(Clone, Debug, Builder, Deserialize)]
164#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
165pub struct SetSoftStakingParams {
166    ///
167    /// The `soft_staking` parameter.
168    ///
169    /// This field is **required.
170    #[builder(setter(into))]
171    #[serde(rename = "softStaking")]
172    pub soft_staking: bool,
173    /// Request validity window in milliseconds.
174    ///
175    /// This field is **optional.
176    #[builder(setter(into), default)]
177    #[serde(rename = "recvWindow", default)]
178    pub recv_window: Option<i64>,
179}
180
181impl SetSoftStakingParams {
182    /// Create a builder for [`set_soft_staking`].
183    ///
184    /// Required parameters:
185    ///
186    /// * `soft_staking` — bool
187    ///
188    #[must_use]
189    pub fn builder(soft_staking: bool) -> SetSoftStakingParamsBuilder {
190        SetSoftStakingParamsBuilder::default().soft_staking(soft_staking)
191    }
192}
193
194#[async_trait]
195impl SoftStakingApi for SoftStakingApiClient {
196    async fn get_soft_staking_product_list(
197        &self,
198        params: GetSoftStakingProductListParams,
199    ) -> anyhow::Result<RestApiResponse<models::GetSoftStakingProductListResponse>> {
200        let GetSoftStakingProductListParams {
201            asset,
202            current,
203            size,
204            recv_window,
205        } = params;
206
207        let mut query_params = BTreeMap::new();
208        let body_params = BTreeMap::new();
209
210        if let Some(rw) = asset {
211            query_params.insert("asset".to_string(), json!(rw));
212        }
213
214        if let Some(rw) = current {
215            query_params.insert("current".to_string(), json!(rw));
216        }
217
218        if let Some(rw) = size {
219            query_params.insert("size".to_string(), json!(rw));
220        }
221
222        if let Some(rw) = recv_window {
223            query_params.insert("recvWindow".to_string(), json!(rw));
224        }
225
226        send_request::<models::GetSoftStakingProductListResponse>(
227            &self.configuration,
228            "/sapi/v1/soft-staking/list",
229            reqwest::Method::GET,
230            query_params,
231            body_params,
232            if HAS_TIME_UNIT {
233                self.configuration.time_unit
234            } else {
235                None
236            },
237            true,
238        )
239        .await
240    }
241
242    async fn get_soft_staking_rewards_history(
243        &self,
244        params: GetSoftStakingRewardsHistoryParams,
245    ) -> anyhow::Result<RestApiResponse<models::GetSoftStakingRewardsHistoryResponse>> {
246        let GetSoftStakingRewardsHistoryParams {
247            asset,
248            start_time,
249            end_time,
250            current,
251            size,
252            recv_window,
253        } = params;
254
255        let mut query_params = BTreeMap::new();
256        let body_params = BTreeMap::new();
257
258        if let Some(rw) = asset {
259            query_params.insert("asset".to_string(), json!(rw));
260        }
261
262        if let Some(rw) = start_time {
263            query_params.insert("startTime".to_string(), json!(rw));
264        }
265
266        if let Some(rw) = end_time {
267            query_params.insert("endTime".to_string(), json!(rw));
268        }
269
270        if let Some(rw) = current {
271            query_params.insert("current".to_string(), json!(rw));
272        }
273
274        if let Some(rw) = size {
275            query_params.insert("size".to_string(), json!(rw));
276        }
277
278        if let Some(rw) = recv_window {
279            query_params.insert("recvWindow".to_string(), json!(rw));
280        }
281
282        send_request::<models::GetSoftStakingRewardsHistoryResponse>(
283            &self.configuration,
284            "/sapi/v1/soft-staking/history/rewardsRecord",
285            reqwest::Method::GET,
286            query_params,
287            body_params,
288            if HAS_TIME_UNIT {
289                self.configuration.time_unit
290            } else {
291                None
292            },
293            true,
294        )
295        .await
296    }
297
298    async fn set_soft_staking(
299        &self,
300        params: SetSoftStakingParams,
301    ) -> anyhow::Result<RestApiResponse<models::SetSoftStakingResponse>> {
302        let SetSoftStakingParams {
303            soft_staking,
304            recv_window,
305        } = params;
306
307        let mut query_params = BTreeMap::new();
308        let body_params = BTreeMap::new();
309
310        query_params.insert("softStaking".to_string(), json!(soft_staking));
311
312        if let Some(rw) = recv_window {
313            query_params.insert("recvWindow".to_string(), json!(rw));
314        }
315
316        send_request::<models::SetSoftStakingResponse>(
317            &self.configuration,
318            "/sapi/v1/soft-staking/set",
319            reqwest::Method::GET,
320            query_params,
321            body_params,
322            if HAS_TIME_UNIT {
323                self.configuration.time_unit
324            } else {
325                None
326            },
327            true,
328        )
329        .await
330    }
331}
332
333#[cfg(all(test, feature = "staking"))]
334mod tests {
335    use super::*;
336    use crate::TOKIO_SHARED_RT;
337    use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
338    use async_trait::async_trait;
339    use std::collections::HashMap;
340
341    struct DummyRestApiResponse<T> {
342        inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
343        status: u16,
344        headers: HashMap<String, String>,
345        rate_limits: Option<Vec<RestApiRateLimit>>,
346    }
347
348    impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
349        fn from(dummy: DummyRestApiResponse<T>) -> Self {
350            Self {
351                data_fn: dummy.inner,
352                status: dummy.status,
353                headers: dummy.headers,
354                rate_limits: dummy.rate_limits,
355            }
356        }
357    }
358
359    struct MockSoftStakingApiClient {
360        force_error: bool,
361    }
362
363    #[async_trait]
364    impl SoftStakingApi for MockSoftStakingApiClient {
365        async fn get_soft_staking_product_list(
366            &self,
367            _params: GetSoftStakingProductListParams,
368        ) -> anyhow::Result<RestApiResponse<models::GetSoftStakingProductListResponse>> {
369            if self.force_error {
370                return Err(ConnectorError::ConnectorClientError {
371                    msg: "ResponseError".to_string(),
372                    code: None,
373                }
374                .into());
375            }
376
377            let resp_json: Value = serde_json::from_str(r#"{"status":true,"totalRewardsUsdt":"3.09827182","rows":[{"asset":"BNB","minAmount":"0.5","maxCap":"1000","apr":"0.0015","stakedAmount":"2.14","totalProfit":"0.00171234"}],"total":2}"#).unwrap_or_else(|_| serde_json::json!({}));
378            let dummy_response: models::GetSoftStakingProductListResponse =
379                serde_json::from_value(resp_json.clone())
380                    .expect("should parse into models::GetSoftStakingProductListResponse");
381
382            let dummy = DummyRestApiResponse {
383                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
384                status: 200,
385                headers: HashMap::new(),
386                rate_limits: None,
387            };
388
389            Ok(dummy.into())
390        }
391
392        async fn get_soft_staking_rewards_history(
393            &self,
394            _params: GetSoftStakingRewardsHistoryParams,
395        ) -> anyhow::Result<RestApiResponse<models::GetSoftStakingRewardsHistoryResponse>> {
396            if self.force_error {
397                return Err(ConnectorError::ConnectorClientError {
398                    msg: "ResponseError".to_string(),
399                    code: None,
400                }
401                .into());
402            }
403
404            let resp_json: Value = serde_json::from_str(r#"{"rows":[{"asset":"BNB","rewards":"0.00000557","rewardAsset":"BNB","avgAmount":"2.14","time":1754007978000}],"total":2}"#).unwrap_or_else(|_| serde_json::json!({}));
405            let dummy_response: models::GetSoftStakingRewardsHistoryResponse =
406                serde_json::from_value(resp_json.clone())
407                    .expect("should parse into models::GetSoftStakingRewardsHistoryResponse");
408
409            let dummy = DummyRestApiResponse {
410                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
411                status: 200,
412                headers: HashMap::new(),
413                rate_limits: None,
414            };
415
416            Ok(dummy.into())
417        }
418
419        async fn set_soft_staking(
420            &self,
421            _params: SetSoftStakingParams,
422        ) -> anyhow::Result<RestApiResponse<models::SetSoftStakingResponse>> {
423            if self.force_error {
424                return Err(ConnectorError::ConnectorClientError {
425                    msg: "ResponseError".to_string(),
426                    code: None,
427                }
428                .into());
429            }
430
431            let resp_json: Value = serde_json::from_str(r#"{"success":true}"#)
432                .unwrap_or_else(|_| serde_json::json!({}));
433            let dummy_response: models::SetSoftStakingResponse =
434                serde_json::from_value(resp_json.clone())
435                    .expect("should parse into models::SetSoftStakingResponse");
436
437            let dummy = DummyRestApiResponse {
438                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
439                status: 200,
440                headers: HashMap::new(),
441                rate_limits: None,
442            };
443
444            Ok(dummy.into())
445        }
446    }
447
448    #[test]
449    fn get_soft_staking_product_list_required_params_success() {
450        TOKIO_SHARED_RT.block_on(async {
451            let client = MockSoftStakingApiClient { force_error: false };
452
453            let params = GetSoftStakingProductListParams::builder().build().unwrap();
454
455            let resp_json: Value = serde_json::from_str(r#"{"status":true,"totalRewardsUsdt":"3.09827182","rows":[{"asset":"BNB","minAmount":"0.5","maxCap":"1000","apr":"0.0015","stakedAmount":"2.14","totalProfit":"0.00171234"}],"total":2}"#).unwrap_or_else(|_| serde_json::json!({}));
456            let expected_response : models::GetSoftStakingProductListResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetSoftStakingProductListResponse");
457
458            let resp = client.get_soft_staking_product_list(params).await.expect("Expected a response");
459            let data_future = resp.data();
460            let actual_response = data_future.await.unwrap();
461            assert_eq!(actual_response, expected_response);
462        });
463    }
464
465    #[test]
466    fn get_soft_staking_product_list_optional_params_success() {
467        TOKIO_SHARED_RT.block_on(async {
468            let client = MockSoftStakingApiClient { force_error: false };
469
470            let params = GetSoftStakingProductListParams::builder().asset("BTC".to_string()).current(1).size(10).recv_window(5000).build().unwrap();
471
472            let resp_json: Value = serde_json::from_str(r#"{"status":true,"totalRewardsUsdt":"3.09827182","rows":[{"asset":"BNB","minAmount":"0.5","maxCap":"1000","apr":"0.0015","stakedAmount":"2.14","totalProfit":"0.00171234"}],"total":2}"#).unwrap_or_else(|_| serde_json::json!({}));
473            let expected_response : models::GetSoftStakingProductListResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetSoftStakingProductListResponse");
474
475            let resp = client.get_soft_staking_product_list(params).await.expect("Expected a response");
476            let data_future = resp.data();
477            let actual_response = data_future.await.unwrap();
478            assert_eq!(actual_response, expected_response);
479        });
480    }
481
482    #[test]
483    fn get_soft_staking_product_list_response_error() {
484        TOKIO_SHARED_RT.block_on(async {
485            let client = MockSoftStakingApiClient { force_error: true };
486
487            let params = GetSoftStakingProductListParams::builder().build().unwrap();
488
489            match client.get_soft_staking_product_list(params).await {
490                Ok(_) => panic!("Expected an error"),
491                Err(err) => {
492                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
493                }
494            }
495        });
496    }
497
498    #[test]
499    fn get_soft_staking_rewards_history_required_params_success() {
500        TOKIO_SHARED_RT.block_on(async {
501            let client = MockSoftStakingApiClient { force_error: false };
502
503            let params = GetSoftStakingRewardsHistoryParams::builder().build().unwrap();
504
505            let resp_json: Value = serde_json::from_str(r#"{"rows":[{"asset":"BNB","rewards":"0.00000557","rewardAsset":"BNB","avgAmount":"2.14","time":1754007978000}],"total":2}"#).unwrap_or_else(|_| serde_json::json!({}));
506            let expected_response : models::GetSoftStakingRewardsHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetSoftStakingRewardsHistoryResponse");
507
508            let resp = client.get_soft_staking_rewards_history(params).await.expect("Expected a response");
509            let data_future = resp.data();
510            let actual_response = data_future.await.unwrap();
511            assert_eq!(actual_response, expected_response);
512        });
513    }
514
515    #[test]
516    fn get_soft_staking_rewards_history_optional_params_success() {
517        TOKIO_SHARED_RT.block_on(async {
518            let client = MockSoftStakingApiClient { force_error: false };
519
520            let params = GetSoftStakingRewardsHistoryParams::builder().asset("BTC".to_string()).start_time(1623319461670).end_time(1641782889000).current(1).size(10).recv_window(5000).build().unwrap();
521
522            let resp_json: Value = serde_json::from_str(r#"{"rows":[{"asset":"BNB","rewards":"0.00000557","rewardAsset":"BNB","avgAmount":"2.14","time":1754007978000}],"total":2}"#).unwrap_or_else(|_| serde_json::json!({}));
523            let expected_response : models::GetSoftStakingRewardsHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::GetSoftStakingRewardsHistoryResponse");
524
525            let resp = client.get_soft_staking_rewards_history(params).await.expect("Expected a response");
526            let data_future = resp.data();
527            let actual_response = data_future.await.unwrap();
528            assert_eq!(actual_response, expected_response);
529        });
530    }
531
532    #[test]
533    fn get_soft_staking_rewards_history_response_error() {
534        TOKIO_SHARED_RT.block_on(async {
535            let client = MockSoftStakingApiClient { force_error: true };
536
537            let params = GetSoftStakingRewardsHistoryParams::builder()
538                .build()
539                .unwrap();
540
541            match client.get_soft_staking_rewards_history(params).await {
542                Ok(_) => panic!("Expected an error"),
543                Err(err) => {
544                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
545                }
546            }
547        });
548    }
549
550    #[test]
551    fn set_soft_staking_required_params_success() {
552        TOKIO_SHARED_RT.block_on(async {
553            let client = MockSoftStakingApiClient { force_error: false };
554
555            let params = SetSoftStakingParams::builder(true).build().unwrap();
556
557            let resp_json: Value = serde_json::from_str(r#"{"success":true}"#)
558                .unwrap_or_else(|_| serde_json::json!({}));
559            let expected_response: models::SetSoftStakingResponse =
560                serde_json::from_value(resp_json.clone())
561                    .expect("should parse into models::SetSoftStakingResponse");
562
563            let resp = client
564                .set_soft_staking(params)
565                .await
566                .expect("Expected a response");
567            let data_future = resp.data();
568            let actual_response = data_future.await.unwrap();
569            assert_eq!(actual_response, expected_response);
570        });
571    }
572
573    #[test]
574    fn set_soft_staking_optional_params_success() {
575        TOKIO_SHARED_RT.block_on(async {
576            let client = MockSoftStakingApiClient { force_error: false };
577
578            let params = SetSoftStakingParams::builder(true)
579                .recv_window(5000)
580                .build()
581                .unwrap();
582
583            let resp_json: Value = serde_json::from_str(r#"{"success":true}"#)
584                .unwrap_or_else(|_| serde_json::json!({}));
585            let expected_response: models::SetSoftStakingResponse =
586                serde_json::from_value(resp_json.clone())
587                    .expect("should parse into models::SetSoftStakingResponse");
588
589            let resp = client
590                .set_soft_staking(params)
591                .await
592                .expect("Expected a response");
593            let data_future = resp.data();
594            let actual_response = data_future.await.unwrap();
595            assert_eq!(actual_response, expected_response);
596        });
597    }
598
599    #[test]
600    fn set_soft_staking_response_error() {
601        TOKIO_SHARED_RT.block_on(async {
602            let client = MockSoftStakingApiClient { force_error: true };
603
604            let params = SetSoftStakingParams::builder(true).build().unwrap();
605
606            match client.set_soft_staking(params).await {
607                Ok(_) => panic!("Expected an error"),
608                Err(err) => {
609                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
610                }
611            }
612        });
613    }
614}