Skip to main content

binance_sdk/w3w_prediction/rest_api/apis/
transfer_api.rs

1/*
2 * Prediction Trading REST API
3 *
4 * Place and manage prediction market orders, query positions, and transfer funds via the Prediction Trading 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::w3w_prediction::rest_api::models;
29
30const HAS_TIME_UNIT: bool = false;
31
32#[async_trait]
33pub trait TransferApi: Send + Sync {
34    async fn create_inbound_transfer(
35        &self,
36        params: CreateInboundTransferParams,
37    ) -> anyhow::Result<RestApiResponse<models::CreateInboundTransferResponse>>;
38    async fn create_outbound_transfer(
39        &self,
40        params: CreateOutboundTransferParams,
41    ) -> anyhow::Result<RestApiResponse<models::CreateOutboundTransferResponse>>;
42    async fn query_transfer_list(
43        &self,
44        params: QueryTransferListParams,
45    ) -> anyhow::Result<RestApiResponse<models::QueryTransferListResponse>>;
46    async fn query_transfer_status(
47        &self,
48        params: QueryTransferStatusParams,
49    ) -> anyhow::Result<RestApiResponse<models::QueryTransferStatusResponse>>;
50}
51
52#[derive(Debug, Clone)]
53pub struct TransferApiClient {
54    configuration: ConfigurationRestApi,
55}
56
57impl TransferApiClient {
58    pub fn new(configuration: ConfigurationRestApi) -> Self {
59        Self { configuration }
60    }
61}
62
63#[allow(non_camel_case_types)]
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub enum CreateInboundTransferAccountTypeEnum {
66    #[serde(rename = "SPOT")]
67    Spot,
68    #[serde(rename = "FUNDING")]
69    Funding,
70}
71
72impl CreateInboundTransferAccountTypeEnum {
73    #[must_use]
74    pub fn as_str(&self) -> &'static str {
75        match self {
76            Self::Spot => "SPOT",
77            Self::Funding => "FUNDING",
78        }
79    }
80}
81
82impl std::str::FromStr for CreateInboundTransferAccountTypeEnum {
83    type Err = Box<dyn std::error::Error + Send + Sync>;
84
85    fn from_str(s: &str) -> Result<Self, Self::Err> {
86        match s {
87            "SPOT" => Ok(Self::Spot),
88            "FUNDING" => Ok(Self::Funding),
89            other => Err(format!("invalid CreateInboundTransferAccountTypeEnum: {}", other).into()),
90        }
91    }
92}
93
94#[allow(non_camel_case_types)]
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub enum CreateOutboundTransferAccountTypeEnum {
97    #[serde(rename = "SPOT")]
98    Spot,
99    #[serde(rename = "FUNDING")]
100    Funding,
101}
102
103impl CreateOutboundTransferAccountTypeEnum {
104    #[must_use]
105    pub fn as_str(&self) -> &'static str {
106        match self {
107            Self::Spot => "SPOT",
108            Self::Funding => "FUNDING",
109        }
110    }
111}
112
113impl std::str::FromStr for CreateOutboundTransferAccountTypeEnum {
114    type Err = Box<dyn std::error::Error + Send + Sync>;
115
116    fn from_str(s: &str) -> Result<Self, Self::Err> {
117        match s {
118            "SPOT" => Ok(Self::Spot),
119            "FUNDING" => Ok(Self::Funding),
120            other => {
121                Err(format!("invalid CreateOutboundTransferAccountTypeEnum: {}", other).into())
122            }
123        }
124    }
125}
126
127#[allow(non_camel_case_types)]
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub enum CreateOutboundTransferSourceBizEnum {
130    #[serde(rename = "USER_TRANSFER")]
131    UserTransfer,
132    #[serde(rename = "PREDICTION_BUY")]
133    PredictionBuy,
134}
135
136impl CreateOutboundTransferSourceBizEnum {
137    #[must_use]
138    pub fn as_str(&self) -> &'static str {
139        match self {
140            Self::UserTransfer => "USER_TRANSFER",
141            Self::PredictionBuy => "PREDICTION_BUY",
142        }
143    }
144}
145
146impl std::str::FromStr for CreateOutboundTransferSourceBizEnum {
147    type Err = Box<dyn std::error::Error + Send + Sync>;
148
149    fn from_str(s: &str) -> Result<Self, Self::Err> {
150        match s {
151            "USER_TRANSFER" => Ok(Self::UserTransfer),
152            "PREDICTION_BUY" => Ok(Self::PredictionBuy),
153            other => Err(format!("invalid CreateOutboundTransferSourceBizEnum: {}", other).into()),
154        }
155    }
156}
157
158#[allow(non_camel_case_types)]
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub enum QueryTransferListDirectionEnum {
161    #[serde(rename = "INBOUND")]
162    Inbound,
163    #[serde(rename = "OUTBOUND")]
164    Outbound,
165}
166
167impl QueryTransferListDirectionEnum {
168    #[must_use]
169    pub fn as_str(&self) -> &'static str {
170        match self {
171            Self::Inbound => "INBOUND",
172            Self::Outbound => "OUTBOUND",
173        }
174    }
175}
176
177impl std::str::FromStr for QueryTransferListDirectionEnum {
178    type Err = Box<dyn std::error::Error + Send + Sync>;
179
180    fn from_str(s: &str) -> Result<Self, Self::Err> {
181        match s {
182            "INBOUND" => Ok(Self::Inbound),
183            "OUTBOUND" => Ok(Self::Outbound),
184            other => Err(format!("invalid QueryTransferListDirectionEnum: {}", other).into()),
185        }
186    }
187}
188
189/// Request parameters for the [`create_inbound_transfer`] operation.
190///
191/// This struct holds all of the inputs you can pass when calling
192/// [`create_inbound_transfer`](#method.create_inbound_transfer).
193#[derive(Clone, Debug, Builder)]
194#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
195pub struct CreateInboundTransferParams {
196    /// Wallet ID
197    ///
198    /// This field is **required.
199    #[builder(setter(into))]
200    pub wallet_id: String,
201    /// User's prediction wallet address
202    ///
203    /// This field is **required.
204    #[builder(setter(into))]
205    pub wallet_address: String,
206    /// Transfer amount in wei (18 decimals). Must be > 0. Example: `1000000000000000000` = 1 USDT
207    ///
208    /// This field is **required.
209    #[builder(setter(into))]
210    pub from_token_amount: String,
211    /// Destination CEX account. Enum: `SPOT`, `FUNDING`
212    ///
213    /// This field is **required.
214    #[builder(setter(into))]
215    pub account_type: CreateInboundTransferAccountTypeEnum,
216    /// Source token symbol. Default `USDT`
217    ///
218    /// This field is **optional.
219    #[builder(setter(into), default)]
220    pub from_token: Option<String>,
221    /// Destination token symbol. Default `USDT`
222    ///
223    /// This field is **optional.
224    #[builder(setter(into), default)]
225    pub to_token: Option<String>,
226    /// Chain ID. Default `56` (BSC)
227    ///
228    /// This field is **optional.
229    #[builder(setter(into), default)]
230    pub chain_id: Option<String>,
231}
232
233impl CreateInboundTransferParams {
234    /// Create a builder for [`create_inbound_transfer`].
235    ///
236    /// Required parameters:
237    ///
238    /// * `wallet_id` — Wallet ID
239    /// * `wallet_address` — User's prediction wallet address
240    /// * `from_token_amount` — Transfer amount in wei (18 decimals). Must be > 0. Example: `1000000000000000000` = 1 USDT
241    /// * `account_type` — Destination CEX account. Enum: `SPOT`, `FUNDING`
242    ///
243    #[must_use]
244    pub fn builder(
245        wallet_id: String,
246        wallet_address: String,
247        from_token_amount: String,
248        account_type: CreateInboundTransferAccountTypeEnum,
249    ) -> CreateInboundTransferParamsBuilder {
250        CreateInboundTransferParamsBuilder::default()
251            .wallet_id(wallet_id)
252            .wallet_address(wallet_address)
253            .from_token_amount(from_token_amount)
254            .account_type(account_type)
255    }
256}
257/// Request parameters for the [`create_outbound_transfer`] operation.
258///
259/// This struct holds all of the inputs you can pass when calling
260/// [`create_outbound_transfer`](#method.create_outbound_transfer).
261#[derive(Clone, Debug, Builder)]
262#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
263pub struct CreateOutboundTransferParams {
264    /// Wallet ID
265    ///
266    /// This field is **required.
267    #[builder(setter(into))]
268    pub wallet_id: String,
269    /// User's prediction wallet address
270    ///
271    /// This field is **required.
272    #[builder(setter(into))]
273    pub wallet_address: String,
274    /// Transfer amount in wei (18 decimals). Must be > 0. Example: `1000000000000000000` = 1 USDT
275    ///
276    /// This field is **required.
277    #[builder(setter(into))]
278    pub from_token_amount: String,
279    /// Source CEX account. Enum: `SPOT`, `FUNDING`
280    ///
281    /// This field is **required.
282    #[builder(setter(into))]
283    pub account_type: CreateOutboundTransferAccountTypeEnum,
284    /// Business source. Enum: `USER_TRANSFER`, `PREDICTION_BUY`
285    ///
286    /// This field is **required.
287    #[builder(setter(into))]
288    pub source_biz: CreateOutboundTransferSourceBizEnum,
289    /// Source token symbol. Default `USDT`
290    ///
291    /// This field is **optional.
292    #[builder(setter(into), default)]
293    pub from_token: Option<String>,
294    /// Destination token symbol. Default `USDT`
295    ///
296    /// This field is **optional.
297    #[builder(setter(into), default)]
298    pub to_token: Option<String>,
299    /// Chain ID. Default `56` (BSC)
300    ///
301    /// This field is **optional.
302    #[builder(setter(into), default)]
303    pub chain_id: Option<String>,
304}
305
306impl CreateOutboundTransferParams {
307    /// Create a builder for [`create_outbound_transfer`].
308    ///
309    /// Required parameters:
310    ///
311    /// * `wallet_id` — Wallet ID
312    /// * `wallet_address` — User's prediction wallet address
313    /// * `from_token_amount` — Transfer amount in wei (18 decimals). Must be > 0. Example: `1000000000000000000` = 1 USDT
314    /// * `account_type` — Source CEX account. Enum: `SPOT`, `FUNDING`
315    /// * `source_biz` — Business source. Enum: `USER_TRANSFER`, `PREDICTION_BUY`
316    ///
317    #[must_use]
318    pub fn builder(
319        wallet_id: String,
320        wallet_address: String,
321        from_token_amount: String,
322        account_type: CreateOutboundTransferAccountTypeEnum,
323        source_biz: CreateOutboundTransferSourceBizEnum,
324    ) -> CreateOutboundTransferParamsBuilder {
325        CreateOutboundTransferParamsBuilder::default()
326            .wallet_id(wallet_id)
327            .wallet_address(wallet_address)
328            .from_token_amount(from_token_amount)
329            .account_type(account_type)
330            .source_biz(source_biz)
331    }
332}
333/// Request parameters for the [`query_transfer_list`] operation.
334///
335/// This struct holds all of the inputs you can pass when calling
336/// [`query_transfer_list`](#method.query_transfer_list).
337#[derive(Clone, Debug, Builder)]
338#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
339pub struct QueryTransferListParams {
340    /// User's prediction wallet address
341    ///
342    /// This field is **required.
343    #[builder(setter(into))]
344    pub wallet_address: String,
345    /// Start date. Format: `yyyy-MM-dd`. Must be ≤ `endDate`
346    ///
347    /// This field is **required.
348    #[builder(setter(into))]
349    pub start_date: String,
350    /// End date. Format: `yyyy-MM-dd`. Must be ≥ `startDate`
351    ///
352    /// This field is **required.
353    #[builder(setter(into))]
354    pub end_date: String,
355    /// Filter by token symbol (e.g. `USDT`)
356    ///
357    /// This field is **optional.
358    #[builder(setter(into), default)]
359    pub token_symbol: Option<String>,
360    /// Filter by direction. Enum: `INBOUND`, `OUTBOUND`
361    ///
362    /// This field is **optional.
363    #[builder(setter(into), default)]
364    pub direction: Option<QueryTransferListDirectionEnum>,
365    /// Pagination offset. Default `0`
366    ///
367    /// This field is **optional.
368    #[builder(setter(into), default)]
369    pub offset: Option<i32>,
370    /// Page size. Default `20`, range 1–100
371    ///
372    /// This field is **optional.
373    #[builder(setter(into), default)]
374    pub limit: Option<i32>,
375    /// Request validity window in milliseconds
376    ///
377    /// This field is **optional.
378    #[builder(setter(into), default)]
379    pub recv_window: Option<i64>,
380}
381
382impl QueryTransferListParams {
383    /// Create a builder for [`query_transfer_list`].
384    ///
385    /// Required parameters:
386    ///
387    /// * `wallet_address` — User's prediction wallet address
388    /// * `start_date` — Start date. Format: `yyyy-MM-dd`. Must be ≤ `endDate`
389    /// * `end_date` — End date. Format: `yyyy-MM-dd`. Must be ≥ `startDate`
390    ///
391    #[must_use]
392    pub fn builder(
393        wallet_address: String,
394        start_date: String,
395        end_date: String,
396    ) -> QueryTransferListParamsBuilder {
397        QueryTransferListParamsBuilder::default()
398            .wallet_address(wallet_address)
399            .start_date(start_date)
400            .end_date(end_date)
401    }
402}
403/// Request parameters for the [`query_transfer_status`] operation.
404///
405/// This struct holds all of the inputs you can pass when calling
406/// [`query_transfer_status`](#method.query_transfer_status).
407#[derive(Clone, Debug, Builder)]
408#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
409pub struct QueryTransferStatusParams {
410    /// Transfer ID returned from outbound/inbound transfer
411    ///
412    /// This field is **required.
413    #[builder(setter(into))]
414    pub transfer_id: String,
415    /// Request validity window in milliseconds
416    ///
417    /// This field is **optional.
418    #[builder(setter(into), default)]
419    pub recv_window: Option<i64>,
420}
421
422impl QueryTransferStatusParams {
423    /// Create a builder for [`query_transfer_status`].
424    ///
425    /// Required parameters:
426    ///
427    /// * `transfer_id` — Transfer ID returned from outbound/inbound transfer
428    ///
429    #[must_use]
430    pub fn builder(transfer_id: String) -> QueryTransferStatusParamsBuilder {
431        QueryTransferStatusParamsBuilder::default().transfer_id(transfer_id)
432    }
433}
434
435#[async_trait]
436impl TransferApi for TransferApiClient {
437    async fn create_inbound_transfer(
438        &self,
439        params: CreateInboundTransferParams,
440    ) -> anyhow::Result<RestApiResponse<models::CreateInboundTransferResponse>> {
441        let CreateInboundTransferParams {
442            wallet_id,
443            wallet_address,
444            from_token_amount,
445            account_type,
446            from_token,
447            to_token,
448            chain_id,
449        } = params;
450
451        let mut query_params = BTreeMap::new();
452        let body_params = BTreeMap::new();
453
454        query_params.insert("walletId".to_string(), json!(wallet_id));
455
456        query_params.insert("walletAddress".to_string(), json!(wallet_address));
457
458        query_params.insert("fromTokenAmount".to_string(), json!(from_token_amount));
459
460        query_params.insert("accountType".to_string(), json!(account_type));
461
462        if let Some(rw) = from_token {
463            query_params.insert("fromToken".to_string(), json!(rw));
464        }
465
466        if let Some(rw) = to_token {
467            query_params.insert("toToken".to_string(), json!(rw));
468        }
469
470        if let Some(rw) = chain_id {
471            query_params.insert("chainId".to_string(), json!(rw));
472        }
473
474        send_request::<models::CreateInboundTransferResponse>(
475            &self.configuration,
476            "/sapi/v1/w3w/wallet/prediction/transfer/inbound",
477            reqwest::Method::POST,
478            query_params,
479            body_params,
480            if HAS_TIME_UNIT {
481                self.configuration.time_unit
482            } else {
483                None
484            },
485            true,
486        )
487        .await
488    }
489
490    async fn create_outbound_transfer(
491        &self,
492        params: CreateOutboundTransferParams,
493    ) -> anyhow::Result<RestApiResponse<models::CreateOutboundTransferResponse>> {
494        let CreateOutboundTransferParams {
495            wallet_id,
496            wallet_address,
497            from_token_amount,
498            account_type,
499            source_biz,
500            from_token,
501            to_token,
502            chain_id,
503        } = params;
504
505        let mut query_params = BTreeMap::new();
506        let body_params = BTreeMap::new();
507
508        query_params.insert("walletId".to_string(), json!(wallet_id));
509
510        query_params.insert("walletAddress".to_string(), json!(wallet_address));
511
512        query_params.insert("fromTokenAmount".to_string(), json!(from_token_amount));
513
514        query_params.insert("accountType".to_string(), json!(account_type));
515
516        query_params.insert("sourceBiz".to_string(), json!(source_biz));
517
518        if let Some(rw) = from_token {
519            query_params.insert("fromToken".to_string(), json!(rw));
520        }
521
522        if let Some(rw) = to_token {
523            query_params.insert("toToken".to_string(), json!(rw));
524        }
525
526        if let Some(rw) = chain_id {
527            query_params.insert("chainId".to_string(), json!(rw));
528        }
529
530        send_request::<models::CreateOutboundTransferResponse>(
531            &self.configuration,
532            "/sapi/v1/w3w/wallet/prediction/transfer/outbound",
533            reqwest::Method::POST,
534            query_params,
535            body_params,
536            if HAS_TIME_UNIT {
537                self.configuration.time_unit
538            } else {
539                None
540            },
541            true,
542        )
543        .await
544    }
545
546    async fn query_transfer_list(
547        &self,
548        params: QueryTransferListParams,
549    ) -> anyhow::Result<RestApiResponse<models::QueryTransferListResponse>> {
550        let QueryTransferListParams {
551            wallet_address,
552            start_date,
553            end_date,
554            token_symbol,
555            direction,
556            offset,
557            limit,
558            recv_window,
559        } = params;
560
561        let mut query_params = BTreeMap::new();
562        let body_params = BTreeMap::new();
563
564        query_params.insert("walletAddress".to_string(), json!(wallet_address));
565
566        query_params.insert("startDate".to_string(), json!(start_date));
567
568        query_params.insert("endDate".to_string(), json!(end_date));
569
570        if let Some(rw) = token_symbol {
571            query_params.insert("tokenSymbol".to_string(), json!(rw));
572        }
573
574        if let Some(rw) = direction {
575            query_params.insert("direction".to_string(), json!(rw));
576        }
577
578        if let Some(rw) = offset {
579            query_params.insert("offset".to_string(), json!(rw));
580        }
581
582        if let Some(rw) = limit {
583            query_params.insert("limit".to_string(), json!(rw));
584        }
585
586        if let Some(rw) = recv_window {
587            query_params.insert("recvWindow".to_string(), json!(rw));
588        }
589
590        send_request::<models::QueryTransferListResponse>(
591            &self.configuration,
592            "/sapi/v1/w3w/wallet/prediction/transfer/list",
593            reqwest::Method::GET,
594            query_params,
595            body_params,
596            if HAS_TIME_UNIT {
597                self.configuration.time_unit
598            } else {
599                None
600            },
601            true,
602        )
603        .await
604    }
605
606    async fn query_transfer_status(
607        &self,
608        params: QueryTransferStatusParams,
609    ) -> anyhow::Result<RestApiResponse<models::QueryTransferStatusResponse>> {
610        let QueryTransferStatusParams {
611            transfer_id,
612            recv_window,
613        } = params;
614
615        let mut query_params = BTreeMap::new();
616        let body_params = BTreeMap::new();
617
618        query_params.insert("transferId".to_string(), json!(transfer_id));
619
620        if let Some(rw) = recv_window {
621            query_params.insert("recvWindow".to_string(), json!(rw));
622        }
623
624        send_request::<models::QueryTransferStatusResponse>(
625            &self.configuration,
626            "/sapi/v1/w3w/wallet/prediction/transfer/status",
627            reqwest::Method::GET,
628            query_params,
629            body_params,
630            if HAS_TIME_UNIT {
631                self.configuration.time_unit
632            } else {
633                None
634            },
635            true,
636        )
637        .await
638    }
639}
640
641#[cfg(all(test, feature = "w3w_prediction"))]
642mod tests {
643    use super::*;
644    use crate::TOKIO_SHARED_RT;
645    use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
646    use async_trait::async_trait;
647    use std::collections::HashMap;
648
649    struct DummyRestApiResponse<T> {
650        inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
651        status: u16,
652        headers: HashMap<String, String>,
653        rate_limits: Option<Vec<RestApiRateLimit>>,
654    }
655
656    impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
657        fn from(dummy: DummyRestApiResponse<T>) -> Self {
658            Self {
659                data_fn: dummy.inner,
660                status: dummy.status,
661                headers: dummy.headers,
662                rate_limits: dummy.rate_limits,
663            }
664        }
665    }
666
667    struct MockTransferApiClient {
668        force_error: bool,
669    }
670
671    #[async_trait]
672    impl TransferApi for MockTransferApiClient {
673        async fn create_inbound_transfer(
674            &self,
675            _params: CreateInboundTransferParams,
676        ) -> anyhow::Result<RestApiResponse<models::CreateInboundTransferResponse>> {
677            if self.force_error {
678                return Err(ConnectorError::ConnectorClientError {
679                    msg: "ResponseError".to_string(),
680                    code: None,
681                }
682                .into());
683            }
684
685            let resp_json: Value = serde_json::from_str(
686                r#"{"transferId":"tf_20260525_in_001","status":"PROCESSING"}"#,
687            )
688            .unwrap_or_else(|_| serde_json::json!({}));
689            let dummy_response: models::CreateInboundTransferResponse =
690                serde_json::from_value(resp_json.clone())
691                    .expect("should parse into models::CreateInboundTransferResponse");
692
693            let dummy = DummyRestApiResponse {
694                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
695                status: 200,
696                headers: HashMap::new(),
697                rate_limits: None,
698            };
699
700            Ok(dummy.into())
701        }
702
703        async fn create_outbound_transfer(
704            &self,
705            _params: CreateOutboundTransferParams,
706        ) -> anyhow::Result<RestApiResponse<models::CreateOutboundTransferResponse>> {
707            if self.force_error {
708                return Err(ConnectorError::ConnectorClientError {
709                    msg: "ResponseError".to_string(),
710                    code: None,
711                }
712                .into());
713            }
714
715            let resp_json: Value = serde_json::from_str(
716                r#"{"transferId":"tf_20260525_out_001","status":"PROCESSING"}"#,
717            )
718            .unwrap_or_else(|_| serde_json::json!({}));
719            let dummy_response: models::CreateOutboundTransferResponse =
720                serde_json::from_value(resp_json.clone())
721                    .expect("should parse into models::CreateOutboundTransferResponse");
722
723            let dummy = DummyRestApiResponse {
724                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
725                status: 200,
726                headers: HashMap::new(),
727                rate_limits: None,
728            };
729
730            Ok(dummy.into())
731        }
732
733        async fn query_transfer_list(
734            &self,
735            _params: QueryTransferListParams,
736        ) -> anyhow::Result<RestApiResponse<models::QueryTransferListResponse>> {
737            if self.force_error {
738                return Err(ConnectorError::ConnectorClientError {
739                    msg: "ResponseError".to_string(),
740                    code: None,
741                }
742                .into());
743            }
744
745            let resp_json: Value = serde_json::from_str(r#"{"transfers":[{"transferId":"tf_20260525_out_001","direction":"OUTBOUND","status":"SUCCESS","walletAddress":"0x12e32db8817e292508c34111cbc4b23340df542c","fromToken":"USDT","fromTokenAmount":"100.00","toToken":"USDT","toTokenAmount":"100.00","errorCode":"errorCode","errorMessage":"errorMessage","createTime":"2026-05-25T04:00:00.000+00:00","updateTime":"2026-05-25T04:00:05.000+00:00","completeAt":"2026-05-25T04:00:05.000+00:00"}]}"#).unwrap_or_else(|_| serde_json::json!({}));
746            let dummy_response: models::QueryTransferListResponse =
747                serde_json::from_value(resp_json.clone())
748                    .expect("should parse into models::QueryTransferListResponse");
749
750            let dummy = DummyRestApiResponse {
751                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
752                status: 200,
753                headers: HashMap::new(),
754                rate_limits: None,
755            };
756
757            Ok(dummy.into())
758        }
759
760        async fn query_transfer_status(
761            &self,
762            _params: QueryTransferStatusParams,
763        ) -> anyhow::Result<RestApiResponse<models::QueryTransferStatusResponse>> {
764            if self.force_error {
765                return Err(ConnectorError::ConnectorClientError {
766                    msg: "ResponseError".to_string(),
767                    code: None,
768                }
769                .into());
770            }
771
772            let resp_json: Value = serde_json::from_str(r#"{"transferId":"tf_20260525_out_001","direction":"OUTBOUND","status":"COMPLETED","fromToken":"USDT","fromTokenAmount":"100.00","toToken":"USDT","toTokenAmount":"100.00","errorCode":"errorCode","errorMessage":"errorMessage","createTime":"2026-05-25T04:00:00.000+00:00","updateTime":"2026-05-25T04:00:05.000+00:00"}"#).unwrap_or_else(|_| serde_json::json!({}));
773            let dummy_response: models::QueryTransferStatusResponse =
774                serde_json::from_value(resp_json.clone())
775                    .expect("should parse into models::QueryTransferStatusResponse");
776
777            let dummy = DummyRestApiResponse {
778                inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
779                status: 200,
780                headers: HashMap::new(),
781                rate_limits: None,
782            };
783
784            Ok(dummy.into())
785        }
786    }
787
788    #[test]
789    fn create_inbound_transfer_required_params_success() {
790        TOKIO_SHARED_RT.block_on(async {
791            let client = MockTransferApiClient { force_error: false };
792
793            let params = CreateInboundTransferParams::builder(
794                "5b5c1ec3be4e4416a5872b21c1ca5d20".to_string(),
795                "0x12e32db8817e292508c34111cbc4b23340df542c".to_string(),
796                "1000000000000000000".to_string(),
797                CreateInboundTransferAccountTypeEnum::Spot,
798            )
799            .build()
800            .unwrap();
801
802            let resp_json: Value = serde_json::from_str(
803                r#"{"transferId":"tf_20260525_in_001","status":"PROCESSING"}"#,
804            )
805            .unwrap_or_else(|_| serde_json::json!({}));
806            let expected_response: models::CreateInboundTransferResponse =
807                serde_json::from_value(resp_json.clone())
808                    .expect("should parse into models::CreateInboundTransferResponse");
809
810            let resp = client
811                .create_inbound_transfer(params)
812                .await
813                .expect("Expected a response");
814            let data_future = resp.data();
815            let actual_response = data_future.await.unwrap();
816            assert_eq!(actual_response, expected_response);
817        });
818    }
819
820    #[test]
821    fn create_inbound_transfer_optional_params_success() {
822        TOKIO_SHARED_RT.block_on(async {
823            let client = MockTransferApiClient { force_error: false };
824
825            let params = CreateInboundTransferParams::builder(
826                "5b5c1ec3be4e4416a5872b21c1ca5d20".to_string(),
827                "0x12e32db8817e292508c34111cbc4b23340df542c".to_string(),
828                "1000000000000000000".to_string(),
829                CreateInboundTransferAccountTypeEnum::Spot,
830            )
831            .from_token("USDT".to_string())
832            .to_token("USDT".to_string())
833            .chain_id("56".to_string())
834            .build()
835            .unwrap();
836
837            let resp_json: Value = serde_json::from_str(
838                r#"{"transferId":"tf_20260525_in_001","status":"PROCESSING"}"#,
839            )
840            .unwrap_or_else(|_| serde_json::json!({}));
841            let expected_response: models::CreateInboundTransferResponse =
842                serde_json::from_value(resp_json.clone())
843                    .expect("should parse into models::CreateInboundTransferResponse");
844
845            let resp = client
846                .create_inbound_transfer(params)
847                .await
848                .expect("Expected a response");
849            let data_future = resp.data();
850            let actual_response = data_future.await.unwrap();
851            assert_eq!(actual_response, expected_response);
852        });
853    }
854
855    #[test]
856    fn create_inbound_transfer_response_error() {
857        TOKIO_SHARED_RT.block_on(async {
858            let client = MockTransferApiClient { force_error: true };
859
860            let params = CreateInboundTransferParams::builder(
861                "5b5c1ec3be4e4416a5872b21c1ca5d20".to_string(),
862                "0x12e32db8817e292508c34111cbc4b23340df542c".to_string(),
863                "1000000000000000000".to_string(),
864                CreateInboundTransferAccountTypeEnum::Spot,
865            )
866            .build()
867            .unwrap();
868
869            match client.create_inbound_transfer(params).await {
870                Ok(_) => panic!("Expected an error"),
871                Err(err) => {
872                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
873                }
874            }
875        });
876    }
877
878    #[test]
879    fn create_outbound_transfer_required_params_success() {
880        TOKIO_SHARED_RT.block_on(async {
881            let client = MockTransferApiClient { force_error: false };
882
883            let params = CreateOutboundTransferParams::builder(
884                "5b5c1ec3be4e4416a5872b21c1ca5d20".to_string(),
885                "0x12e32db8817e292508c34111cbc4b23340df542c".to_string(),
886                "1000000000000000000".to_string(),
887                CreateOutboundTransferAccountTypeEnum::Spot,
888                CreateOutboundTransferSourceBizEnum::UserTransfer,
889            )
890            .build()
891            .unwrap();
892
893            let resp_json: Value = serde_json::from_str(
894                r#"{"transferId":"tf_20260525_out_001","status":"PROCESSING"}"#,
895            )
896            .unwrap_or_else(|_| serde_json::json!({}));
897            let expected_response: models::CreateOutboundTransferResponse =
898                serde_json::from_value(resp_json.clone())
899                    .expect("should parse into models::CreateOutboundTransferResponse");
900
901            let resp = client
902                .create_outbound_transfer(params)
903                .await
904                .expect("Expected a response");
905            let data_future = resp.data();
906            let actual_response = data_future.await.unwrap();
907            assert_eq!(actual_response, expected_response);
908        });
909    }
910
911    #[test]
912    fn create_outbound_transfer_optional_params_success() {
913        TOKIO_SHARED_RT.block_on(async {
914            let client = MockTransferApiClient { force_error: false };
915
916            let params = CreateOutboundTransferParams::builder(
917                "5b5c1ec3be4e4416a5872b21c1ca5d20".to_string(),
918                "0x12e32db8817e292508c34111cbc4b23340df542c".to_string(),
919                "1000000000000000000".to_string(),
920                CreateOutboundTransferAccountTypeEnum::Spot,
921                CreateOutboundTransferSourceBizEnum::UserTransfer,
922            )
923            .from_token("USDT".to_string())
924            .to_token("USDT".to_string())
925            .chain_id("56".to_string())
926            .build()
927            .unwrap();
928
929            let resp_json: Value = serde_json::from_str(
930                r#"{"transferId":"tf_20260525_out_001","status":"PROCESSING"}"#,
931            )
932            .unwrap_or_else(|_| serde_json::json!({}));
933            let expected_response: models::CreateOutboundTransferResponse =
934                serde_json::from_value(resp_json.clone())
935                    .expect("should parse into models::CreateOutboundTransferResponse");
936
937            let resp = client
938                .create_outbound_transfer(params)
939                .await
940                .expect("Expected a response");
941            let data_future = resp.data();
942            let actual_response = data_future.await.unwrap();
943            assert_eq!(actual_response, expected_response);
944        });
945    }
946
947    #[test]
948    fn create_outbound_transfer_response_error() {
949        TOKIO_SHARED_RT.block_on(async {
950            let client = MockTransferApiClient { force_error: true };
951
952            let params = CreateOutboundTransferParams::builder(
953                "5b5c1ec3be4e4416a5872b21c1ca5d20".to_string(),
954                "0x12e32db8817e292508c34111cbc4b23340df542c".to_string(),
955                "1000000000000000000".to_string(),
956                CreateOutboundTransferAccountTypeEnum::Spot,
957                CreateOutboundTransferSourceBizEnum::UserTransfer,
958            )
959            .build()
960            .unwrap();
961
962            match client.create_outbound_transfer(params).await {
963                Ok(_) => panic!("Expected an error"),
964                Err(err) => {
965                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
966                }
967            }
968        });
969    }
970
971    #[test]
972    fn query_transfer_list_required_params_success() {
973        TOKIO_SHARED_RT.block_on(async {
974            let client = MockTransferApiClient { force_error: false };
975
976            let params = QueryTransferListParams::builder("0x12e32db8817e292508c34111cbc4b23340df542c".to_string(),"2026-05-01".to_string(),"2026-05-25".to_string(),).build().unwrap();
977
978            let resp_json: Value = serde_json::from_str(r#"{"transfers":[{"transferId":"tf_20260525_out_001","direction":"OUTBOUND","status":"SUCCESS","walletAddress":"0x12e32db8817e292508c34111cbc4b23340df542c","fromToken":"USDT","fromTokenAmount":"100.00","toToken":"USDT","toTokenAmount":"100.00","errorCode":"errorCode","errorMessage":"errorMessage","createTime":"2026-05-25T04:00:00.000+00:00","updateTime":"2026-05-25T04:00:05.000+00:00","completeAt":"2026-05-25T04:00:05.000+00:00"}]}"#).unwrap_or_else(|_| serde_json::json!({}));
979            let expected_response : models::QueryTransferListResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::QueryTransferListResponse");
980
981            let resp = client.query_transfer_list(params).await.expect("Expected a response");
982            let data_future = resp.data();
983            let actual_response = data_future.await.unwrap();
984            assert_eq!(actual_response, expected_response);
985        });
986    }
987
988    #[test]
989    fn query_transfer_list_optional_params_success() {
990        TOKIO_SHARED_RT.block_on(async {
991            let client = MockTransferApiClient { force_error: false };
992
993            let params = QueryTransferListParams::builder("0x12e32db8817e292508c34111cbc4b23340df542c".to_string(),"2026-05-01".to_string(),"2026-05-25".to_string(),).token_symbol("USDT".to_string()).direction(QueryTransferListDirectionEnum::Inbound).offset(0).limit(20).recv_window(5000).build().unwrap();
994
995            let resp_json: Value = serde_json::from_str(r#"{"transfers":[{"transferId":"tf_20260525_out_001","direction":"OUTBOUND","status":"SUCCESS","walletAddress":"0x12e32db8817e292508c34111cbc4b23340df542c","fromToken":"USDT","fromTokenAmount":"100.00","toToken":"USDT","toTokenAmount":"100.00","errorCode":"errorCode","errorMessage":"errorMessage","createTime":"2026-05-25T04:00:00.000+00:00","updateTime":"2026-05-25T04:00:05.000+00:00","completeAt":"2026-05-25T04:00:05.000+00:00"}]}"#).unwrap_or_else(|_| serde_json::json!({}));
996            let expected_response : models::QueryTransferListResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::QueryTransferListResponse");
997
998            let resp = client.query_transfer_list(params).await.expect("Expected a response");
999            let data_future = resp.data();
1000            let actual_response = data_future.await.unwrap();
1001            assert_eq!(actual_response, expected_response);
1002        });
1003    }
1004
1005    #[test]
1006    fn query_transfer_list_response_error() {
1007        TOKIO_SHARED_RT.block_on(async {
1008            let client = MockTransferApiClient { force_error: true };
1009
1010            let params = QueryTransferListParams::builder(
1011                "0x12e32db8817e292508c34111cbc4b23340df542c".to_string(),
1012                "2026-05-01".to_string(),
1013                "2026-05-25".to_string(),
1014            )
1015            .build()
1016            .unwrap();
1017
1018            match client.query_transfer_list(params).await {
1019                Ok(_) => panic!("Expected an error"),
1020                Err(err) => {
1021                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
1022                }
1023            }
1024        });
1025    }
1026
1027    #[test]
1028    fn query_transfer_status_required_params_success() {
1029        TOKIO_SHARED_RT.block_on(async {
1030            let client = MockTransferApiClient { force_error: false };
1031
1032            let params = QueryTransferStatusParams::builder("tf_20260525_out_001".to_string(),).build().unwrap();
1033
1034            let resp_json: Value = serde_json::from_str(r#"{"transferId":"tf_20260525_out_001","direction":"OUTBOUND","status":"COMPLETED","fromToken":"USDT","fromTokenAmount":"100.00","toToken":"USDT","toTokenAmount":"100.00","errorCode":"errorCode","errorMessage":"errorMessage","createTime":"2026-05-25T04:00:00.000+00:00","updateTime":"2026-05-25T04:00:05.000+00:00"}"#).unwrap_or_else(|_| serde_json::json!({}));
1035            let expected_response : models::QueryTransferStatusResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::QueryTransferStatusResponse");
1036
1037            let resp = client.query_transfer_status(params).await.expect("Expected a response");
1038            let data_future = resp.data();
1039            let actual_response = data_future.await.unwrap();
1040            assert_eq!(actual_response, expected_response);
1041        });
1042    }
1043
1044    #[test]
1045    fn query_transfer_status_optional_params_success() {
1046        TOKIO_SHARED_RT.block_on(async {
1047            let client = MockTransferApiClient { force_error: false };
1048
1049            let params = QueryTransferStatusParams::builder("tf_20260525_out_001".to_string(),).recv_window(5000).build().unwrap();
1050
1051            let resp_json: Value = serde_json::from_str(r#"{"transferId":"tf_20260525_out_001","direction":"OUTBOUND","status":"COMPLETED","fromToken":"USDT","fromTokenAmount":"100.00","toToken":"USDT","toTokenAmount":"100.00","errorCode":"errorCode","errorMessage":"errorMessage","createTime":"2026-05-25T04:00:00.000+00:00","updateTime":"2026-05-25T04:00:05.000+00:00"}"#).unwrap_or_else(|_| serde_json::json!({}));
1052            let expected_response : models::QueryTransferStatusResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::QueryTransferStatusResponse");
1053
1054            let resp = client.query_transfer_status(params).await.expect("Expected a response");
1055            let data_future = resp.data();
1056            let actual_response = data_future.await.unwrap();
1057            assert_eq!(actual_response, expected_response);
1058        });
1059    }
1060
1061    #[test]
1062    fn query_transfer_status_response_error() {
1063        TOKIO_SHARED_RT.block_on(async {
1064            let client = MockTransferApiClient { force_error: true };
1065
1066            let params = QueryTransferStatusParams::builder("tf_20260525_out_001".to_string())
1067                .build()
1068                .unwrap();
1069
1070            match client.query_transfer_status(params).await {
1071                Ok(_) => panic!("Expected an error"),
1072                Err(err) => {
1073                    assert_eq!(err.to_string(), "Connector client error: ResponseError");
1074                }
1075            }
1076        });
1077    }
1078}