Skip to main content

binance_sdk/staking/rest_api/apis/
soft_staking_api.rs

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