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