1#![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#[derive(Clone, Debug, Builder, Deserialize)]
194#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
195pub struct CreateInboundTransferParams {
196 #[builder(setter(into))]
200 #[serde(rename = "walletId")]
201 pub wallet_id: String,
202 #[builder(setter(into))]
206 #[serde(rename = "walletAddress")]
207 pub wallet_address: String,
208 #[builder(setter(into))]
212 #[serde(rename = "fromTokenAmount")]
213 pub from_token_amount: String,
214 #[builder(setter(into))]
218 #[serde(rename = "accountType")]
219 pub account_type: CreateInboundTransferAccountTypeEnum,
220 #[builder(setter(into), default)]
224 #[serde(rename = "fromToken", default)]
225 pub from_token: Option<String>,
226 #[builder(setter(into), default)]
230 #[serde(rename = "toToken", default)]
231 pub to_token: Option<String>,
232 #[builder(setter(into), default)]
236 #[serde(rename = "chainId", default)]
237 pub chain_id: Option<String>,
238}
239
240impl CreateInboundTransferParams {
241 #[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#[derive(Clone, Debug, Builder, Deserialize)]
269#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
270pub struct CreateOutboundTransferParams {
271 #[builder(setter(into))]
275 #[serde(rename = "walletId")]
276 pub wallet_id: String,
277 #[builder(setter(into))]
281 #[serde(rename = "walletAddress")]
282 pub wallet_address: String,
283 #[builder(setter(into))]
287 #[serde(rename = "fromTokenAmount")]
288 pub from_token_amount: String,
289 #[builder(setter(into))]
293 #[serde(rename = "accountType")]
294 pub account_type: CreateOutboundTransferAccountTypeEnum,
295 #[builder(setter(into))]
299 #[serde(rename = "sourceBiz")]
300 pub source_biz: CreateOutboundTransferSourceBizEnum,
301 #[builder(setter(into), default)]
305 #[serde(rename = "fromToken", default)]
306 pub from_token: Option<String>,
307 #[builder(setter(into), default)]
311 #[serde(rename = "toToken", default)]
312 pub to_token: Option<String>,
313 #[builder(setter(into), default)]
317 #[serde(rename = "chainId", default)]
318 pub chain_id: Option<String>,
319}
320
321impl CreateOutboundTransferParams {
322 #[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#[derive(Clone, Debug, Builder, Deserialize)]
353#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
354pub struct QueryTransferListParams {
355 #[builder(setter(into))]
359 #[serde(rename = "walletAddress")]
360 pub wallet_address: String,
361 #[builder(setter(into))]
365 #[serde(rename = "startDate")]
366 pub start_date: String,
367 #[builder(setter(into))]
371 #[serde(rename = "endDate")]
372 pub end_date: String,
373 #[builder(setter(into), default)]
377 #[serde(rename = "tokenSymbol", default)]
378 pub token_symbol: Option<String>,
379 #[builder(setter(into), default)]
383 #[serde(rename = "direction", default)]
384 pub direction: Option<QueryTransferListDirectionEnum>,
385 #[builder(setter(into), default)]
389 #[serde(rename = "offset", default)]
390 pub offset: Option<i32>,
391 #[builder(setter(into), default)]
395 #[serde(rename = "limit", default)]
396 pub limit: Option<i32>,
397 #[builder(setter(into), default)]
401 #[serde(rename = "recvWindow", default)]
402 pub recv_window: Option<i64>,
403}
404
405impl QueryTransferListParams {
406 #[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#[derive(Clone, Debug, Builder, Deserialize)]
431#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
432pub struct QueryTransferStatusParams {
433 #[builder(setter(into))]
437 #[serde(rename = "transferId")]
438 pub transfer_id: String,
439 #[builder(setter(into), default)]
443 #[serde(rename = "recvWindow", default)]
444 pub recv_window: Option<i64>,
445}
446
447impl QueryTransferStatusParams {
448 #[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}