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)]
194#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
195pub struct CreateInboundTransferParams {
196 #[builder(setter(into))]
200 pub wallet_id: String,
201 #[builder(setter(into))]
205 pub wallet_address: String,
206 #[builder(setter(into))]
210 pub from_token_amount: String,
211 #[builder(setter(into))]
215 pub account_type: CreateInboundTransferAccountTypeEnum,
216 #[builder(setter(into), default)]
220 pub from_token: Option<String>,
221 #[builder(setter(into), default)]
225 pub to_token: Option<String>,
226 #[builder(setter(into), default)]
230 pub chain_id: Option<String>,
231}
232
233impl CreateInboundTransferParams {
234 #[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#[derive(Clone, Debug, Builder)]
262#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
263pub struct CreateOutboundTransferParams {
264 #[builder(setter(into))]
268 pub wallet_id: String,
269 #[builder(setter(into))]
273 pub wallet_address: String,
274 #[builder(setter(into))]
278 pub from_token_amount: String,
279 #[builder(setter(into))]
283 pub account_type: CreateOutboundTransferAccountTypeEnum,
284 #[builder(setter(into))]
288 pub source_biz: CreateOutboundTransferSourceBizEnum,
289 #[builder(setter(into), default)]
293 pub from_token: Option<String>,
294 #[builder(setter(into), default)]
298 pub to_token: Option<String>,
299 #[builder(setter(into), default)]
303 pub chain_id: Option<String>,
304}
305
306impl CreateOutboundTransferParams {
307 #[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#[derive(Clone, Debug, Builder)]
338#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
339pub struct QueryTransferListParams {
340 #[builder(setter(into))]
344 pub wallet_address: String,
345 #[builder(setter(into))]
349 pub start_date: String,
350 #[builder(setter(into))]
354 pub end_date: String,
355 #[builder(setter(into), default)]
359 pub token_symbol: Option<String>,
360 #[builder(setter(into), default)]
364 pub direction: Option<QueryTransferListDirectionEnum>,
365 #[builder(setter(into), default)]
369 pub offset: Option<i32>,
370 #[builder(setter(into), default)]
374 pub limit: Option<i32>,
375 #[builder(setter(into), default)]
379 pub recv_window: Option<i64>,
380}
381
382impl QueryTransferListParams {
383 #[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#[derive(Clone, Debug, Builder)]
408#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
409pub struct QueryTransferStatusParams {
410 #[builder(setter(into))]
414 pub transfer_id: String,
415 #[builder(setter(into), default)]
419 pub recv_window: Option<i64>,
420}
421
422impl QueryTransferStatusParams {
423 #[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}